diff --git a/lib/matlabcontrol-4.1.0.jar b/lib/matlabcontrol-4.1.0.jar new file mode 100644 index 0000000..93622af Binary files /dev/null and b/lib/matlabcontrol-4.1.0.jar differ diff --git a/pom.xml b/pom.xml index 558b08c..a27d4f5 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ spring-jms ${spring.version} - + org.apache.activemq @@ -94,6 +94,11 @@ commons-io 1.3.2 + + org.matlabcontrol + matlabcontrol + 4.1.0 + diff --git a/src/main/java/org/powertac/samplebroker/PortfolioManagerProxy.java b/src/main/java/org/powertac/samplebroker/PortfolioManagerProxy.java new file mode 100644 index 0000000..092b290 --- /dev/null +++ b/src/main/java/org/powertac/samplebroker/PortfolioManagerProxy.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2012-2013 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 org.powertac.samplebroker; + +import matlabcontrol.MatlabConnectionException; +import matlabcontrol.MatlabInvocationException; +import matlabcontrol.MatlabProxy; +import matlabcontrol.MatlabProxyFactory; +import org.apache.log4j.Logger; +import org.joda.time.Instant; +import org.powertac.common.*; +import org.powertac.common.msg.*; +import org.powertac.common.repo.CustomerRepo; +import org.powertac.common.repo.TariffRepo; +import org.powertac.common.repo.TimeslotRepo; +import org.powertac.samplebroker.core.BrokerPropertiesService; +import org.powertac.samplebroker.interfaces.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; + + +/** + * Handles portfolio-management responsibilities for the broker. This + * includes composing and offering tariffs, keeping track of customers and their + * usage, monitoring tariff offerings from competing brokers. + *

+ * A more complete broker implementation might split this class into two or + * more classes; the keys are to decide which messages each class handles, + * what each class does on the activate() method, and what data needs to be + * managed and shared. + * + * @author John Collins + */ +@Service // Spring creates a single instance at startup +public class PortfolioManagerProxy + implements PortfolioManager, Initializable, Activatable +{ + static private Logger log = Logger.getLogger(PortfolioManagerProxy.class); + + // Spring fills in Autowired dependencies through a naming convention + @Autowired + private BrokerPropertiesService propertiesService; + + @Autowired + private TimeslotRepo timeslotRepo; + + @Autowired + private TariffRepo tariffRepo; + + @Autowired + private CustomerRepo customerRepo; + + @Autowired + private MarketManager marketManager; + + @Autowired + private TimeService timeService; + + private MatlabProxy proxy; + + /** + * Default constructor registers for messages, must be called after + * message router is available. + */ + public PortfolioManagerProxy () + { + super(); + + try { + proxy = new MatlabProxyFactory().getProxy(); + proxy.eval("cd src/main/matlab/portfoliomanager;"); + } + catch (MatlabConnectionException e) { + e.printStackTrace(); + System.exit(1); + } + catch (MatlabInvocationException e) { + e.printStackTrace(); + System.exit(1); + } + } + + /** + * Per-game initialization. Configures parameters and registers + * message handlers. + */ + @Override // from Initializable + public void initialize (BrokerContext context) + { + propertiesService.configureMe(this); + + try { + proxy.feval("mInit", context, timeslotRepo, tariffRepo, + customerRepo, marketManager, timeService, log, null); + } + catch (MatlabInvocationException mie) { + mie.printStackTrace(); + } + } + + // -------------- data access ------------------ + /** + * Returns total usage for a given timeslot (represented as a simple index). + */ + @Override + public double collectUsage (int index) + { + double result = 0.0; + try { + Object[] returnArguments = proxy.returningFeval("collectUsage", 1, index); + result = ((double[]) returnArguments[0])[0]; + + if (Double.isNaN(result)) { + result = 0.0; + } + } + catch (MatlabInvocationException mie) { + mie.printStackTrace(); + } + + return -result; // convert to needed energy account balance + } + + // -------------- 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 void handleMessage (CustomerBootstrapData cbd) + { + try { + proxy.feval("msgCustomerBootstrapData", cbd); + } + catch (MatlabInvocationException mie) { + mie.printStackTrace(); + } + } + + /** + * Handles a TariffSpecification. These are sent by the server when new tariffs are + * published. If it's not ours, then it's a competitor's tariff. We keep track of + * competing tariffs locally, and we also store them in the tariffRepo. + */ + public void handleMessage (TariffSpecification spec) + { + try { + proxy.feval("msgTariffSpecification", spec); + } + catch (MatlabInvocationException mie) { + mie.printStackTrace(); + } + } + + /** + * Handles a TariffStatus message. This should do something when the status + * is not SUCCESS. + */ + public void handleMessage (TariffStatus ts) + { + try { + proxy.feval("msgTariffStatus", ts); + } + catch (MatlabInvocationException mie) { + mie.printStackTrace(); + } + } + + /** + * Handles a TariffTransaction. We only care about certain types: PRODUCE, + * CONSUME, SIGNUP, and WITHDRAW. + */ + public void handleMessage (TariffTransaction ttx) + { + try { + proxy.feval("msgTariffTransaction", ttx); + } + catch (MatlabInvocationException mie) { + mie.printStackTrace(); + } + } + + /** + * Handles a TariffRevoke message from the server, indicating that some + * tariff has been revoked. + */ + public void handleMessage (TariffRevoke tr) + { + try { + proxy.feval("msgTariffRevoke", tr); + } + catch (MatlabInvocationException mie) { + mie.printStackTrace(); + } + } + + /** + * Handles a BalancingControlEvent, sent when a BalancingOrder is + * exercised by the DU. + */ + public void handleMessage (BalancingControlEvent bce) + { + try { + proxy.feval("msgBalancingControlEvent", bce); + } + catch (MatlabInvocationException mie) { + mie.printStackTrace(); + } + } + + // --------------- activation ----------------- + + /** + * Called after TimeslotComplete msg received. Note that activation order + * among modules is non-deterministic. + */ + @Override // from Activatable + public void activate (int timeslotIndex) + { + try { + proxy.feval("activate", timeslotIndex); + } + catch (MatlabInvocationException mie) { + mie.printStackTrace(); + } + } +} diff --git a/src/main/java/org/powertac/samplebroker/PortfolioManagerService.java b/src/main/java/org/powertac/samplebroker/PortfolioManagerService.java deleted file mode 100644 index 6b9bc10..0000000 --- a/src/main/java/org/powertac/samplebroker/PortfolioManagerService.java +++ /dev/null @@ -1,654 +0,0 @@ -/* - * Copyright (c) 2012-2013 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 org.powertac.samplebroker; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - -import org.apache.log4j.Logger; -import org.joda.time.Instant; -import org.powertac.common.Broker; -import org.powertac.common.Competition; -import org.powertac.common.CustomerInfo; -import org.powertac.common.IdGenerator; -import org.powertac.common.Rate; -import org.powertac.common.TariffSpecification; -import org.powertac.common.TariffTransaction; -import org.powertac.common.TimeService; -import org.powertac.common.config.ConfigurableValue; -import org.powertac.common.enumerations.PowerType; -import org.powertac.common.msg.BalancingControlEvent; -import org.powertac.common.msg.BalancingOrder; -import org.powertac.common.msg.CustomerBootstrapData; -import org.powertac.common.msg.TariffRevoke; -import org.powertac.common.msg.TariffStatus; -import org.powertac.common.repo.CustomerRepo; -import org.powertac.common.repo.TariffRepo; -import org.powertac.common.repo.TimeslotRepo; -import org.powertac.samplebroker.core.BrokerPropertiesService; -import org.powertac.samplebroker.interfaces.Activatable; -import org.powertac.samplebroker.interfaces.BrokerContext; -import org.powertac.samplebroker.interfaces.Initializable; -import org.powertac.samplebroker.interfaces.MarketManager; -import org.powertac.samplebroker.interfaces.PortfolioManager; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** - * Handles portfolio-management responsibilities for the broker. This - * includes composing and offering tariffs, keeping track of customers and their - * usage, monitoring tariff offerings from competing brokers. - * - * A more complete broker implementation might split this class into two or - * more classes; the keys are to decide which messages each class handles, - * what each class does on the activate() method, and what data needs to be - * managed and shared. - * - * @author John Collins - */ -@Service // Spring creates a single instance at startup -public class PortfolioManagerService -implements PortfolioManager, Initializable, Activatable -{ - static private Logger log = Logger.getLogger(PortfolioManagerService.class); - - private BrokerContext brokerContext; // master - - // Spring fills in Autowired dependencies through a naming convention - @Autowired - private BrokerPropertiesService propertiesService; - - @Autowired - private TimeslotRepo timeslotRepo; - - @Autowired - private TariffRepo tariffRepo; - - @Autowired - private CustomerRepo customerRepo; - - @Autowired - private MarketManager marketManager; - - @Autowired - private TimeService timeService; - - // ---- Portfolio records ----- - // 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> customerProfiles; - private HashMap> customerSubscriptions; - private HashMap> competingTariffs; - - // Configurable parameters for tariff composition - // Override defaults in src/main/resources/config/broker.config - // or in top-level config file - @ConfigurableValue(valueType = "Double", - description = "target profit margin") - private double defaultMargin = 0.5; - - @ConfigurableValue(valueType = "Double", - description = "Fixed cost/kWh") - private double fixedPerKwh = -0.06; - - @ConfigurableValue(valueType = "Double", - description = "Default daily meter charge") - private double defaultPeriodicPayment = -1.0; - - /** - * Default constructor registers for messages, must be called after - * message router is available. - */ - public PortfolioManagerService () - { - super(); - } - - /** - * Per-game initialization. Configures parameters and registers - * message handlers. - */ - @Override // from Initializable -// @SuppressWarnings("unchecked") - public void initialize (BrokerContext context) - { - this.brokerContext = context; - propertiesService.configureMe(this); - customerProfiles = new HashMap>(); - customerSubscriptions = new HashMap>(); - competingTariffs = new HashMap>(); - } - - // -------------- data access ------------------ - - /** - * Returns the CustomerRecord for the given type and customer, creating it - * if necessary. - */ - CustomerRecord getCustomerRecordByPowerType (PowerType type, - CustomerInfo customer) - { - HashMap customerMap = - customerProfiles.get(type); - if (customerMap == null) { - customerMap = new HashMap(); - customerProfiles.put(type, customerMap); - } - CustomerRecord record = customerMap.get(customer); - if (record == null) { - record = new CustomerRecord(customer); - customerMap.put(customer, record); - } - return record; - } - - /** - * Returns the customer record for the given tariff spec and customer, - * creating it if necessary. - */ - CustomerRecord getCustomerRecordByTariff (TariffSpecification spec, - CustomerInfo customer) - { - HashMap customerMap = - customerSubscriptions.get(spec); - if (customerMap == null) { - customerMap = new HashMap(); - 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; - } - - /** - * Finds the list of competing tariffs for the given PowerType. - */ - List getCompetingTariffs (PowerType powerType) - { - List result = competingTariffs.get(powerType); - if (result == null) { - result = new ArrayList(); - competingTariffs.put(powerType, result); - } - return result; - } - - /** - * Adds a new competing tariff to the list. - */ - private void addCompetingTariff (TariffSpecification spec) - { - getCompetingTariffs(spec.getPowerType()).add(spec); - } - - /** - * Returns total usage for a given timeslot (represented as a simple index). - */ - @Override - public double collectUsage (int index) - { - double result = 0.0; - for (HashMap customerMap : customerSubscriptions.values()) { - for (CustomerRecord record : customerMap.values()) { - result += record.getUsage(index); - } - } - return -result; // convert to needed energy account balance - } - - // -------------- 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 void handleMessage (CustomerBootstrapData cbd) - { - CustomerInfo customer = - customerRepo.findByNameAndPowerType(cbd.getCustomerName(), - cbd.getPowerType()); - CustomerRecord record = getCustomerRecordByPowerType(cbd.getPowerType(), customer); - int offset = (timeslotRepo.currentTimeslot().getSerialNumber() - - cbd.getNetUsage().length); - int subs = record.subscribedPopulation; - record.subscribedPopulation = customer.getPopulation(); - for (int i = 0; i < cbd.getNetUsage().length; i++) { - record.produceConsume(cbd.getNetUsage()[i], i); - } - record.subscribedPopulation = subs; - } - - /** - * Handles a TariffSpecification. These are sent by the server when new tariffs are - * published. If it's not ours, then it's a competitor's tariff. We keep track of - * competing tariffs locally, and we also store them in the tariffRepo. - */ - public void handleMessage (TariffSpecification spec) - { - Broker theBroker = spec.getBroker(); - if (brokerContext.getBrokerUsername().equals(theBroker.getUsername())) { - if (theBroker != brokerContext) - // strange bug, seems harmless for now - log.info("Resolution failed for broker " + theBroker.getUsername()); - // if it's ours, just log it, because we already put it in the repo - TariffSpecification original = - tariffRepo.findSpecificationById(spec.getId()); - if (null == original) - log.error("Spec " + spec.getId() + " not in local repo"); - log.info("published " + spec); - } - else { - // otherwise, keep track of competing tariffs, and record in the repo - addCompetingTariff(spec); - tariffRepo.addSpecification(spec); - } - } - - /** - * Handles a TariffStatus message. This should do something when the status - * is not SUCCESS. - */ - public void handleMessage (TariffStatus ts) - { - log.info("TariffStatus: " + ts.getStatus()); - } - - /** - * Handles a TariffTransaction. We only care about certain types: PRODUCE, - * CONSUME, SIGNUP, and WITHDRAW. - */ - public void handleMessage(TariffTransaction ttx) - { - // make sure we have this tariff - TariffSpecification newSpec = ttx.getTariffSpec(); - if (newSpec == null) { - log.error("TariffTransaction type=" + ttx.getTxType() - + " for unknown spec"); - } - else { - TariffSpecification oldSpec = - tariffRepo.findSpecificationById(newSpec.getId()); - if (oldSpec != newSpec) { - log.error("Incoming spec " + newSpec.getId() + " not matched in repo"); - } - } - TariffTransaction.Type txType = ttx.getTxType(); - CustomerRecord record = getCustomerRecordByTariff(ttx.getTariffSpec(), - ttx.getCustomerInfo()); - - if (TariffTransaction.Type.SIGNUP == txType) { - // keep track of customer counts - record.signup(ttx.getCustomerCount()); - } - else if (TariffTransaction.Type.WITHDRAW == txType) { - // customers presumably found a better deal - record.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() != record.subscribedPopulation) { - log.warn("production by subset " + ttx.getCustomerCount() + - " of subscribed population " + record.subscribedPopulation); - } - record.produceConsume(ttx.getKWh(), ttx.getPostedTime()); - } - else if (TariffTransaction.Type.CONSUME == txType) { - if (ttx.getCustomerCount() != record.subscribedPopulation) { - log.warn("consumption by subset " + ttx.getCustomerCount() + - " of subscribed population " + record.subscribedPopulation); - } - record.produceConsume(ttx.getKWh(), ttx.getPostedTime()); - } - } - - /** - * Handles a TariffRevoke message from the server, indicating that some - * tariff has been revoked. - */ - public void handleMessage (TariffRevoke tr) - { - Broker source = tr.getBroker(); - log.info("Revoke tariff " + tr.getTariffId() - + " from " + tr.getBroker().getUsername()); - // if it's from some other broker, we need to remove it from the - // tariffRepo, and from the competingTariffs list - if (!(source.getUsername().equals(brokerContext.getBrokerUsername()))) { - log.info("clear out competing tariff"); - TariffSpecification original = - tariffRepo.findSpecificationById(tr.getTariffId()); - if (null == original) { - log.warn("Original tariff " + tr.getTariffId() + " not found"); - return; - } - tariffRepo.removeSpecification(original.getId()); - List candidates = - competingTariffs.get(original.getPowerType()); - if (null == candidates) { - log.warn("Candidate list is null"); - return; - } - candidates.remove(original); - } - } - - /** - * Handles a BalancingControlEvent, sent when a BalancingOrder is - * exercised by the DU. - */ - public void handleMessage (BalancingControlEvent bce) - { - log.info("BalancingControlEvent " + bce.getKwh()); - } - - // --------------- activation ----------------- - /** - * Called after TimeslotComplete msg received. Note that activation order - * among modules is non-deterministic. - */ - @Override // from Activatable - public void activate (int timeslotIndex) - { - if (customerSubscriptions.size() == 0) { - // we (most likely) have no tariffs - createInitialTariffs(); - } - else { - // we have some, are they good enough? - improveTariffs(); - } - } - - // 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 () - { - // remember that market prices are per mwh, but tariffs are by kwh - double marketPrice = marketManager.getMeanMarketPrice() / 1000.0; - // for each power type representing a customer population, - // create a tariff that's better than what's available - 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()); - tariffRepo.addSpecification(spec); - brokerContext.sendMessage(spec); - } - } - - // Checks to see whether our tariffs need fine-tuning - private void improveTariffs() - { - // quick magic-number hack to inject a balancing order - int timeslotIndex = timeslotRepo.currentTimeslot().getSerialNumber(); - if (371 == timeslotIndex) { - for (TariffSpecification spec : - tariffRepo.findTariffSpecificationsByBroker(brokerContext.getBroker())) { - if (PowerType.INTERRUPTIBLE_CONSUMPTION == spec.getPowerType()) { - BalancingOrder order = new BalancingOrder(brokerContext.getBroker(), - spec, - 0.5, - spec.getRates().get(0).getMinValue() * 0.9); - brokerContext.sendMessage(order); - } - } - } - // magic-number hack to supersede a tariff - if (380 == timeslotIndex) { - // find the existing CONSUMPTION tariff - TariffSpecification oldc = null; - List candidates = - tariffRepo.findTariffSpecificationsByBroker(brokerContext.getBroker()); - if (null == candidates || 0 == candidates.size()) - log.error("No tariffs found for broker"); - else { - // oldc = candidates.get(0); - for (TariffSpecification candidate: candidates) { - if (candidate.getPowerType() == PowerType.CONSUMPTION) { - oldc = candidate; - break; - } - } - if (null == oldc) { - log.warn("No CONSUMPTION tariffs found"); - } - else { - double rateValue = oldc.getRates().get(0).getValue(); - // create a new CONSUMPTION tariff - TariffSpecification spec = - new TariffSpecification(brokerContext.getBroker(), - PowerType.CONSUMPTION) - .withPeriodicPayment(defaultPeriodicPayment * 1.1); - Rate rate = new Rate().withValue(rateValue); - spec.addRate(rate); - if (null != oldc) - spec.addSupersedes(oldc.getId()); - mungId(spec, 6); - tariffRepo.addSpecification(spec); - brokerContext.sendMessage(spec); - // revoke the old one - TariffRevoke revoke = - new TariffRevoke(brokerContext.getBroker(), oldc); - brokerContext.sendMessage(revoke); - } - } - } - } - - private void mungId (TariffSpecification spec, int i) - { - long id = spec.getId(); - long baseId = - id - IdGenerator.extractPrefix(id) * IdGenerator.getMultiplier(); - Field idField = findIdField(spec.getClass()); - try { - idField.setAccessible(true); - idField.setLong(spec, baseId + i * IdGenerator.getMultiplier()); - } - catch (Exception e) { - log.error(e.toString()); - } - } - - private Field findIdField (Class clazz) - { - try { - Field idField = clazz.getDeclaredField("id"); - return idField; - } - catch (NoSuchFieldException e) { - Class superclass = clazz.getSuperclass(); - if (null == superclass) { - return null; - } - return findIdField(superclass); - } - catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return null; - } - } - - // ------------- test-support methods ---------------- - double getUsageForCustomer (CustomerInfo customer, - TariffSpecification tariffSpec, - int index) - { - CustomerRecord record = getCustomerRecordByTariff(tariffSpec, customer); - return record.getUsage(index); - } - - // test-support method - HashMap getRawUsageForCustomer (CustomerInfo customer) - { - HashMap result = new HashMap(); - for (PowerType type : customerProfiles.keySet()) { - CustomerRecord record = customerProfiles.get(type).get(customer); - if (record != null) { - result.put(type, record.usage); - } - } - return result; - } - - // test-support method - HashMap getCustomerCounts() - { - HashMap result = new HashMap(); - for (TariffSpecification spec : customerSubscriptions.keySet()) { - HashMap customerMap = customerSubscriptions.get(spec); - for (CustomerRecord record : customerMap.values()) { - result.put(record.customer.getName() + spec.getPowerType(), - record.subscribedPopulation); - } - } - 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. - */ - class CustomerRecord - { - CustomerInfo customer; - int subscribedPopulation = 0; - double[] usage; - double alpha = 0.3; - - /** - * Creates an empty record - */ - CustomerRecord (CustomerInfo customer) - { - super(); - this.customer = customer; - this.usage = new double[brokerContext.getUsageRecordLength()]; - } - - CustomerRecord (CustomerRecord oldRecord) - { - super(); - this.customer = oldRecord.customer; - this.usage = Arrays.copyOf(oldRecord.usage, brokerContext.getUsageRecordLength()); - } - - // Returns the CustomerInfo for this record - CustomerInfo getCustomerInfo () - { - return customer; - } - - // 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; - } - - // Customer produces or consumes power. We assume the kwh value is negative - // for production, positive for consumption - void produceConsume (double kwh, Instant when) - { - int index = getIndex(when); - produceConsume(kwh, index); - } - - // store profile data at the given index - void produceConsume (double kwh, int rawIndex) - { - int index = getIndex(rawIndex); - double kwhPerCustomer = 0.0; - if (subscribedPopulation > 0) { - kwhPerCustomer = kwh / (double)subscribedPopulation; - } - 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; - } - log.debug("consume " + kwh + " at " + index + - ", customer " + customer.getName()); - } - - double getUsage (int index) - { - if (index < 0) { - log.warn("usage requested for negative index " + index); - index = 0; - } - 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; - } - } -} diff --git a/src/main/java/org/powertac/samplebroker/core/BrokerMain.java b/src/main/java/org/powertac/samplebroker/core/BrokerMain.java index 33fa26f..c344235 100644 --- a/src/main/java/org/powertac/samplebroker/core/BrokerMain.java +++ b/src/main/java/org/powertac/samplebroker/core/BrokerMain.java @@ -15,18 +15,12 @@ */ package org.powertac.samplebroker.core; -//import org.apache.log4j.Logger; -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; - /** * This is the top level of the Power TAC server. * @author John Collins */ -public class BrokerMain +public class BrokerMain implements Runnable { - //static private Logger log = Logger.getLogger(BrokerMain.class); - /** * Sets up the broker. Single command-line arg is the username */ @@ -34,8 +28,26 @@ public static void main (String[] args) { BrokerRunner runner = new BrokerRunner(); runner.processCmdLine(args); - + // if we get here, it's time to exit System.exit(0); } + + private static String[] mainArgs; + + public static void mainMatlab (String[] args) + { + // This is some dirty hacking because Matlab doesn't load jars properly + ClassPathHacker.loadJarDynamically(); + + mainArgs = args; + Thread t = new Thread( new BrokerMain() ); + t.start(); + } + + public void run() + { + BrokerRunner runner = new BrokerRunner(); + runner.processCmdLine(mainArgs); + } } diff --git a/src/main/java/org/powertac/samplebroker/core/BrokerPropertiesService.java b/src/main/java/org/powertac/samplebroker/core/BrokerPropertiesService.java index fbaf988..0696a41 100644 --- a/src/main/java/org/powertac/samplebroker/core/BrokerPropertiesService.java +++ b/src/main/java/org/powertac/samplebroker/core/BrokerPropertiesService.java @@ -15,11 +15,6 @@ */ package org.powertac.samplebroker.core; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.util.Collection; - import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.ConfigurationException; @@ -33,6 +28,10 @@ import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; +import java.io.File; +import java.io.IOException; +import java.util.Collection; + /** * @author jcollins */ @@ -199,6 +198,11 @@ public void setProperty (String key, Object value) private boolean validXmlResource (Resource xml) { try { + xml.getInputStream(); + return true; + + /* TODO Doesn't seem to work with files inside a jar? + Jar is added to the classpath, and then the String path = xml.getFile().getPath(); for (String regex : excludedPaths) { if (path.matches(regex)) { @@ -206,6 +210,7 @@ private boolean validXmlResource (Resource xml) } } return true; + */ } catch (IOException e) { log.error("Should not happen: " + e.toString()); diff --git a/src/main/java/org/powertac/samplebroker/core/BrokerRunner.java b/src/main/java/org/powertac/samplebroker/core/BrokerRunner.java index 0179f6a..27143a2 100644 --- a/src/main/java/org/powertac/samplebroker/core/BrokerRunner.java +++ b/src/main/java/org/powertac/samplebroker/core/BrokerRunner.java @@ -15,21 +15,22 @@ */ package org.powertac.samplebroker.core; -import java.io.File; -import java.util.Date; -import java.util.Enumeration; - import joptsimple.OptionException; import joptsimple.OptionParser; import joptsimple.OptionSet; import joptsimple.OptionSpec; - import org.apache.log4j.Appender; import org.apache.log4j.FileAppender; +import org.apache.log4j.Level; import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import java.io.File; +import java.util.Date; +import java.util.Enumeration; + /** * Multi-session broker runner. The Spring context is re-built for each * session. @@ -115,7 +116,7 @@ else if (options.has(repeatHoursOption)) { // Re-open the logfiles reopenLogs(counter); - + // initialize and run if (null == context) { context = new ClassPathXmlApplicationContext("broker.xml"); @@ -126,7 +127,7 @@ else if (options.has(repeatHoursOption)) { } // get the broker reference and delegate the rest context.registerShutdownHook(); - broker = (PowerTacBroker)context.getBeansOfType(PowerTacBroker.class).values().toArray()[0]; + broker = (PowerTacBroker) context.getBeansOfType(PowerTacBroker.class).values().toArray()[0]; System.out.println("Starting session " + counter); broker.startSession(configFile, jmsUrl, noNtp, queueName, serverQueue, end); if (null != repeatCount) @@ -144,17 +145,53 @@ private void reopenLogs(int counter) Logger root = Logger.getRootLogger(); @SuppressWarnings("unchecked") Enumeration rootAppenders = root.getAllAppenders(); - FileAppender logOutput = (FileAppender) rootAppenders.nextElement(); - // assume there's only the one, and that it's a file appender - logOutput.setFile("log/broker" + counter + ".trace"); - logOutput.activateOptions(); - + FileAppender logOutput; + + try { + logOutput = (FileAppender) rootAppenders.nextElement(); + // assume there's only the one, and that it's a file appender + logOutput.setFile("log/broker" + counter + ".trace"); + logOutput.activateOptions(); + } + catch (Exception ignored) { + try { + PatternLayout layout = new PatternLayout("%r %-5p %c{2}: %m%n"); + String fileName = "log/broker" + counter + ".trace"; + logOutput = new FileAppender(layout, fileName); + logOutput.activateOptions(); + root.addAppender(logOutput); + + root.setLevel(Level.WARN); + } + catch (Exception ignored2) { + // TODO What? + } + } + Logger state = Logger.getLogger("State"); @SuppressWarnings("unchecked") Enumeration stateAppenders = state.getAllAppenders(); - FileAppender stateOutput = (FileAppender) stateAppenders.nextElement(); - // assume there's only the one, and that it's a file appender - stateOutput.setFile("log/broker" + counter + ".state"); - stateOutput.activateOptions(); + FileAppender stateOutput; + + try { + stateOutput = (FileAppender) stateAppenders.nextElement(); + // assume there's only the one, and that it's a file appender + stateOutput.setFile("log/broker" + counter + ".state"); + stateOutput.activateOptions(); + } + catch (Exception ignored) { + try { + PatternLayout layout = new PatternLayout("%r::%m%n"); + String fileName = "log/broker" + counter + ".state"; + stateOutput = new FileAppender(layout, fileName); + stateOutput.activateOptions(); + state.addAppender(stateOutput); + + state.setLevel(Level.INFO); + } + catch (Exception ignored2) { + // TODO What? + } + } } } diff --git a/src/main/java/org/powertac/samplebroker/core/ClassPathHacker.java b/src/main/java/org/powertac/samplebroker/core/ClassPathHacker.java new file mode 100644 index 0000000..dcc9392 --- /dev/null +++ b/src/main/java/org/powertac/samplebroker/core/ClassPathHacker.java @@ -0,0 +1,73 @@ +package org.powertac.samplebroker.core; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + + +public class ClassPathHacker +{ + public static void loadJarDynamically () + { + String jarPath = BrokerMain.class.getProtectionDomain() + .getCodeSource().getLocation().getFile(); + URLClassLoader ucl = (URLClassLoader) ClassLoader.getSystemClassLoader(); + boolean found = false; + for (URL url : ucl.getURLs()) { + if (url.getFile().equals(jarPath)) { + found = true; + break; + } + } + + if (!found) { + ClassPathHacker.addFile(jarPath); + } + } + + private static void addFile (final String s) + { + try { + addURL((new File(s)).toURI().toURL()); + } + catch (IOException ignored) { + System.out.println(); + System.out.println("Unable to load " + s + "dynamically, exiting!"); + System.out.println(); + System.exit(1); + } + } + + private static void addURL (final URL u) throws IOException + { + final URLClassLoader urlClassLoader = getUrlClassLoader(); + + try { + final Method method = getAddUrlMethod(); + method.setAccessible(true); + method.invoke(urlClassLoader, u); + } + catch (final Exception e) { + throw new IOException("Error, could not add URL to system classloader"); + } + } + + private static Method getAddUrlMethod () throws NoSuchMethodException + { + final Class urlclassloader = URLClassLoader.class; + return urlclassloader.getDeclaredMethod("addURL", new Class[]{URL.class}); + } + + private static URLClassLoader getUrlClassLoader () + { + final ClassLoader sysloader = ClassLoader.getSystemClassLoader(); + if (sysloader instanceof URLClassLoader) { + return (URLClassLoader) sysloader; + } + else { + throw new IllegalStateException("Not an UrlClassLoader: " + sysloader); + } + } +} \ No newline at end of file diff --git a/src/main/matlab/portfoliomanager/CustomerRecord.m b/src/main/matlab/portfoliomanager/CustomerRecord.m new file mode 100644 index 0000000..f2a4bae --- /dev/null +++ b/src/main/matlab/portfoliomanager/CustomerRecord.m @@ -0,0 +1,109 @@ +classdef CustomerRecord < handle + % 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. + + properties + customer = 'None'; + subscribedPopulation = 0; + usage = []; + alpha = 0.3; + end + + methods + function obj = CustomerRecord (inp) + global pmManager + + if strcmp(class(inp), 'org.powertac.common.CustomerInfo') + obj.customer = inp; + for i=1:pmManager.context.getUsageRecordLength() + obj.usage(i) = 0; + end + elseif strcmp(class(inp), 'CustomerRecord') + obj.customer = inp.customer; + obj.usage = repmat(inp.usage, 1); + elseif isnumeric(inp) && isempty(inp) + obj.customer = inp; + for i=1:pmManager.context.getUsageRecordLength() + obj.usage(i) = 0; + end + end + + while length(obj.usage) < pmManager.context.getUsageRecordLength() + obj.usage(length(obj.usage) + 1) = 0; + end + end + + % Adds new individuals to the count + function signup (obj, population) + obj.subscribedPopulation = min(obj.customer.getPopulation(), ... + obj.subscribedPopulation + population); + end + + % Removes individuals from the count + function withdraw (obj, population) + obj.subscribedPopulation = obj.subscribedPopulation - population; + end + + function produceConsume (obj, kwh, rawIndex) + % Customer produces or consumes power. We assume the kwh value is negative + % for production, positive for consumption + % store profile data at the given index + + global pmManager + + index = obj.getIndex(rawIndex); + if obj.subscribedPopulation == 0.0 + kwhPerCustomer = 0; + else + kwhPerCustomer = kwh / obj.subscribedPopulation; + end + oldUsage = obj.usage(index); + + if oldUsage == 0.0 + % assume this is the first time + obj.usage(index) = kwhPerCustomer; + else + % exponential smoothing + obj.usage(index) = obj.alpha * kwhPerCustomer + (1.0 - obj.alpha) * oldUsage; + end + + if isnumeric(obj.customer) && isempty(obj.customer) + name = 'null'; + else + name = char(obj.customer.getName()); + end + + msg = horzcat('consume ', num2str(kwh), ' at ', int2str(index), ... + ', customer ', name); + pmManager.log.debug(msg); + end + + function result = getUsage (obj, index) + global pmManager + + if index < 1 + msg = horzcat('usage requested for negative index ', int2str(index)); + pmManager.log.warn(msg); + index = 1; + end + + result = obj.usage(obj.getIndex(index)) * obj.subscribedPopulation; + end + + function index = getIndex (obj, rawIndex) + global pmManager + + if strcmp(class(rawIndex), 'double') + index = rawIndex; + elseif strcmp(class(rawIndex), 'org.joda.time.Instant') + diff = rawIndex.getMillis() - pmManager.timeService.getBase(); + duration = org.powertac.common.Competition.currentCompetition().getTimeslotDuration(); + index = floor(diff / duration) + 1; + end + + index = mod(index-1, pmManager.context.getUsageRecordLength()) + 1; + end + end +end \ No newline at end of file diff --git a/src/main/matlab/portfoliomanager/activate.m b/src/main/matlab/portfoliomanager/activate.m new file mode 100644 index 0000000..a178f4e --- /dev/null +++ b/src/main/matlab/portfoliomanager/activate.m @@ -0,0 +1,15 @@ +function activate (timeslotIndex) + % Called after TimeslotComplete msg received. Note that activation order + % among modules is non-deterministic. + + global pmManager + + if length(pmManager.customerSubscriptions) == 0 + % we (most likely) have no tariffs + createInitialTariffs(); + else + % we have some, are they good enough? + improveTariffs(); + end + +end diff --git a/src/main/matlab/portfoliomanager/collectUsage.m b/src/main/matlab/portfoliomanager/collectUsage.m new file mode 100644 index 0000000..5c5708b --- /dev/null +++ b/src/main/matlab/portfoliomanager/collectUsage.m @@ -0,0 +1,14 @@ +function result = collectUsage (index) + % Returns total usage for a given timeslot (represented as a simple index). + + global pmManager + + result = 0.0; + for k1 = keys(pmManager.customerSubscriptions) + customer_map = pmManager.customerSubscriptions(cell2mat(k1)); + for k2 = keys(customer_map) + record = customer_map(cell2mat(k2)); + result = result + record.getUsage(index); + end + end +end \ No newline at end of file diff --git a/src/main/matlab/portfoliomanager/createInitialTariffs.m b/src/main/matlab/portfoliomanager/createInitialTariffs.m new file mode 100644 index 0000000..4cc6e27 --- /dev/null +++ b/src/main/matlab/portfoliomanager/createInitialTariffs.m @@ -0,0 +1,44 @@ +function createInitialTariffs () + % Creates initial tariffs for the main power types. These are simple + % fixed-rate two-part tariffs that give the broker a fixed margin. + + global pmManager + + % remember that market prices are per mwh, but tariffs are by kwh + marketPrice = pmManager.marketManager.getMeanMarketPrice() / 1000; + + % for each power type representing a customer population, + % create a tariff that's better than what's available + for ptString = keys(pmManager.customerProfiles) + pt = org.powertac.common.enumerations.PowerType.valueOf(cell2mat(ptString)); + + % we'll just do fixed-rate tariffs for now + if pt.isConsumption() + rateValue = (marketPrice + pmManager.fixedPerKwh) * ... + (1.0 + pmManager.defaultMargin); + else + rateValue = -2.0 * marketPrice; + end + + if pt.isInterruptible() + % Magic number!! price break for interruptible + rateValue = rateValue * 0.7; + end + + broker = pmManager.context.getBroker(); + spec = org.powertac.common.TariffSpecification(broker, pt). ... + withPeriodicPayment(pmManager.defaultPeriodicPayment); + + rate = org.powertac.common.Rate().withValue(rateValue); + + if pt.isInterruptible() + % set max curtailment + rate.withMaxCurtailment(0.1); + end + + spec.addRate(rate); + pmManager.customerSubscriptions(char(spec)) = containers.Map; + pmManager.tariffRepo.addSpecification(spec); + pmManager.context.sendMessage(spec); + end +end \ No newline at end of file diff --git a/src/main/matlab/portfoliomanager/getCustomerRecordByPowerType.m b/src/main/matlab/portfoliomanager/getCustomerRecordByPowerType.m new file mode 100644 index 0000000..0c96ddf --- /dev/null +++ b/src/main/matlab/portfoliomanager/getCustomerRecordByPowerType.m @@ -0,0 +1,26 @@ +function record = getCustomerRecordByPowerType (powerType, customer) + global pmManager + + typeString = char(powerType); + % customerName = char(customer.getName()); + if isnumeric(customer) && isempty(customer) + % customer might be null (Java) == [] (MatLab) + customerName = 'null'; + else + customerName = char(customer.getName()); + end + + if ~pmManager.customerProfiles.isKey(typeString) + customerMap = containers.Map; + pmManager.customerProfiles(typeString) = customerMap; + else + customerMap = pmManager.customerProfiles(typeString); + end + + if ~customerMap.isKey(customerName) + record = CustomerRecord(customer); + customerMap(customerName) = record; + else + record = customerMap(customerName); + end +end diff --git a/src/main/matlab/portfoliomanager/getCustomerRecordByTariff.m b/src/main/matlab/portfoliomanager/getCustomerRecordByTariff.m new file mode 100644 index 0000000..75de397 --- /dev/null +++ b/src/main/matlab/portfoliomanager/getCustomerRecordByTariff.m @@ -0,0 +1,27 @@ +function record = getCustomerRecordByTariff (spec, customer) + global pmManager + + specString = char(spec); + if isnumeric(customer) && isempty(customer) + % customer might be null (Java) == [] (MatLab) + customerName = 'null'; + else + customerName = char(customer.getName()); + end + + if ~pmManager.customerSubscriptions.isKey(specString) + customerMap = containers.Map; + pmManager.customerSubscriptions(specString) = customerMap; + else + customerMap = pmManager.customerSubscriptions(specString); + end + + if ~customerMap.isKey(customerName) + % seed with the generic record for this customer + oldRecord = getCustomerRecordByPowerType(spec.getPowerType(), customer); + record = CustomerRecord(oldRecord); + customerMap(customerName) = record; + else + record = customerMap(customerName); + end +end \ No newline at end of file diff --git a/src/main/matlab/portfoliomanager/improveTariffs.m b/src/main/matlab/portfoliomanager/improveTariffs.m new file mode 100644 index 0000000..714fee2 --- /dev/null +++ b/src/main/matlab/portfoliomanager/improveTariffs.m @@ -0,0 +1,93 @@ +function improveTariffs () + % Checks to see whether our tariffs need fine-tuning + + global pmManager + + timeslotIndex = pmManager.timeslotRepo.currentTimeslot().getSerialNumber(); + + % quick magic-number hack to inject a balancing order + if timeslotIndex == 371 + broker = pmManager.context.getBroker(); + test = org.powertac.common.enumerations.PowerType.INTERRUPTIBLE_CONSUMPTION; + specs = pmManager.tariffRepo.findTariffSpecificationsByBroker(broker).toArray(); + for i = 1:length(specs) + spec = specs(i); + if spec.getPowerType() == test + order = org.powertac.common.msg.BalancingOrder(... + broker, spec, 0.5, spec.getRates().get(0).getMinValue() * 0.9); + pmManager.context.sendMessage(order); + end + end + end + + % magic-number hack to supersede a tariff + if timeslotIndex == 380 + broker = pmManager.context.getBroker(); + test = org.powertac.common.enumerations.PowerType.CONSUMPTION; + + % find the existing CONSUMPTION tariff + oldc = []; + candidates = pmManager.tariffRepo.findTariffSpecificationsByBroker(broker).toArray(); + if isnumeric(candidates) || isempty(candidates) % Test for null + pmManager.log.error('No tariffs found for broker'); + else + for i = 1:length(candidates) + candidate = candidates(i); + if candidate.getPowerType() == test + oldc = candidate; + break; + end + end + + if oldc == [] + pmManager.log.warn('No CONSUMPTION tariffs found'); + else + rateValue = oldc.getRates().get(0).getValue(); + % create a new CONSUMPTION tariff + spec = org.powertac.common.TariffSpecification(broker, test). ... + withPeriodicPayment(pmManager.defaultPeriodicPayment * 1.1); + rate = org.powertac.common.Rate().withValue(rateValue); + spec.addRate(rate); + spec.addSupersedes(oldc.getId()); + + pmManager.tariffRepo.addSpecification(spec); + pmManager.context.sendMessage(spec); + % revoke the old one + revoke = org.powertac.common.msg.TariffRevoke(broker, oldc); + pmManager.context.sendMessage(revoke); + end + end + end + +% % magic-number hack to supersede a tariff +% if timeslotIndex == 380 +% broker = pmManager.context.getBroker(); +% type = org.powertac.common.enumerations.PowerType.CONSUMPTION; +% +% % find the existing CONSUMPTION tariff +% oldc = []; +% candidates = pmManager.tariffRepo.findTariffSpecificationsByPowerType(type); +% if isnumeric(candidates) || isempty(candidates) % Test for null +% pmManager.log.error('No CONSUMPTION tariffs found'); +% end +% candidates = candidates.toArray(); +% oldc = candidates(1); +% +% rateValue = oldc.getRates().get(0).getValue(); +% +% % create a new CONSUMPTION tariff +% spec = org.powertac.common.TariffSpecification(broker, type). ... +% withPeriodicPayment(pmManager.defaultPeriodicPayment * 1.1); +% rate = org.powertac.common.Rate().withValue(rateValue); +% +% if ~isnumeric(oldc) && ~isempty(oldc) +% spec.addSupersedes(oldc.getId()); +% end +% +% pmManager.tariffRepo.addSpecification(spec); +% pmManager.context.sendMessage(spec); +% % revoke the old one +% revoke = org.powertac.common.msg.TariffRevoke(broker, oldc); +% pmManager.context.sendMessage(revoke); +% end +end diff --git a/src/main/matlab/portfoliomanager/mInit.m b/src/main/matlab/portfoliomanager/mInit.m new file mode 100644 index 0000000..3de6069 --- /dev/null +++ b/src/main/matlab/portfoliomanager/mInit.m @@ -0,0 +1,25 @@ +function mInit (var0, var1, var2, var3, var4, var5, var6, var7) + global pmManager + + pmManager.context = var0; + pmManager.timeslotRepo = var1; + pmManager.tariffRepo = var2; + pmManager.customerRepo = var3; + pmManager.marketManager = var4; + pmManager.timeService = var5; + pmManager.log = var6; + pmManager.null = var7; + + % Configurable parameters for tariff composition + pmManager.defaultMargin = 0.5; + pmManager.fixedPerKwh = -0.06; + pmManager.defaultPeriodicPayment = -1.0; + + % ---- Portfolio records ----- + % 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. + pmManager.customerProfiles = containers.Map; + pmManager.customerSubscriptions = containers.Map; + pmManager.competingTariffs = containers.Map; +end \ No newline at end of file diff --git a/src/main/matlab/portfoliomanager/msgBalancingControlEvent.m b/src/main/matlab/portfoliomanager/msgBalancingControlEvent.m new file mode 100644 index 0000000..763cca3 --- /dev/null +++ b/src/main/matlab/portfoliomanager/msgBalancingControlEvent.m @@ -0,0 +1,10 @@ +function msgBalancingControlEvent (bce) + % Handles a BalancingControlEvent, sent when a BalancingOrder is + % exercised by the DU. + + global pmManager + + kwh = bce.getKwh(); + msg = horzcat('BalancingControlEvent ', num2str(kwh)); + pmManager.log.info(msg); +end \ No newline at end of file diff --git a/src/main/matlab/portfoliomanager/msgCustomerBootstrapData.m b/src/main/matlab/portfoliomanager/msgCustomerBootstrapData.m new file mode 100644 index 0000000..e1162bf --- /dev/null +++ b/src/main/matlab/portfoliomanager/msgCustomerBootstrapData.m @@ -0,0 +1,23 @@ +function msgCustomerBootstrapData (cbd) + % Handles CustomerBootstrapData by populating the customer model + % corresponding to the given customer and power type. This gives the + % broker a running start. + + global pmManager + + name = cbd.getCustomerName(); + type = cbd.getPowerType(); + usage = cbd.getNetUsage(); + + customer = pmManager.customerRepo.findByNameAndPowerType(name, type); + record = getCustomerRecordByPowerType(cbd.getPowerType(), customer); + + check1 = record.subscribedPopulation; + + subs = record.subscribedPopulation; + record.subscribedPopulation = customer.getPopulation(); + for i=1:length(usage) + record.produceConsume(usage(i), i); + end + record.subscribedPopulation = subs; +end \ No newline at end of file diff --git a/src/main/matlab/portfoliomanager/msgTariffRevoke.m b/src/main/matlab/portfoliomanager/msgTariffRevoke.m new file mode 100644 index 0000000..0fbcd7e --- /dev/null +++ b/src/main/matlab/portfoliomanager/msgTariffRevoke.m @@ -0,0 +1,37 @@ +function msgTariffRevoke (tr) + % Handles a TariffRevoke message from the server, indicating that some + % tariff has been revoked. + + global pmManager + + source = tr.getBroker(); + name = char(source.getUsername()); + contextName = char(pmManager.context.getBrokerUsername()); + + % if it's from some other broker, we need to remove it from the + % tariffRepo, and from the competingTariffs list + if ~strcmp(name, contextName) + pmManager.log.info('clear out competing tariff'); + + original = pmManager.tariffRepo.findSpecificationById(tr.getTariffId()); + + if isnumeric(original) && isempty(original) + msg = horzcat('Original tariff ', int2str(tr.getTariffId()), 'not found'); + pmManager.log.warn(msg); + return; + end + + pmManager.tariffRepo.removeSpecification(original.getId()); + + typeString = char(original.getPowerType()); + if ~pmManager.competingTariffs.isKey(typeString) + pmManager.log.warn('Candidate list is null'); + return; + else + % candidates is an array with tariff specs, remove original + candidates = pmManager.competingTariffs(typeString); + candidates = candidates(candidates~=original); + pmManager.competingTariffs(typeString) = candidates; + end + end +end \ No newline at end of file diff --git a/src/main/matlab/portfoliomanager/msgTariffSpecification.m b/src/main/matlab/portfoliomanager/msgTariffSpecification.m new file mode 100644 index 0000000..87c752a --- /dev/null +++ b/src/main/matlab/portfoliomanager/msgTariffSpecification.m @@ -0,0 +1,40 @@ +function msgTariffSpecification (spec) + % Handles a TariffSpecification. These are sent by the server when new tariffs + % are published. If it's not ours, then it's a competitor's tariff. We keep + % track of competing tariffs locally, and we also store them in the tariffRepo. + + global pmManager + + theBroker = spec.getBroker(); + theBrokerName = char(theBroker.getUsername()); + contextBrokerName = char(pmManager.context.getBrokerUsername()); + + if strcmp(theBrokerName, contextBrokerName) + % if it's ours, just log it, because we already put it in the repo + original = pmManager.tariffRepo.findSpecificationById(spec.getId()); + if ~isa(original, 'org.powertac.common.TariffSpecification') + pmManager.log.error(horzcat('Spec ', int2str(spec.getId()), ' not in local repo')); + end + pmManager.log.info(horzcat('published ', char(spec.toString()))); + else + % otherwise, keep track of competing tariffs, and record in the repo + addCompetingTariff(spec); + pmManager.tariffRepo.addSpecification(spec); + end +end + +function addCompetingTariff (spec) + % Finds the list of competing tariffs for the given PowerType, add tariff + global pmManager + + typeString = char(spec.getPowerType()); + + if ~pmManager.competingTariffs.isKey(typeString) + tariffs = []; + else + tariffs = pmManager.competingTariffs(typeString); + end + + tariffs = [tariffs spec]; + pmManager.competingTariffs(typeString) = tariffs; +end \ No newline at end of file diff --git a/src/main/matlab/portfoliomanager/msgTariffStatus.m b/src/main/matlab/portfoliomanager/msgTariffStatus.m new file mode 100644 index 0000000..e019e92 --- /dev/null +++ b/src/main/matlab/portfoliomanager/msgTariffStatus.m @@ -0,0 +1,10 @@ +function msgTariffStatus (ts) + % Handles a TariffStatus message. This should do something when the status + % is not SUCCESS. + + global pmManager + + status = ts.getStatus(); + msg = horzcat('TariffStatus: ', char(status)); + pmManager.log.info(msg); +end \ No newline at end of file diff --git a/src/main/matlab/portfoliomanager/msgTariffTransaction.m b/src/main/matlab/portfoliomanager/msgTariffTransaction.m new file mode 100644 index 0000000..7273e07 --- /dev/null +++ b/src/main/matlab/portfoliomanager/msgTariffTransaction.m @@ -0,0 +1,55 @@ +function msgTariffTransaction (ttx) + % Handles a TariffTransaction. We only care about certain types: + % PRODUCE, CONSUME, SIGNUP, and WITHDRAW. + + global pmManager + + txTypeString = char(ttx.getTxType()); + newSpec = ttx.getTariffSpec(); + + % make sure we have this tariff + if ~isa(newSpec, 'org.powertac.common.TariffSpecification') + msg = horzcat('TariffTransaction type=', txTypeString, ' for unknown spec'); + pmManager.log.error(msg); + else + oldSpec = pmManager.tariffRepo.findSpecificationById(newSpec.getId()); + if oldSpec.getId() ~= newSpec.getId() + msg = horzcat('Incoming spec ', num2str(newSpec.getId()), ... + ' not matched in repo'); + pmManager.log.error(msg); + end + end + + % ttx.getCustomerInfo() might return null => [] in MatLab + record = getCustomerRecordByTariff(newSpec, ttx.getCustomerInfo()); + + enumType = 'org.powertac.common.TariffTransaction$Type'; + SIGNUP = javaMethod('valueOf', enumType, 'SIGNUP'); + WITHDRAW = javaMethod('valueOf', enumType, 'WITHDRAW'); + PRODUCE = javaMethod('valueOf', enumType, 'PRODUCE'); + PUBLISH = javaMethod('valueOf', enumType, 'PUBLISH'); + + if strcmp(txTypeString, char(SIGNUP)) + % keep track of customer counts + record.signup(ttx.getCustomerCount()); + elseif strcmp(txTypeString, char(WITHDRAW)) + % customers presumably found a better deal + record.withdraw(ttx.getCustomerCount()); + elseif strcmp(txTypeString, char(PRODUCE)) + % if ttx count and subscribe population don't match, it will be hard + % to estimate per-individual production + if ttx.getCustomerCount() ~= record.subscribedPopulation + msg = horzcat('production by subset ', num2str(ttx.getCustomerCount()),... + ' of subscribed population ', int2str(record.subscribedPopulation)); + pmManager.log.warn(msg); + end + record.produceConsume(ttx.getKWh(), ttx.getPostedTime()); + elseif strcmp(txTypeString, char(PUBLISH)) + if ttx.getCustomerCount() ~= record.subscribedPopulation + msg = horzcat('production by subset ', num2str(ttx.getCustomerCount()),... + ' of subscribed population ', int2str(record.subscribedPopulation)); + pmManager.log.warn(msg); + end + record.produceConsume(ttx.getKWh(), ttx.getPostedTime()); + end +end diff --git a/src/main/matlab/portfoliomanager/testFunction.m b/src/main/matlab/portfoliomanager/testFunction.m new file mode 100644 index 0000000..d618b5d --- /dev/null +++ b/src/main/matlab/portfoliomanager/testFunction.m @@ -0,0 +1,3 @@ +function result = testFunction (var0) + result = 10 * var0; +end diff --git a/src/test/java/org/powertac/samplebroker/PortfolioManagerTest.java b/src/test/java/org/powertac/samplebroker/PortfolioManagerTest.java deleted file mode 100644 index 7f6ae7e..0000000 --- a/src/test/java/org/powertac/samplebroker/PortfolioManagerTest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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 org.powertac.samplebroker; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -import java.util.Arrays; - -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.Instant; -import org.junit.Before; -import org.junit.Test; -import org.powertac.common.CustomerInfo; -import org.powertac.common.TimeService; -import org.powertac.common.Timeslot; -import org.powertac.common.enumerations.PowerType; -import org.powertac.common.msg.CustomerBootstrapData; -import org.powertac.common.repo.CustomerRepo; -import org.powertac.common.repo.TimeslotRepo; -import org.powertac.samplebroker.core.BrokerPropertiesService; -import org.powertac.samplebroker.core.PowerTacBroker; -import org.springframework.test.util.ReflectionTestUtils; - -/** - * @author jcollins - */ -public class PortfolioManagerTest -{ - private TimeslotRepo timeslotRepo; - private CustomerRepo customerRepo; - - private PortfolioManagerService portfolioManagerService; - private PowerTacBroker broker; - private Instant baseTime; - - /** - * - */ - @Before - public void setUp () throws Exception - { - broker = mock(PowerTacBroker.class); - timeslotRepo = mock(TimeslotRepo.class); - customerRepo = new CustomerRepo(); - BrokerPropertiesService bps = mock(BrokerPropertiesService.class); - when(broker.getUsageRecordLength()).thenReturn(7*24); - portfolioManagerService = new PortfolioManagerService(); - ReflectionTestUtils.setField(portfolioManagerService, - "timeslotRepo", - timeslotRepo); - ReflectionTestUtils.setField(portfolioManagerService, - "customerRepo", - customerRepo); - ReflectionTestUtils.setField(portfolioManagerService, - "propertiesService", - bps); - portfolioManagerService.initialize(broker); - - // set the time - baseTime = - new DateTime(2011, 2, 1, 0, 0, 0, 0, DateTimeZone.UTC).toInstant(); - } - - /** - * Test customer boot data - */ - @Test - public void testCustomerBootstrap () - { - // set up a competition - CustomerInfo podunk = new CustomerInfo("Podunk", 3); - customerRepo.add(podunk); - CustomerInfo midvale = new CustomerInfo("Midvale", 1000); - customerRepo.add(midvale); - // create a Timeslot for use by the bootstrap data - Timeslot ts0 = new Timeslot(8*24, baseTime.plus(TimeService.DAY * 8)); - when(timeslotRepo.currentTimeslot()).thenReturn(ts0); - // send to broker and check - double[] podunkData = new double[7*24]; - Arrays.fill(podunkData, 3.6); - double[] midvaleData = new double[7*24]; - Arrays.fill(midvaleData, 1600.0); - CustomerBootstrapData boot = - new CustomerBootstrapData(podunk, PowerType.CONSUMPTION, podunkData); - portfolioManagerService.handleMessage(boot); - boot = new CustomerBootstrapData(midvale, PowerType.CONSUMPTION, midvaleData); - portfolioManagerService.handleMessage(boot); - double[] podunkUsage = - portfolioManagerService.getRawUsageForCustomer(podunk).get(PowerType.CONSUMPTION); - assertNotNull("podunk usage is recorded", podunkUsage); - assertEquals("correct usage value for podunk", 1.2, podunkUsage[23], 1e-6); - double[] midvaleUsage = - portfolioManagerService.getRawUsageForCustomer(midvale).get(PowerType.CONSUMPTION); - assertNotNull("midvale usage is recorded", midvaleUsage); - assertEquals("correct usage value for midvale", 1.6, midvaleUsage[27], 1e-6); - } - - // other tests needed... -} diff --git a/test.m b/test.m new file mode 100644 index 0000000..655b93e --- /dev/null +++ b/test.m @@ -0,0 +1,6 @@ +% Run this with : matlab -nodisplay -nosplash -nodesktop -r test + +javaaddpath('target/sample-broker.jar'); + +o = org.powertac.samplebroker.core.BrokerMain; +javaMethod('mainMatlab', o, '--jms-url=tcp://130.115.197.116:61616');