/******************************************************************************* * 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.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import agentspring.role.AbstractRole; import agentspring.role.Role; import agentspring.role.RoleComponent; import emlab.domain.agent.EnergyConsumer; import emlab.domain.agent.EnergyProducer; import emlab.domain.contract.Contract; import emlab.domain.contract.LongTermContract; import emlab.domain.contract.LongTermContractOffer; import emlab.domain.contract.LongTermContractType; import emlab.domain.gis.Zone; import emlab.domain.market.Bid; import emlab.domain.market.electricity.ElectricitySpotMarket; import emlab.domain.market.electricity.Segment; import emlab.domain.market.electricity.SegmentClearingPoint; import emlab.domain.technology.PowerPlant; import emlab.repository.Reps; import emlab.util.MapValueComparator; /** * {@link EnergyProducer} submits offers to the {@link ElectricitySpotMarket}. * One {@link Bid} per {@link PowerPlant}. * * @author <a href="mailto:A.Chmieliauskas@tudelft.nl">Alfredas * Chmieliauskas</a> @author <a * href="mailto:E.J.L.Chappin@tudelft.nl">Emile Chappin</a> * */ @RoleComponent public class SelectLongTermElectricityContractsRole extends AbstractRole<EnergyConsumer> implements Role<EnergyConsumer> { @Autowired Reps reps; HashMap<Zone, ExistingContractsInformation> existingContractsInformations; @Transactional public void act(EnergyConsumer consumer) { logger.info("{} will now accept offers or not", consumer); existingContractsInformations = new HashMap<Zone, ExistingContractsInformation>(); // Make an overview of the capacity of existing contracts per ltc type. // Store that in a nested info class. for (Zone zone : reps.genericRepository.findAll(Zone.class)) { ExistingContractsInformation info = new ExistingContractsInformation( zone, consumer); existingContractsInformations.put(zone, info); info.updateExisingContractsInformation(); } // Rank the offers, by multiplying by better factors, // TODO based on past performance?? Map<LongTermContractOffer, Double> offersUnsorted = new HashMap<LongTermContractOffer, Double>(); // Adjust the price based on duration of the offer for (LongTermContractOffer offer : findLongTermContractOffersActiveAtTime()) { double duration = offer.getDuration().getDuration(); double thisPrice = offer.getPrice() * (1 + ((duration-1)*consumer.getContractDurationPreferenceFactor())); offersUnsorted.put(offer, thisPrice); } @SuppressWarnings("unused") MapValueComparator comp = new MapValueComparator(offersUnsorted); Map<LongTermContractOffer, Double> offersRanked = new TreeMap<LongTermContractOffer, Double>( comp); offersRanked.putAll(offersUnsorted); for (LongTermContractOffer offer : offersRanked.keySet()) { // is this offer still valid (i.e. no other conflicting offer been // accepted?) boolean stillvalid = true; // if a similar offer has been accepted (same power plant) // we have to ignore this offer if (reps.contractRepository .findLongTermContractForPowerPlantActiveAtTime( offer.getUnderlyingPowerPlant(), getCurrentTick()) != null) { stillvalid = false; } else { // check whether there is load to meet this type. double volumeInContractTypePossible = consumer .getLtcMaximumCoverageFraction() * determineVolumeForContractType( offer.getLongTermContractType(), offer.getZone()); // check what the capacity is of existing contracts for this ltc // type. double volumeInCurrentContracts = existingContractsInformations .get(offer.getZone()).capacityContractedInZonesForLTCType .get(offer.getLongTermContractType()); if (volumeInCurrentContracts + offer.getCapacity() > volumeInContractTypePossible) { logger.info("Contract impossible for {}", offer .getLongTermContractType().getName()); stillvalid = false; } else { logger.info("Contract possible for {}", offer .getLongTermContractType().getName()); } logger.info( "Volume in current contracts: {}, while possible for load: {}", volumeInCurrentContracts, volumeInContractTypePossible); } if (stillvalid) { // It is possible, but do we want it? // determine the weighted average spot price for this contract // if it is then adjusted price, make a contract. double hours = 0d; double weightedElectricitySpotPrices = 0d; for (Segment s : offer.getLongTermContractType().getSegments()) { hours += s.getLengthInHours(); SegmentClearingPoint point = (SegmentClearingPoint) reps.clearingPointRepositoryOld .findClearingPointForSegmentAndTime(s, getCurrentTick() - 1); weightedElectricitySpotPrices += point.getPrice() * s.getLengthInHours(); logger.info("Found a clearing point {} for segment {}", point, s); } double averageElectricityPrice = weightedElectricitySpotPrices / hours; double price = offersUnsorted.get(offer); if (price < (averageElectricityPrice*consumer.getContractWillingnessToPayFactor())) { reps.contractRepository.submitLongTermContractForElectricity( offer.getUnderlyingPowerPlant(), offer .getUnderlyingPowerPlant().getOwner(), consumer, offer.getUnderlyingPowerPlant() .getLocation().getZone(), offer.getPrice(), offer.getUnderlyingPowerPlant() .getAvailableCapacity(getCurrentTick()), offer.getLongTermContractType(), getCurrentTick(), offer.getDuration(), true, offer.getMainFuel(), offer.getFuelPassThroughFactor(), offer .getCo2PassThroughFactor(), offer .getFuelPriceStart(), offer .getCo2PriceStart()); logger.info( "Accepted LTC offer type {}, duration {}, submitted contract", offer.getLongTermContractType(), offer.getDuration()); logger.warn( "Accepted LTC for powerplant {}, price {} euro/MWh", offer.getUnderlyingPowerPlant(), offer.getPrice()); // Update the info on existing contracts for this zone existingContractsInformations.get( offer.getUnderlyingPowerPlant().getLocation() .getZone()) .updateExisingContractsInformation(); } } } } public double determineVolumeForContractType(LongTermContractType type, Zone zone) { // calculate minimum load of the segments in this contract type double minimumLoadInSegmentsOfContractType = Double.MAX_VALUE; for (Segment segment : type.getSegments()) { double loadOfSegment = reps.marketRepository .findSegmentLoadForElectricitySpotMarketForZone(zone, segment).getBaseLoad() * reps.marketRepository.findElectricitySpotMarketForZone(zone) .getDemandGrowthTrend().getValue(getCurrentTick()); if (loadOfSegment < minimumLoadInSegmentsOfContractType) { minimumLoadInSegmentsOfContractType = loadOfSegment; } } logger.info( "For ltc type {}, the lowest load of the segments covered is {}", type, minimumLoadInSegmentsOfContractType); return minimumLoadInSegmentsOfContractType; } public Iterable<LongTermContractOffer> findLongTermContractOffersActiveAtTime() { List<LongTermContractOffer> list = new ArrayList<LongTermContractOffer>(); for (LongTermContractOffer ltcOffer : reps.genericRepository .findAll(LongTermContractOffer.class)) { // If active if (ltcOffer.getStart() == getCurrentTick()) { list.add(ltcOffer); } } return list; } /** * Contains the existing contracts information for a consumer for a zone. * * @author ejlchappin * */ private class ExistingContractsInformation { private Zone zone; private EnergyConsumer consumer; private HashMap<LongTermContractType, Double> capacityContractedInZonesForLTCType; public ExistingContractsInformation(Zone zone, EnergyConsumer consumer) { this.zone = zone; this.consumer = consumer; capacityContractedInZonesForLTCType = new HashMap<LongTermContractType, Double>(); } public void updateExisingContractsInformation() { for (LongTermContractType type : reps.genericRepository .findAll(LongTermContractType.class)) { capacityContractedInZonesForLTCType .put(type, findCapacityOfLongTermContractsForEnergyConsumerAlreadyActiveAtTimeForSegmentsForZone( consumer, type)); } } private double findCapacityOfLongTermContractsForEnergyConsumerAlreadyActiveAtTimeForSegmentsForZone( EnergyConsumer consumer, LongTermContractType type) { double maxCapacity = 0d; for (Segment segment : type.getSegments()) { double thisCapacity = 0d; for (Contract c : reps.contractRepository .findLongTermContractsForEnergyConsumerForSegmentForZoneActiveAtTime( consumer, segment, zone, getCurrentTick())) { LongTermContract ltc = (LongTermContract) c; thisCapacity += ltc.getCapacity(); logger.info( "Found existing contract {} active in segment {}", ltc, segment); } // More contracts for this segment? Keep track of the largest // contracted capacity of each of the valid segments if (thisCapacity > maxCapacity) { maxCapacity = thisCapacity; } } return maxCapacity; } } }