emlab.role.AbstractEnergyProducerRole.java Source code

Java tutorial

Introduction

Here is the source code for emlab.role.AbstractEnergyProducerRole.java

Source

/*******************************************************************************
 * 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;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.math.optimization.GoalType;
import org.apache.commons.math.optimization.OptimizationException;
import org.apache.commons.math.optimization.RealPointValuePair;
import org.apache.commons.math.optimization.linear.LinearConstraint;
import org.apache.commons.math.optimization.linear.LinearObjectiveFunction;
import org.apache.commons.math.optimization.linear.Relationship;
import org.apache.commons.math.optimization.linear.SimplexSolver;
import org.springframework.beans.factory.annotation.Autowired;

import agentspring.role.AbstractRole;
import agentspring.trend.GeometricTrend;
import emlab.domain.agent.CommoditySupplier;
import emlab.domain.agent.EnergyProducer;
import emlab.domain.agent.Government;
import emlab.domain.market.CO2Auction;
import emlab.domain.market.ClearingPoint;
import emlab.domain.market.DecarbonizationMarket;
import emlab.domain.market.electricity.ElectricitySpotMarket;
import emlab.domain.market.electricity.PowerPlantDispatchPlan;
import emlab.domain.technology.PowerGeneratingTechnology;
import emlab.domain.technology.PowerPlant;
import emlab.domain.technology.Substance;
import emlab.domain.technology.SubstanceShareInFuelMix;
import emlab.repository.Reps;

public abstract class AbstractEnergyProducerRole extends AbstractRole<EnergyProducer> {

    @Autowired
    Reps reps;

    public double calculateMarketCapacity(PowerGeneratingTechnology technology, long time) {
        double capacity = 0d;
        for (PowerPlant plant : getReps().powerPlantRepository.findOperationalPowerPlantsByTechnology(technology,
                time)) {
            capacity += plant.getAvailableCapacity(getCurrentTick());
        }
        logger.info("Capacity for technology {} is {}", technology.getName(), capacity);
        return capacity;
    }

    public double calculateMarketCapacity(ElectricitySpotMarket market, PowerGeneratingTechnology technology,
            long time) {
        double capacity = 0d;
        for (PowerPlant plant : getReps().powerPlantRepository.findOperationalPowerPlantsByTechnology(technology,
                time)) {
            if (plant.getLocation().getZone().equals(market.getZone())) {
                capacity += plant.getAvailableCapacity(time);
            }
        }
        logger.info("Capacity for technology {} is {}", technology.getName(), capacity);
        return capacity;
    }

    public double calculateOwnerCapacityOfType(ElectricitySpotMarket market, PowerGeneratingTechnology technology,
            long time, EnergyProducer owner) {
        double capacity = 0d;
        for (PowerPlant plant : getReps().powerPlantRepository.findOperationalPowerPlantsByTechnology(technology,
                time)) {
            if (plant.getLocation().getZone().equals(market.getZone()) && plant.getOwner().equals(owner)) {
                capacity += plant.getAvailableCapacity(time);
            }
        }
        logger.info("Capacity for technology {} is {}", technology.getName(), capacity);
        return capacity;
    }

    public double calculateTotalOwnerCapacity(ElectricitySpotMarket market, long time, EnergyProducer owner) {
        double capacity = 0d;
        getReps().powerPlantRepository.findOperationalPowerPlantsByOwnerAndMarket(
                (emlab.domain.agent.EnergyProducer) owner, market, time);
        for (PowerPlant plant : getReps().powerPlantRepository.findOperationalPowerPlantsByOwnerAndMarket(owner,
                market, time)) {
            capacity += plant.getAvailableCapacity(time);
        }
        logger.info("Capacity for owner {} is {}", owner, capacity);
        return capacity;
    }

    public double calculateTotalOwnerCapacityInPipeline(ElectricitySpotMarket market, long time,
            EnergyProducer owner) {
        double capacity = 0d;
        for (PowerPlant plant : getReps().powerPlantRepository.findPowerPlantsByOwnerAndMarketInPipeline(owner,
                market, getCurrentTick())) {
            capacity += plant.getAvailableCapacity(time);
        }
        logger.info("Capacity in pipeline for owner {} is {}", owner, capacity);
        return capacity;
    }

    public double calculateMarketCapacityEverInstalledUpToGivenTime(PowerGeneratingTechnology technology,
            long time) {
        double capacity = 0d;

        for (PowerPlant plant : getReps().powerPlantRepository.findPowerPlantsByTechnology(technology)) {
            if (plant.getConstructionStartTime() <= time) {
                capacity += plant.getAvailableCapacity(getCurrentTick());
            }
        }
        logger.info("Capacity for technology {} is {}", technology.getName(), capacity);
        return capacity;
    }

    public double calculateMarginalCost(PowerPlant powerPlant) {
        double mc = 0d;
        // fuel cost
        mc += calculateMarginalCostExclCO2MarketCost(powerPlant);
        mc += calculateCO2MarketMarginalCost(powerPlant);
        logger.info("Margincal cost for plant {} is {}", powerPlant.getName(), mc);
        return mc;
    }

    public double calculateMarginalCO2Cost(PowerPlant powerPlant) {
        double mc = 0d;
        // fuel cost
        mc += calculateCO2TaxMarginalCost(powerPlant);
        mc += calculateCO2MarketMarginalCost(powerPlant);
        logger.info("Margincal cost for plant {} is {}", powerPlant.getName(), mc);
        return mc;
    }

    public double calculateMarginalCostExclCO2MarketCost(PowerPlant powerPlant) {
        double mc = 0d;
        // fuel cost
        mc += calculateMarginalFuelCost(powerPlant);
        mc += calculateCO2TaxMarginalCost(powerPlant);
        logger.info("Margincal cost excluding CO2 auction/market cost for plant {} is {}", powerPlant.getName(),
                mc);
        return mc;
    }

    public double calculateMarginalFuelCost(PowerPlant powerPlant) {
        double fc = 0d;
        // fuel cost for each fuel
        for (SubstanceShareInFuelMix mix : powerPlant.getFuelMix()) {

            double amount = mix.getShare();
            logger.info("Calculating need for fuel: {} units of {}", mix.getShare(), mix.getSubstance().getName());
            double fuelPrice = findLastKnownPriceForSubstance(mix.getSubstance());
            fc += amount * fuelPrice;
            logger.info("Calculating marginal cost and found a fuel price which is {} per unit of fuel", fuelPrice);
        }

        return fc;
    }

    /**
     * Finds the last known price on a specific market. We try to get it for this tick, previous tick, or from a possible supplier directly. If multiple prices are found, the average is returned. This
     * is the case for electricity spot markets, as they may have segments.
     * 
     * @param substance
     *            the price we want for
     * @return the (average) price found
     */
    public double findLastKnownPriceOnMarket(DecarbonizationMarket market) {
        Double average = calculateAverageMarketPriceBasedOnClearingPoints(
                getReps().clearingPointRepositoryOld.findClearingPointsForMarketAndTime(market, getCurrentTick()));
        Substance substance = market.getSubstance();

        if (average != null) {
            logger.info("Average price found on market for this tick for {}", substance.getName());
            return average;
        }

        average = calculateAverageMarketPriceBasedOnClearingPoints(getReps().clearingPointRepositoryOld
                .findClearingPointsForMarketAndTime(market, getCurrentTick() - 1));
        if (average != null) {
            logger.info("Average price found on market for previous tick for {}", substance.getName());
            return average;
        }

        if (market.getReferencePrice() > 0) {
            logger.info("Found a reference price found for market for {}", substance.getName());
            return market.getReferencePrice();
        }

        for (CommoditySupplier supplier : getReps().genericRepository.findAll(CommoditySupplier.class)) {
            if (supplier.getSubstance().equals(substance)) {

                return supplier.getPriceOfCommodity().getValue(getCurrentTick());
            }
        }

        logger.info("No price has been found for {}", substance.getName());
        return 0d;
    }

    /**
     * Finds the last known price for a substance. We try to find the market for it and get it get the price on that market for this tick, previous tick, or from a possible supplier directly. If
     * multiple prices are found, the average is returned. This is the case for electricity spot markets, as they may have segments.
     * 
     * @param substance
     *            the price we want for
     * @return the (average) price found
     */
    public double findLastKnownPriceForSubstance(Substance substance) {

        DecarbonizationMarket market = getReps().marketRepository.findFirstMarketBySubstance(substance);
        if (market == null) {
            logger.warn("No market found for {} so no price can be found", substance.getName());
            return 0d;
        } else {
            return findLastKnownPriceOnMarket(market);
        }
    }

    /**
     * Calculates the volume-weighted average price on a market based on a set of clearingPoints.
     * 
     * @param clearingPoints
     *            the clearingPoints with the volumes and prices
     * @return the weighted average
     */
    private Double calculateAverageMarketPriceBasedOnClearingPoints(Iterable<ClearingPoint> clearingPoints) {
        double priceTimesVolume = 0d;
        double volume = 0d;

        for (ClearingPoint point : clearingPoints) {
            priceTimesVolume += point.getPrice() * point.getVolume();
            volume += point.getVolume();
        }
        if (volume > 0) {
            return priceTimesVolume / volume;
        }
        return null;
    }

    public double calculateCO2MarketMarginalCost(PowerPlant powerPlant) {
        double co2Intensity = powerPlant.calculateEmissionIntensity();
        CO2Auction auction = getReps().genericRepository.findFirst(CO2Auction.class);
        double co2Price = findLastKnownPriceOnMarket(auction);
        return co2Intensity * co2Price;
    }

    public double calculateCO2MarketCost(PowerPlant powerPlant) {
        double co2Intensity = powerPlant.calculateEmissionIntensity();
        CO2Auction auction = getReps().genericRepository.findFirst(CO2Auction.class);
        double co2Price = findLastKnownPriceOnMarket(auction);
        double electricityOutput = powerPlant.calculateElectricityOutputAtTime(getCurrentTick());
        return co2Intensity * co2Price * electricityOutput;
    }

    /**
     * Calculates the payment effective part of the national CO2 price. In this case you only pay the excess over the EU carbon market price to your own government.
     * 
     * @param powerPlant
     * @return
     */
    public double calculatePaymentEffictiveCO2NationalMinimumPriceCost(PowerPlant powerPlant) {
        double co2Intensity = powerPlant.calculateEmissionIntensity();
        CO2Auction auction = getReps().genericRepository.findFirst(CO2Auction.class);
        double co2Price = findLastKnownPriceOnMarket(auction);
        double electricityOutput = powerPlant.calculateElectricityOutputAtTime(getCurrentTick());
        double nationalMinCo2price = getReps().nationalGovernmentRepository
                .findNationalGovernmentByPowerPlant(powerPlant).getMinNationalCo2PriceTrend()
                .getValue(getCurrentTick());
        double paymentEffectivePartOfNationalCO2;
        if (nationalMinCo2price > co2Price)
            paymentEffectivePartOfNationalCO2 = nationalMinCo2price - co2Price;
        else
            paymentEffectivePartOfNationalCO2 = 0;
        return co2Intensity * paymentEffectivePartOfNationalCO2 * electricityOutput;
    }

    public double calculateCO2TaxMarginalCost(PowerPlant powerPlant) {
        double co2Intensity = powerPlant.calculateEmissionIntensity();
        Government government = getReps().genericRepository.findFirst(Government.class);
        double co2Tax = government.getCO2Tax(getCurrentTick());
        return co2Intensity * co2Tax;
    }

    public double findLastKnownCO2Price() {
        Government government = getReps().genericRepository.findFirst(Government.class);
        CO2Auction auction = getReps().genericRepository.findFirst(CO2Auction.class);
        double co2Price = findLastKnownPriceOnMarket(auction);
        double co2Tax = government.getCO2Tax(getCurrentTick());
        return co2Price + co2Tax;
    }

    public double calculateCO2Tax(PowerPlant powerPlant) {
        double co2Intensity = powerPlant.calculateEmissionIntensity();
        double electricityOutput = powerPlant.calculateElectricityOutputAtTime(getCurrentTick());
        Government government = getReps().genericRepository.findFirst(Government.class);
        double co2Tax = government.getCO2Tax(getCurrentTick());
        double taxToPay = (co2Intensity * electricityOutput) * co2Tax;
        return taxToPay;
    }

    public double calculateFixedOperatingCost(PowerPlant powerPlant) {

        double norm = powerPlant.getTechnology().getFixedOperatingCost();
        long timeConstructed = powerPlant.getConstructionStartTime() + powerPlant.calculateActualLeadtime();
        double mod = powerPlant.getTechnology().getFixedOperatingCostModifierAfterLifetime();
        long lifetime = powerPlant.calculateActualLifetime();

        GeometricTrend trend = new GeometricTrend();
        trend.setGrowthRate(mod);
        trend.setStart(norm);

        double currentCost = trend.getValue(getCurrentTick() - (timeConstructed + lifetime));
        return currentCost;
    }

    public double calculateAverageEnergyDensityInOperation(PowerPlant powerPlant) {
        double energyDensity = 0d;
        for (SubstanceShareInFuelMix share : powerPlant.getFuelMix()) {
            energyDensity += share.getSubstance().getEnergyDensity() * share.getShare();
        }
        return energyDensity;
    }

    public double calculateAveragePastOperatingProfit(PowerPlant pp, long horizon) {

        double averageFractionInMerit = 0d;
        for (long i = -horizon; i <= 0; i++) {
            averageFractionInMerit += calculatePastOperatingProfitInclFixedOMCost(pp, getCurrentTick() + i) / i;
        }
        return averageFractionInMerit;
    }

    public double calculatePastOperatingProfitInclFixedOMCost(PowerPlant plant, long time) {
        double pastOP = 0d;
        // TODO get all accepted supply bids and calculate income
        // TODO get all accepted demand bids and calculate costs
        // TODO get the CO2 cost
        // TODO get the fixed cost
        pastOP += calculateFixedOperatingCost(plant);
        return pastOP;
    }

    /**
     * The fuel mix is calculated with a linear optimization model of the possible fuels and the requirements.
     * 
     * @param substancePriceMap
     *            contains the possible fuels and their market prices
     * @param minimumFuelMixQuality
     *            is the minimum fuel quality needed for the power plant to work
     * @param efficiency
     *            of the plant determines the need for fuel per MWhe
     * @param co2TaxLevel
     *            is part of the cost for CO2
     * @param co2AuctionPrice
     *            is part of the cost for CO2
     * @return the fuel mix
     */
    public Set<SubstanceShareInFuelMix> calculateFuelMix(PowerPlant plant, Map<Substance, Double> substancePriceMap,
            double co2Price) {

        double efficiency = plant.getActualEfficiency();

        Set<SubstanceShareInFuelMix> fuelMix = (plant.getFuelMix() == null) ? new HashSet<SubstanceShareInFuelMix>()
                : plant.getFuelMix();

        int numberOfFuels = substancePriceMap.size();
        if (numberOfFuels == 0) {
            logger.info("No fuels, so no operation mode is set. Empty fuel mix is returned");
            return new HashSet<SubstanceShareInFuelMix>();
        } else if (numberOfFuels == 1) {
            SubstanceShareInFuelMix ssifm = null;
            if (!fuelMix.isEmpty()) {
                ssifm = fuelMix.iterator().next();
            } else {
                ssifm = new SubstanceShareInFuelMix().persist();
                fuelMix.add(ssifm);
            }

            Substance substance = substancePriceMap.keySet().iterator().next();

            ssifm.setShare(calculateFuelConsumptionWhenOnlyOneFuelIsUsed(substance, efficiency));
            ssifm.setSubstance(substance);
            logger.info("Setting fuel consumption for {} to {}", ssifm.getSubstance().getName(), ssifm.getShare());

            return fuelMix;
        } else {

            double minimumFuelMixQuality = plant.getTechnology().getMinimumFuelQuality();

            double[] fuelAndCO2Costs = new double[numberOfFuels];
            double[] fuelDensities = new double[numberOfFuels];
            double[] fuelQuality = new double[numberOfFuels];

            int i = 0;
            for (Substance substance : substancePriceMap.keySet()) {
                fuelAndCO2Costs[i] = substancePriceMap.get(substance) + substance.getCo2Density() * (co2Price);
                fuelDensities[i] = substance.getEnergyDensity();
                fuelQuality[i] = substance.getQuality() - minimumFuelMixQuality;
                i++;
            }

            logger.info("Fuel prices: {}", fuelAndCO2Costs);
            logger.info("Fuel densities: {}", fuelDensities);
            logger.info("Fuel purities: {}", fuelQuality);

            // Objective function = minimize fuel cost (fuel
            // consumption*fuelprices
            // + CO2 intensity*co2 price/tax)
            LinearObjectiveFunction function = new LinearObjectiveFunction(fuelAndCO2Costs, 0d);

            List<LinearConstraint> constraints = new ArrayList<LinearConstraint>();

            // Constraint 1: total fuel density * fuel consumption should match
            // required energy input
            constraints.add(new LinearConstraint(fuelDensities, Relationship.EQ, (1 / efficiency)));

            // Constraint 2&3: minimum fuel quality (times fuel consumption)
            // required
            // The equation is derived from (example for 2 fuels): q1 * x1 / (x1+x2) + q2 * x2 / (x1+x2) >= qmin
            // so that the fuelquality weighted by the mass percentages is greater than the minimum fuel quality.
            constraints.add(new LinearConstraint(fuelQuality, Relationship.GEQ, 0));

            try {
                SimplexSolver solver = new SimplexSolver();
                RealPointValuePair solution = solver.optimize(function, constraints, GoalType.MINIMIZE, true);

                logger.info("Succesfully solved a linear optimization for fuel mix");

                int f = 0;
                Iterator<SubstanceShareInFuelMix> iterator = plant.getFuelMix().iterator();
                for (Substance substance : substancePriceMap.keySet()) {
                    double share = solution.getPoint()[f];

                    SubstanceShareInFuelMix ssifm;
                    if (iterator.hasNext()) {
                        ssifm = iterator.next();
                    } else {
                        ssifm = new SubstanceShareInFuelMix().persist();
                        fuelMix.add(ssifm);
                    }

                    double fuelConsumptionPerMWhElectricityProduced = convertFuelShareToMassVolume(share);
                    logger.info("Setting fuel consumption for {} to {}", substance.getName(),
                            fuelConsumptionPerMWhElectricityProduced);
                    ssifm.setShare(fuelConsumptionPerMWhElectricityProduced);
                    ssifm.setSubstance(substance);
                    f++;
                }

                logger.info("If single fired, it would have been: {}",
                        calculateFuelConsumptionWhenOnlyOneFuelIsUsed(substancePriceMap.keySet().iterator().next(),
                                efficiency));
                return fuelMix;
            } catch (OptimizationException e) {
                logger.warn(
                        "Failed to determine the correct fuel mix. Adding only fuel number 1 in fuel mix out of {} substances and minimum quality of {}",
                        substancePriceMap.size(), minimumFuelMixQuality);
                logger.info("The fuel added is: {}", substancePriceMap.keySet().iterator().next().getName());

                // Override the old one
                fuelMix = new HashSet<SubstanceShareInFuelMix>();
                SubstanceShareInFuelMix ssifm = new SubstanceShareInFuelMix().persist();
                Substance substance = substancePriceMap.keySet().iterator().next();

                ssifm.setShare(calculateFuelConsumptionWhenOnlyOneFuelIsUsed(substance, efficiency));
                ssifm.setSubstance(substance);
                logger.info("Setting fuel consumption for {} to {}", ssifm.getSubstance().getName(),
                        ssifm.getShare());
                fuelMix.add(ssifm);
                return fuelMix;
            }
        }
    }

    public double convertFuelShareToMassVolume(double share) {
        return share * 3600;
    }

    public double calculateFuelConsumptionWhenOnlyOneFuelIsUsed(Substance substance, double efficiency) {

        double fuelConsumptionPerMWhElectricityProduced = convertFuelShareToMassVolume(
                1 / (efficiency * substance.getEnergyDensity()));

        return fuelConsumptionPerMWhElectricityProduced;

    }

    /**
     * Calculates the actual investment cost of a power plant per year, by using the exogenous modifier.
     * 
     * @param powerPlant
     * @return the actual efficiency
     */
    /*
     * public double determineAnnuitizedInvestmentCost(PowerPlant powerPlant, long time) {
     * 
     * double invNorm = powerPlant.getTechnology().getAnnuitizedInvestmentCost(); double modifierExo = calculateExogenousModifier(powerPlant.getTechnology(). getInvestmentCostModifierExogenous(),
     * time);
     * 
     * double annuitizedInvestmentCost = invNorm * modifierExo; logger.info("Investment cost of plant{} is {}", powerPlant, annuitizedInvestmentCost); return annuitizedInvestmentCost; }
     */

    public double determineLoanAnnuities(double totalLoan, double payBackTime, double interestRate) {

        double q = 1 + interestRate;
        double annuity = totalLoan * (Math.pow(q, payBackTime) * (q - 1)) / (Math.pow(q, payBackTime) - 1);

        return annuity;
    }

    /**
     * Calculates expected
     * 
     * @param futureTimePoint
     * @return
     */
    protected HashMap<ElectricitySpotMarket, Double> determineExpectedCO2PriceInclTax(long futureTimePoint,
            int yearsAveragingOver) {
        HashMap<ElectricitySpotMarket, Double> co2Prices = new HashMap<ElectricitySpotMarket, Double>();
        CO2Auction co2Auction = reps.marketRepository.findCO2Auction();
        for (ElectricitySpotMarket esm : reps.marketRepository.findAllElectricitySpotMarkets()) {
            double averageOfPastPrices = reps.clearingPointRepository
                    .calculateAverageClearingPriceForMarketAndTimeRange(co2Auction,
                            getCurrentTick() - yearsAveragingOver, (long) (getCurrentTick() - 1));
            double nationalCo2MinPriceinFutureTick = reps.nationalGovernmentRepository
                    .findNationalGovernmentByElectricitySpotMarket(esm).getMinNationalCo2PriceTrend()
                    .getValue(futureTimePoint);
            double co2PriceInCountry = 0d;
            if (averageOfPastPrices > nationalCo2MinPriceinFutureTick) {
                co2PriceInCountry = averageOfPastPrices;
            } else {
                co2PriceInCountry = nationalCo2MinPriceinFutureTick;
            }
            co2PriceInCountry += reps.genericRepository.findFirst(Government.class).getCO2Tax(futureTimePoint);
            co2Prices.put(esm, Double.valueOf(co2PriceInCountry));
        }
        return co2Prices;
    }

    public abstract Reps getReps();
}