16 package emlab.gen.role.market;
18 import java.util.HashMap;
19 import java.util.List;
22 import org.apache.commons.math.stat.regression.SimpleRegression;
23 import org.springframework.beans.factory.annotation.Autowired;
24 import org.springframework.data.neo4j.support.Neo4jTemplate;
25 import org.springframework.transaction.annotation.Transactional;
27 import agentspring.role.AbstractRole;
55 public abstract class AbstractClearElectricitySpotMarketRole<T
extends DecarbonizationModel> extends AbstractRole<T> {
61 private Neo4jTemplate
template;
63 protected final double epsilon = 1e-3;
65 class MarketSegmentClearingOutcome {
66 HashMap<ElectricitySpotMarket, Double> loads =
new HashMap<ElectricitySpotMarket, Double>();
67 HashMap<ElectricitySpotMarket, Double> prices =
new HashMap<ElectricitySpotMarket, Double>();
68 HashMap<ElectricitySpotMarket, Double> supplies =
new HashMap<ElectricitySpotMarket, Double>();
71 public String toString() {
72 return new String(
"Market outcome: loads " + loads +
" prices: " + prices +
" supplies: " + supplies);
76 class GlobalSegmentClearingOutcome {
77 Map<ElectricitySpotMarket, Double> loads;
78 Map<ElectricitySpotMarket, Double> supplies =
new HashMap<ElectricitySpotMarket, Double>();
84 public String toString() {
85 return "Global Data; loads: " + loads +
", supplies: " + supplies +
" globalLoad: " + globalLoad +
", globalSupply: "
90 public class CO2Iteration {
91 public boolean stable;
92 public double co2Price;
93 public double co2Emissions;
97 public class CO2PriceStability extends CO2Iteration {
99 public boolean positive;
100 public double iterationSpeedFactor;
101 public double changeInDeviationFromLastStep;
105 public class CO2SecantSearch extends CO2Iteration {
106 public boolean twoPricesExistWithBelowAboveEmissions;
107 public double higherCO2Price;
108 public int iteration = 0;
109 public PriceEmissionPair tooLowEmissionsPair;
110 public PriceEmissionPair tooHighEmissionsPair;
111 public double bankingEffectiveMinimumPrice;
119 public String toString() {
121 return "Stable: " + this.stable +
", co2Price: " + this.co2Price +
", co2Emissions: " + this.co2Emissions;
126 class PriceEmissionPair {
128 public double emission;
131 CO2SecantSearch co2PriceSecantSearchUpdate(CO2SecantSearch co2SecantSearch,
DecarbonizationModel model,
132 Government government,
boolean forecast,
long clearingTick,
double co2CapAdjustment) {
134 co2SecantSearch.stable =
false;
135 double capDeviationCriterion = model.getCapDeviationCriterion();
136 double co2Cap = government.getCo2Cap(clearingTick) + co2CapAdjustment;
137 co2SecantSearch.co2Emissions = determineTotalEmissionsBasedOnPowerPlantDispatchPlan(forecast, clearingTick);
139 double deviation = (co2SecantSearch.co2Emissions - co2Cap) / co2Cap;
142 if (Math.abs(deviation) < capDeviationCriterion) {
144 co2SecantSearch.stable =
true;
145 return co2SecantSearch;
150 if (co2SecantSearch.tooHighEmissionsPair != null && co2SecantSearch.tooLowEmissionsPair != null) {
151 co2SecantSearch.twoPricesExistWithBelowAboveEmissions =
true;
152 }
else if (co2SecantSearch.co2Price == government.getMinCo2Price(clearingTick)
153 && co2SecantSearch.co2Emissions < co2Cap) {
157 co2SecantSearch.stable =
true;
158 return co2SecantSearch;
159 }
else if (co2SecantSearch.co2Price >= government.getCo2Penalty(clearingTick)
160 && co2SecantSearch.co2Emissions >= co2Cap) {
163 co2SecantSearch.co2Price = government.getCo2Penalty(clearingTick);
164 co2SecantSearch.stable =
true;
165 return co2SecantSearch;
171 if (co2SecantSearch.twoPricesExistWithBelowAboveEmissions) {
175 co2SecantSearch.tooHighEmissionsPair.price = co2SecantSearch.co2Price;
176 co2SecantSearch.tooHighEmissionsPair.emission = co2SecantSearch.co2Emissions;
178 co2SecantSearch.tooLowEmissionsPair.price = co2SecantSearch.co2Price;
179 co2SecantSearch.tooLowEmissionsPair.emission = co2SecantSearch.co2Emissions;
182 double p2 = co2SecantSearch.tooHighEmissionsPair.price;
183 double p1 = co2SecantSearch.tooLowEmissionsPair.price;
184 double e2 = co2SecantSearch.tooHighEmissionsPair.emission - co2Cap;
185 double e1 = co2SecantSearch.tooLowEmissionsPair.emission - co2Cap;
188 if (co2SecantSearch.iteration < 5) {
189 co2SecantSearch.co2Price = p1 - (e1 * (p2 - p1) / (e2 - e1));
190 co2SecantSearch.iteration++;
193 co2SecantSearch.co2Price = (p1 + p2) / 2;
194 co2SecantSearch.iteration = 0;
201 if (co2SecantSearch.tooHighEmissionsPair == null)
202 co2SecantSearch.tooHighEmissionsPair =
new PriceEmissionPair();
204 co2SecantSearch.tooHighEmissionsPair.price = co2SecantSearch.co2Price;
205 co2SecantSearch.tooHighEmissionsPair.emission = co2SecantSearch.co2Emissions;
207 if (co2SecantSearch.tooLowEmissionsPair == null) {
208 co2SecantSearch.co2Price = (co2SecantSearch.co2Price != 0d) ? ((co2SecantSearch.co2Price * 2 < government
209 .getCo2Penalty(clearingTick)) ? (co2SecantSearch.co2Price * 2) : government
210 .getCo2Penalty(clearingTick)) : 5d;
213 double p2 = co2SecantSearch.tooHighEmissionsPair.price;
214 double p1 = co2SecantSearch.tooLowEmissionsPair.price;
215 double e2 = co2SecantSearch.tooHighEmissionsPair.emission - co2Cap;
216 double e1 = co2SecantSearch.tooLowEmissionsPair.emission - co2Cap;
218 co2SecantSearch.co2Price = p1 - (e1 * (p2 - p1) / (e2 - e1));
219 co2SecantSearch.iteration++;
225 if (co2SecantSearch.tooLowEmissionsPair == null)
226 co2SecantSearch.tooLowEmissionsPair =
new PriceEmissionPair();
228 co2SecantSearch.tooLowEmissionsPair.price = co2SecantSearch.co2Price;
229 co2SecantSearch.tooLowEmissionsPair.emission = co2SecantSearch.co2Emissions;
231 if (co2SecantSearch.tooHighEmissionsPair == null) {
232 co2SecantSearch.co2Price = (co2SecantSearch.co2Price / 2);
235 double p2 = co2SecantSearch.tooHighEmissionsPair.price;
236 double p1 = co2SecantSearch.tooLowEmissionsPair.price;
237 double e2 = co2SecantSearch.tooHighEmissionsPair.emission - co2Cap;
238 double e1 = co2SecantSearch.tooLowEmissionsPair.emission - co2Cap;
240 co2SecantSearch.co2Price = p1 - (e1 * (p2 - p1) / (e2 - e1));
242 co2SecantSearch.iteration++;
246 if (co2SecantSearch.co2Price < 0.5
247 || co2SecantSearch.co2Price - government.getMinCo2Price(clearingTick) < 0.5) {
248 co2SecantSearch.co2Price = government.getMinCo2Price(clearingTick);
249 co2SecantSearch.stable =
true;
256 return co2SecantSearch;
269 double clearGlobalMarketWithNoCapacityConstraints(Segment segment, GlobalSegmentClearingOutcome globalOutcome,
270 boolean forecast,
long clearingTick) {
272 double marginalPlantMarginalCost = Double.MAX_VALUE;
274 for (PowerPlantDispatchPlan plan : reps.powerPlantDispatchPlanRepository.findSortedPowerPlantDispatchPlansForSegmentForTime(
275 segment, clearingTick, forecast)) {
276 ElectricitySpotMarket myMarket = (ElectricitySpotMarket) plan.getBiddingMarket();
279 double plantSupply = determineProductionOnSpotMarket(plan, globalOutcome.globalSupply, globalOutcome.globalLoad);
281 if (plantSupply > 0) {
284 marginalPlantMarginalCost = plan.getPrice();
285 globalOutcome.supplies.put(myMarket, globalOutcome.supplies.get(myMarket) + plantSupply);
286 globalOutcome.globalSupply = +globalOutcome.globalSupply + plantSupply;
293 return marginalPlantMarginalCost;
307 void determineCommitmentOfPowerPlantsOnTheBasisOfLongTermContracts(List<Segment> segments,
boolean forecast) {
309 for (EnergyProducer producer : reps.genericRepository.findAll(EnergyProducer.class)) {
311 for (Segment segment : segments) {
315 double contractedCapacityInSegment = 0;
317 for (LongTermContract ltc : reps.contractRepository.findLongTermContractsForEnergyProducerForSegmentActiveAtTime(producer,
318 segment, getCurrentTick())) {
319 contractedCapacityInSegment += ltc.getCapacity();
323 for (PowerPlantDispatchPlan plan : reps.powerPlantDispatchPlanRepository
324 .findAllPowerPlantDispatchPlansForEnergyProducerForTimeAndSegment(segment, producer,
325 getCurrentTick(), forecast)) {
326 PowerPlant plant = plan.getPowerPlant();
328 double availableCapacity = plant.getAvailableCapacity(getCurrentTick(), segment, segments.size());
333 if (plant.getTechnology().isApplicableForLongTermContract()) {
335 if (contractedCapacityInSegment - availableCapacity > 0) {
339 plan.setCapacityLongTermContract(availableCapacity);
343 plan.setCapacityLongTermContract(contractedCapacityInSegment);
348 plan.setCapacityLongTermContract(0);
354 plan.setAmount(availableCapacity - plan.getCapacityLongTermContract());
355 contractedCapacityInSegment -= plan.getCapacityLongTermContract();
370 Map<ElectricitySpotMarket, Double> determineActualDemandForSpotMarkets(Segment segment, Map<ElectricitySpotMarket,Double> demandGrowthMap) {
372 if (demandGrowthMap == null) {
373 demandGrowthMap =
new HashMap<ElectricitySpotMarket, Double>();
374 for (ElectricitySpotMarket market : reps.marketRepository.findAllElectricitySpotMarkets()) {
375 demandGrowthMap.put(market, market.getDemandGrowthTrend().getValue(getCurrentTick()));
379 Map<ElectricitySpotMarket, Double> loadInMarkets =
new HashMap<ElectricitySpotMarket, Double>();
381 for (ElectricitySpotMarket market : reps.marketRepository.findAllElectricitySpotMarkets()) {
382 double baseLoad = reps.segmentLoadRepository.returnSegmentBaseLoadBySegmentAndMarket(segment, market);
384 load = baseLoad * demandGrowthMap.get(market);
387 double loadCoveredByLTC = 0d;
390 for (EnergyConsumer consumer : reps.genericRepository.findAll(EnergyConsumer.class)) {
392 for (LongTermContract ltc : reps.contractRepository.findLongTermContractsForEnergyConsumerForSegmentForZoneActiveAtTime(
393 consumer, segment, market.getZone(), getCurrentTick())) {
395 loadCoveredByLTC += ltc.getCapacity();
401 loadInMarkets.put(market, load - loadCoveredByLTC);
404 return loadInMarkets;
414 double determineTotalLoadFromLoadMap(Map<ElectricitySpotMarket, Double> loadInMarkets) {
415 double totalLoad = 0d;
416 for (ElectricitySpotMarket market : loadInMarkets.keySet()) {
417 totalLoad += loadInMarkets.get(market);
432 double determineProductionOnSpotMarket(PowerPlantDispatchPlan plan,
double supplySoFar,
double load) {
434 double plantCapacity = plan.getAmount();
435 double plantSupply = 0d;
439 if ((supplySoFar + plantCapacity) < load) {
442 plantSupply = plantCapacity;
443 plan.setStatus(Bid.ACCEPTED);
448 plantSupply = load - supplySoFar;
449 if (plantSupply - epsilon > 0) {
450 plan.setStatus(Bid.PARTLY_ACCEPTED);
452 plan.setStatus(Bid.FAILED);
457 plan.setAcceptedAmount(plantSupply);
467 double determineTotalEmissionsBasedOnPowerPlantDispatchPlan(
boolean forecast,
long clearingTick) {
468 double totalEmissions = 0d;
470 for (PowerPlantDispatchPlan plan : reps.powerPlantDispatchPlanRepository.findAllPowerPlantDispatchPlansForTime(
471 clearingTick, forecast)) {
472 double operationalCapacity = plan.getCapacityLongTermContract() + plan.getAcceptedAmount();
473 double emissionIntensity = plan.getPowerPlant().calculateEmissionIntensity();
474 double hours = plan.getSegment().getLengthInHours();
475 totalEmissions += operationalCapacity * emissionIntensity * hours;
479 return totalEmissions;
488 double determineTotalEmissionsBasedOnPowerPlantDispatchPlanForEnergyProducer(
boolean forecast,
long clearingTick,
489 EnergyProducer producer) {
490 double totalEmissions = 0d;
492 for (PowerPlantDispatchPlan plan : reps.powerPlantDispatchPlanRepository
493 .findAllAcceptedPowerPlantDispatchPlansForEnergyProducerForTime(producer, clearingTick, forecast)) {
494 double operationalCapacity = plan.getCapacityLongTermContract() + plan.getAcceptedAmount();
495 double emissionIntensity = plan.getPowerPlant().calculateEmissionIntensity();
496 double hours = plan.getSegment().getLengthInHours();
497 totalEmissions += operationalCapacity * emissionIntensity * hours;
502 return totalEmissions;
516 CO2PriceStability determineStabilityOfCO2andElectricityPricesAndAdjustIfNecessary(CO2PriceStability co2PriceStability,
517 DecarbonizationModel model, Government government,
boolean forecast,
520 double co2Cap = government.getCo2Cap(clearingTick);
521 double minimumCo2Price = government.getMinCo2Price(clearingTick);
522 double co2Penalty = government.getCo2Penalty(clearingTick);
523 double iterationSpeedCriterion = model.getIterationSpeedCriterion();
524 double capDeviationCriterion = model.getCapDeviationCriterion();
526 co2PriceStability.co2Emissions = determineTotalEmissionsBasedOnPowerPlantDispatchPlan(forecast, clearingTick);
527 double deviation = (co2PriceStability.co2Emissions - co2Cap) / co2Cap;
530 logger.warn(
"Cap {} (euro/ton) vs emissions {} (euro/ton)", co2Cap, co2PriceStability.co2Emissions);
531 logger.warn(
"Tick {} Deviation: {} %", clearingTick, deviation * 100);
535 if (Math.abs(deviation) < capDeviationCriterion) {
536 logger.warn(
"Deviation is less than capDeviationCriterion");
537 co2PriceStability.stable =
true;
538 }
else if (co2PriceStability.iterationSpeedFactor < iterationSpeedCriterion) {
539 logger.warn(
"Deviation iterationSpeedFactor is less than iterationSpeedCriterion");
540 co2PriceStability.stable =
true;
541 }
else if (co2PriceStability.co2Price == minimumCo2Price && co2PriceStability.co2Emissions < co2Cap) {
542 logger.warn(
"Deviation CO2 price has reached minimum");
545 co2PriceStability.stable =
true;
547 }
else if (co2PriceStability.co2Price >= co2Penalty && co2PriceStability.co2Emissions >= co2Cap) {
549 logger.warn(
"CO2 price ceiling reached {}", co2PriceStability.co2Price);
550 co2PriceStability.stable =
true;
552 co2PriceStability.co2Price = co2PriceStability.co2Price * (1 + deviation * co2PriceStability.iterationSpeedFactor);
553 logger.warn(
"Deviation updated CO2 price to {}", co2PriceStability.co2Price);
559 if (co2PriceStability.co2Price == 0 && co2PriceStability.co2Emissions >= co2Cap) {
560 logger.warn(
"Deviation resetting CO2 price to 2");
561 co2PriceStability.co2Price = 2;
562 co2PriceStability.stable =
true;
566 if ((co2PriceStability.positive && deviation < 0) || (!co2PriceStability.positive && deviation > 0)) {
567 co2PriceStability.iterationSpeedFactor = co2PriceStability.iterationSpeedFactor / 2;
568 logger.warn(
"Deviation speed factor decreased {}", co2PriceStability.iterationSpeedFactor);
575 if ((co2PriceStability.co2Price < (0.1 + minimumCo2Price)) && (co2PriceStability.co2Emissions < co2Cap)) {
576 logger.warn(
"Deviation reseting CO2 price to minimum");
577 co2PriceStability.co2Price = minimumCo2Price;
582 co2PriceStability.positive =
false;
584 co2PriceStability.positive =
true;
587 return co2PriceStability;
598 double findLastKnownPriceForSubstance(Substance substance) {
600 DecarbonizationMarket market = reps.marketRepository.findFirstMarketBySubstance(substance);
601 if (market == null) {
602 logger.warn(
"No market found for {} so no price can be found", substance.getName());
605 return findLastKnownPriceOnMarket(market);
617 double findLastKnownPriceOnMarket(DecarbonizationMarket market) {
618 Double average = calculateAverageMarketPriceBasedOnClearingPoints(reps.clearingPointRepositoryOld
619 .findClearingPointsForMarketAndTime(market, getCurrentTick(),
false));
620 Substance substance = market.getSubstance();
622 if (average != null) {
623 logger.info(
"Average price found on market for this tick for {}", substance.getName());
627 average = calculateAverageMarketPriceBasedOnClearingPoints(reps.clearingPointRepositoryOld.findClearingPointsForMarketAndTime(
628 market, getCurrentTick() - 1,
false));
629 if (average != null) {
630 logger.info(
"Average price found on market for previous tick for {}", substance.getName());
634 if (market.getReferencePrice() > 0) {
635 logger.info(
"Found a reference price found for market for {}", substance.getName());
636 return market.getReferencePrice();
639 for (CommoditySupplier supplier : reps.genericRepository.findAll(CommoditySupplier.class)) {
640 if (supplier.getSubstance().equals(substance)) {
642 logger.info(
"Price found for {} by asking the supplier {} directly", substance.getName(), supplier.getName());
643 return supplier.getPriceOfCommodity().getValue(getCurrentTick());
647 logger.info(
"No price has been found for {}", substance.getName());
658 private Double calculateAverageMarketPriceBasedOnClearingPoints(Iterable<ClearingPoint> clearingPoints) {
659 double priceTimesVolume = 0d;
662 for (ClearingPoint point : clearingPoints) {
663 priceTimesVolume += point.getPrice() * point.getVolume();
664 volume += point.getVolume();
667 return priceTimesVolume / volume;
672 public Reps getReps() {
676 public Map<Substance, Double> predictFuelPrices(
long numberOfYearsBacklookingForForecasting,
long futureTimePoint) {
678 Map<Substance, Double> expectedFuelPrices =
new HashMap<Substance, Double>();
679 for (Substance substance : reps.substanceRepository.findAllSubstancesTradedOnCommodityMarkets()) {
681 Iterable<ClearingPoint> cps = reps.clearingPointRepository
682 .findAllClearingPointsForSubstanceTradedOnCommodityMarkesAndTimeRange(substance, getCurrentTick()
683 - (numberOfYearsBacklookingForForecasting - 1), getCurrentTick() - 1,
686 SimpleRegression gtr =
new SimpleRegression();
687 for (ClearingPoint clearingPoint : cps) {
689 gtr.addData(clearingPoint.getTime(), clearingPoint.getPrice());
691 gtr.addData(getCurrentTick(), findLastKnownPriceForSubstance(substance));
692 double forecast = gtr.predict(futureTimePoint);
693 if (Double.isNaN(forecast)) {
694 expectedFuelPrices.put(substance, findLastKnownPriceForSubstance(substance));
696 expectedFuelPrices.put(substance, forecast);
699 return expectedFuelPrices;
702 public Map<ElectricitySpotMarket, Double> predictDemand(
long numberOfYearsBacklookingForForecasting,
703 long futureTimePoint) {
704 Map<ElectricitySpotMarket, Double> expectedDemand =
new HashMap<ElectricitySpotMarket, Double>();
705 for (ElectricitySpotMarket elm : reps.template.findAll(ElectricitySpotMarket.class)) {
706 GeometricTrendRegression gtr =
new GeometricTrendRegression();
707 for (
long time = getCurrentTick(); time > getCurrentTick() - numberOfYearsBacklookingForForecasting
708 && time >= 0; time = time - 1) {
709 gtr.addData(time, elm.getDemandGrowthTrend().getValue(time));
711 double forecast = gtr.predict(futureTimePoint);
712 if (Double.isNaN(forecast))
713 forecast = elm.getDemandGrowthTrend().getValue(getCurrentTick());
714 expectedDemand.put(elm, forecast);
716 return expectedDemand;
720 void updatePowerPlanDispatchPlansWithNewCO2Prices(
double co2Price,
721 Map<ElectricitySpotMarket, Double> nationalMinCo2Prices,
long clearingTick,
boolean forecast) {
722 for (PowerPlantDispatchPlan plan : reps.powerPlantDispatchPlanRepository.findAllPowerPlantDispatchPlansForTime(
723 clearingTick, forecast)) {
724 if (nationalMinCo2Prices.get(plan.getBiddingMarket()) > co2Price) {
725 plan.setPrice(plan.getBidWithoutCO2()
726 + (nationalMinCo2Prices.get(plan.getBiddingMarket()) * plan.getPowerPlant().calculateEmissionIntensity()));
728 plan.setPrice(plan.getBidWithoutCO2() + (co2Price * plan.getPowerPlant().calculateEmissionIntensity()));