/******************************************************************************* * Copyright 2012 the original author or authors. * * 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 emlab.role.market; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.neo4j.support.Neo4jTemplate; import org.springframework.transaction.annotation.Transactional; import agentspring.role.Role; import agentspring.role.RoleComponent; import emlab.domain.agent.DecarbonizationModel; import emlab.domain.agent.Government; import emlab.domain.agent.NationalGovernment; import emlab.domain.gis.Zone; import emlab.domain.market.CO2Auction; import emlab.domain.market.ClearingPoint; import emlab.domain.market.electricity.ElectricitySpotMarket; import emlab.domain.market.electricity.PowerPlantDispatchPlan; import emlab.domain.market.electricity.Segment; import emlab.domain.technology.Interconnector; import emlab.domain.technology.PowerPlant; import emlab.domain.technology.Substance; import emlab.repository.Reps; import emlab.util.Utils; /** * Creates and clears the {@link ElectricitySpotMarket} for two {@link Zone}s. The market is divided into {@link Segment}s and cleared for each segment. A global CO2 emissions market is cleared. The * process is iterative and the target is to let the total emissions match the cap. * * @author <a href="mailto:E.J.L.Chappin@tudelft.nl">Emile Chappin</a> * * @author <a href="mailto:A.Chmieliauskas@tudelft.nl">Alfredas Chmieliauskas</a> * * @author <a href="mailto:J.C.Richstein@tudelft.nl">Joern C. Richstein</a> * */ @RoleComponent public class ClearIterativeCO2AndElectricitySpotMarketTwoCountryRole extends AbstractClearElectricitySpotMarketRole<DecarbonizationModel> implements Role<DecarbonizationModel> { @Autowired private Reps reps; @Autowired SubmitOffersToElectricitySpotMarketRole submitOffersToElectricitySpotMarketRole; @Autowired Neo4jTemplate template; @Transactional public void act(DecarbonizationModel model) { // find all operational power plants and store the ones operational to a // list. logger.info("Clearing the CO2 and electricity spot markets using iteration for 2 countries "); // find all fuel prices Map<Substance, Double> fuelPriceMap = new HashMap<Substance, Double>(); for (Substance substance : template.findAll(Substance.class)) { fuelPriceMap.put(substance, findLastKnownPriceForSubstance(substance)); } // find all interconnectors Interconnector interconnector = template.findAll(Interconnector.class).iterator().next(); // find all segments List<Segment> segments = Utils.asList(reps.segmentRepository.findAll()); // find the EU government Government government = template.findAll(Government.class).iterator().next(); // find national minimum CO2 prices. Initial Map size is 2. Map<ElectricitySpotMarket, Double> nationalMinCo2Prices = new HashMap<ElectricitySpotMarket, Double>(2); Iterable<NationalGovernment> nationalGovernments = template.findAll(NationalGovernment.class); for (NationalGovernment nG : nationalGovernments) { if (model.isCo2TradingImplemented()) { nationalMinCo2Prices.put(reps.marketRepository.findElectricitySpotMarketByNationalGovernment(nG), nG .getMinNationalCo2PriceTrend().getValue(getCurrentTick())); } else { nationalMinCo2Prices.put(reps.marketRepository.findElectricitySpotMarketByNationalGovernment(nG), 0d); } } CO2Auction co2Auction = template.findAll(CO2Auction.class).iterator().next(); if (model.isCo2TradingImplemented()) { // Old Iteration // CO2PriceStability co2PriceStability = new CO2PriceStability(); // co2PriceStability.stable = false; // co2PriceStability.positive = false; // co2PriceStability.iterationSpeedFactor = model.getIterationSpeedFactor(); // co2PriceStability.co2Price = findLastKnownPriceOnMarket(co2Auction); // New Iteration CO2SecantSearch co2SecantSearch = new CO2SecantSearch(); co2SecantSearch.stable = false; co2SecantSearch.twoPricesExistWithBelowAboveEmissions = false; co2SecantSearch.co2Price = findLastKnownPriceOnMarket(co2Auction); co2SecantSearch.tooHighEmissionsPair = null; co2SecantSearch.tooLowEmissionsPair = null; // Change Iteration algorithm here, and a few lines below... ClearingPoint lastClearingPointOfCo2Market = reps.clearingPointRepositoryOld.findClearingPointForMarketAndTime(co2Auction, getCurrentTick() - 1); if (lastClearingPointOfCo2Market != null) { co2SecantSearch.co2Emissions = lastClearingPointOfCo2Market.getVolume(); } else { co2SecantSearch.co2Emissions = 0d; } int breakOffIterator = 0; while (!co2SecantSearch.stable) { if (breakOffIterator > 15) { logger.warn("Iteration cancelled, last found CO2 Price is used."); break; } // Clear the electricity markets with the expected co2Price updatePowerPlanDispatchPlansWithNewCO2Prices(co2SecantSearch.co2Price, nationalMinCo2Prices); if (model.isLongTermContractsImplemented()) determineCommitmentOfPowerPlantsOnTheBasisOfLongTermContracts(segments); for (Segment segment : segments) { clearOneOrTwoConnectedElectricityMarketsAtAGivenCO2PriceForOneSegment(interconnector.getCapacity(), segment, government); } // Change Iteration algorithm here // co2PriceStability = determineStabilityOfCO2andElectricityPricesAndAdjustIfNecessary(co2PriceStability, model, government); co2SecantSearch = co2PriceSecantSearchUpdate(co2SecantSearch, model, government); breakOffIterator++; } // Check if iteration is stable if fuel mix change is considered. // co2SecantSearch.stable = false; // co2SecantSearch.twoPricesExistWithBelowAboveEmissions = false; // co2SecantSearch.tooHighEmissionsPair = null; // co2SecantSearch.tooLowEmissionsPair = null; // // while (!co2SecantSearch.stable && co2SecantSearch.co2Price != findLastKnownPriceOnMarket(co2Auction)) { // // submitOffersToElectricitySpotMarketRole.updateMarginalCostInclCO2AfterFuelMixChange(co2SecantSearch.co2Price, // nationalMinCo2Prices); // if (model.isLongTermContractsImplemented()) // determineCommitmentOfPowerPlantsOnTheBasisOfLongTermContracts(segments); // // for (Segment segment : segments) { // clearTwoConnectedElectricityMarketsAtAGivenCO2PriceForOneSegment(interconnector.getCapacity(), segment, government); // } // // co2SecantSearch = co2PriceSecantSearchUpdate(co2SecantSearch, model, government); // } // Save the resulting CO2 price to the CO2 auction reps.clearingPointRepositoryOld.createOrUpdateClearingPoint(co2Auction, co2SecantSearch.co2Price, co2SecantSearch.co2Emissions, getCurrentTick()); } else { if (model.isLongTermContractsImplemented()) determineCommitmentOfPowerPlantsOnTheBasisOfLongTermContracts(segments); for (Segment segment : segments) { clearOneOrTwoConnectedElectricityMarketsAtAGivenCO2PriceForOneSegment(interconnector.getCapacity(), segment, government); } } } /** * Clears a time segment of all electricity markets for a given CO2 price. * * @param powerPlants * to be used * @param markets * to clear * @return the total CO2 emissions */ @Transactional void clearOneOrTwoConnectedElectricityMarketsAtAGivenCO2PriceForOneSegment(double interconnectorCapacity, Segment segment, Government government) { GlobalSegmentClearingOutcome globalOutcome = new GlobalSegmentClearingOutcome(); globalOutcome.loads = determineActualDemandForSpotMarkets(segment); globalOutcome.globalLoad = determineTotalLoadFromLoadMap(globalOutcome.loads); // Keep track of supply per market. Start at 0. for (ElectricitySpotMarket m : reps.marketRepository.findAllElectricitySpotMarkets()) { globalOutcome.supplies.put(m, 0d); } // empty list of plants that are supplying. double marginalPlantMarginalCost = clearGlobalMarketWithNoCapacityConstraints(segment, globalOutcome); // For each plant in the cost-ordered list // Determine the flow over the interconnector. ElectricitySpotMarket firstMarket = reps.marketRepository.findAllElectricitySpotMarkets().iterator().next(); double loadInFirstMarket = globalOutcome.loads.get(firstMarket); double supplyInFirstMarket = globalOutcome.supplies.get(firstMarket); // Interconnector flow defined as from market A --> market B = positive double interconnectorFlow = supplyInFirstMarket - loadInFirstMarket; logger.info("Before market coupling interconnector flow: {}, available interconnector capacity {}", interconnectorFlow, interconnectorCapacity); // if interconnector is not limiting or there is only one market, there is one price if (reps.marketRepository.countAllElectricitySpotMarkets() < 2 || Math.abs(interconnectorFlow) <= interconnectorCapacity) { // Set the price to the bid of the marginal plant. for (ElectricitySpotMarket market : reps.marketRepository.findAllElectricitySpotMarkets()) { double supplyInThisMarket = globalOutcome.supplies.get(market); // globalOutcome.globalSupply += supplyInThisMarket; if (globalOutcome.globalLoad <= globalOutcome.globalSupply) { globalOutcome.globalPrice = marginalPlantMarginalCost; } else { globalOutcome.globalPrice = market.getValueOfLostLoad(); } // updatePowerDispatchPlansAfterTwoCountryClearingIsComplete(segment); reps.clearingPointRepositoryOld.createOrUpdateSegmentClearingPoint(segment, market, globalOutcome.globalPrice, supplyInThisMarket, getCurrentTick()); logger.info("Stored a system-uniform price for market " + market + " / segment " + segment + " -- supply " + supplyInThisMarket + " -- price: " + globalOutcome.globalPrice); } } else { MarketSegmentClearingOutcome marketOutcomes = new MarketSegmentClearingOutcome(); for (ElectricitySpotMarket m : reps.marketRepository.findAllElectricitySpotMarkets()) { marketOutcomes.supplies.put(m, 0d); marketOutcomes.prices.put(m, m.getValueOfLostLoad()); } // else there are two prices logger.info("There should be multiple prices, but first we should do market coupling."); boolean firstImporting = true; if (interconnectorFlow > 0) { firstImporting = false; } boolean first = true; for (ElectricitySpotMarket market : reps.marketRepository.findAllElectricitySpotMarkets()) { // Update the load for this market. Which is market's true load // +/- the full interconnector capacity, based on direction of // the flow if ((first && firstImporting) || (!first && !firstImporting)) { marketOutcomes.loads.put(market, globalOutcome.loads.get(market) - interconnectorCapacity); } else { marketOutcomes.loads.put(market, globalOutcome.loads.get(market) + interconnectorCapacity); } first = false; } // For each plant in the cost-ordered list clearTwoInterconnectedMarketsGivenAnInterconnectorAdjustedLoad(segment, marketOutcomes); // updatePowerDispatchPlansAfterTwoCountryClearingIsComplete(segment); for (ElectricitySpotMarket market : reps.marketRepository.findAllElectricitySpotMarkets()) { if (marketOutcomes.supplies.get(market) < marketOutcomes.loads.get(market)) { marketOutcomes.prices.put(market, market.getValueOfLostLoad()); } } // Only for debugging purposes // logger.warn("Outcomes: {}", marketOutcomes); // for (ElectricitySpotMarket market : markets) { // logger.warn( // "Segment " + segment.getSegmentID() + // ": PPD capacity: {} MW, PP capacity: {}, Peak-Query: " // + // reps.powerPlantRepository.calculatePeakCapacityOfOperationalPowerPlantsInMarket(market, // getCurrentTick()), // determineCapacityInMarketBasedOnTreemapAndDispatchPlans(marginalCostMap, // segment, market, markets), // determinePeakCapacityInMarketBasedOnTreemapAndPowerPlants(marginalCostMap, // segment, market, markets) // + "Normal Capacity: " // + // determineCapacityInMarketBasedOnTreemapAndPowerPlants(marginalCostMap, // market, markets) // + "Query Capacity: " // + // reps.powerPlantRepository.calculateCapacityOfOperationalPowerPlantsInMarket(market, // getCurrentTick())); // } for (ElectricitySpotMarket market : reps.marketRepository.findAllElectricitySpotMarkets()) { reps.clearingPointRepositoryOld.createOrUpdateSegmentClearingPoint(segment, market, marketOutcomes.prices.get(market), marketOutcomes.supplies.get(market), getCurrentTick()); // logger.warn("Stored a market specific price for market " + // market + " / segment " + segment + " -- supply " // + marketOutcomes.supplies.get(market) + " -- demand: " + // marketOutcomes.loads.get(market) + " -- price: " // + marketOutcomes.prices.get(market)); } @SuppressWarnings("unused") int i = 0; } } void clearTwoInterconnectedMarketsGivenAnInterconnectorAdjustedLoad(Segment segment, MarketSegmentClearingOutcome marketOutcomes) { for (PowerPlantDispatchPlan plan : reps.powerPlantDispatchPlanRepository.findSortedPowerPlantDispatchPlansForSegmentForTime( segment, getCurrentTick())) { // If it is in the right market PowerPlant plant = plan.getPowerPlant(); ElectricitySpotMarket myMarket = (ElectricitySpotMarket) plan.getBiddingMarket(); // Make it produce as long as there is load. double plantSupply = determineProductionOnSpotMarket(plan, marketOutcomes.supplies.get(myMarket), marketOutcomes.loads.get(myMarket)); if (plantSupply > 0) { // Plant is producing, store the information to // determine price and so on. marketOutcomes.supplies.put(myMarket, marketOutcomes.supplies.get(myMarket) + plantSupply); marketOutcomes.prices.put(myMarket, plan.getPrice()); // logger.warn("Storing price: {} for plant {} in market " + // myMarket, plantCost.getValue(), plant); } } } public Reps getReps() { return reps; } }