emlab.role.investment.InvestInPowerGenerationTechnologiesRole.java Source code

Java tutorial

Introduction

Here is the source code for emlab.role.investment.InvestInPowerGenerationTechnologiesRole.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.investment;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

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.BigBank;
import emlab.domain.agent.EnergyProducer;
import emlab.domain.agent.PowerPlantManufacturer;
import emlab.domain.contract.CashFlow;
import emlab.domain.contract.Loan;
import emlab.domain.gis.Zone;
import emlab.domain.market.electricity.ElectricitySpotMarket;
import emlab.domain.market.electricity.Segment;
import emlab.domain.market.electricity.SegmentLoad;
import emlab.domain.technology.PowerGeneratingTechnology;
import emlab.domain.technology.PowerGridNode;
import emlab.domain.technology.PowerPlant;
import emlab.domain.technology.Substance;
import emlab.domain.technology.SubstanceShareInFuelMix;
import emlab.repository.Reps;
import emlab.role.AbstractEnergyProducerRole;
import emlab.util.MapValueComparator;

/**
 * {@link EnergyProducer}s decide to invest in new {@link PowerPlant}
 * 
 * @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 JCRichstein
 */
@RoleComponent
public class InvestInPowerGenerationTechnologiesRole extends AbstractEnergyProducerRole
        implements Role<EnergyProducer> {

    @Autowired
    Reps reps;

    public Reps getReps() {
        return reps;
    }

    // market expectations
    Map<ElectricitySpotMarket, MarketInformation> marketInfoMap = new HashMap<ElectricitySpotMarket, MarketInformation>();

    public void act(EnergyProducer agent) {

        long futureTimePoint = getCurrentTick() + agent.getInvestmentFutureTimeHorizon();
        // logger.warn(agent + " is looking at timepoint " + futureTimePoint);

        // ==== Expectations ===

        // Fuel Prices
        Map<Substance, Double> expectedFuelPrices = new HashMap<Substance, Double>();
        for (Substance substance : reps.genericRepository.findAll(Substance.class)) {
            // use last price
            expectedFuelPrices.put(substance, findLastKnownPriceForSubstance(substance));// TODO
                                                                                         // use
                                                                                         // expected
                                                                                         // fuel
                                                                                         // price
        }

        // CO2
        Map<ElectricitySpotMarket, Double> expectedCO2Price = determineExpectedCO2PriceInclTax(futureTimePoint, 3);// TODO
                                                                                                                   // use
                                                                                                                   // expected
                                                                                                                   // co2
                                                                                                                   // price

        // Investment decision
        for (ElectricitySpotMarket market : reps.genericRepository.findAllAtRandom(ElectricitySpotMarket.class)) {

            MarketInformation marketInformation = new MarketInformation(market, expectedFuelPrices,
                    expectedCO2Price.get(market).doubleValue(), futureTimePoint);
            /*
             * if (marketInfoMap.containsKey(market) && marketInfoMap.get(market).time == futureTimePoint) { marketInformation = marketInfoMap.get(market); } else { marketInformation = new
             * MarketInformation(market, expectedFuelPrices, expectedCO2Price, futureTimePoint); marketInfoMap.put(market, marketInformation); }
             */

            // logger.warn(agent + " is expecting a CO2 price of " +
            // expectedCO2Price.get(market) + " Euro/MWh at timepoint "
            // + futureTimePoint + " in Market " + market);

            double highestValue = Double.MIN_VALUE;
            PowerGeneratingTechnology bestTechnology = null;

            for (PowerGeneratingTechnology technology : reps.genericRepository
                    .findAll(PowerGeneratingTechnology.class)) {

                PowerPlant plant = new PowerPlant();
                plant.specifyNotPersist(getCurrentTick(), agent, getNodeForZone(market.getZone()), technology);
                // if too much capacity of this technology in the pipeline (not
                // limited to the 5 years)
                double expectedInstalledCapacityOfTechnology = reps.powerPlantRepository
                        .calculateCapacityOfExpectedOperationalPowerPlantsInMarketAndTechnology(market, technology,
                                futureTimePoint);
                double expectedOwnedTotalCapacityInMarket = reps.powerPlantRepository
                        .calculateCapacityOfExpectedOperationalPowerPlantsInMarketByOwner(market, futureTimePoint,
                                agent);
                double expectedOwnedCapacityInMarketOfThisTechnology = reps.powerPlantRepository
                        .calculateCapacityOfExpectedOperationalPowerPlantsInMarketByOwnerAndTechnology(market,
                                technology, futureTimePoint, agent);
                double capacityOfTechnologyInPipeline = reps.powerPlantRepository
                        .calculateCapacityOfPowerPlantsByTechnologyInPipeline(technology, getCurrentTick());
                double operationalCapacityOfTechnology = reps.powerPlantRepository
                        .calculateCapacityOfOperationalPowerPlantsByTechnology(technology, getCurrentTick());

                if ((expectedInstalledCapacityOfTechnology + technology.getCapacity())
                        / (marketInformation.maxExpectedLoad + technology.getCapacity()) > technology
                                .getMaximumInstalledCapacityFractionInCountry()) {
                    // logger.warn(agent +
                    // " will not invest in {} technology because there's too much of this type in the market",
                    // technology);
                } else if (expectedOwnedCapacityInMarketOfThisTechnology > expectedOwnedTotalCapacityInMarket
                        * technology.getMaximumInstalledCapacityFractionPerAgent()) {
                    // logger.warn(agent +
                    // " will not invest in {} technology because there's too much capacity planned by him",
                    // technology);
                } else if ((capacityOfTechnologyInPipeline > operationalCapacityOfTechnology)
                        && capacityOfTechnologyInPipeline > 3000) { // TODO:
                    // Dirty
                    // hack,
                    // but
                    // reflects
                    // that
                    // you
                    // cannot
                    // expand
                    // a
                    // technology
                    // out
                    // of
                    // zero.

                    // logger.warn(agent +
                    // " will not invest in {} technology because there's too much capacity in the pipeline",
                    // technology);
                } else if (plant.getActualInvestedCapital()
                        * (1 - agent.getDebtRatioOfInvestments()) > agent.getDownpaymentFractionOfCash()
                                * agent.getCash()) {
                    // logger.warn(agent +
                    // " will not invest in {} technology as he does not have enough money for downpayment",
                    // technology); // TODO:
                    // Modifier
                    // for
                    // investment
                    // costs
                    // is
                    // missing
                    // here
                } else {

                    Map<Substance, Double> myFuelPrices = new HashMap<Substance, Double>();
                    for (Substance fuel : technology.getFuels()) {
                        myFuelPrices.put(fuel, expectedFuelPrices.get(fuel));
                    }
                    Set<SubstanceShareInFuelMix> fuelMix = calculateFuelMix(plant, myFuelPrices,
                            expectedCO2Price.get(market));
                    plant.setFuelMix(fuelMix);

                    double expectedMarginalCost = determineExpectedMarginalCost(plant, expectedFuelPrices,
                            expectedCO2Price.get(market));
                    double runningHours = 0d;
                    double expectedGrossProfit = 0d;

                    // logger.warn("Agent {}  found that the installed capacity in the market {} in future to be "
                    // + marketInformation.capacitySum +
                    // "and expectde maximum demand to be " +
                    // marketInformation.maxExpectedLoad,
                    // agent, market);
                    long numberOfSegments = reps.segmentRepository.count();

                    // TODO somehow the prices of long-term contracts could also
                    // be used here to determine the expected profit. Maybe not
                    // though...
                    for (SegmentLoad segmentLoad : market.getLoadDurationCurve()) {
                        double expectedElectricityPrice = marketInformation.expectedElectricityPricesPerSegment
                                .get(segmentLoad.getSegment());
                        double hours = segmentLoad.getSegment().getLengthInHours();
                        if (expectedMarginalCost <= expectedElectricityPrice) {
                            runningHours += hours;
                            expectedGrossProfit += (expectedElectricityPrice - expectedMarginalCost) * hours
                                    * plant.getAvailableCapacity(futureTimePoint, segmentLoad.getSegment(),
                                            numberOfSegments);
                        }
                    }

                    // logger.warn(agent +
                    // "expects technology {} to have {} running", technology,
                    // runningHours);
                    // expect to meet minimum running hours?
                    if (runningHours < plant.getTechnology().getMinimumRunningHours()) {
                        // logger.warn(agent
                        // + " will not invest in {} technology as he expect to have {} running, which is lower then required",
                        // technology, runningHours);
                    } else {

                        double fixedOMCost = calculateFixedOperatingCost(plant);// /
                                                                                // plant.getTechnology().getCapacity();

                        double operatingProfit = expectedGrossProfit - fixedOMCost; // TODO
                                                                                    // should
                                                                                    // we
                                                                                    // not
                                                                                    // exclude
                                                                                    // fixed
                                                                                    // cost,
                                                                                    // or
                                                                                    // name
                                                                                    // that
                                                                                    // NET
                                                                                    // profit?

                        // TODO Alter discount rate on the basis of the amount
                        // in long-term contracts?
                        // TODO Alter discount rate on the basis of other stuff,
                        // such as amount of money, market share, portfolio
                        // size.

                        // Calculation of weighted average cost of capital,
                        // based on the companies debt-ratio
                        double wacc = (1 - agent.getDebtRatioOfInvestments()) * agent.getEquityInterestRate()
                                + agent.getDebtRatioOfInvestments() * agent.getLoanInterestRate();

                        // Creation of out cash-flow during power plant building
                        // phase (note that the cash-flow is negative!)
                        TreeMap<Integer, Double> discountedProjectCapitalOutflow = calculateSimplePowerPlantInvestmentCashFlow(
                                technology.getDepreciationTime(), technology.getExpectedLeadtime(),
                                plant.getActualInvestedCapital(), 0);
                        // Creation of in cashflow during operation
                        TreeMap<Integer, Double> discountedProjectCashInflow = calculateSimplePowerPlantInvestmentCashFlow(
                                technology.getDepreciationTime(), technology.getExpectedLeadtime(), 0,
                                operatingProfit);

                        double discountedCapitalCosts = npv(discountedProjectCapitalOutflow, wacc);// are
                                                                                                   // defined
                                                                                                   // negative!!
                                                                                                   // technology.getCapacity();

                        // logger.warn("Agent {}  found that the discounted capital for technology {} to be "
                        // + discountedCapitalCosts, agent,
                        // technology);

                        double discountedOpProfit = npv(discountedProjectCashInflow, wacc);

                        // logger.warn("Agent {}  found the expected prices to be {}",
                        // agent,
                        // marketInformation.expectedElectricityPricesPerSegment);
                        // logger.warn("Agent {}  found that the projected discounted inflows for technology {} to be "
                        // + discountedOpProfit,
                        // agent, technology);

                        double projectValue = discountedOpProfit + discountedCapitalCosts;

                        // logger.warn(
                        // "Agent {}  found the project value for technology {} to be "
                        // + Math.round(projectValue /
                        // plant.getTechnology().getCapacity()) +
                        // " EUR/kW (running hours: "
                        // + runningHours + "", agent, technology);

                        // double projectTotalValue = projectValuePerMW *
                        // plant.getTechnology().getCapacity();

                        // double projectReturnOnInvestment = discountedOpProfit
                        // / (-discountedCapitalCosts);

                        /*
                         * Divide by capacity, in order not to favour large power plants (which have the single largest NPV
                         */

                        if (projectValue > 0 && projectValue / plant.getTechnology().getCapacity() > highestValue) {
                            highestValue = projectValue / plant.getTechnology().getCapacity();
                            bestTechnology = plant.getTechnology();
                        }
                    }

                }
            }

            if (bestTechnology != null) {
                // logger.warn("Agent {} invested in technology {} at tick " + getCurrentTick(), agent, bestTechnology);

                PowerPlant plant = new PowerPlant();
                plant.specifyAndPersist(getCurrentTick(), agent, getNodeForZone(market.getZone()), bestTechnology);
                PowerPlantManufacturer manufacturer = reps.genericRepository
                        .findFirst(PowerPlantManufacturer.class);
                BigBank bigbank = reps.genericRepository.findFirst(BigBank.class);

                double investmentCostPayedByEquity = plant.getActualInvestedCapital()
                        * (1 - agent.getDebtRatioOfInvestments());
                double investmentCostPayedByDebt = plant.getActualInvestedCapital()
                        * agent.getDebtRatioOfInvestments();
                double downPayment = investmentCostPayedByEquity;
                createSpreadOutDownPayments(agent, manufacturer, downPayment, plant);

                double amount = determineLoanAnnuities(investmentCostPayedByDebt,
                        plant.getTechnology().getDepreciationTime(), agent.getLoanInterestRate());
                // logger.warn("Loan amount is: " + amount);
                Loan loan = reps.loanRepository.createLoan(agent, bigbank, amount,
                        plant.getTechnology().getDepreciationTime(), getCurrentTick(), plant);
                // Create the loan
                plant.createOrUpdateLoan(loan);

            } else {
                // logger.warn("{} found no suitable technology anymore to invest in at tick "
                // + getCurrentTick(), agent);
                // agent will not participate in the next round of investment if
                // he does not invest now
                setNotWillingToInvest(agent);
            }
        }
    }

    // Creates n downpayments of equal size in each of the n building years of a
    // power plant
    @Transactional
    private void createSpreadOutDownPayments(EnergyProducer agent, PowerPlantManufacturer manufacturer,
            double totalDownPayment, PowerPlant plant) {
        int buildingTime = plant.getTechnology().getExpectedLeadtime();
        for (int i = 0; i < buildingTime; i++) {
            reps.nonTransactionalCreateRepository.createCashFlow(agent, manufacturer,
                    totalDownPayment / buildingTime, CashFlow.DOWNPAYMENT, getCurrentTick() + i, plant);
        }
    }

    @Transactional
    private void setNotWillingToInvest(EnergyProducer agent) {
        agent.setWillingToInvest(false);
    }

    // Create a powerplant investment and operation cash-flow in the form of a
    // map. If only investment, or operation costs should be considered set
    // totalInvestment or operatingProfit to 0
    private TreeMap<Integer, Double> calculateSimplePowerPlantInvestmentCashFlow(int depriacationTime,
            int buildingTime, double totalInvestment, double operatingProfit) {
        TreeMap<Integer, Double> investmentCashFlow = new TreeMap<Integer, Double>();
        double equalTotalDownPaymentInstallement = totalInvestment / buildingTime;
        for (int i = 0; i < buildingTime; i++) {
            investmentCashFlow.put(new Integer(i), -equalTotalDownPaymentInstallement);
        }
        for (int i = buildingTime; i < depriacationTime + buildingTime; i++) {
            investmentCashFlow.put(new Integer(i), operatingProfit);
        }

        return investmentCashFlow;
    }

    private double npv(TreeMap<Integer, Double> netCashFlow, double wacc) {
        double npv = 0;
        for (Integer iterator : netCashFlow.keySet()) {
            npv += netCashFlow.get(iterator).doubleValue() / Math.pow(1 + wacc, iterator.intValue());
        }
        return npv;
    }

    public double determineExpectedMarginalCost(PowerPlant plant, Map<Substance, Double> expectedFuelPrices,
            double expectedCO2Price) {
        double mc = determineExpectedMarginalFuelCost(plant, expectedFuelPrices);
        double co2Intensity = plant.calculateEmissionIntensity();
        mc += co2Intensity * expectedCO2Price;
        return mc;
    }

    public double determineExpectedMarginalFuelCost(PowerPlant powerPlant,
            Map<Substance, Double> expectedFuelPrices) {
        double fc = 0d;
        for (SubstanceShareInFuelMix mix : powerPlant.getFuelMix()) {
            double amount = mix.getShare();
            double fuelPrice = expectedFuelPrices.get(mix.getSubstance());
            fc += amount * fuelPrice;
        }
        return fc;
    }

    private PowerGridNode getNodeForZone(Zone zone) {
        for (PowerGridNode node : reps.genericRepository.findAll(PowerGridNode.class)) {
            if (node.getZone().equals(zone)) {
                return node;
            }
        }
        return null;
    }

    private class MarketInformation {

        Map<Segment, Double> expectedElectricityPricesPerSegment;
        double maxExpectedLoad = 0d;
        Map<PowerPlant, Double> meritOrder;
        double capacitySum;

        MarketInformation(ElectricitySpotMarket market, Map<Substance, Double> fuelPrices, double co2price,
                long time) {
            // determine expected power prices
            expectedElectricityPricesPerSegment = new HashMap<Segment, Double>();
            Map<PowerPlant, Double> marginalCostMap = new HashMap<PowerPlant, Double>();
            capacitySum = 0d;

            // get merit order for this market
            for (PowerPlant plant : reps.powerPlantRepository.findExpectedOperationalPowerPlantsInMarket(market,
                    time)) {

                double plantMarginalCost = determineExpectedMarginalCost(plant, fuelPrices, co2price);
                marginalCostMap.put(plant, plantMarginalCost);
                capacitySum += plant.getTechnology().getCapacity();
            }

            MapValueComparator comp = new MapValueComparator(marginalCostMap);
            meritOrder = new TreeMap<PowerPlant, Double>(comp);
            meritOrder.putAll(marginalCostMap);

            long numberOfSegments = reps.segmentRepository.count();

            double demandFactor = market.getDemandGrowthTrend().getValue(time);

            // find expected prices per segment given merit order
            for (SegmentLoad segmentLoad : market.getLoadDurationCurve()) {

                double expectedSegmentLoad = segmentLoad.getBaseLoad() * demandFactor;

                if (expectedSegmentLoad > maxExpectedLoad) {
                    maxExpectedLoad = expectedSegmentLoad;
                }

                double segmentSupply = 0d;
                double segmentPrice = 0d;

                for (Entry<PowerPlant, Double> plantCost : meritOrder.entrySet()) {
                    PowerPlant plant = plantCost.getKey();
                    double plantCapacity = 0d;
                    // Determine available capacity in the future in this
                    // segment
                    plantCapacity = plant.getExpectedAvailableCapacity(time, segmentLoad.getSegment(),
                            numberOfSegments);

                    // logger.warn("Capacity of plant " + plant.toString() +
                    // " is " +
                    // plantCapacity/plant.getTechnology().getCapacity());
                    if (segmentSupply < expectedSegmentLoad) {
                        segmentSupply += plantCapacity;
                        segmentPrice = plantCost.getValue();
                    }

                }

                // logger.warn("Segment " +
                // segmentLoad.getSegment().getSegmentID() + " supply equals " +
                // segmentSupply + " and segment demand equals " +
                // expectedSegmentLoad);

                if (segmentSupply >= expectedSegmentLoad) {
                    expectedElectricityPricesPerSegment.put(segmentLoad.getSegment(), segmentPrice);
                } else {
                    expectedElectricityPricesPerSegment.put(segmentLoad.getSegment(), market.getValueOfLostLoad());
                }

            }
        }
    }

}