/*******************************************************************************
* 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.domain.technology;
import java.util.Set;
import org.neo4j.graphdb.Direction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.data.annotation.Transient;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.annotation.RelatedTo;
import org.springframework.transaction.annotation.Transactional;
import agentspring.trend.GeometricTrend;
import emlab.domain.agent.EnergyProducer;
import emlab.domain.contract.Loan;
import emlab.domain.market.electricity.PowerPlantDispatchPlan;
import emlab.domain.market.electricity.Segment;
import emlab.repository.PowerPlantDispatchPlanRepository;
import emlab.repository.Reps;
/**
* Representation of a power plant
*
* @author jcrichstein
* @author ejlchappin
*
*/
@Configurable
@NodeEntity
public class PowerPlant {
@Transient
@Autowired private PowerPlantDispatchPlanRepository powerPlantDispatchPlanRepository;
@RelatedTo(type = "TECHNOLOGY", elementClass = PowerGeneratingTechnology.class, direction = Direction.OUTGOING)
private PowerGeneratingTechnology technology;
@RelatedTo(type = "FUEL_MIX", elementClass = SubstanceShareInFuelMix.class, direction = Direction.OUTGOING)
private Set<SubstanceShareInFuelMix> fuelMix;
@RelatedTo(type = "POWERPLANT_OWNER", elementClass = EnergyProducer.class, direction = Direction.OUTGOING)
private EnergyProducer owner;
@RelatedTo(type = "LOCATION", elementClass = PowerGridNode.class, direction = Direction.OUTGOING)
private PowerGridNode location;
@RelatedTo(type = "LOAN", elementClass = Loan.class, direction = Direction.OUTGOING)
private Loan loan;
/**
* dismantleTime is set to 1000 as a signifier, that the powerplant is not
* yet dismantled.
*/
private long dismantleTime;
private long constructionStartTime;
private long actualLeadtime;
private long actualPermittime;
private long actualLifetime;
private double biomassCoCombiostionRate;
private String label;
private double actualInvestedCapital;
private double actualEfficiency;
private double expectedEndOfLife;
public boolean isOperational(long currentTick) {
double finishedConstruction = getConstructionStartTime() + calculateActualPermittime()
+ calculateActualLeadtime();
if (finishedConstruction <= currentTick) {
// finished construction
if (getDismantleTime() == 1000) {
// No dismantletime set, therefore must be not yet dismantled.
return true;
} else if (getDismantleTime() > currentTick) {
// Dismantle time set, but not yet reached
return true;
} else if (getDismantleTime() <= currentTick) {
// Dismantle time passed so not operational
return false;
}
}
// Construction not yet finished.
return false;
}
public boolean isExpectedToBeOperational(long time) {
double finishedConstruction = getConstructionStartTime() + calculateActualPermittime()
+ calculateActualLeadtime();
if (finishedConstruction <= time) {
// finished construction
if (finishedConstruction + getTechnology().getExpectedLifetime() > time) {
// Powerplant is not expected to be dismantled
return true;
}
}
// Construction not yet finished.
return false;
}
public boolean isInPipeline(long currentTick) {
double finishedConstruction = getConstructionStartTime() + calculateActualPermittime()
+ calculateActualLeadtime();
if (finishedConstruction > currentTick) {
// finished construction
if (getDismantleTime() == 1000) {
// No dismantletime set, therefore must be not yet dismantled.
return true;
} else if (getDismantleTime() > currentTick) {
// Dismantle time set, but not yet reached
return true;
} else if (getDismantleTime() <= currentTick) {
// Dismantle time passed so not operational
return false;
}
}
// Construction finished
return false;
}
public double getAvailableCapacity(long currentTick, Segment segment, long numberOfSegments) {
if (isOperational(currentTick)) {
double factor = 1;
if (segment != null) {// if no segment supplied, assume we want full
// capacity
double segmentID = segment.getSegmentID();
if ((int) segmentID != (int) 1) {
double min = getTechnology().getPeakSegmentDependentAvailability();
double max = getTechnology().getBaseSegmentDependentAvailability();
double segmentPortion = (numberOfSegments - segmentID) / (numberOfSegments - 1); // start
// counting
// at
// 1.
double range = max - min;
factor = max - segmentPortion * range;
int i = 0;
} else {
factor = getTechnology().getPeakSegmentDependentAvailability();
}
}
return getTechnology().getCapacity() * factor;
} else {
return 0;
}
}
public double getExpectedAvailableCapacity(long futureTick, Segment segment, long numberOfSegments) {
if (isExpectedToBeOperational(futureTick)) {
double factor = 1;
if (segment != null) {// if no segment supplied, assume we want full
// capacity
double segmentID = segment.getSegmentID();
double min = getTechnology().getPeakSegmentDependentAvailability();
double max = getTechnology().getBaseSegmentDependentAvailability();
double segmentPortion = (numberOfSegments - segmentID) / (numberOfSegments - 1); // start
// counting
// at
// 1.
double range = max - min;
factor = max - segmentPortion * range;
}
return getTechnology().getCapacity() * factor;
} else {
return 0;
}
}
public double getAvailableCapacity(long currentTick) {
if (isOperational(currentTick)) {
return getTechnology().getCapacity();
} else {
return 0;
}
}
public long calculateActualLeadtime() {
long actual;
actual = getActualLeadtime();
if (actual <= 0) {
actual = getTechnology().getExpectedLeadtime();
}
return actual;
}
public long calculateActualPermittime() {
long actual;
actual = getActualPermittime();
if (actual <= 0) {
actual = getTechnology().getExpectedPermittime();
}
return actual;
}
public long calculateActualLifetime() {
long actual;
actual = getActualLifetime();
if (actual <= 0) {
actual = getTechnology().getExpectedLifetime();
}
return actual;
}
/**
* Determines whether a plant is still in its technical lifetime. The end of
* the technical lifetime is determined by the construction start time, the
* permit time, the lead time and the actual lifetime.
*
* @param currentTick
* @return whether the plant is still in its technical lifetime.
*/
public boolean isWithinTechnicalLifetime(long currentTick) {
long endOfTechnicalLifetime = getConstructionStartTime() + calculateActualPermittime()
+ calculateActualLeadtime() + calculateActualLifetime();
if (endOfTechnicalLifetime <= currentTick) {
return false;
}
return true;
}
public PowerGridNode getLocation() {
return location;
}
public void setLocation(PowerGridNode location) {
this.location = location;
}
public PowerGeneratingTechnology getTechnology() {
return technology;
}
public void setTechnology(PowerGeneratingTechnology technology) {
this.technology = technology;
}
public long getConstructionStartTime() {
return constructionStartTime;
}
public void setConstructionStartTime(long constructionStartTime) {
this.constructionStartTime = constructionStartTime;
}
public EnergyProducer getOwner() {
return owner;
}
public void setOwner(EnergyProducer owner) {
this.owner = owner;
}
public void setActualLifetime(long actualLifetime) {
this.actualLifetime = actualLifetime;
}
public long getActualLifetime() {
return actualLifetime;
}
public void setActualPermittime(long actualPermittime) {
this.actualPermittime = actualPermittime;
}
public long getActualPermittime() {
return actualPermittime;
}
public void setActualLeadtime(long actualLeadtime) {
this.actualLeadtime = actualLeadtime;
}
public long getActualLeadtime() {
return actualLeadtime;
}
public long getDismantleTime() {
return dismantleTime;
}
public void setDismantleTime(long dismantleTime) {
this.dismantleTime = dismantleTime;
}
public String getName() {
return label;
}
public void setName(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public double getActualInvestedCapital() {
return actualInvestedCapital;
}
public void setActualInvestedCapital(double actualInvestedCapital) {
this.actualInvestedCapital = actualInvestedCapital;
}
public Set<SubstanceShareInFuelMix> getFuelMix() {
return fuelMix;
}
public void setFuelMix(Set<SubstanceShareInFuelMix> fuelMix) {
this.fuelMix = fuelMix;
}
public Loan getLoan() {
return loan;
}
public void setLoan(Loan loan) {
this.loan = loan;
}
public double getActualEfficiency() {
return actualEfficiency;
}
public void setActualEfficiency(double actualEfficiency) {
this.actualEfficiency = actualEfficiency;
}
public String toString() {
return this.getName() + " power plant";
}
/**
* Sets the actual capital that is needed to build the power plant. It
* considers the exogenous modifier and automatically adjusts for the actual
* building and permit time.
*
* @param timeOfPermitorBuildingStart
*/
public void calculateAndSetActualInvestedCapital(long timeOfPermitorBuildingStart) {
double invNorm = this.getTechnology().getBaseInvestmentCost();
double modifierExo = this.getTechnology().getInvestmentCostModifierExogenous();
// Adjust the exogenous modifier to the given time.
GeometricTrend trendExo = new GeometricTrend();
trendExo.setGrowthRate(modifierExo);
trendExo.setStart(1);
modifierExo = trendExo.getValue(timeOfPermitorBuildingStart + getActualLeadtime() + getActualPermittime());
this.actualInvestedCapital = invNorm * modifierExo;
}
public void calculateAndSetActualEfficiency(long timeOfPermitorBuildingStart) {
double effNorm = this.getTechnology().getEfficiency();
//Calculate exogenous modifier
double effModExo = this.getTechnology().getEfficiencyModifierExogenous();
GeometricTrend trendExo = new GeometricTrend();
trendExo.setGrowthRate(effModExo);
trendExo.setStart(1);
double trendExoValue = trendExo.getValue(this.getConstructionStartTime());
//Calculate endogenous modifier
/* double effModEndo = this.getTechnology().getEfficiencyModifierEndogenous();
double startCap = 0d;
double currentCap = calculateMarketCapacityEverInstalledUpToGivenTime(this.getTechnology(), timeOfPermitorBuildingStart);
int i = 0;
while (startCap == 0 && i <= getCurrentTick()) {
startCap = calculateMarketCapacityEverInstalledUpToGivenTime(powerPlant.getTechnology(), i);
i++;
}
GeometricTrend trendEndo = new GeometricTrend();
trendEndo.setGrowthRate(effModEndo);
trendEndo.setStart(1);
// Put in -1 here otherwise you'll get a modifier>1 at the start
double trendEndoValue = trendEndo.getValue((long) (currentCap / startCap) - 1);*/
double trendEndoValue = 1;
double currentEff = effNorm * trendExoValue * trendEndoValue;
this.setActualEfficiency(currentEff);
}
public double calculateEmissionIntensity() {
double emission = 0d;
for (SubstanceShareInFuelMix sub : this.getFuelMix()) {
Substance substance = sub.getSubstance();
double fuelAmount = sub.getShare();
double co2density = substance.getCo2Density() * (1 - this.getTechnology().getCo2CaptureEffciency());
// determine the total cost per MWh production of this plant
double emissionForThisFuel = fuelAmount * co2density;
emission += emissionForThisFuel;
}
return emission;
}
public double calculateElectricityOutputAtTime(long time) {
// TODO This is in MWh (so hours of segment included!!)
double amount = 0d;
for (PowerPlantDispatchPlan plan : powerPlantDispatchPlanRepository.findAllPowerPlantDispatchPlansForPowerPlantForTime(this, time)) {
amount += plan.getSegment().getLengthInHours() * (plan.getCapacityLongTermContract() + plan.getAcceptedAmount());
}
return amount;
}
public double calculateCO2EmissionsAtTime(long time) {
return this.calculateEmissionIntensity() * calculateElectricityOutputAtTime(time);
}
@Transactional
public void dismantlePowerPlant(long time) {
this.setDismantleTime(time);
}
/**
* Persists and specifies the properties of a new Power Plant (which needs
* to be created separately before with new PowerPlant();
*
* Do not forget that any change made here should be reflected in the
* ElectricityProducerFactory!!
*
* @param time
* @param energyProducer
* @param location
* @param technology
*
* @author J.C.Richstein
*/
@Transactional
public void specifyAndPersist(long time, EnergyProducer energyProducer, PowerGridNode location,
PowerGeneratingTechnology technology) {
specifyNotPersist(time, energyProducer, location, technology);
}
public void specifyNotPersist(long time, EnergyProducer energyProducer, PowerGridNode location,
PowerGeneratingTechnology technology) {
String label = energyProducer.getName() + " - " + technology.getName();
this.setName(label);
this.setTechnology(technology);
this.setOwner(energyProducer);
this.setLocation(location);
this.setConstructionStartTime(time);
this.setActualLeadtime(this.technology.getExpectedLeadtime());
this.setActualPermittime(this.technology.getExpectedPermittime());
this.calculateAndSetActualEfficiency(time);
assert(this.getActualEfficiency()<=1);
this.setDismantleTime(1000);
this.calculateAndSetActualInvestedCapital(time);
this.setExpectedEndOfLife(time + getActualPermittime() + getActualLeadtime()
+ getTechnology().getExpectedLifetime());
}
@Transactional
public void createOrUpdateLoan(Loan loan) {
this.setLoan(loan);
}
public double getExpectedEndOfLife() {
return expectedEndOfLife;
}
public void setExpectedEndOfLife(double expectedEndOfLife) {
this.expectedEndOfLife = expectedEndOfLife;
}
@Transactional
public void updateFuelMix(Set<SubstanceShareInFuelMix> fuelMix) {
this.setFuelMix(fuelMix);
}
}