// Get a beginning-of-week time for consistent tariff evaluation private Instant getNextSunday() { Instant result = getNowInstant(); int hour = result.get(DateTimeFieldType.hourOfDay()); if (hour > 0) result = result.plus((24 - hour) * TimeService.HOUR); int day = result.get(DateTimeFieldType.dayOfWeek()); result = result.plus((7 - day) * TimeService.DAY); return result; }
// Check production transactions @Test public void testProduction() { Instant exp = now.plus(TimeService.WEEK * 10); TariffSpecification tariffSpec = new TariffSpecification(broker, PowerType.PRODUCTION) .withExpiration(exp) .withMinDuration(TimeService.WEEK * 4) .withSignupPayment(-34.2) .withEarlyWithdrawPayment(35.0) .addRate(new Rate().withValue(0.102)); tariff = new Tariff(tariffSpec); tariff.init(); // subscribe and consume in the first timeslot TariffSubscription tsub = tariffMarketService.subscribeToTariff(tariff, customer, 4); assertEquals("four customers committed", 4, tsub.getCustomersCommitted()); tsub.usePower(-244.6); // production assertEquals("correct total usage", -244.6 / 4, tsub.getTotalUsage(), 1e-6); assertEquals("correct realized price", 0.102, tariff.getRealizedPrice(), 1e-6); // def txs = TariffTransaction.findAllByPostedTime(timeService.currentTime); // assertEquals("two transactions", 2, txs.size()) // TariffTransaction ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime, // TariffTransactionType.SIGNUP) // assertNotNull("found signup tx", ttx) // assertEquals("correct charge", -34.2 * 4, ttx.charge, 1e-6) // ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime, // TariffTransactionType.PRODUCE) // assertNotNull("found production tx", ttx) // assertEquals("correct amount", -244.6, ttx.quantity) // assertEquals("correct charge", -0.102 * 244.6, ttx.charge, 1e-6) }
// Returns the next date/time when the given shift index will occur Instant indexToInstant(int index) { Instant now = getNowInstant(); int probe = index; // get the probe within the shift schedule while (probe < 0) { probe += shiftSchedule.length; } while (probe > shiftSchedule.length) { probe -= shiftSchedule.length; } int nowIndex = indexOfShift(now); if (nowIndex <= index) { return (now.plus(TimeService.HOUR * (index - nowIndex))); } return (now.plus(TimeService.HOUR * (shiftSchedule.length + index - nowIndex))); }
// subscription withdrawal without and with penalty @Test public void testEarlyWithdraw() { Instant exp = now.plus(TimeService.WEEK * 10); TariffSpecification tariffSpec = new TariffSpecification(broker, PowerType.CONSUMPTION) .withExpiration(exp) .withMinDuration(TimeService.WEEK * 4) .withSignupPayment(-33.2) .withEarlyWithdrawPayment(42.1) .addRate(new Rate().withValue(0.121)); tariff = new Tariff(tariffSpec); tariff.init(); TariffSubscription tsub = tariffMarketService.subscribeToTariff(tariff, customer, 5); // move time forward 2 weeks, withdraw 2 customers Instant wk2 = now.plus(TimeService.WEEK * 2); timeService.setCurrentTime(wk2); tsub.unsubscribe(2); verify(mockAccounting) .addTariffTransaction( TariffTransaction.Type.WITHDRAW, tariff, customer.getCustomerInfo(), 2, 0.0, 42.1 * 2); // def txs = TariffTransaction.findAllByPostedTime(wk2) // assertEquals("one transaction", 1, txs.size()) // assertEquals("correct txType", TariffTransactionType.WITHDRAW, txs[0].txType) // assertEquals("correct charge", 42.1*2, txs[0].charge) assertEquals("three customers committed", 3, tsub.getCustomersCommitted()); // move time forward another week, add 4 customers and drop 1 Instant wk3 = now.plus(TimeService.WEEK * 2 + TimeService.HOUR * 6); timeService.setCurrentTime(wk3); TariffSubscription tsub1 = tariffMarketService.subscribeToTariff(tariff, customer, 4); assertEquals("same subscription", tsub, tsub1); tsub1.unsubscribe(1); // txs = TariffTransaction.findAllByPostedTime(wk3) // assertEquals("two transactions", 2, txs.size()) // TariffTransaction ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime, // // TariffTransactionType.SIGNUP) // assertNotNull("found signup tx", ttx) // assertEquals("correct charge", -33.2 * 4, ttx.charge) // ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime, // TariffTransactionType.WITHDRAW) // assertNotNull("found withdraw tx", ttx) // assertEquals("correct charge", 42.1, ttx.charge) assertEquals("six customers committed", 6, tsub1.getCustomersCommitted()); }
// Check consumption transactions @Test public void testConsumption() { Instant exp = now.plus(TimeService.WEEK * 10); TariffSpecification tariffSpec = new TariffSpecification(broker, PowerType.CONSUMPTION) .withExpiration(exp) .withMinDuration(TimeService.WEEK * 4) .withSignupPayment(-33.2) .addRate(new Rate().withValue(0.121)); tariff = new Tariff(tariffSpec); tariff.init(); // subscribe and consume in the first timeslot TariffSubscription tsub = tariffMarketService.subscribeToTariff(tariff, customer, 4); assertEquals("four customers committed", 4, tsub.getCustomersCommitted()); tsub.usePower(24.4); // consumption assertEquals("correct total usage", 24.4 / 4, tsub.getTotalUsage(), 1e-6); assertEquals("correct realized price", 0.121, tariff.getRealizedPrice(), 1e-6); // def txs = TariffTransaction.findAllByPostedTime(timeService.getCurrentTime()); // assertEquals("two transactions", 2, txs.size()) // TariffTransaction ttx = // TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime, // TariffTransactionType.SIGNUP) // assertNotNull("found signup tx", ttx) // assertEquals("correct charge", -33.2 * 4, ttx.charge, 1e-6) // ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime, // TariffTransactionType.CONSUME) // assertNotNull("found consumption tx", ttx) // assertEquals("correct amount", 24.4, ttx.quantity) // assertEquals("correct charge", 0.121 * 24.4, ttx.charge, 1e-6) // just consume in the second timeslot Instant hour = now.plus(TimeService.HOUR); timeService.setCurrentTime(hour); tsub.usePower(32.8); // consumption assertEquals("correct total usage", (24.4 + 32.8) / 4, tsub.getTotalUsage(), 1e-6); assertEquals("correct realized price", 0.121, tariff.getRealizedPrice(), 1e-6); // txs = TariffTransaction.findAllByPostedTime(timeService.getCurrentTime()) // assertEquals("one transaction", 1, txs.size()) // ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.getCurrentTime(), // TariffTransactionType.CONSUME) // assertNotNull("found consumption tx", ttx) // assertEquals("correct amount", 32.8, ttx.quantity) // assertEquals("correct charge", 0.121 * 32.8, ttx.charge, 1e-6) }
// set commitment leadtime to a larger number and make sure ordering // behavior is correct @Test public void testGenerateOrders2() { // set up the genco with commitment leadtime=3 PluginConfig config = new PluginConfig("Genco", "").addConfiguration("commitmentLeadtime", "3"); genco.configure(config); // all defaults // capture orders final ArrayList<Order> orderList = new ArrayList<Order>(); doAnswer( new Answer() { public Object answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); orderList.add((Order) args[0]); return null; } }) .when(mockProxy) .routeMessage(isA(Order.class)); // set up some timeslots Timeslot ts0 = timeslotRepo.makeTimeslot(start); ts0.disable(); assertEquals("first ts has sn=0", 0, ts0.getSerialNumber()); timeslotRepo.makeTimeslot(start.plus(TimeService.HOUR)); timeslotRepo.makeTimeslot(start.plus(TimeService.HOUR * 2)); timeslotRepo.makeTimeslot(start.plus(TimeService.HOUR * 3)); timeslotRepo.makeTimeslot(start.plus(TimeService.HOUR * 4)); assertEquals("4 enabled timeslots", 4, timeslotRepo.enabledTimeslots().size()); // generate orders and check genco.generateOrders(start, timeslotRepo.enabledTimeslots()); assertEquals("two orders", 2, orderList.size()); Order first = orderList.get(0); assertEquals("first order for ts3", 3, first.getTimeslot().getSerialNumber()); assertEquals("first order price", 1.0, first.getLimitPrice(), 1e-6); assertEquals("first order for 50 mwh", -100.0, first.getMWh(), 1e-6); Order second = orderList.get(1); assertEquals("second order for ts4", 4, second.getTimeslot().getSerialNumber()); assertEquals("second order price", 1.0, second.getLimitPrice(), 1e-6); assertEquals("second order for 100 mwh", -100.0, second.getMWh(), 1e-6); }
@Test public void testGenerateOrders() { // set up the genco PluginConfig config = new PluginConfig("Genco", ""); genco.configure(config); // all defaults // capture orders final ArrayList<Order> orderList = new ArrayList<Order>(); doAnswer( new Answer() { public Object answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); orderList.add((Order) args[0]); return null; } }) .when(mockProxy) .routeMessage(isA(Order.class)); // set up some timeslots Timeslot ts1 = timeslotRepo.makeTimeslot(start); ts1.disable(); Timeslot ts2 = timeslotRepo.makeTimeslot(start.plus(TimeService.HOUR)); Timeslot ts3 = timeslotRepo.makeTimeslot(start.plus(TimeService.HOUR * 2)); assertEquals("2 enabled timeslots", 2, timeslotRepo.enabledTimeslots().size()); // 50 mwh already sold in ts2 MarketPosition posn2 = new MarketPosition(genco, ts2, -50.0); genco.addMarketPosition(posn2, ts2); // generate orders and check genco.generateOrders(start, timeslotRepo.enabledTimeslots()); assertEquals("two orders", 2, orderList.size()); Order first = orderList.get(0); assertEquals("first order for ts2", ts2, first.getTimeslot()); assertEquals("first order price", 1.0, first.getLimitPrice(), 1e-6); assertEquals("first order for 50 mwh", -50.0, first.getMWh(), 1e-6); Order second = orderList.get(1); assertEquals("second order for ts3", ts3, second.getTimeslot()); assertEquals("second order price", 1.0, second.getLimitPrice(), 1e-6); assertEquals("second order for 100 mwh", -100.0, second.getMWh(), 1e-6); }
@Before public void setUp() { broker = new Broker("Joe"); now = new DateTime(2011, 1, 10, 0, 0, 0, 0, DateTimeZone.UTC).toInstant(); timeService.setCurrentTime(now); Instant exp = now.plus(TimeService.WEEK * 10); TariffSpecification tariffSpec = new TariffSpecification(broker, PowerType.CONSUMPTION) .withExpiration(exp) .withMinDuration(TimeService.WEEK * 8) .addRate(new Rate().withValue(0.121)); tariff = new Tariff(tariffSpec); tariff.init(); customerInfo = new CustomerInfo("Charley", 100); customer = new AbstractCustomer(customerInfo); reset(mockAccounting); }
// subscription with non-zero signup bonus @Test public void testSignupBonus() { Instant exp = now.plus(TimeService.WEEK * 10); TariffSpecification tariffSpec = new TariffSpecification(broker, PowerType.CONSUMPTION) .withExpiration(exp) .withMinDuration(TimeService.WEEK * 4) .withSignupPayment(-33.2) .addRate(new Rate().withValue(0.121)); tariff = new Tariff(tariffSpec); tariff.init(); TariffSubscription tsub = tariffMarketService.subscribeToTariff(tariff, customer, 5); assertNotNull("non-null subscription", tsub); assertEquals("five customers committed", 5, tsub.getCustomersCommitted()); verify(mockAccounting) .addTariffTransaction( TariffTransaction.Type.SIGNUP, tariff, customer.getCustomerInfo(), 5, 0.0, -33.2 * 5); }
// Check two-part tariff @Test public void testTwoPart() { Instant exp = now.plus(TimeService.WEEK * 10); TariffSpecification tariffSpec = new TariffSpecification(broker, PowerType.CONSUMPTION) .withExpiration(exp) .withMinDuration(TimeService.WEEK * 4) .withSignupPayment(-31.2) .withPeriodicPayment(1.3) .addRate(new Rate().withValue(0.112)); tariff = new Tariff(tariffSpec); tariff.init(); // subscribe and consume in the first timeslot TariffSubscription tsub = tariffMarketService.subscribeToTariff(tariff, customer, 6); assertEquals("six customers committed", 6, tsub.getCustomersCommitted()); tsub.usePower(28.8); // consumption assertEquals("correct total usage", 28.8 / 6, tsub.getTotalUsage(), 1e-6); assertEquals( "correct realized price", (0.112 * 28.8 + 6 * 1.3) / 28.8, tariff.getRealizedPrice(), 1e-6); // def txs = TariffTransaction.findAllByPostedTime(timeService.currentTime); // assertEquals("two transactions", 3, txs.size()) // TariffTransaction ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime, // TariffTransactionType.SIGNUP) // assertNotNull("found signup tx", ttx) // assertEquals("correct charge", -31.2 * 6, ttx.charge, 1e-6) // ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime, // TariffTransactionType.CONSUME) // assertNotNull("found consumption tx", ttx) // assertEquals("correct amount", 28.8, ttx.quantity) // assertEquals("correct charge", 0.112 * 28.8, ttx.charge, 1e-6) // ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime, // TariffTransactionType.PERIODIC) // assertNotNull("found periodoc tx", ttx) // assertEquals("correct charge", 6 * 1.3, ttx.charge, 1e-6) }
// Computes constraints on future energy needs // Amounts are energy needed to run the chargers. Energy input to trucks // will be smaller due to charge efficiency. ShiftEnergy[] getFutureEnergyNeeds(Instant start, int horizon, double initialCharging) { Instant seStart = start; int index = indexOfShift(start); // current time is likely to be partway into first shift Shift currentShift = shiftSchedule[index]; // might be null int duration = 0; while (shiftSchedule[index] == currentShift) { duration += 1; index = nextShiftIndex(index); } Shift nextShift = shiftSchedule[index]; // this gives us the info we need to start the sequence ArrayList<ShiftEnergy> data = new ArrayList<ShiftEnergy>(); data.add(new ShiftEnergy(seStart, index, duration)); seStart = seStart.plus(duration * TimeService.HOUR); int elapsed = duration; // add shifts until we run off the end of the horizon // keep in mind that a shift can be null while (elapsed < horizon) { duration = 0; while (nextShift == shiftSchedule[index]) { index = nextShiftIndex(index); duration += 1; } nextShift = shiftSchedule[index]; data.add(new ShiftEnergy(seStart, index, duration)); elapsed += duration; seStart = seStart.plus(duration * TimeService.HOUR); } // now we convert to array, then walk backward and fill in energy needs ShiftEnergy[] result = data.toArray(new ShiftEnergy[data.size()]); double shortage = 0.0; for (int i = result.length - 1; i >= 0; i--) { int endx = result[i].endIndex; int prev = previousShiftIndex(endx); currentShift = shiftSchedule[prev]; Shift end = shiftSchedule[endx]; double needed = 0.0; if (null != end) { // Assume we need, at the end of each shift, enough energy to // run the next shift needed = (end.getTrucks() * end.getDuration() * getTruckKW()) / getChargeEfficiency(); } // chargers is min of charger capacity and battery availability int chargers = getNChargers(); int availableBatteries = nBatteries; if (null != currentShift) { availableBatteries -= currentShift.getTrucks(); } chargers = (int) Math.min(chargers, availableBatteries); double available = getMaxChargeKW() * result[i].getDuration() * chargers / getChargeEfficiency(); double surplus = available - needed - shortage; shortage = Math.max(0.0, -(available - needed - shortage)); result[i].setEnergyNeeded(needed); result[i].setMaxSurplus(surplus); } // finally, we need to update the first element with // the current battery charge. double finalSurplus = result[0].getMaxSurplus(); if (finalSurplus > 0.0) { result[0].setMaxSurplus(finalSurplus + initialCharging); } else if (shortage > 0.0) { result[0].setMaxSurplus(initialCharging - shortage); } return result; }
@Override public IntervalWindow assignWindow(Instant timestamp) { long start = timestamp.getMillis() - timestamp.plus(size).minus(offset).getMillis() % size.getMillis(); return new IntervalWindow(new Instant(start), size); }