Java tutorial
/* * TacTex - a power trading agent that competed in the Power Trading Agent Competition (Power TAC) www.powertac.org * Copyright (c) 2013-2016 Daniel Urieli and Peter Stone {urieli,pstone}@cs.utexas.edu * * * This file is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * This file incorporates work covered by the following copyright and * permission notice: * * Copyright (c) 2012 by the original author * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package edu.utexas.cs.tactex; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Random; import java.util.Set; import org.apache.activemq.transport.stomp.Stomp.Headers.Send; import org.apache.commons.math3.linear.ArrayRealVector; import org.apache.log4j.Logger; import org.joda.time.Instant; import org.powertac.common.Broker; import org.powertac.common.Competition; //import org.powertac.common.ConfigServerBroker; import org.powertac.common.CustomerInfo; import org.powertac.common.HourlyCharge; import org.powertac.common.Rate; import org.powertac.common.TariffMessage; import org.powertac.common.TariffSpecification; import org.powertac.common.TariffTransaction; import org.powertac.common.TimeService; import org.powertac.common.enumerations.PowerType; import org.powertac.common.msg.CustomerBootstrapData; import org.powertac.common.msg.PauseRelease; import org.powertac.common.msg.PauseRequest; import org.powertac.common.msg.TariffRevoke; import org.powertac.common.msg.TariffStatus; import org.powertac.common.msg.VariableRateUpdate; import org.powertac.common.repo.CustomerRepo; import org.powertac.common.repo.TimeslotRepo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import edu.utexas.cs.tactex.interfaces.Activatable; import edu.utexas.cs.tactex.interfaces.BrokerContext; import edu.utexas.cs.tactex.interfaces.CostCurvesPredictor; import edu.utexas.cs.tactex.interfaces.EnergyPredictionManager; import edu.utexas.cs.tactex.interfaces.Initializable; import edu.utexas.cs.tactex.interfaces.MarketManager; import edu.utexas.cs.tactex.interfaces.PortfolioManager; import edu.utexas.cs.tactex.interfaces.TariffRepoMgr; import edu.utexas.cs.tactex.utils.BrokerUtils; import edu.utexas.cs.tactex.utils.MsgVerification; /** * Handles portfolio-management responsibilities for the broker. * @author John Collins */ @Service public class PortfolioManagerService implements PortfolioManager, Initializable, Activatable { static private Logger log = Logger.getLogger(PortfolioManagerService.class); private BrokerContext brokerContext; // master @Autowired private TimeslotRepo timeslotRepo; @Autowired private TariffRepoMgr tariffRepoMgr; @Autowired private CustomerRepo customerRepo; @Autowired private TimeService timeService; @Autowired private MarketManager marketManager; @Autowired private ContextManagerService contextManager; @Autowired private EnergyPredictionManager energyPredictionManager; @Autowired private CostCurvesPredictor costCurvesPredictor; @Autowired private ConfiguratorFactoryService configuratorFactoryService; private Random randomGen = new Random(); // parameters private static final double DEFAULT_MARGIN = 0.5; private static final double FIXED_PER_KWH = -0.06; // 2 * max-distribution-fee = 2 * -0.030 private static final double DEFAULT_PERIODIC_PAYMENT = -1.0; // /////////////////////////////////////////////////// // FIELDS THAT NEED TO BE INITIALIZED IN initialize() // EACH FIELD SHOULD BE ADDED TO test_initialize() !!! // /////////////////////////////////////////////////// // ---- Portfolio records ----- private HashMap<CustomerInfo, CustomerRecord> customerProfiles; // Customer records indexed by power type and by tariff. Note that the // CustomerRecord instances are NOT shared between these structures, because // we need to keep track of subscriptions by tariff. private HashMap<PowerType, HashMap<CustomerInfo, CustomerRecord>> customerProfilesByPowerType; private HashMap<TariffSpecification, HashMap<CustomerInfo, CustomerRecord>> customerSubscriptions; private HashMap<PowerType, List<TariffSpecification>> competingTariffs; int bootstrapTimeSlotNum; boolean gameStart; boolean tookAllCrashedAction; private TariffSpecification lastPublishedSpec; private int lastTimeSpecPublished; private int activateTS; //private double randomRatio; /** * Default constructor registers for messages, must be called after * message router is available. */ public PortfolioManagerService() { super(); } /** * Sets up message handling */ @Override @SuppressWarnings("unchecked") public void initialize(BrokerContext brokerContext) { // NEVER CALL ANY SERVICE METHOD FROM HERE, SINCE THEY ARE NOT GUARANTEED // TO BE initalize()'d. // Exception: it is OK to call configuratorFactory's public // (application-wide) constants this.brokerContext = brokerContext; customerProfiles = new HashMap<CustomerInfo, CustomerRecord>(); customerProfilesByPowerType = new HashMap<PowerType, HashMap<CustomerInfo, CustomerRecord>>(); customerSubscriptions = new HashMap<TariffSpecification, HashMap<CustomerInfo, CustomerRecord>>(); competingTariffs = new HashMap<PowerType, List<TariffSpecification>>(); bootstrapTimeSlotNum = -1; gameStart = true; tookAllCrashedAction = false; // random tariff undercutting ratio //randomRatio = randomGen.nextDouble() * 2; // [0,2] //log.info("randomRatio " + randomRatio); } // -------------- Message handlers ------------------- /** * Handles CustomerBootstrapData by populating the customer model * corresponding to the given customer and power type. This gives the * broker a running start. */ public synchronized void handleMessage(CustomerBootstrapData cbd) { produceConsume(cbd); } /** * Handles a TariffSpecification. These are sent out when new tariffs are * published. If it's not ours, then it's a competitor. */ public synchronized void handleMessage(TariffSpecification spec) { if (specPublishedByMe(spec)) { log.info("published " + spec); addOwnTariff(spec); } else { // otherwise, keep track addCompetingTariff(spec); } } /** * Handles a TariffRevoke message from the server, indicating that some * tariff has been revoked. Should revert what is done by * handleMessage (TariffSpecification) */ public synchronized void handleMessage(TariffRevoke tr) { Broker revokingBroker = tr.getBroker(); log.info("Revoke tariff " + tr.getTariffId() + " from " + revokingBroker.getUsername()); TariffSpecification revokedTariffSpec = tariffRepoMgr.findSpecificationById(tr.getTariffId()); if (null == revokedTariffSpec) { log.error("Original tariff " + tr.getTariffId() + " not found"); return; } // clean tariff from repo tariffRepoMgr.removeRevokedSpec(revokedTariffSpec); // if it's from competitor, remove it from the // competingTariffs list if (!specPublishedByMe(revokedTariffSpec)) { log.info("clear out competing tariff " + tr.getTariffId()); getCompetingTariffs(revokedTariffSpec.getPowerType()).remove(revokedTariffSpec); } else { customerSubscriptions.remove(revokedTariffSpec); } } /** * Handles a TariffStatus message. */ public synchronized void handleMessage(TariffStatus ts) { log.info("TariffStatus: " + ts.getStatus()); if (ts.getStatus() != TariffStatus.Status.success) { log.error("TariffStatus is not success: " + ts.getStatus()); } } /** * Handles a TariffTransaction. We only care about certain types: PRODUCE, * CONSUME, SIGNUP, and WITHDRAW. */ public synchronized void handleMessage(TariffTransaction ttx) { if (!MsgVerification.isLegalTransaction(ttx, tariffRepoMgr, customerRepo)) { return; } TariffTransaction.Type txType = ttx.getTxType(); if (MsgVerification.customerTransactionTypes.contains(txType)) { log.info("Currently ignoring charge and kwh when handling customerTransactionTypes"); CustomerRecord tariffCustomerRecord = getCustomerRecordByTariff(ttx.getTariffSpec(), ttx.getCustomerInfo()); if (TariffTransaction.Type.SIGNUP == txType) { // keep track of customer counts tariffCustomerRecord.signup(ttx.getCustomerCount()); } else if (TariffTransaction.Type.WITHDRAW == txType) { // customers presumably found a better deal tariffCustomerRecord.withdraw(ttx.getCustomerCount()); } else if (TariffTransaction.Type.PRODUCE == txType) { // if ttx count and subscribe population don't match, it will be hard // to estimate per-individual production if (ttx.getCustomerCount() != tariffCustomerRecord.getSubscribedPopulation()) { log.warn("production by subset " + ttx.getCustomerCount() + " of subscribed population " + tariffCustomerRecord.getSubscribedPopulation()); } produceConsume(ttx); } else if (TariffTransaction.Type.CONSUME == txType) { if (ttx.getCustomerCount() != tariffCustomerRecord.getSubscribedPopulation()) { log.warn("consumption by subset " + ttx.getCustomerCount() + " of subscribed population " + tariffCustomerRecord.getSubscribedPopulation()); } produceConsume(ttx); } else if (TariffTransaction.Type.PERIODIC == txType) { log.debug("need to take care of PERIODIC transaction"); } } else { if (TariffTransaction.Type.PUBLISH == txType) { log.debug("need to take care of PUBLISH transaction"); } else if (TariffTransaction.Type.REVOKE == txType) { log.debug("need to take care of REVOKE transaction"); } } } // --------------- activation ----------------- /** * Note: non-synchronized. It takes time to execute and we don't want it to * cause incoming-message drop. We therefore syncronize fragments called from * activate. * (non-Javadoc) * @see edu.utexas.cs.tactex.PortfolioManager#activate() */ @Override public void activate(int currentTimeslotIndex) { try { log.info("activate, ts " + currentTimeslotIndex); activateTS = currentTimeslotIndex; //if (configuratorFactoryService.isUseInit13()) { // ==================================== // the 2013 method for initial tariffs // ==================================== if (gameStart) { // new game started createInitialTariffs(); gameStart = false; } else { // we have some, are they good enough? improveTariffs(currentTimeslotIndex); //publishHourlyCharges(currentTimeslotIndex); } //} //else { // // // // ==================================== // // the 2014? method for initial tariffs // // ==================================== // // // // TODO later, configure all useCanUse in this file, // // maybe in one location // boolean useCanUse = true; // // // TODO Remove hard coded and do it using bootstrapTimeSlotNum // if (currentTimeslotIndex == 360) { // // we have no tariffs, default broker's are the only ones we see // createTwoInitialTariffs(useCanUse); // } // else if (currentTimeslotIndex == 365) { // createReactiveInitialTariffs(useCanUse); // } // else { // // we have some, are they good enough? // improveTariffs(currentTimeslotIndex); // publishHourlyCharges(currentTimeslotIndex); // } //} log.info("done-activate"); } catch (Throwable e) { log.error("caught exception from activate(): ", e); //e.printStackTrace(); } } // -------------- data access and subroutines ------------------ /** * Returns total usage for a given timeslot (represented as a simple index). */ @Override public synchronized double collectUsage(int index) { double result = 0.0; for (HashMap<CustomerInfo, CustomerRecord> customerMap : customerSubscriptions.values()) { for (CustomerRecord record : customerMap.values()) { result += record.getUsage(index, false); } } return -result; // convert to needed energy account balance } /** * Note: not synchronized (since might take time) but calls * synchronized methods to access potentially shared. Look * for *sync* below */ @Override public double collectShiftedUsage(int targetTimeslot, int currentTimeslotIndex) { double result = 0.0; // *sync*: the following two lines call synchronized methods HashMap<TariffSpecification, HashMap<CustomerInfo, Double>> tariffSubscriptions = BrokerUtils .revertKeyMapping(BrokerUtils.initializePredictedFromCurrentSubscriptions( BrokerUtils.revertKeyMapping(getCustomerSubscriptions()))); for (Entry<TariffSpecification, HashMap<CustomerInfo, Integer>> entry : getCustomerSubscriptions() .entrySet()) { TariffSpecification spec = entry.getKey(); for (Entry<CustomerInfo, Integer> custInfo2subs : entry.getValue().entrySet()) { CustomerInfo cust = custInfo2subs.getKey(); Integer subs = custInfo2subs.getValue(); double custSpecSubsShiftedUsage = energyPredictionManager.getShiftedUsageFromBrokerPerspective( // <spec,cust> => subs spec, cust, subs, // current-time => target-time targetTimeslot, currentTimeslotIndex, tariffSubscriptions); result += custSpecSubsShiftedUsage; } } return -result; // convert to needed energy account balance } private synchronized HashMap<TariffSpecification, HashMap<CustomerInfo, Integer>> getCustomerSubscriptions() { HashMap<TariffSpecification, HashMap<CustomerInfo, Integer>> result = new HashMap<TariffSpecification, HashMap<CustomerInfo, Integer>>(); for (Entry<TariffSpecification, HashMap<CustomerInfo, CustomerRecord>> entry : customerSubscriptions .entrySet()) { TariffSpecification spec = entry.getKey(); HashMap<CustomerInfo, CustomerRecord> oldValue = entry.getValue(); //if (spec.getPowerType().getGenericType() == genericPowerType) { //if (spec.getPowerType() == powerType) { // create new map for new tariff spec HashMap<CustomerInfo, Integer> newValue = new HashMap<CustomerInfo, Integer>(); for (Entry<CustomerInfo, CustomerRecord> e : oldValue.entrySet()) { // extract subscribed population from record and put it in new value newValue.put(e.getKey(), e.getValue().getSubscribedPopulation()); } result.put(spec, newValue); //} } return result; } @Override public HashMap<TariffSpecification, HashMap<CustomerInfo, Integer>> getCustomerSubscriptions( PowerType powerType) { HashMap<TariffSpecification, HashMap<CustomerInfo, Integer>> result = new HashMap<TariffSpecification, HashMap<CustomerInfo, Integer>>(); for (Entry<TariffSpecification, HashMap<CustomerInfo, CustomerRecord>> entry : customerSubscriptions .entrySet()) { TariffSpecification spec = entry.getKey(); HashMap<CustomerInfo, CustomerRecord> oldValue = entry.getValue(); //if (spec.getPowerType().getGenericType() == genericPowerType) { if (spec.getPowerType() == powerType) { // create new map for new tariff spec HashMap<CustomerInfo, Integer> newValue = new HashMap<CustomerInfo, Integer>(); for (Entry<CustomerInfo, CustomerRecord> e : oldValue.entrySet()) { // extract subscribed population from record and put it in new value newValue.put(e.getKey(), e.getValue().getSubscribedPopulation()); } result.put(spec, newValue); } } return result; } /** * Finds the list of competing tariffs for the given PowerType. */ List<TariffSpecification> getCompetingTariffs(PowerType powerType) { List<TariffSpecification> result = competingTariffs.get(powerType); if (result == null) { result = new ArrayList<TariffSpecification>(); competingTariffs.put(powerType, result); } return result; } /** * enables getting tariffs of all types that "canUse" * the given powerType * * @param powerType * @return */ List<TariffSpecification> getCompetingTariffsThatCanUse(PowerType myPowerType) { List<TariffSpecification> result = new ArrayList<TariffSpecification>(); for (PowerType competingPowerType : competingTariffs.keySet()) { if (competingPowerType.canUse(myPowerType)) { result.addAll(getCompetingTariffs(competingPowerType)); } } return result; } /** * enables getting tariffs of all types that "canByUseBy" * the given powerType * * @param myPowerType * @return */ List<TariffSpecification> getCompetingTariffsThatCanBeUsedBy(PowerType myPowerType) { List<TariffSpecification> result = new ArrayList<TariffSpecification>(); for (PowerType competingPowerType : competingTariffs.keySet()) { if (myPowerType.canUse(competingPowerType)) { result.addAll(getCompetingTariffs(competingPowerType)); } } return result; } private CustomerRecord getCustomerGeneralRecord(CustomerInfo customer) { if (customer == null) { log.error("getCustomerGeneralRecord() called with null key"); } CustomerRecord record = customerProfiles.get(customer); if (record == null) { record = new CustomerRecord(customer); customerProfiles.put(customer, record); } return record; } /** * Returns the CustomerRecord for the given type and customer, creating it * if necessary. */ private CustomerRecord getCustomerRecordByPowerType(PowerType type, CustomerInfo customer) { if (type == null || customer == null) { log.error("getCustomerRecordByPowerType() called with null key"); } HashMap<CustomerInfo, CustomerRecord> customerMap = customerProfilesByPowerType.get(type); if (customerMap == null) { customerMap = new HashMap<CustomerInfo, CustomerRecord>(); customerProfilesByPowerType.put(type, customerMap); } CustomerRecord record = customerMap.get(customer); if (record == null) { record = new CustomerRecord(getCustomerGeneralRecord(customer)); customerMap.put(customer, record); } return record; } /** * Returns the customer record for the given tariff spec and customer, * creating it if necessary. */ private CustomerRecord getCustomerRecordByTariff(TariffSpecification spec, CustomerInfo customer) { if (spec == null || customer == null) { log.error("getCustomerRecordByTariff() called with null key"); } HashMap<CustomerInfo, CustomerRecord> customerMap = customerSubscriptions.get(spec); if (customerMap == null) { customerMap = new HashMap<CustomerInfo, CustomerRecord>(); customerSubscriptions.put(spec, customerMap); } CustomerRecord record = customerMap.get(customer); if (record == null) { // seed with the generic record for this customer record = new CustomerRecord(getCustomerRecordByPowerType(spec.getPowerType(), customer)); customerMap.put(customer, record); } return record; } /** * @param spec */ private void addOwnTariff(TariffSpecification spec) { boolean success = tariffRepoMgr.addToRepo(spec); if (success) { // instead of: // customerSubscriptions.put(spec, new HashMap<CustomerInfo, CustomerRecord>()); // do: // initialize all *relevant* customer entries for this tariff: for (CustomerInfo customer : customerRepo.list()) { // the following is a "retrieve and create if needed" function and // therefore it initializes the spec's entry and each relevant // customer's entry if (customer.getPowerType().canUse(spec.getPowerType())) { getCustomerRecordByTariff(spec, customer); } } } else { log.error("failed to add published tariff to repo " + spec.getId()); } } /** * Adds a new competing tariff to the list. */ private void addCompetingTariff(TariffSpecification spec) { // now we are adding it to the tariffRepoMgr boolean success = tariffRepoMgr.addToRepo(spec); if (!success) { log.error("How come a competing tariff is failed to be added to repo?"); log.error("competing tariff ignored: " + spec.getId()); return; } getCompetingTariffs(spec.getPowerType()).add(spec); } // Creates initial tariffs for the main power types. These are simple // fixed-rate two-part tariffs that give the broker a fixed margin. private void createInitialTariffs() { double marketPrice = -marketManager.getMeanMarketPricePerKWH(); // for each power type representing a customer population, // create a tariff that's better than what's available double hardCodedBaseRate = ((marketPrice + FIXED_PER_KWH) * (1.0 + DEFAULT_MARGIN)) - 0.000129833; double baseRate = 0.75 /*1.0*/ /*2.5*/ /*1.25*/ * hardCodedBaseRate; // market + 2*dist-fee * 1.5 - confusing-term(0). it's -0.129 in the default test log.info("THEBASERATE is " + baseRate); TariffSpecification spec; Rate rate; PowerType pt; List<TariffSpecification> specsToPublish = new ArrayList<TariffSpecification>(); // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // do not remove these tariff suggestions // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // We currently assume there is at least one tariff we suggest for // consumption (and in the future I might assume the same for production, // etc.) the assumption is made inside tariff suggestion makers, (and maybe // other places) pt = PowerType.CONSUMPTION; // spec = new TariffSpecification(brokerContext.getBroker(), pt); // //.withPeriodicPayment(DEFAULT_PERIODIC_PAYMENT); // rate = new Rate().withValue(baseRate * 1.105); // spec.addRate(rate); // //publishTariffMessage(spec); // specsToPublish.add(spec); // // spec = new TariffSpecification(brokerContext.getBroker(), pt); // //.withPeriodicPayment(DEFAULT_PERIODIC_PAYMENT); // rate = new Rate().withValue(baseRate * 1.07); // spec.addRate(rate); // //publishTariffMessage(spec); // specsToPublish.add(spec); // // spec = new TariffSpecification(brokerContext.getBroker(), pt); // //.withPeriodicPayment(DEFAULT_PERIODIC_PAYMENT); // rate = new Rate().withValue(baseRate * 1.035); // spec.addRate(rate); // //publishTariffMessage(spec); // specsToPublish.add(spec); // // // spec = new TariffSpecification(brokerContext.getBroker(), pt); // //.withPeriodicPayment(DEFAULT_PERIODIC_PAYMENT); // rate = new Rate().withValue(baseRate * 1); // -0.129 in the default test // spec.addRate(rate); // //publishTariffMessage(spec); // specsToPublish.add(spec); if (!configuratorFactoryService.randomizeSpecs()) { spec = new TariffSpecification(brokerContext.getBroker(), pt); //.withPeriodicPayment(DEFAULT_PERIODIC_PAYMENT); rate = new Rate().withValue(baseRate * 0.965); spec.addRate(rate); spec = configuratorFactoryService.randomizeSpecs() ? randomizeSpec(spec) : spec; //publishTariffMessage(spec); specsToPublish.add(spec); spec = new TariffSpecification(brokerContext.getBroker(), pt); //.withPeriodicPayment(DEFAULT_PERIODIC_PAYMENT); rate = new Rate().withValue(baseRate * 0.93); spec.addRate(rate); spec = configuratorFactoryService.randomizeSpecs() ? randomizeSpec(spec) : spec; //publishTariffMessage(spec); specsToPublish.add(spec); spec = new TariffSpecification(brokerContext.getBroker(), pt); //.withPeriodicPayment(DEFAULT_PERIODIC_PAYMENT); rate = new Rate().withValue(baseRate * 0.895); spec.addRate(rate); spec = configuratorFactoryService.randomizeSpecs() ? randomizeSpec(spec) : spec; //publishTariffMessage(spec); specsToPublish.add(spec); spec = new TariffSpecification(brokerContext.getBroker(), pt); //.withPeriodicPayment(DEFAULT_PERIODIC_PAYMENT); rate = new Rate().withValue(baseRate * 0.86); spec.addRate(rate); spec = configuratorFactoryService.randomizeSpecs() ? randomizeSpec(spec) : spec; //publishTariffMessage(spec); specsToPublish.add(spec); spec = new TariffSpecification(brokerContext.getBroker(), pt); //.withPeriodicPayment(DEFAULT_PERIODIC_PAYMENT); rate = new Rate().withValue(baseRate * 0.825); spec.addRate(rate); spec = configuratorFactoryService.randomizeSpecs() ? randomizeSpec(spec) : spec; //publishTariffMessage(spec); specsToPublish.add(spec); } if (!isCooperativeStrategy()) { // publish all created tariffs for (TariffSpecification tariffSpec : specsToPublish) { publishTariffMessage(tariffSpec); } } else { // cooperative step-1 spec = new TariffSpecification(brokerContext.getBroker(), pt); //.withPeriodicPayment(DEFAULT_PERIODIC_PAYMENT); rate = new Rate().withValue(-(0.500 - 0.010 * randomGen.nextDouble())); // -0.129 in the default test spec.addRate(rate); spec = configuratorFactoryService.randomizeSpecs() ? randomizeSpec(spec) : spec; publishTariffMessage(spec); } if (configuratorFactoryService.isUseSolar()) { log.debug("verify production tariffs don't leak into the consumption code"); pt = PowerType.SOLAR_PRODUCTION; log.warn("using hard coded rate for production"); double ARTIFICIAL_INFLATE = 0.0; // 0.005; double productionRate = 0.015; // + 0.002 * randomGen.nextDouble() + ARTIFICIAL_INFLATE; spec = new TariffSpecification(brokerContext.getBroker(), pt); rate = new Rate().withValue(productionRate); spec.addRate(rate); publishTariffMessage(spec); } //for (PowerType pt : customerProfiles.keySet()) { //// we'll just do fixed-rate tariffs for now //double rateValue; //if (pt.isConsumption()) //rateValue = ((marketPrice + fixedPerKwh) * (1.0 + defaultMargin)); //else ////rateValue = (-1.0 * marketPrice / (1.0 + defaultMargin)); //rateValue = -2.0 * marketPrice; //if (pt.isInterruptible()) //rateValue *= 0.7; // Magic number!! price break for interruptible //TariffSpecification spec = //new TariffSpecification(brokerContext.getBroker(), pt) //.withPeriodicPayment(defaultPeriodicPayment); //Rate rate = new Rate().withValue(rateValue); //if (pt.isInterruptible()) { //// set max curtailment //rate.withMaxCurtailment(0.1); //} //spec.addRate(rate); //customerSubscriptions.put(spec, new HashMap<CustomerInfo, CustomerRecord>()); //tariffRepo.addSpecification(spec); //brokerContext.sendMessage(spec); //} // } // Checks to see whether our tariffs need fine-tuning private void improveTariffs(int currentTimeslotIndex) { if (bootstrapTimeSlotNum == -1) { bootstrapTimeSlotNum = Competition.currentCompetition().getBootstrapDiscardedTimeslots() + Competition.currentCompetition().getBootstrapTimeslotCount(); } // special case: check if all agents crashed if (!tookAllCrashedAction && areAllBrokersCrashed(currentTimeslotIndex)) { executeAllCrashedAction(); tookAllCrashedAction = true; return; } // normal case - no one crashed if (isPublishingSlot(currentTimeslotIndex, PowerType.CONSUMPTION)) { log.info("checking whether to publish consumption-tariffs, timeslot " + currentTimeslotIndex); log.debug("currently doing check-and-publish-tariffs for all CONSUMPTION together"); boolean useCanUse = true; checkAndPossiblyPublishConsumptionTariff(currentTimeslotIndex, useCanUse); } if (isPublishingSlot(currentTimeslotIndex, PowerType.PRODUCTION) && configuratorFactoryService.isUseSolar()) { log.info("checking whether to publish production-tariffs, timeslot " + currentTimeslotIndex); log.debug("currently doing check-and-publish-tariffs for SOLAR-PRODUCTION only"); boolean useCanUse = true; checkAndPossiblyPublishProductionTariff(currentTimeslotIndex, useCanUse); } } /** * Revoke all my tariffs. Should be called at most once per game */ private void executeAllCrashedAction() { log.warn("executeAllCrashedAction()"); List<TariffSpecification> mySpecs = tariffRepoMgr .findTariffSpecificationsByBroker(brokerContext.getBroker()); for (TariffSpecification spec : mySpecs) { // revoke the old one TariffRevoke revoke = new TariffRevoke(brokerContext.getBroker(), spec); brokerContext.sendMessage(revoke); } // publish extreme specs // // CONSUMPTION TariffSpecification consumptionSpec = new TariffSpecification(brokerContext.getBroker(), PowerType.CONSUMPTION); Rate rate = new Rate().withValue(-0.492); // this is what utility-arch chose for this case consumptionSpec.addRate(rate); publishTariffMessage(consumptionSpec); // // SOLAR_PRODUCTION if (configuratorFactoryService.isUseSolar()) { TariffSpecification productionSpec = new TariffSpecification(brokerContext.getBroker(), PowerType.SOLAR_PRODUCTION); rate = new Rate().withValue(0.0155); productionSpec.addRate(rate); publishTariffMessage(productionSpec); } } private boolean areAllBrokersCrashed(int currentTimeslotIndex) { // don't check in the first day if (currentTimeslotIndex < 360 + 24) { return false; } List<TariffSpecification> otherBrokersTariffs = getAllCompetitorTariffs(true); for (TariffSpecification spec : otherBrokersTariffs) { if (!spec.getBroker().getUsername().equals("default broker")) { // found competing tariff, not all crashed return false; } } log.warn("It seems that all agents crashed"); return true; } /** * We publish only slots right before customers evaluate tariffs * @param powerType */ private boolean isPublishingSlot(int timeslotIndex, PowerType powerType) { if (powerType == PowerType.CONSUMPTION) { if (timeslotIndex == 360 + 1) { return true; } if (timeslotIndex == 360 + 4) { return false; } boolean checkWhetherToPublish; if (timeslotIndex - bootstrapTimeSlotNum < configuratorFactoryService.initialPublishingPeriod()) { checkWhetherToPublish = (timeslotIndex - (bootstrapTimeSlotNum)) % 6 == 4; // not 5 since too close to 6 and might not complete in time } else { checkWhetherToPublish = (timeslotIndex - (bootstrapTimeSlotNum)) % 24 == 4; } return checkWhetherToPublish; } if (powerType == PowerType.PRODUCTION) { if (timeslotIndex == 360 + 2) { return false; } boolean checkWhetherToPublish; if (timeslotIndex - bootstrapTimeSlotNum < configuratorFactoryService.initialPublishingPeriod()) { checkWhetherToPublish = (timeslotIndex - (bootstrapTimeSlotNum)) % 6 == 2; // not 1, since subsciptions still updated? not 3 since to close to 4 (cons-tariffs, above) } else { checkWhetherToPublish = (timeslotIndex - (bootstrapTimeSlotNum)) % 24 == 4 + 12; } return checkWhetherToPublish; } log.warn("unknown power type"); return false; } private void checkAndPossiblyPublishConsumptionTariff(int currentTimeslotIndex, boolean useCanUse) { //if (ConfigServerBroker.isPauseServer()) { // brokerContext.sendMessage(new PauseRequest(brokerContext.getBroker())); //} boolean randomize = configuratorFactoryService.randomizeSpecs(); if (!randomize) { if (configuratorFactoryService.isUseUtilityArch()) { // THIS IS OUR NORMAL, STRONGEST STRATEGY for consumption tariffs log.info("USING UTILITY-ARCHITECTURE "); List<TariffSpecification> competitorTariffs = getAllCompetitorTariffs(useCanUse); // CHANGESOLAR HashMap<TariffSpecification, HashMap<CustomerInfo, Integer>> tariffSubscriptions = getCustomerSubscriptions(/*PowerType.CONSUMPTION*/); //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // LATTE: // The following function call encapsulates lines 2-26 of the LATTE // algorithm (for consumption tariffs). // // Note: LATTE's subroutine names do not directly correspond // to function names in this code. // //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // //log.info("checkAndPossiblyPublishConsumptionTariff() " + timeslotIndex + " " + (useCanUse ? "" : "not") + " using canUse"); List<TariffMessage> tariffActions = configuratorFactoryService.getConsumptionTariffActionGenerator() .selectTariffActions(useCanUse, tariffSubscriptions, competitorTariffs, marketManager, contextManager, costCurvesPredictor, currentTimeslotIndex, configuratorFactoryService.isUseRevoke(), brokerContext.getBroker()); // add 4 tariffs if it's second publication period and we are 'cooperative' if (currentTimeslotIndex < (360 + 6) && // first step utility-arch used isCooperativeStrategy() && // using 'cooperative' first-step with single (rather than 5) tariffs tariffActions.size() != 0 && // utility-arch recommends publishing tariffActions.get(0).getClass() == TariffSpecification.class) { // should always be true in timestep < 366 for (int i = 1; i <= 4; ++i) { TariffSpecification spec = new TariffSpecification(brokerContext.getBroker(), PowerType.CONSUMPTION); double recommendedRate = ((TariffSpecification) tariffActions.get(0)).getRates().get(0) .getValue(); //double zeroCenteredRandom = randomGen.nextDouble() - 0.5; //Rate rate = new Rate().withValue(recommendedRate + 0.0005 * zeroCenteredRandom); // -0.129 in the default test Rate rate = new Rate().withValue( recommendedRate + i * 0.001 + randomGen.nextDouble() * 0.00001 /*<= that's 0.001*/); // -0.129 in the default test spec.addRate(rate); tariffActions.add(spec); } } log.info("number of suggested specs: (spec-actions):" + tariffActions.size()); for (TariffMessage action : tariffActions) { publishTariffMessage(action); } } else { // not using utility architecture - using a baseline strategy log.warn("USING UNDERCUTTING "); ////////////////////////////////////////////////////// //UNDERCUTTING STRATEGY // //// publish 80%-95% between market and competing PowerType pt = PowerType.CONSUMPTION; double avgMktPrice = -marketManager.getMeanMarketPricePerKWH(); double priceLowerBound = avgMktPrice + contextManager.getDistributionFee(); double competingMinRateValue = getCompetingMinRateValue(pt, priceLowerBound); double myMinRateValue = getMyMinRateValue(pt); if (-priceLowerBound < -competingMinRateValue && -competingMinRateValue <= -myMinRateValue) { TariffSpecification spec = new TariffSpecification(brokerContext.getBroker(), pt); double randomElem = 0.8 + randomGen.nextDouble() * (0.95 - 0.8); double rateValue = avgMktPrice + randomElem * (competingMinRateValue - avgMktPrice); Rate rate = new Rate().withValue(rateValue); spec.addRate(rate); publishTariffMessage(spec); } // //END - UNDERCUTTING STRATEGY ////////////////////////////////////////////////////// } } else { // randomizing.. log.warn("USING RANDOMIZE "); // /////////////////////////////////////////////////////////////////// // THIS IS A STRATEGY FOR CONFUSING THE OPPONENTS DURING QUALIFICATION // /////////////////////////////////////////////////////////////////// // Computing some prelimnary values needed for any // strategy below PowerType pt = PowerType.CONSUMPTION; //log.info("PowerType " + pt); // //// get competing min rate double avgMktPrice = -marketManager.getMeanMarketPricePerKWH(); log.info("avgMktPrice " + avgMktPrice + " timeslot " + currentTimeslotIndex); double priceLowerBound = avgMktPrice /*ignore dist-fee so I lose money..*/;// + contextManager.getDistributionFee(); log.info("priceLowerBound " + priceLowerBound); double competingMinRateValue = getCompetingMinRateValue(pt, priceLowerBound); log.info("competing min rate " + competingMinRateValue); double myMinRateValue = getMyMinRateValue(pt); log.info("myMinRateValue " + myMinRateValue); // if (-priceLowerBound < -competingMinRateValue && // TODO: fix using '-' to something better -competingMinRateValue <= -myMinRateValue * 1.1 /* even if above me, within 10%*/ ) { log.info("publishing"); ////////////////////////////////////////////////////// //UNDERCUTTING STRATEGY // //// publish 66% between market and competing TariffSpecification spec = new TariffSpecification(brokerContext.getBroker(), pt); //// .withPeriodicPayment(defaultPeriodicPayment); double randomElem = 0.4 + randomGen.nextDouble() * (0.7 - 0.4); double rateValue = avgMktPrice + randomElem * (competingMinRateValue - avgMktPrice); ////double rateValue = avgMktPrice + randomRatio * (competingMinRateValue - avgMktPrice); Rate rate = new Rate().withValue(rateValue); //// if interuptible, improve curtailement ratio //if (pt.isInterruptible()) { //double minCurtailRatio = getMinCurtailRatio(pt); //// TODO: but note that currently I am not using curtailment //rate.withMaxCurtailment(minCurtailRatio * 0.95); //} spec.addRate(rate); spec = configuratorFactoryService.randomizeSpecs() ? randomizeSpec(spec) : spec; publishTariffMessage(spec); // //END - UNDERCUTTING STRATEGY // ////////////////////////////////////////////////////// //Publish intermeidate rate between undercutting and competing ////////////////////////////////////////////////////// //// publish additional rate between the competing and the low //spec = new TariffSpecification(brokerContext.getBroker(), pt); //// .withPeriodicPayment(defaultPeriodicPayment); //rateValue = avgMktPrice + (randomRatio + 0.5 * (1 - randomRatio) ) * (competingMinRateValue - avgMktPrice); //rate = new Rate().withValue(rateValue); //// if interuptible, improve curtailement ratio //if (pt.isInterruptible()) { //double minCurtailRatio = getMinCurtailRatio(pt); //// TODO: but note that currently I am not using curtailment //rate.withMaxCurtailment(minCurtailRatio * 0.95); //} //spec.addRate(rate); //publishTariffMessage(spec); // //END Publish intermeidate rate between undercutting and competing ////////////////////////////////////////////////////// // ////////////////////////////////////////////////////// //SURROUNDING STRATEGY // //TariffSpecification spec; //Rate rate; // //spec = new TariffSpecification(brokerContext.getBroker(), pt); //// .withPeriodicPayment(defaultPeriodicPayment); //double rateValue = avgMktPrice + 0.97 * (competingMinRateValue - avgMktPrice); ////double rateValue = avgMktPrice + randomRatio * (competingMinRateValue - avgMktPrice); //rate = new Rate().withValue(rateValue); //// if interuptible, improve curtailement ratio //if (pt.isInterruptible()) { //double minCurtailRatio = getMinCurtailRatio(pt); //// TODO: but note that currently I am not using curtailment //rate.withMaxCurtailment(minCurtailRatio * 0.95); //} //spec.addRate(rate); //publishTariffMessage(spec); // //// publish additional rate above the competing //spec = new TariffSpecification(brokerContext.getBroker(), pt); //// .withPeriodicPayment(defaultPeriodicPayment); //rateValue = avgMktPrice + 1.015364 * (competingMinRateValue - avgMktPrice); //rate = new Rate().withValue(rateValue); //// if interuptible, improve curtailement ratio //if (pt.isInterruptible()) { //double minCurtailRatio = getMinCurtailRatio(pt); //// TODO: but note that currently I am not using curtailment //rate.withMaxCurtailment(minCurtailRatio * 0.95); //} //spec.addRate(rate); //publishTariffMessage(spec); // //// publish additional rate above the competing //spec = new TariffSpecification(brokerContext.getBroker(), pt); //// .withPeriodicPayment(defaultPeriodicPayment); //rateValue = avgMktPrice + 1.06 * (competingMinRateValue - avgMktPrice); //rate = new Rate().withValue(rateValue); //// if interuptible, improve curtailement ratio //if (pt.isInterruptible()) { //double minCurtailRatio = getMinCurtailRatio(pt); //// TODO: but note that currently I am not using curtailment //rate.withMaxCurtailment(minCurtailRatio * 0.95); //} //spec.addRate(rate); //publishTariffMessage(spec); // //spec = new TariffSpecification(brokerContext.getBroker(), pt); //// .withPeriodicPayment(defaultPeriodicPayment); //rateValue = avgMktPrice + 1.10 * (competingMinRateValue - avgMktPrice); //rate = new Rate().withValue(rateValue); //// if interuptible, improve curtailement ratio //if (pt.isInterruptible()) { //double minCurtailRatio = getMinCurtailRatio(pt); //// TODO: but note that currently I am not using curtailment //rate.withMaxCurtailment(minCurtailRatio * 0.95); //} //spec.addRate(rate); //publishTariffMessage(spec); // // //END - SURROUNDING STRATEGY ////////////////////////////////////////////////////// // ////////////////////////////////////////////////////// //TOU 1, 1.1, 1 1.1, 1, 1.1 ////////////////////////////////////////////////////// //double rateValue = avgMktPrice + 0.99209218347182712 * (competingMinRateValue - avgMktPrice); // //TariffSpecification spec; //Rate rate1, rate2, rate3; //rate1 = new Rate().withValue(rateValue).withDailyBegin(0).withDailyEnd(7); //rate2 = new Rate().withValue(1.1 * rateValue).withDailyBegin(8).withDailyEnd(17); //rate3 = new Rate().withValue(rateValue).withDailyBegin(17).withDailyEnd(23); //spec = new TariffSpecification(brokerContext.getBroker(), pt); //spec.addRate(rate1).addRate(rate2).addRate(rate3); //publishTariffMessage(spec); // //rate1 = new Rate().withValue(1.1 * rateValue).withDailyBegin(0).withDailyEnd(7); //rate2 = new Rate().withValue(rateValue).withDailyBegin(8).withDailyEnd(17); //rate3 = new Rate().withValue(1.1 * rateValue).withDailyBegin(17).withDailyEnd(23); //spec = new TariffSpecification(brokerContext.getBroker(), pt); //spec.addRate(rate1).addRate(rate2).addRate(rate3); //publishTariffMessage(spec); // //END - TOU 1, 1.1, 1 1.1, 1, 1.1 ////////////////////////////////////////////////////// // // //// publish the competing rate //spec = new TariffSpecification(brokerContext.getBroker(), pt); //// .withPeriodicPayment(defaultPeriodicPayment); //rateValue = competingMinRateValue; //rate = new Rate().withValue(rateValue); //// if interuptible, improve curtailement ratio //if (pt.isInterruptible()) { //double minCurtailRatio = getMinCurtailRatio(pt); //// TODO: but note that currently I am not using curtailment //rate.withMaxCurtailment(minCurtailRatio * 0.95); //} //spec.addRate(rate); //publishTariffMessage(spec); // // //VARIABLE RATE ////////////////////////////////////////////////////// //// publish 91% between market and competing //TariffSpecification vrspec = new TariffSpecification(brokerContext.getBroker(), pt); //// .withPeriodicPayment(defaultPeriodicPayment); //double rateValue = avgMktPrice + 0.911234 * (competingMinRateValue - avgMktPrice); //double minValue = avgMktPrice; //double expectedMean = rateValue; //double maxValue = rateValue + (rateValue - avgMktPrice); // TODO: assuming min <= rate <= max //Rate vrate = new Rate().withNoticeInterval(3) //.withFixed(false) //.withMinValue(minValue) //.withExpectedMean(expectedMean) //.withMaxValue(maxValue); //vrspec.addRate(vrate); //publishTariffMessage(vrspec); // //END - VARIABLE RATE ////////////////////////////////////////////////////// } } //if (ConfigServerBroker.isPauseServer()) { // brokerContext.sendMessage(new PauseRelease(brokerContext.getBroker())); //} } private boolean isCooperativeStrategy() { boolean isCoop = BrokerUtils.getNumberOfBrokers() <= configuratorFactoryService.getCoopMaxBrkrs(); log.debug("is cooperative strategy: " + isCoop); return isCoop; } private void checkAndPossiblyPublishProductionTariff(int currentTimeslotIndex, boolean useCanUse) { //if (ConfigServerBroker.isPauseServer()) { // brokerContext.sendMessage(new PauseRequest(brokerContext.getBroker())); //} if (configuratorFactoryService.isUseUtilityArch()) { // THIS IS OUR NORMAL, STRONG STRATEGY for production tariffs log.info("USING UTILITY-ARCHITECTURE "); List<TariffSpecification> competitorTariffs = getAllCompetitorTariffs(useCanUse); // CHANGESOLAR HashMap<TariffSpecification, HashMap<CustomerInfo, Integer>> tariffSubscriptions = getCustomerSubscriptions(/*PowerType.CONSUMPTION*/); //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // LATTE: // The following function call encapsulates lines 2-26 of the LATTE // algorithm (for production tariffs). // // Note: LATTE's subroutine names do not directly correspond // to function names in this code. // //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // //log.info("checkAndPossiblyPublishConsumptionTariff() " + timeslotIndex + " " + (useCanUse ? "" : "not") + " using canUse"); List<TariffMessage> tariffsToPublish = configuratorFactoryService.getProductionTariffActionGenerator() .selectTariffActions(useCanUse, tariffSubscriptions, competitorTariffs, marketManager, contextManager, costCurvesPredictor, currentTimeslotIndex, configuratorFactoryService.isUseRevoke(), brokerContext.getBroker()); log.info("number of suggested specs: " + tariffsToPublish.size()); for (TariffMessage action : tariffsToPublish) { publishTariffMessage(action); } } //if (ConfigServerBroker.isPauseServer()) { // brokerContext.sendMessage(new PauseRelease(brokerContext.getBroker())); //} } private synchronized List<TariffSpecification> getAllCompetitorTariffs(boolean useCanUse) { // CHANGESOLAR Set<TariffSpecification> competitorTariffSet = new HashSet<TariffSpecification>(); if (useCanUse) { competitorTariffSet.addAll(getCompetingTariffsThatCanUse(PowerType.CONSUMPTION)); competitorTariffSet.addAll(getCompetingTariffsThatCanBeUsedBy(PowerType.CONSUMPTION)); competitorTariffSet.addAll(getCompetingTariffsThatCanUse(PowerType.SOLAR_PRODUCTION)); competitorTariffSet.addAll(getCompetingTariffsThatCanBeUsedBy(PowerType.SOLAR_PRODUCTION)); } else { competitorTariffSet.addAll(getCompetingTariffs(PowerType.CONSUMPTION)); competitorTariffSet.addAll(getCompetingTariffs(PowerType.SOLAR_PRODUCTION)); } List<TariffSpecification> competitorTariffs = new ArrayList<TariffSpecification>(); competitorTariffs.addAll(competitorTariffSet); return competitorTariffs; } /** * @param spec * @return */ private TariffSpecification randomizeSpec(TariffSpecification spec) { double rateValue = spec.getRates().get(0).getValue(); PowerType pt = spec.getPowerType(); int rnd = randomGen.nextInt(4); if (rnd == 0) { return spec; // no randomization, fixed rate } else if (rnd == 1) { // TOU 1, 1.1, 1 TariffSpecification newspec; Rate rate1, rate2, rate3; rate1 = new Rate().withValue(rateValue).withDailyBegin(0).withDailyEnd(7); rate2 = new Rate().withValue(1.001 * rateValue).withDailyBegin(8).withDailyEnd(17); rate3 = new Rate().withValue(rateValue).withDailyBegin(18).withDailyEnd(23); newspec = new TariffSpecification(brokerContext.getBroker(), pt); newspec.addRate(rate1).addRate(rate2).addRate(rate3); return newspec; } else if (rnd == 2) { // TOU 1.1, 1, 1.1 TariffSpecification newspec; Rate rate1, rate2, rate3; rate1 = new Rate().withValue(1.001 * rateValue).withDailyBegin(0).withDailyEnd(7); rate2 = new Rate().withValue(rateValue).withDailyBegin(8).withDailyEnd(17); rate3 = new Rate().withValue(1.001 * rateValue).withDailyBegin(18).withDailyEnd(23); newspec = new TariffSpecification(brokerContext.getBroker(), pt); newspec.addRate(rate1).addRate(rate2).addRate(rate3); return newspec; } else { //VARIABLE RATE ////////////////////////////////////////////////////// // publish 91% between market and competing TariffSpecification vrspec = new TariffSpecification(brokerContext.getBroker(), pt); double minValue = rateValue * 0.999; double expectedMean = rateValue; double maxValue = rateValue * 1.001; Rate vrate = new Rate().withNoticeInterval(3).withFixed(false).withMinValue(minValue) .withExpectedMean(expectedMean).withMaxValue(maxValue); vrspec.addRate(vrate); return vrspec; //END - VARIABLE RATE ////////////////////////////////////////////////////// } } private <T> void printSubscriptions( HashMap<TariffSpecification, HashMap<CustomerInfo, T>> customerSubscriptions2) { for (TariffSpecification spec : customerSubscriptions2.keySet()) { log.info("ps TariffSpec: " + spec); for (CustomerInfo c : customerSubscriptions2.get(spec).keySet()) { log.info("ps customer " + c + " subscriptions " + customerSubscriptions2.get(spec).get(c)); } } } /** * * This method updates the {@link CustomerRecord} in all * the relevant HashMaps. Bootstrap data normally includes * timeslots {24, 25,...,359} * * @param cbd * */ private void produceConsume(CustomerBootstrapData cbd) { PowerType powerType = cbd.getPowerType(); String customerName = cbd.getCustomerName(); CustomerInfo customer = customerRepo.findByNameAndPowerType(customerName, powerType); // NOTE: in general it's safer to get the more specific record first, since // specific records are initialized with more generic ones, so we don't // want a double update to happen. Specifically, currently perhaps it // doesn't matter because this is the start of the game. CustomerRecord powerTypeRecord = getCustomerRecordByPowerType(powerType, customer); CustomerRecord generalRecord = getCustomerGeneralRecord(customer); // - what is offset: it is the first 24 hours that are ignored // - Bootstrap data normally includes timeslots {24, 25,...,359} // - normally 24 timeslots are discarded and the first timeslot in bootstrap record is 24 int offset = Competition.currentCompetition().getBootstrapDiscardedTimeslots(); for (int i = 0; i < cbd.getNetUsage().length; i++) { int bootstrapTimeslot = i + offset; powerTypeRecord.produceConsume(cbd.getNetUsage()[i], customer.getPopulation(), bootstrapTimeslot, true); generalRecord.produceConsume(cbd.getNetUsage()[i], customer.getPopulation(), bootstrapTimeslot, true); } } /** * @param ttx */ private void produceConsume(TariffTransaction ttx) { // defensive sanity check TariffTransaction.Type txType = ttx.getTxType(); if (TariffTransaction.Type.CONSUME != txType && TariffTransaction.Type.PRODUCE != txType) { log.warn("produceConsume is called with the wrong type of transaction - ignoring..."); return; } CustomerInfo customer = ttx.getCustomerInfo(); TariffSpecification tariffSpec = ttx.getTariffSpec(); int customerCount = ttx.getCustomerCount(); int postedTime = ttx.getPostedTimeslotIndex(); PowerType powerType = tariffSpec.getPowerType(); double kWh = ttx.getKWh(); // IMPORTANT: produceConsume must be called from the most specific to the // most general, since if a specific record doesn't exist - it is // initialized from the more generic one, so any other ordering would // result in double updates for the more specific records CustomerRecord tariffRecord = getCustomerRecordByTariff(tariffSpec, customer); tariffRecord.produceConsume(kWh, customerCount, postedTime, true /*hack, true means update both*/); // update general records only if fixed-rate // tariff (want them to hold default profile) // boolean fixed = false; if (tariffSpec.getRates().size() == 1 && tariffSpec.getRates().get(0).isFixed()) { fixed = true; } CustomerRecord profileRecord = getCustomerRecordByPowerType(powerType, customer); profileRecord.produceConsume(kWh, customerCount, postedTime, fixed); CustomerRecord generalRecord = getCustomerGeneralRecord(customer); generalRecord.produceConsume(kWh, customerCount, postedTime, fixed); } /* * This function is unused on TacTex's normal strategy, which doesn't use * variable rate tariffs */ private void publishHourlyCharges(int currentTimeslotIndex) { for (TariffSpecification spec : tariffRepoMgr.findTariffSpecificationsByPowerType(PowerType.CONSUMPTION)) { // scan only my tariffs if (specPublishedByMe(spec)) { for (Rate r : spec.getRates()) { if (r.isFixed() == false) { // need to send a hourly charge long noticeInterval = r.getNoticeInterval(); int destinationTimeslot = (int) (currentTimeslotIndex + noticeInterval + 1);// + 1 just in case log.info("DU noticeInterval " + noticeInterval + " currentTimeslot " + currentTimeslotIndex + " destinationTimeslot " + destinationTimeslot); double marketPrice = -marketManager.getMarketAvgPricePerSlotKWH(destinationTimeslot); log.info("DU marketPrice " + marketPrice); double minValue = Math.abs(r.getMinValue()); double maxValue = Math.abs(r.getMaxValue()); double rateValue = Math.abs(marketPrice + r.getExpectedMean() - minValue); log.info("DU minValue " + minValue + " maxValue " + maxValue + " rateValue " + rateValue); // trim if needed rateValue = Math.min(Math.max(rateValue, minValue), maxValue); log.info("DU rateValue after trimming " + rateValue); rateValue = -rateValue; // this is the rate's sign log.info("DU final rateValue " + rateValue); HourlyCharge h = new HourlyCharge( timeService.getCurrentTime().plus((noticeInterval + 1) * TimeService.HOUR), rateValue); r.addHourlyCharge(h); h.setRateId(r.getId()); VariableRateUpdate v = new VariableRateUpdate(brokerContext.getBroker(), r, h); brokerContext.sendMessage(v); } } } } } private boolean specPublishedByMe(TariffSpecification spec) { return spec.getBroker().getUsername().equals(brokerContext.getBrokerUsername()); } private void publishTariffMessage(TariffMessage action) { if (action.getClass() == TariffSpecification.class) { lastPublishedSpec = (TariffSpecification) action; lastTimeSpecPublished = activateTS; } brokerContext.sendMessage(action); } private double getCompetingMinRateValue(PowerType pt, double priceLowerBound) { // rates are negative (i.e. from customer's perspective) so I actually want // the highest one, which means the highest from customer perspective double competingMinRateValue = -Double.MAX_VALUE; for (TariffSpecification competingTariff : getCompetingTariffsThatCanUse(pt)) { Broker theBroker = competingTariff.getBroker(); if (!specPublishedByMe(competingTariff)) { for (Rate r : competingTariff.getRates()) { double value; if (r.isFixed()) { value = r.getMinValue(); } else { value = r.getExpectedMean(); } // we ignore prices below market if (priceLowerBound > value && value > competingMinRateValue) { competingMinRateValue = value; } } } } return competingMinRateValue; } private double getCompetingMaxProdRateValue(PowerType pt, double rateUpperBound) { // rates are positive (i.e. from customer's perspective) so I want // the highest one - the best for the customer double competingMaxProdRateValue = 0; for (TariffSpecification competingTariff : getCompetingTariffs(pt)) { if (!specPublishedByMe(competingTariff)) { for (Rate r : competingTariff.getRates()) { double value = r.getMinValue(); // we ignore prices below market if (rateUpperBound > value && value > competingMaxProdRateValue) { competingMaxProdRateValue = value; } } } } return competingMaxProdRateValue; } private double getMyMinRateValue(PowerType pt) { // rates are negative (i.e. from customer's perspective) so I actually want // the highest one, which means the highest from customer perspective. double myMinRateValue = -Double.MAX_VALUE; for (TariffSpecification spec : tariffRepoMgr.findTariffSpecificationsByPowerType(pt)) { if (specPublishedByMe(spec)) { for (Rate r : spec.getRates()) { double value; if (r.isFixed()) { value = r.getMinValue(); } else { value = r.getExpectedMean(); } if (value > myMinRateValue) { myMinRateValue = value; } } } } return myMinRateValue; } private double getMyMaxProdRateValue(PowerType pt) { // rates are positive (i.e. from customer's perspective) so I actually want // the highest one - the best for the customer double myMaxRateValue = -Double.MAX_VALUE; for (TariffSpecification spec : tariffRepoMgr.findTariffSpecificationsByPowerType(pt)) { if (specPublishedByMe(spec)) { for (Rate r : spec.getRates()) { double value = r.getMinValue(); if (value > myMaxRateValue) { myMaxRateValue = value; } } } } return myMaxRateValue; } private double getMinCurtailRatio(PowerType pt) { double minCurtailRatio = Double.MAX_VALUE; for (TariffSpecification competingTariff : getCompetingTariffs(pt)) { for (Rate r : competingTariff.getRates()) { double value = r.getMaxCurtailment(); if (value < minCurtailRatio && 0 <= value && value <= 1) { minCurtailRatio = value; } } } return minCurtailRatio; } // ------------- test-support methods ---------------- double getUsageForCustomer(CustomerInfo customer, TariffSpecification tariffSpec, int index) { CustomerRecord record = getCustomerRecordByTariff(tariffSpec, customer); return record.getUsage(index, false); } //test-support method ArrayRealVector getRawUsageForCustomerByTariff(CustomerInfo customer, TariffSpecification spec) { return getCustomerRecordByTariff(spec, customer).getUsageArray(false); } // test-support method HashMap<PowerType, double[]> getRawUsageForCustomerByPowerType(CustomerInfo customer) { HashMap<PowerType, double[]> result = new HashMap<PowerType, double[]>(); for (PowerType type : customerProfilesByPowerType.keySet()) { CustomerRecord record = customerProfilesByPowerType.get(type).get(customer); if (record != null) { result.put(type, record.usage); } } return result; } public synchronized ArrayRealVector getGeneralRawUsageForCustomer(CustomerInfo customer, boolean fixed) { return getCustomerGeneralRecord(customer).getUsageArray(fixed); } // test-support method HashMap<String, Integer> getCustomerCounts() { HashMap<String, Integer> result = new HashMap<String, Integer>(); for (TariffSpecification spec : customerSubscriptions.keySet()) { HashMap<CustomerInfo, CustomerRecord> customerMap = customerSubscriptions.get(spec); for (CustomerRecord record : customerMap.values()) { result.put(record.customer.getName() + spec.getPowerType(), record.getSubscribedPopulation()); } } return result; } //-------------------- Customer-model recording --------------------- /** * Keeps track of customer status and usage. Usage is stored * per-customer-unit, but reported as the product of the per-customer * quantity and the subscribed population. This allows the broker to use * historical usage data as the subscribed population shifts. */ public class CustomerRecord { private CustomerInfo customer; private int subscribedPopulation = 0; private double[] usage; private double[] fixedusage; private double alpha = 0.3; /** * Creates an empty record */ CustomerRecord(CustomerInfo customer) { super(); this.customer = customer; this.usage = new double[configuratorFactoryService.CONSTANTS.USAGE_RECORD_LENGTH()]; this.fixedusage = new double[configuratorFactoryService.CONSTANTS.USAGE_RECORD_LENGTH()]; } CustomerRecord(CustomerRecord oldRecord) { super(); this.customer = oldRecord.customer; this.usage = Arrays.copyOf(oldRecord.usage, configuratorFactoryService.CONSTANTS.USAGE_RECORD_LENGTH()); this.fixedusage = Arrays.copyOf(oldRecord.fixedusage, configuratorFactoryService.CONSTANTS.USAGE_RECORD_LENGTH()); } // Returns the CustomerInfo for this record CustomerInfo getCustomerInfo() { return customer; } ArrayRealVector getUsageArray(boolean fixed) { if (fixed) return new ArrayRealVector(fixedusage); else return new ArrayRealVector(usage); } // Adds new individuals to the count void signup(int population) { subscribedPopulation = Math.min(customer.getPopulation(), subscribedPopulation + population); } // Removes individuals from the count void withdraw(int population) { subscribedPopulation -= population; if (subscribedPopulation < 0) { log.error("subscribed population < 0: " + subscribedPopulation + ", resetting to 0"); subscribedPopulation = 0; } } // Customer produces or consumes power. We assume the kwh value is negative // for production, positive for consumption //void produceConsume (double kwh, int population, Instant when) //{ // int index = getIndex(when); // produceConsume(kwh, population, index); //} // The usage length is one week: 7*24, so the second week enters into // similar slots as the first week, and a slot is updated using an // exponential smoothing // // store profile data at the given index void produceConsume(double kwh, int population, int rawIndex, boolean fixed) { log.debug("produce consume is averaging regardless of the number of customers"); int index = getIndex(rawIndex); double kwhPerCustomer = kwh / (double) population; double oldUsage = usage[index]; if (oldUsage == 0.0) { // assume this is the first time usage[index] = kwhPerCustomer; } else { // exponential smoothing usage[index] = alpha * kwhPerCustomer + (1.0 - alpha) * oldUsage; } if (fixed) { double oldUsage1 = fixedusage[index]; if (oldUsage1 == 0.0) { // assume this is the first time fixedusage[index] = kwhPerCustomer; } else { // exponential smoothing fixedusage[index] = alpha * kwhPerCustomer + (1.0 - alpha) * oldUsage1; } } } double getUsage(int index, boolean fixed) { if (index < 0) { log.warn("usage requested for negative index " + index); index = 0; } if (fixed) return (fixedusage[getIndex(index)] * (double) subscribedPopulation); else return (usage[getIndex(index)] * (double) subscribedPopulation); } // we assume here that timeslot index always matches the number of // timeslots that have passed since the beginning of the simulation. int getIndex(Instant when) { int result = (int) ((when.getMillis() - timeService.getBase()) / (Competition.currentCompetition().getTimeslotDuration())); return result; } private int getIndex(int rawIndex) { return rawIndex % usage.length; } public int getSubscribedPopulation() { return subscribedPopulation; } } }