/*******************************************************************************
* 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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import agentspring.role.Role;
import agentspring.role.RoleComponent;
import emlab.domain.agent.EnergyProducer;
import emlab.domain.contract.LongTermContractDuration;
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.domain.technology.Substance;
import emlab.repository.Reps;
import emlab.role.AbstractEnergyProducerRole;
/**
* {@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 SubmitLongTermElectricityContractsRole extends
AbstractEnergyProducerRole implements Role<EnergyProducer> {
@Autowired
Reps reps;
@Override
public Reps getReps() {
return reps;
}
@Transactional
public void act(EnergyProducer producer) {
// TODO Contracts for checking assigned to power plants??
// When dismantling, take over existing contract by new power plant?
for (PowerPlant plant : reps.powerPlantRepository
.findOperationalPowerPlantsByOwner(producer, getCurrentTick())) {
// if it is not in the first tick,
// and if the plant is within the technical lifetime
// and if it is applicable for LTC's (i.e. not wind)
// and if there is no contract yet
if (getCurrentTick() > 0
&& plant.isWithinTechnicalLifetime(getCurrentTick())
&& plant.getTechnology().isApplicableForLongTermContract()
&& reps.contractRepository
.findLongTermContractForPowerPlantActiveAtTime(
plant, getCurrentTick()) == null) {
Zone zone = plant.getLocation().getZone();
double capacity = plant.getAvailableCapacity(getCurrentTick());
double fuelPriceStart = 0d;
Substance mainFuel = null;
if (!plant.getFuelMix().isEmpty()) {
mainFuel = plant.getFuelMix().iterator().next()
.getSubstance();
fuelPriceStart = findLastKnownPriceForSubstance(mainFuel);
}
double co2PriceStart = findLastKnownCO2Price();
for (LongTermContractType type : reps.genericRepository
.findAll(LongTermContractType.class)) {
double hours = 0d;
for (Segment s : type.getSegments()) {
hours += s.getLengthInHours();
}
double averageElectricityPrice = determineAverageElectricityPrice(
hours, (int) producer.getLongTermContractPastTimeHorizon(), type);
// Count the hours in this contract type.
logger.info("LTC type: {}, number of hours: {}", type,
hours);
logger.info("Weighted average e-price: {}",
averageElectricityPrice);
double passThroughCO2 = determineCO2Cost(plant)
/ determineTotalAverageCost(plant, hours);
double passThroughFuel = determineFuelCost(plant)
/ determineTotalAverageCost(plant, hours);
long minimumDuration = Long.MAX_VALUE;
long maximumDuration = Long.MIN_VALUE;
for (LongTermContractDuration duration : reps.genericRepository
.findAll(LongTermContractDuration.class)) {
if (duration.getDuration() < minimumDuration) {
minimumDuration = duration.getDuration();
}
if (duration.getDuration() > maximumDuration) {
maximumDuration = duration.getDuration();
}
}
logger.info("Minimum duration: {} and maximum: {}",
minimumDuration, maximumDuration);
for (LongTermContractDuration duration : reps.genericRepository
.findAll(LongTermContractDuration.class)) {
double minimumPrice = (1 + producer
.getLongTermContractMargin())
* determineTotalAverageCost(plant, hours);
double maximumPrice = averageElectricityPrice;
// TODO use risk double riskAversionFactor = 1;
// Check whether the maximum price is at
// least the minimum contract price. Otherwise,
// we'll use the minimum price.
double thisPrice = 0d;
if (maximumPrice < minimumPrice) {
thisPrice = minimumPrice;
} else {
thisPrice = minimumPrice
+ (((maximumDuration - duration
.getDuration()) * (maximumPrice - minimumPrice)) / (maximumDuration - minimumDuration));
}
// Skip the first tick for now. Otherwise the
// contracts in the
LongTermContractOffer offer = reps.contractRepository
.submitLongTermContractOfferForElectricity(
producer, plant, zone, thisPrice,
capacity, type, getCurrentTick(),
duration, mainFuel, passThroughFuel,
passThroughCO2, fuelPriceStart,
co2PriceStart);
logger.info(
"Submitted offer for type {}. The offer: {}",
type, offer);
}
}
}
}
}
/**
* Determines the average electricity price weighted by hours of segments of a previous number of years.
* @param hours the total hours in these segments
* @param type the contract type
* @param the horizon to look back
* @return the average price
*/
private double determineAverageElectricityPrice(double hours, int horizon,
LongTermContractType type) {
// Keep track of the number of prices found and the averages
int nrOfPrices = 0;
double averageElectricityPrice = 0d;
// For each of the previous years
for (int i = -horizon; i <= -1; i++) {
// Still valid
boolean valid = true;
double weightedElectricitySpotPrices = 0d;
// For each of the segments in this type
for (Segment s : type.getSegments()) {
// Try to find a price for this year
SegmentClearingPoint point = (SegmentClearingPoint) reps.clearingPointRepositoryOld
.findClearingPointForSegmentAndTime(s,
getCurrentTick() + i);
// If there is a price, add it multiplied to the number of hours to the total.
if(point != null){
weightedElectricitySpotPrices += point.getPrice()
* s.getLengthInHours();
} else {
// Otherwise, no valid price is found for this year
valid = false;
}
}
// If valid prices were found, we can use it.
if(valid){
nrOfPrices++;
averageElectricityPrice += weightedElectricitySpotPrices / hours;
}
}
// Return the average
return averageElectricityPrice/nrOfPrices;
}
/**
* Determines the total average cost for a power plant, based on fixed and
* variable cost, averaged out over a number of running hours.
*
* @param plant
* @param hours
* @return
*/
private double determineTotalAverageCost(PowerPlant plant, double hours) {
double fixedOMCost = calculateFixedOperatingCost(plant)
/ plant.getAvailableCapacity(getCurrentTick());
double fixedcapitalCost = plant.getActualInvestedCapital() //Doesn't consider the cost of capital, but just the overall invested capital
/ plant.getTechnology().getDepreciationTime()/ plant.getAvailableCapacity(getCurrentTick());
return determineFuelCost(plant) + determineCO2Cost(plant)
+ ((fixedOMCost + fixedcapitalCost) / hours);
}
private double determineCO2Cost(PowerPlant plant) {
return calculateMarginalCO2Cost(plant);
}
private double determineFuelCost(PowerPlant plant) {
return calculateMarginalFuelCost(plant);
}
}