diff --git a/.github/workflows/centos7.yml b/.github/workflows/centos7.yml index a2f6452ed1..4211f7fea3 100644 --- a/.github/workflows/centos7.yml +++ b/.github/workflows/centos7.yml @@ -5,6 +5,7 @@ on: types: [created] push: branches: + - feature/ramping_model_scalian - develop - dependabot/* diff --git a/src/libs/antares/study/include/antares/study/parts/thermal/cluster.h b/src/libs/antares/study/include/antares/study/parts/thermal/cluster.h index 3b32763d51..10c3ad3692 100644 --- a/src/libs/antares/study/include/antares/study/parts/thermal/cluster.h +++ b/src/libs/antares/study/include/antares/study/parts/thermal/cluster.h @@ -34,6 +34,8 @@ #include #include #include +#include + namespace Antares { @@ -339,6 +341,32 @@ class ThermalCluster final : public Cluster, public std::enable_shared_from_this double marketBidCost = 0; //! Variable O&M cost (euros/MWh) double variableomcost = 0; + + struct Ramping + { + //! Cost of power increase (euros/MW) + double powerIncreaseCost; + //! Cost of power decrease (euros/MW) + double powerDecreaseCost; + //! Maximum hourly upward power ramping rate (MW/hour) + double maxUpwardPowerRampingRate; + //! Maximum hourly downward power ramping rate (MW/hour) + double maxDownwardPowerRampingRate; + + Ramping() : + powerIncreaseCost(0.), + powerDecreaseCost(0.), + maxUpwardPowerRampingRate(0.), + maxDownwardPowerRampingRate(0.) + { + } + + void reset(); + bool checkValidity(Area* area, Data::ClusterName clusterName); + friend std::ostream& operator<<(std::ostream&, const Ramping& ramping); + }; + std::optional ramping; + //@} /*! diff --git a/src/libs/antares/study/parts/thermal/cluster.cpp b/src/libs/antares/study/parts/thermal/cluster.cpp index b8a313b99a..58e27a82d2 100644 --- a/src/libs/antares/study/parts/thermal/cluster.cpp +++ b/src/libs/antares/study/parts/thermal/cluster.cpp @@ -158,6 +158,9 @@ void Data::ThermalCluster::copyFrom(const ThermalCluster& cluster) minUpTime = cluster.minUpTime; minDownTime = cluster.minDownTime; + // ramping + ramping = cluster.ramping; + // spinning spinning = cluster.spinning; @@ -447,6 +450,9 @@ void Data::ThermalCluster::reset() marketBidCost = 0.; variableomcost = 0.; costsTimeSeries.resize(1, CostsTimeSeries()); + + // ramping + ramping.reset(); // modulation modulation.resize(thermalModulationMax, HOURS_PER_YEAR); @@ -576,6 +582,18 @@ bool Data::ThermalCluster::integrityCheck() ret = false; }*/ + // ramping + if (ramping) + { + // if the ramping model is ill defined, then we disable the ramping model for this cluster + bool ramping_ret = ramping.value().checkValidity(parentArea, pName); + if(!ramping_ret) + { + ramping.reset(); + } + ret = ramping_ret && ret; + } + return ret; } @@ -789,5 +807,56 @@ bool ThermalCluster::isActive() const { return enabled && !mustrun; } +void ThermalCluster::Ramping::reset() +{ + powerIncreaseCost = 0; + powerDecreaseCost = 0; + maxUpwardPowerRampingRate = 0; + maxDownwardPowerRampingRate = 0; +} + +bool ThermalCluster::Ramping::checkValidity(Area* parentArea, Data::ClusterName clusterName) +{ + bool ret = true; + + if (maxUpwardPowerRampingRate <= 0) + { + logs.error() << "Thermal cluster: " << parentArea->name << '/' << clusterName + << ": The maximum upward power ramping rate must greater than zero." + << "Ramping is disabled for this thermal cluster"; + ret = false; + } + if (maxDownwardPowerRampingRate <= 0) + { + logs.error() << "Thermal cluster: " << parentArea->name << '/' << clusterName + << ": The maximum downward power ramping rate must greater than zero." + << "Ramping is disabled for this thermal cluster"; + ret = false; + } + if (powerIncreaseCost < 0) + { + logs.error() << "Thermal cluster: " << parentArea->name << '/' << clusterName + << ": The ramping power increase cost must be positive or null." + << "Ramping is disabled for this thermal cluster"; + ret = false; + } + if (powerDecreaseCost < 0) + { + logs.error() << "Thermal cluster: " << parentArea->name << '/' << clusterName + << ": The ramping power decrease cost must be positive or null." + << "Ramping is disabled for this thermal cluster"; + ret = false; + } + return ret; +} + +std::ostream& operator<<(std::ostream& os, const ThermalCluster::Ramping& r) +{ + return os << r.powerIncreaseCost << '\t' + << r.powerDecreaseCost << '\t' + << r.maxUpwardPowerRampingRate << '\t' + << r.maxDownwardPowerRampingRate; +} + } // namespace Data } // namespace Antares diff --git a/src/libs/antares/study/parts/thermal/cluster_list.cpp b/src/libs/antares/study/parts/thermal/cluster_list.cpp index d739750615..29cff4c163 100644 --- a/src/libs/antares/study/parts/thermal/cluster_list.cpp +++ b/src/libs/antares/study/parts/thermal/cluster_list.cpp @@ -260,6 +260,23 @@ static bool ThermalClusterLoadFromProperty(ThermalCluster& cluster, const IniFil if (p->key == "startup-cost") return p->value.to(cluster.startupCost); + // initialize the ramping attributes only if ramping is enabled, else ignore these properties + if (p->key == "power-increase-cost") + return (cluster.ramping)? p->value.to(cluster.ramping.value().powerIncreaseCost) : true; + if (p->key == "power-decrease-cost") + return (cluster.ramping) ? p->value.to(cluster.ramping.value().powerDecreaseCost) + : true; + if (p->key == "max-upward-power-ramping-rate") + return (cluster.ramping) + ? p->value.to(cluster.ramping.value().maxUpwardPowerRampingRate) + : true; + if (p->key == "max-downward-power-ramping-rate") + return (cluster.ramping) + ? p->value.to(cluster.ramping.value().maxDownwardPowerRampingRate) : true; + // we ignore this property as it was already handled in ThermalClusterLoadFromSection + if (p->key == "ramping-enabled") + return true; + if (p->key == "unitcount") return p->value.to(cluster.unitCount); if (p->key == "volatility.planned") @@ -286,6 +303,16 @@ bool ThermalClusterLoadFromSection(const AnyString& filename, cluster.setName(section.name); + // initialize the ramping attributes only if ramping-enabled=true + auto* rampingEnabledProperty = section.find("ramping-enabled"); + if(rampingEnabledProperty) + { + bool rampingEnabled = false; + bool attributeOK = rampingEnabledProperty->value.to(rampingEnabled); + if (rampingEnabled && attributeOK) + cluster.ramping = ThermalCluster::Ramping(); + } + if (section.firstProperty) { // Browse all properties @@ -420,6 +447,17 @@ bool ThermalClusterList::saveToFolder(const AnyString& folder) const if (!Utils::isZero(c->variableomcost)) s->add("variableomcost", Utils::round(c->variableomcost, 3)); + // ramping (only if ramping is enabled) + if (c->ramping && c->ramping.value().powerIncreaseCost != 0) + s->add("power-increase-cost", Math::Round(c->ramping.value().powerIncreaseCost, 3)); + if (c->ramping && c->ramping.value().powerDecreaseCost != 0) + s->add("power-decrease-cost", Math::Round(c->ramping.value().powerDecreaseCost, 3)); + if (c->ramping && c->ramping.value().maxUpwardPowerRampingRate != 0) + s->add("max-upward-power-ramping-rate", + Math::Round(c->ramping.value().maxUpwardPowerRampingRate, 3)); + if (c->ramping && c->ramping.value().maxDownwardPowerRampingRate != 0) + s->add("max-downward-power-ramping-rate", + Math::Round(c->ramping.value().maxDownwardPowerRampingRate, 3)); //pollutant factor for (auto const& [key, val] : Pollutant::namesToEnum) diff --git a/src/solver/optimisation/CMakeLists.txt b/src/solver/optimisation/CMakeLists.txt index 082f391b89..8f224d66e6 100644 --- a/src/solver/optimisation/CMakeLists.txt +++ b/src/solver/optimisation/CMakeLists.txt @@ -71,6 +71,10 @@ set(RTESOLVER_OPT adequacy_patch_csr/constraints/CsrAreaBalance.cpp include/antares/solver/optimisation/adequacy_patch_csr/constraints/CsrBindingConstraintHour.h adequacy_patch_csr/constraints/CsrBindingConstraintHour.cpp + opt_decompte_variables_et_contraintes_rampes_thermiques.cpp + opt_construction_variables_rampes_thermiques.cpp + opt_gestion_des_bornes_rampes_thermiques.cpp + opt_gestion_des_couts_rampes_thermiques.cpp include/antares/solver/optimisation/opt_period_string_generator_base.h include/antares/solver/optimisation/opt_rename_problem.h @@ -127,10 +131,21 @@ set(RTESOLVER_OPT include/antares/solver/optimisation/constraints/MinDownTime.h constraints/MinDownTime.cpp + include/antares/solver/optimisation/constraints/RampingIncreaseRate.h + constraints/RampingIncreaseRate.cpp + include/antares/solver/optimisation/constraints/RampingDecreaseRate.h + constraints/RampingDecreaseRate.cpp + include/antares/solver/optimisation/constraints/PowerOutputVariationIncrease.h + constraints/PowerOutputVariationIncrease.cpp + include/antares/solver/optimisation/constraints/PowerOutputVariationDecrease.h + constraints/PowerOutputVariationDecrease.cpp + include/antares/solver/optimisation/ProblemMatrixEssential.h ProblemMatrixEssential.cpp include/antares/solver/optimisation/LinearProblemMatrixStartUpCosts.h LinearProblemMatrixStartUpCosts.cpp + include/antares/solver/optimisation/LinearProblemMatrixRamping.h + LinearProblemMatrixRamping.cpp include/antares/solver/optimisation/LinearProblemMatrix.h LinearProblemMatrix.cpp include/antares/solver/optimisation/QuadraticProblemMatrix.h @@ -146,6 +161,10 @@ set(RTESOLVER_OPT constraints/HydroPowerGroup.cpp include/antares/solver/optimisation/constraints/HydraulicSmoothingGroup.h constraints/HydraulicSmoothingGroup.cpp + include/antares/solver/optimisation/constraints/RampingIncreaseDecreaseRateGroup.h + constraints/RampingIncreaseDecreaseRateGroup.cpp + include/antares/solver/optimisation/constraints/PowerOutputVariationGroup.h + constraints/PowerOutputVariationGroup.cpp include/antares/solver/optimisation/constraints/MinMaxHydroPowerGroup.h constraints/MinMaxHydroPowerGroup.cpp include/antares/solver/optimisation/constraints/MaxPumpingGroup.h @@ -174,7 +193,6 @@ set(RTESOLVER_OPT variables/VariableManagement.cpp variables/VariableManagerUtils.h variables/VariableManagerUtils.cpp - ) diff --git a/src/solver/optimisation/LinearProblemMatrix.cpp b/src/solver/optimisation/LinearProblemMatrix.cpp index e4b6a9fc11..03ab611858 100644 --- a/src/solver/optimisation/LinearProblemMatrix.cpp +++ b/src/solver/optimisation/LinearProblemMatrix.cpp @@ -24,6 +24,7 @@ #include "antares/solver/utils/filename.h" #include "antares/solver/optimisation/opt_fonctions.h" #include "antares/solver/optimisation/LinearProblemMatrixStartUpCosts.h" +#include "antares/solver/optimisation/LinearProblemMatrixRamping.h" using namespace Antares::Data; @@ -61,6 +62,7 @@ void LinearProblemMatrix::Run() if (problemeHebdo_->OptimisationAvecCoutsDeDemarrage) { LinearProblemMatrixStartUpCosts(problemeHebdo_, false, builder_).Run(); + LinearProblemMatrixRamping(problemeHebdo_, false, builder_).Run(); } return; diff --git a/src/solver/optimisation/LinearProblemMatrixRamping.cpp b/src/solver/optimisation/LinearProblemMatrixRamping.cpp new file mode 100644 index 0000000000..33b76b6139 --- /dev/null +++ b/src/solver/optimisation/LinearProblemMatrixRamping.cpp @@ -0,0 +1,34 @@ +/* +** Copyright 2007-2024, RTE (https://www.rte-france.com) +** See AUTHORS.txt +** SPDX-License-Identifier: MPL-2.0 +** This file is part of Antares-Simulator, +** Adequacy and Performance assessment for interconnected energy networks. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the Mozilla Public Licence 2.0 as published by +** the Mozilla Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** Mozilla Public Licence 2.0 for more details. +** +** You should have received a copy of the Mozilla Public Licence 2.0 +** along with Antares_Simulator. If not, see . +*/ +#include "antares/solver/optimisation/LinearProblemMatrixRamping.h" +using namespace Antares::Data; + +LinearProblemMatrixRamping::LinearProblemMatrixRamping(PROBLEME_HEBDO* problemeHebdo, + bool Simulation, + ConstraintBuilder& builder) : + ProblemMatrixEssential(problemeHebdo), + simulation_(Simulation), + rampingIncreaseDecreaseRateGroup_(problemeHebdo, simulation_, builder), + powerOutputVariationGroup_(problemeHebdo, simulation_, builder) +{ + constraintgroups_ + = {&rampingIncreaseDecreaseRateGroup_, &powerOutputVariationGroup_}; +} \ No newline at end of file diff --git a/src/solver/optimisation/constraints/ConstraintBuilder.cpp b/src/solver/optimisation/constraints/ConstraintBuilder.cpp index f36b7651c5..e8581ddf5f 100644 --- a/src/solver/optimisation/constraints/ConstraintBuilder.cpp +++ b/src/solver/optimisation/constraints/ConstraintBuilder.cpp @@ -49,6 +49,24 @@ ConstraintBuilder& ConstraintBuilder::DispatchableProduction(unsigned int index, return *this; } +ConstraintBuilder& ConstraintBuilder::ProductionDecreaseAboveMin(unsigned int index, + double coeff, + int offset, + int delta) +{ + AddVariable(variableManager_.ProductionDecreaseAboveMin(index, hourInWeek_, offset, delta), coeff); + return *this; +} + +ConstraintBuilder& ConstraintBuilder::ProductionIncreaseAboveMin(unsigned int index, + double coeff, + int offset, + int delta) +{ + AddVariable(variableManager_.ProductionIncreaseAboveMin(index, hourInWeek_, offset, delta), coeff); + return *this; +} + ConstraintBuilder& ConstraintBuilder::NumberOfDispatchableUnits(unsigned int index, double coeff) { AddVariable(variableManager_.NumberOfDispatchableUnits(index, hourInWeek_), coeff); diff --git a/src/solver/optimisation/constraints/PowerOutputVariationDecrease.cpp b/src/solver/optimisation/constraints/PowerOutputVariationDecrease.cpp new file mode 100644 index 0000000000..ebad68ae28 --- /dev/null +++ b/src/solver/optimisation/constraints/PowerOutputVariationDecrease.cpp @@ -0,0 +1,38 @@ +#include "antares/solver/optimisation/constraints/PowerOutputVariationDecrease.h" + +void PowerOutputVariationDecrease::add(int pays, int index, int pdt) +{ + if (!data.Simulation) + { + int cluster = data.PaliersThermiquesDuPays[pays] + .NumeroDuPalierDansLEnsembleDesPaliersThermiques[index]; + double pmaxDUnGroupeDuPalierThermique + = data.PaliersThermiquesDuPays[pays].PmaxDUnGroupeDuPalierThermique[index]; + // constraint : P(t) - P(t-1) + P^- + u * M^-(t) >= 0 + builder.updateHourWithinWeek(pdt) + .DispatchableProduction(cluster, 1.0) + .DispatchableProduction( + cluster, -1.0, -1, builder.data.NombreDePasDeTempsPourUneOptimisation) + .ProductionDecreaseAboveMin(cluster, 1.0) + .NumberStoppingDispatchableUnits(cluster, pmaxDUnGroupeDuPalierThermique) + .greaterThan(); + + if (builder.NumberOfVariables() > 0) + { + ConstraintNamer namer(builder.data.NomDesContraintes); + + namer.UpdateTimeStep(builder.data.weekInTheYear * 168 + pdt); + namer.UpdateArea(builder.data.NomsDesPays[pays]); + + namer.ProductionOutputVariation( + builder.data.nombreDeContraintes, + data.PaliersThermiquesDuPays[pays].NomsDesPaliersThermiques[index]); + } + builder.build(); + } + else + { + builder.data.NbTermesContraintesPourLesRampes += 4; + builder.data.nombreDeContraintes++; + } +} diff --git a/src/solver/optimisation/constraints/PowerOutputVariationGroup.cpp b/src/solver/optimisation/constraints/PowerOutputVariationGroup.cpp new file mode 100644 index 0000000000..c6c0a9713a --- /dev/null +++ b/src/solver/optimisation/constraints/PowerOutputVariationGroup.cpp @@ -0,0 +1,53 @@ +/* +** Copyright 2007-2024, RTE (https://www.rte-france.com) +** See AUTHORS.txt +** SPDX-License-Identifier: MPL-2.0 +** This file is part of Antares-Simulator, +** Adequacy and Performance assessment for interconnected energy networks. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the Mozilla Public Licence 2.0 as published by +** the Mozilla Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** Mozilla Public Licence 2.0 for more details. +** +** You should have received a copy of the Mozilla Public Licence 2.0 +** along with Antares_Simulator. If not, see . +*/ + +#include "antares/solver/optimisation/constraints/PowerOutputVariationGroup.h" + +/** + * @brief build P{min,max}DispatchableGeneration constraints with + * respect to default order + */ +void PowerOutputVariationGroup::BuildConstraints() +{ + auto data = GetStartUpCostsDataFromProblemHebdo(); + PMaxDispatchableGeneration pMaxDispatchableGeneration(builder_, data); + PMinDispatchableGeneration pMinDispatchableGeneration(builder_, data); + for (uint32_t pays = 0; pays < problemeHebdo_->NombreDePays; pays++) + { + const PALIERS_THERMIQUES& PaliersThermiquesDuPays + = problemeHebdo_->PaliersThermiquesDuPays[pays]; + for (int index = 0; index < PaliersThermiquesDuPays.NombreDePaliersThermiques; index++) + { + if (PaliersThermiquesDuPays.maxUpwardPowerRampingRate[index] >= 0) + { + PowerOutputVariationIncrease powerOutputVariationIncrease(builder_, data); + PowerOutputVariationDecrease powerOutputVariationDecrease(builder_, data); + + for (int pdt = 0; pdt < problemeHebdo_->NombreDePasDeTempsPourUneOptimisation; + pdt++) + { + powerOutputVariationIncrease.add(pays, index, pdt); + powerOutputVariationDecrease.add(pays, index, pdt); + } + } + } + } +} \ No newline at end of file diff --git a/src/solver/optimisation/constraints/PowerOutputVariationIncrease.cpp b/src/solver/optimisation/constraints/PowerOutputVariationIncrease.cpp new file mode 100644 index 0000000000..ca098c6fc4 --- /dev/null +++ b/src/solver/optimisation/constraints/PowerOutputVariationIncrease.cpp @@ -0,0 +1,36 @@ +#include "antares/solver/optimisation/constraints/PowerOutputVariationIncrease.h" + +void PowerOutputVariationIncrease::add(int pays, int index, int pdt) +{ + if (!data.Simulation) + { + int cluster = data.PaliersThermiquesDuPays[pays].NumeroDuPalierDansLEnsembleDesPaliersThermiques[index]; + double pmaxDUnGroupeDuPalierThermique + = data.PaliersThermiquesDuPays[pays].PmaxDUnGroupeDuPalierThermique[index]; + // constraint : P(t) - P(t-1) - u * M^+(t) - P^+ <= 0 + builder.updateHourWithinWeek(pdt) + .DispatchableProduction(cluster, 1.0) + .DispatchableProduction(cluster, -1.0, -1, builder.data.NombreDePasDeTempsPourUneOptimisation) + .NumberStartingDispatchableUnits(cluster, -pmaxDUnGroupeDuPalierThermique) + .ProductionIncreaseAboveMin(cluster, -1.0) + .lessThan(); + + if (builder.NumberOfVariables() > 0) + { + ConstraintNamer namer(builder.data.NomDesContraintes); + + namer.UpdateTimeStep(builder.data.weekInTheYear * 168 + pdt); + namer.UpdateArea(builder.data.NomsDesPays[pays]); + + namer.ProductionOutputVariation( + builder.data.nombreDeContraintes, + data.PaliersThermiquesDuPays[pays].NomsDesPaliersThermiques[index]); + } + builder.build(); + } + else + { + builder.data.NbTermesContraintesPourLesRampes += 4; + builder.data.nombreDeContraintes++; + } +} diff --git a/src/solver/optimisation/constraints/RampingDecreaseRate.cpp b/src/solver/optimisation/constraints/RampingDecreaseRate.cpp new file mode 100644 index 0000000000..972e62cc6c --- /dev/null +++ b/src/solver/optimisation/constraints/RampingDecreaseRate.cpp @@ -0,0 +1,39 @@ +#include "antares/solver/optimisation/constraints/RampingDecreaseRate.h" + +void RampingDecreaseRate::add(int pays, int index, int pdt) +{ + if (!data.Simulation) + { + int cluster = data.PaliersThermiquesDuPays[pays] + .NumeroDuPalierDansLEnsembleDesPaliersThermiques[index]; + double maxDownwardPowerRampingRate + = data.PaliersThermiquesDuPays[pays].maxDownwardPowerRampingRate[index]; + double pmaxDUnGroupeDuPalierThermique + = data.PaliersThermiquesDuPays[pays].PmaxDUnGroupeDuPalierThermique[index]; + // constraint : P(t) - P(t-1) + R^- * M(t) + u * M^-(t) + u * M^--(t) > 0 + builder.updateHourWithinWeek(pdt) + .DispatchableProduction(cluster, 1.0) + .DispatchableProduction(cluster, -1.0, -1, builder.data.NombreDePasDeTempsPourUneOptimisation) + .NumberOfDispatchableUnits(cluster, maxDownwardPowerRampingRate) + .NumberStoppingDispatchableUnits(cluster, pmaxDUnGroupeDuPalierThermique) + .greaterThan(); + + if (builder.NumberOfVariables() > 0) + { + ConstraintNamer namer(builder.data.NomDesContraintes); + + namer.UpdateTimeStep(builder.data.weekInTheYear * 168 + pdt); + namer.UpdateArea(builder.data.NomsDesPays[pays]); + + namer.RampingDecreaseRate( + builder.data.nombreDeContraintes, + data.PaliersThermiquesDuPays[pays].NomsDesPaliersThermiques[index]); + } + builder.build(); + } + else + { + builder.data.NbTermesContraintesPourLesRampes += 5; + builder.data.nombreDeContraintes++; + } +} diff --git a/src/solver/optimisation/constraints/RampingIncreaseDecreaseRateGroup.cpp b/src/solver/optimisation/constraints/RampingIncreaseDecreaseRateGroup.cpp new file mode 100644 index 0000000000..7c50582e00 --- /dev/null +++ b/src/solver/optimisation/constraints/RampingIncreaseDecreaseRateGroup.cpp @@ -0,0 +1,53 @@ +/* +** Copyright 2007-2024, RTE (https://www.rte-france.com) +** See AUTHORS.txt +** SPDX-License-Identifier: MPL-2.0 +** This file is part of Antares-Simulator, +** Adequacy and Performance assessment for interconnected energy networks. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the Mozilla Public Licence 2.0 as published by +** the Mozilla Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** Mozilla Public Licence 2.0 for more details. +** +** You should have received a copy of the Mozilla Public Licence 2.0 +** along with Antares_Simulator. If not, see . +*/ + +#include "antares/solver/optimisation/constraints/RampingIncreaseDecreaseRateGroup.h" + +/** + * @brief build P{min,max}DispatchableGeneration constraints with + * respect to default order + */ +void RampingIncreaseDecreaseRateGroup::BuildConstraints() +{ + auto data = GetStartUpCostsDataFromProblemHebdo(); + PMaxDispatchableGeneration pMaxDispatchableGeneration(builder_, data); + PMinDispatchableGeneration pMinDispatchableGeneration(builder_, data); + for (uint32_t pays = 0; pays < problemeHebdo_->NombreDePays; pays++) + { + const PALIERS_THERMIQUES& PaliersThermiquesDuPays + = problemeHebdo_->PaliersThermiquesDuPays[pays]; + for (int index = 0; index < PaliersThermiquesDuPays.NombreDePaliersThermiques; index++) + { + if (PaliersThermiquesDuPays.maxUpwardPowerRampingRate[index] >= 0) + { + RampingIncreaseRate rampingIncreaseRate(builder_, data); + RampingDecreaseRate rampingDecreaseRate(builder_, data); + + for (int pdt = 0; pdt < problemeHebdo_->NombreDePasDeTempsPourUneOptimisation; + pdt++) + { + rampingIncreaseRate.add(pays, index, pdt); + rampingDecreaseRate.add(pays, index, pdt); + } + } + } + } +} \ No newline at end of file diff --git a/src/solver/optimisation/constraints/RampingIncreaseRate.cpp b/src/solver/optimisation/constraints/RampingIncreaseRate.cpp new file mode 100644 index 0000000000..10291e515f --- /dev/null +++ b/src/solver/optimisation/constraints/RampingIncreaseRate.cpp @@ -0,0 +1,39 @@ +#include "antares/solver/optimisation/constraints/RampingIncreaseRate.h" + +void RampingIncreaseRate::add(int pays, int index, int pdt) +{ + if (!data.Simulation) + { + int cluster = data.PaliersThermiquesDuPays[pays] + .NumeroDuPalierDansLEnsembleDesPaliersThermiques[index]; + double maxUpwardPowerRampingRate + = data.PaliersThermiquesDuPays[pays].maxUpwardPowerRampingRate[index]; + double pmaxDUnGroupeDuPalierThermique + = data.PaliersThermiquesDuPays[pays].PmaxDUnGroupeDuPalierThermique[index]; + // constraint : P(t) - P(t-1) - R^+ * M(t) - u * M^+(t) < 0 + + builder.updateHourWithinWeek(pdt) + .DispatchableProduction(cluster, 1.0) + .DispatchableProduction( + cluster, -1.0, -1, builder.data.NombreDePasDeTempsPourUneOptimisation) + .NumberOfDispatchableUnits(cluster, -maxUpwardPowerRampingRate) + .NumberStartingDispatchableUnits(cluster, -pmaxDUnGroupeDuPalierThermique) + .lessThan(); + + if (builder.NumberOfVariables() > 0) + { + ConstraintNamer namer(builder.data.NomDesContraintes); + namer.UpdateTimeStep(builder.data.weekInTheYear * 168 + pdt); + namer.UpdateArea(builder.data.NomsDesPays[pays]); + namer.RampingIncreaseRate( + builder.data.nombreDeContraintes, + data.PaliersThermiquesDuPays[pays].NomsDesPaliersThermiques[index]); + } + builder.build(); + } + else + { + builder.data.NbTermesContraintesPourLesRampes += 4; + builder.data.nombreDeContraintes++; + } +} diff --git a/src/solver/optimisation/constraints/constraint_builder_utils.cpp b/src/solver/optimisation/constraints/constraint_builder_utils.cpp index bca0b9ffeb..6feed0c3a4 100644 --- a/src/solver/optimisation/constraints/constraint_builder_utils.cpp +++ b/src/solver/optimisation/constraints/constraint_builder_utils.cpp @@ -44,5 +44,6 @@ ConstraintBuilderData NewGetConstraintBuilderFromProblemHebdoAndProblemAResoudre problemeHebdo->NomsDesPays, problemeHebdo->weekInTheYear, problemeHebdo->NombreDePasDeTemps, - problemeHebdo->NbTermesContraintesPourLesCoutsDeDemarrage}; + problemeHebdo->NbTermesContraintesPourLesCoutsDeDemarrage, + problemeHebdo->NbTermesContraintesPourLesRampes}; } diff --git a/src/solver/optimisation/include/antares/solver/optimisation/LinearProblemMatrixRamping.h b/src/solver/optimisation/include/antares/solver/optimisation/LinearProblemMatrixRamping.h new file mode 100644 index 0000000000..144075b4cb --- /dev/null +++ b/src/solver/optimisation/include/antares/solver/optimisation/LinearProblemMatrixRamping.h @@ -0,0 +1,46 @@ +/* +** Copyright 2007-2024, RTE (https://www.rte-france.com) +** See AUTHORS.txt +** SPDX-License-Identifier: MPL-2.0 +** This file is part of Antares-Simulator, +** Adequacy and Performance assessment for interconnected energy networks. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the Mozilla Public Licence 2.0 as published by +** the Mozilla Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** Mozilla Public Licence 2.0 for more details. +** +** You should have received a copy of the Mozilla Public Licence 2.0 +** along with Antares_Simulator. If not, see . +*/ +#pragma once +#include "antares/solver/optimisation/opt_structure_probleme_a_resoudre.h" +#include "antares/solver/simulation/sim_structure_probleme_economique.h" +#include "constraints/ConstraintGroup.h" +#include "ProblemMatrixEssential.h" +#include "antares/solver/optimisation/opt_structure_probleme_a_resoudre.h" + +#include "constraints/PowerOutputVariationGroup.h" +#include "constraints/RampingIncreaseDecreaseRateGroup.h" + +#include + +using namespace Antares::Data; + +class LinearProblemMatrixRamping : public ProblemMatrixEssential +{ +public: + explicit LinearProblemMatrixRamping(PROBLEME_HEBDO* problemeHebdo, + bool Simulation, + ConstraintBuilder& builder); + +private: + bool simulation_ = false; + RampingIncreaseDecreaseRateGroup rampingIncreaseDecreaseRateGroup_; + PowerOutputVariationGroup powerOutputVariationGroup_; +}; \ No newline at end of file diff --git a/src/solver/optimisation/include/antares/solver/optimisation/constraints/ConstraintBuilder.h b/src/solver/optimisation/include/antares/solver/optimisation/constraints/ConstraintBuilder.h index 0cea003029..25ba616a26 100644 --- a/src/solver/optimisation/include/antares/solver/optimisation/constraints/ConstraintBuilder.h +++ b/src/solver/optimisation/include/antares/solver/optimisation/constraints/ConstraintBuilder.h @@ -54,6 +54,7 @@ class ConstraintBuilderData const uint32_t& weekInTheYear; const uint32_t& NombreDePasDeTemps; uint32_t& NbTermesContraintesPourLesCoutsDeDemarrage; + uint32_t& NbTermesContraintesPourLesRampes; }; /*! \verbatim @@ -100,6 +101,16 @@ class ConstraintBuilder int offset = 0, int delta = 0); + ConstraintBuilder& ProductionDecreaseAboveMin(unsigned int index, + double coeff, + int offset = 0, + int delta = 0); + + ConstraintBuilder& ProductionIncreaseAboveMin(unsigned int index, + double coeff, + int offset = 0, + int delta = 0); + ConstraintBuilder& NumberOfDispatchableUnits(unsigned int index, double coeff); ConstraintBuilder& NumberStoppingDispatchableUnits(unsigned int index, double coeff); diff --git a/src/solver/optimisation/include/antares/solver/optimisation/constraints/PowerOutputVariationDecrease.h b/src/solver/optimisation/include/antares/solver/optimisation/constraints/PowerOutputVariationDecrease.h new file mode 100644 index 0000000000..d5695b64af --- /dev/null +++ b/src/solver/optimisation/include/antares/solver/optimisation/constraints/PowerOutputVariationDecrease.h @@ -0,0 +1,26 @@ +#pragma once +#include "ConstraintBuilder.h" + +/*! + * represent 'RampingIncreaseRate' Constraint type + */ +class PowerOutputVariationDecrease : private ConstraintFactory +{ +public: + PowerOutputVariationDecrease(ConstraintBuilder& builder, StartUpCostsData& data) : + ConstraintFactory(builder), data(data) + { + } + + /*! + * @brief Add variables to the constraint and update constraints Matrix + * @param pays : area + * @param cluster : global index of the cluster + * @param pdt : timestep + * @param Simulation : --- + */ + void add(int pays, int index, int pdt); + +private: + StartUpCostsData& data; +}; \ No newline at end of file diff --git a/src/solver/optimisation/include/antares/solver/optimisation/constraints/PowerOutputVariationGroup.h b/src/solver/optimisation/include/antares/solver/optimisation/constraints/PowerOutputVariationGroup.h new file mode 100644 index 0000000000..060348afc4 --- /dev/null +++ b/src/solver/optimisation/include/antares/solver/optimisation/constraints/PowerOutputVariationGroup.h @@ -0,0 +1,38 @@ +/* + * Copyright 2007-2024, RTE (https://www.rte-france.com) + * See AUTHORS.txt + * SPDX-License-Identifier: MPL-2.0 + * This file is part of Antares-Simulator, + * Adequacy and Performance assessment for interconnected energy networks. + * + * Antares_Simulator is free software: you can redistribute it and/or modify + * it under the terms of the Mozilla Public Licence 2.0 as published by + * the Mozilla Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Antares_Simulator is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Mozilla Public Licence 2.0 for more details. + * + * You should have received a copy of the Mozilla Public Licence 2.0 + * along with Antares_Simulator. If not, see . + */ + +#pragma once +#include "AbstractStartUpCostsGroup.h" +#include "ConstraintGroup.h" +#include "PowerOutputVariationIncrease.h" +#include "PowerOutputVariationDecrease.h" + +/** + * @brief Group of PowerOutputVariationGroup constraints + * + */ +class PowerOutputVariationGroup : public AbstractStartUpCostsGroup +{ +public: + using AbstractStartUpCostsGroup::AbstractStartUpCostsGroup; + + void BuildConstraints() override; +}; diff --git a/src/solver/optimisation/include/antares/solver/optimisation/constraints/PowerOutputVariationIncrease.h b/src/solver/optimisation/include/antares/solver/optimisation/constraints/PowerOutputVariationIncrease.h new file mode 100644 index 0000000000..5cddb40b68 --- /dev/null +++ b/src/solver/optimisation/include/antares/solver/optimisation/constraints/PowerOutputVariationIncrease.h @@ -0,0 +1,27 @@ +#pragma once +#include "ConstraintBuilder.h" + +/*! + * represent 'RampingIncreaseRate' Constraint type + */ +class PowerOutputVariationIncrease : private ConstraintFactory +{ +public: + PowerOutputVariationIncrease(ConstraintBuilder& builder, StartUpCostsData& data) : + ConstraintFactory(builder), data(data) + { + } + + + /*! + * @brief Add variables to the constraint and update constraints Matrix + * @param pays : area + * @param cluster : global index of the cluster + * @param pdt : timestep + * @param Simulation : --- + */ + void add(int pays, int index, int pdt); + +private: + StartUpCostsData& data; +}; \ No newline at end of file diff --git a/src/solver/optimisation/include/antares/solver/optimisation/constraints/RampingDecreaseRate.h b/src/solver/optimisation/include/antares/solver/optimisation/constraints/RampingDecreaseRate.h new file mode 100644 index 0000000000..0bbfb12223 --- /dev/null +++ b/src/solver/optimisation/include/antares/solver/optimisation/constraints/RampingDecreaseRate.h @@ -0,0 +1,26 @@ +#pragma once +#include "ConstraintBuilder.h" + +/*! + * represent 'RampingIncreaseRate' Constraint type + */ +class RampingDecreaseRate : private ConstraintFactory +{ +public: + RampingDecreaseRate(ConstraintBuilder& builder, StartUpCostsData& data) : + ConstraintFactory(builder), data(data) + { + } + + /*! + * @brief Add variables to the constraint and update constraints Matrix + * @param pays : area + * @param cluster : global index of the cluster + * @param pdt : timestep + * @param Simulation : --- + */ + void add(int pays, int index, int pdt); + +private: + StartUpCostsData& data; +}; \ No newline at end of file diff --git a/src/solver/optimisation/include/antares/solver/optimisation/constraints/RampingIncreaseDecreaseRateGroup.h b/src/solver/optimisation/include/antares/solver/optimisation/constraints/RampingIncreaseDecreaseRateGroup.h new file mode 100644 index 0000000000..729c9cc285 --- /dev/null +++ b/src/solver/optimisation/include/antares/solver/optimisation/constraints/RampingIncreaseDecreaseRateGroup.h @@ -0,0 +1,38 @@ +/* + * Copyright 2007-2024, RTE (https://www.rte-france.com) + * See AUTHORS.txt + * SPDX-License-Identifier: MPL-2.0 + * This file is part of Antares-Simulator, + * Adequacy and Performance assessment for interconnected energy networks. + * + * Antares_Simulator is free software: you can redistribute it and/or modify + * it under the terms of the Mozilla Public Licence 2.0 as published by + * the Mozilla Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Antares_Simulator is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Mozilla Public Licence 2.0 for more details. + * + * You should have received a copy of the Mozilla Public Licence 2.0 + * along with Antares_Simulator. If not, see . + */ + +#pragma once +#include "AbstractStartUpCostsGroup.h" +#include "ConstraintGroup.h" +#include "RampingIncreaseRate.h" +#include "RampingDecreaseRate.h" + +/** + * @brief Group of RampingIncrease/DecreaseRateGroup constraints + * + */ +class RampingIncreaseDecreaseRateGroup : public AbstractStartUpCostsGroup +{ +public: + using AbstractStartUpCostsGroup::AbstractStartUpCostsGroup; + + void BuildConstraints() override; +}; diff --git a/src/solver/optimisation/include/antares/solver/optimisation/constraints/RampingIncreaseRate.h b/src/solver/optimisation/include/antares/solver/optimisation/constraints/RampingIncreaseRate.h new file mode 100644 index 0000000000..ab609a2ed3 --- /dev/null +++ b/src/solver/optimisation/include/antares/solver/optimisation/constraints/RampingIncreaseRate.h @@ -0,0 +1,28 @@ +#pragma once +#include "ConstraintBuilder.h" + +/*! + * represent 'RampingIncreaseRate' Constraint type + */ +class RampingIncreaseRate : private ConstraintFactory +{ +public: + + public: + RampingIncreaseRate(ConstraintBuilder& builder, StartUpCostsData& data) : + ConstraintFactory(builder), data(data) + { + } + + /*! + * @brief Add variables to the constraint and update constraints Matrix + * @param pays : area + * @param cluster : global index of the cluster + * @param pdt : timestep + * @param Simulation : --- + */ + void add(int pays, int index, int pdt); + +private: + StartUpCostsData& data; +}; \ No newline at end of file diff --git a/src/solver/optimisation/include/antares/solver/optimisation/opt_fonctions.h b/src/solver/optimisation/include/antares/solver/optimisation/opt_fonctions.h index 4a3e4a3690..4f33feb726 100644 --- a/src/solver/optimisation/include/antares/solver/optimisation/opt_fonctions.h +++ b/src/solver/optimisation/include/antares/solver/optimisation/opt_fonctions.h @@ -119,6 +119,19 @@ void OPT_InitialiserLeSecondMembreDuProblemeLineaireCoutsDeDemarrage(PROBLEME_HE void OPT_DecompteDesVariablesEtDesContraintesCoutsDeDemarrage(PROBLEME_HEBDO*); void OPT_InitialiserNombreMinEtMaxDeGroupesCoutsDeDemarrage(PROBLEME_HEBDO*); void OPT_AjusterLeNombreMinDeGroupesDemarresCoutsDeDemarrage(PROBLEME_HEBDO*); +void OPT_ConstruireLaListeDesVariablesOptimiseesDuProblemeLineaireRampesThermiques( + PROBLEME_HEBDO* problemeHebdo, + bool Simulation); +void OPT_InitialiserLesBornesDesVariablesDuProblemeLineaireRampesThermiques( + PROBLEME_HEBDO* problemeHebdo, + const int PremierPdtDeLIntervalle, + const int DernierPdtDeLIntervalle); +void OPT_DecompteDesVariablesEtDesContraintesRampesThermiques(PROBLEME_HEBDO* problemeHebdo); +void OPT_InitialiserLesCoutsLineaireRampesThermiques(PROBLEME_HEBDO* problemeHebdo, + const int PremierPdtDeLIntervalle, + const int DernierPdtDeLIntervalle); + + double OPT_SommeDesPminThermiques(const PROBLEME_HEBDO*, int, uint); #endif /* __SOLVER_OPTIMISATION_FUNCTIONS_H__ */ diff --git a/src/solver/optimisation/include/antares/solver/optimisation/opt_rename_problem.h b/src/solver/optimisation/include/antares/solver/optimisation/opt_rename_problem.h index 2e37cc0daa..e20bb38423 100644 --- a/src/solver/optimisation/include/antares/solver/optimisation/opt_rename_problem.h +++ b/src/solver/optimisation/include/antares/solver/optimisation/opt_rename_problem.h @@ -79,6 +79,8 @@ class VariableNamer : public Namer public: using Namer::Namer; void DispatchableProduction(unsigned int variable, const std::string& clusterName); + void ProductionIncreaseAboveMin(unsigned int variable, const std::string& clusterName); + void ProductionDecreaseAboveMin(unsigned int variable, const std::string& clusterName); void NODU(unsigned int variable, const std::string& clusterName); void NumberStoppingDispatchableUnits(unsigned int variable, const std::string& clusterName); void NumberStartingDispatchableUnits(unsigned int variable, const std::string& clusterName); @@ -141,6 +143,9 @@ class ConstraintNamer : public Namer void NbDispUnitsMinBoundSinceMinUpTime(unsigned int constraint, const std::string& clusterName); void MinDownTime(unsigned int constraint, const std::string& clusterName); void PMaxDispatchableGeneration(unsigned int constraint, const std::string& clusterName); + void RampingIncreaseRate(unsigned int constraint, const std::string& clusterName); + void RampingDecreaseRate(unsigned int constraint, const std::string& clusterName); + void ProductionOutputVariation(unsigned int constraint, const std::string& clusterName); void PMinDispatchableGeneration(unsigned int constraint, const std::string& clusterName); void ConsistenceNODU(unsigned int constraint, const std::string& clusterName); void ShortTermStorageLevel(unsigned int constraint, const std::string& name); diff --git a/src/solver/optimisation/opt_alloc_probleme_a_optimiser.cpp b/src/solver/optimisation/opt_alloc_probleme_a_optimiser.cpp index a5f218258d..067b536c42 100644 --- a/src/solver/optimisation/opt_alloc_probleme_a_optimiser.cpp +++ b/src/solver/optimisation/opt_alloc_probleme_a_optimiser.cpp @@ -124,6 +124,7 @@ static void optimisationAllocateProblem(PROBLEME_HEBDO* problemeHebdo, const int NbTermes += 101; /* constraint expressing final level as a sum of stock layers */ NbTermes += problemeHebdo->NbTermesContraintesPourLesCoutsDeDemarrage; + NbTermes += problemeHebdo->NbTermesContraintesPourLesRampes; logs.info(); logs.info() diff --git a/src/solver/optimisation/opt_construction_variables_optimisees_lineaire.cpp b/src/solver/optimisation/opt_construction_variables_optimisees_lineaire.cpp index 3dc7ee7425..3a7463df8d 100644 --- a/src/solver/optimisation/opt_construction_variables_optimisees_lineaire.cpp +++ b/src/solver/optimisation/opt_construction_variables_optimisees_lineaire.cpp @@ -77,9 +77,11 @@ void OPT_ConstruireLaListeDesVariablesOptimiseesDuProblemeLineaire(PROBLEME_HEBD const int palier = PaliersThermiquesDuPays.NumeroDuPalierDansLEnsembleDesPaliersThermiques[index]; const auto& clusterName = PaliersThermiquesDuPays.NomsDesPaliersThermiques[index]; + variableManager.DispatchableProduction(palier, pdt) = NombreDeVariables; ProblemeAResoudre->TypeDeVariable[NombreDeVariables] = VARIABLE_BORNEE_DES_DEUX_COTES; + variableNamer.DispatchableProduction(NombreDeVariables, clusterName); NombreDeVariables++; } @@ -241,8 +243,8 @@ void OPT_ConstruireLaListeDesVariablesOptimiseesDuProblemeLineaire(PROBLEME_HEBD if (problemeHebdo->OptimisationAvecCoutsDeDemarrage) { - OPT_ConstruireLaListeDesVariablesOptimiseesDuProblemeLineaireCoutsDeDemarrage(problemeHebdo, - false); + OPT_ConstruireLaListeDesVariablesOptimiseesDuProblemeLineaireCoutsDeDemarrage(problemeHebdo, false); + OPT_ConstruireLaListeDesVariablesOptimiseesDuProblemeLineaireRampesThermiques(problemeHebdo, false); } return; diff --git a/src/solver/optimisation/opt_construction_variables_rampes_thermiques.cpp b/src/solver/optimisation/opt_construction_variables_rampes_thermiques.cpp new file mode 100644 index 0000000000..7c98a9aec5 --- /dev/null +++ b/src/solver/optimisation/opt_construction_variables_rampes_thermiques.cpp @@ -0,0 +1,61 @@ +#include "antares/solver/optimisation/opt_structure_probleme_a_resoudre.h" + +#include "antares/solver/simulation/sim_extern_variables_globales.h" + +#include "antares/solver/optimisation/opt_fonctions.h" +#include "antares/solver/optimisation/opt_rename_problem.h" + +#include "spx_constantes_externes.h" +#include "variables/VariableManagerUtils.h" + + +void OPT_ConstruireLaListeDesVariablesOptimiseesDuProblemeLineaireRampesThermiques(PROBLEME_HEBDO* problemeHebdo, bool Simulation) +{ + const auto& ProblemeAResoudre = problemeHebdo->ProblemeAResoudre; + + int NombreDePasDeTempsPourUneOptimisation = problemeHebdo->NombreDePasDeTempsPourUneOptimisation; + int& NombreDeVariables = ProblemeAResoudre->NombreDeVariables; + VariableNamer variableNamer(ProblemeAResoudre->NomDesVariables); + + for (int pdt = 0; pdt < NombreDePasDeTempsPourUneOptimisation; pdt++) + { + variableNamer.UpdateTimeStep(problemeHebdo->weekInTheYear * 168 + pdt); + auto& CorrespondanceVarNativesVarOptim = problemeHebdo->CorrespondanceVarNativesVarOptim[pdt]; + + for (uint32_t pays = 0; pays < problemeHebdo->NombreDePays; pays++) + { + const PALIERS_THERMIQUES& PaliersThermiquesDuPays = problemeHebdo->PaliersThermiquesDuPays[pays]; + variableNamer.UpdateArea(problemeHebdo->NomsDesPays[pays]); + for (int index = 0; index < PaliersThermiquesDuPays.NombreDePaliersThermiques; index++) + { + if (PaliersThermiquesDuPays.maxUpwardPowerRampingRate[index] >= 0 ) + { + const int palier = PaliersThermiquesDuPays + .NumeroDuPalierDansLEnsembleDesPaliersThermiques[index]; + const auto& clusterName + = PaliersThermiquesDuPays.NomsDesPaliersThermiques[index]; + + if (!Simulation) + { + CorrespondanceVarNativesVarOptim.powerRampingIncreaseIndex[palier] + = NombreDeVariables; + ProblemeAResoudre->TypeDeVariable[NombreDeVariables] + = VARIABLE_BORNEE_DES_DEUX_COTES; + variableNamer.ProductionIncreaseAboveMin(NombreDeVariables, clusterName); + } + NombreDeVariables++; + + if (!Simulation) + { + CorrespondanceVarNativesVarOptim.powerRampingDecreaseIndex[palier] + = NombreDeVariables; + ProblemeAResoudre->TypeDeVariable[NombreDeVariables] + = VARIABLE_BORNEE_DES_DEUX_COTES; + variableNamer.ProductionDecreaseAboveMin(NombreDeVariables, clusterName); + } + NombreDeVariables++; + } + } + } + } +} diff --git a/src/solver/optimisation/opt_decompte_variables_et_contraintes.cpp b/src/solver/optimisation/opt_decompte_variables_et_contraintes.cpp index 0f4c196745..c3ae246261 100644 --- a/src/solver/optimisation/opt_decompte_variables_et_contraintes.cpp +++ b/src/solver/optimisation/opt_decompte_variables_et_contraintes.cpp @@ -44,9 +44,7 @@ int OPT_DecompteDesVariablesEtDesContraintesDuProblemeAOptimiser(PROBLEME_HEBDO* for (uint32_t pays = 0; pays < problemeHebdo->NombreDePays; pays++) { - ProblemeAResoudre->NombreDeVariables - += problemeHebdo->PaliersThermiquesDuPays[pays].NombreDePaliersThermiques; - + ProblemeAResoudre->NombreDeVariables += problemeHebdo->PaliersThermiquesDuPays[pays].NombreDePaliersThermiques; mxPaliers += problemeHebdo->PaliersThermiquesDuPays[pays].NombreDePaliersThermiques; if (problemeHebdo->CaracteristiquesHydrauliques[pays].PresenceDHydrauliqueModulable) @@ -254,6 +252,7 @@ int OPT_DecompteDesVariablesEtDesContraintesDuProblemeAOptimiser(PROBLEME_HEBDO* if (problemeHebdo->OptimisationAvecCoutsDeDemarrage) { OPT_DecompteDesVariablesEtDesContraintesCoutsDeDemarrage(problemeHebdo); + OPT_DecompteDesVariablesEtDesContraintesRampesThermiques(problemeHebdo); } return mxPaliers; diff --git a/src/solver/optimisation/opt_decompte_variables_et_contraintes_rampes_thermiques.cpp b/src/solver/optimisation/opt_decompte_variables_et_contraintes_rampes_thermiques.cpp new file mode 100644 index 0000000000..4a4b9d5ba7 --- /dev/null +++ b/src/solver/optimisation/opt_decompte_variables_et_contraintes_rampes_thermiques.cpp @@ -0,0 +1,37 @@ +/* +** Copyright 2007-2024, RTE (https://www.rte-france.com) +** See AUTHORS.txt +** SPDX-License-Identifier: MPL-2.0 +** This file is part of Antares-Simulator, +** Adequacy and Performance assessment for interconnected energy networks. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the Mozilla Public Licence 2.0 as published by +** the Mozilla Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** Mozilla Public Licence 2.0 for more details. +** +** You should have received a copy of the Mozilla Public Licence 2.0 +** along with Antares_Simulator. If not, see . +*/ + +#include "antares/solver/simulation/simulation.h" +#include "antares/solver/simulation/sim_extern_variables_globales.h" +#include "antares/solver/optimisation/LinearProblemMatrixRamping.h" +#include "antares/solver/optimisation/constraints/constraint_builder_utils.h" + +#include "antares/solver/optimisation/opt_fonctions.h" + + +void OPT_DecompteDesVariablesEtDesContraintesRampesThermiques(PROBLEME_HEBDO* problemeHebdo) +{ + auto builder_data = NewGetConstraintBuilderFromProblemHebdo(problemeHebdo); + ConstraintBuilder builder(builder_data); + LinearProblemMatrixRamping(problemeHebdo, true, builder).Run(); + + OPT_ConstruireLaListeDesVariablesOptimiseesDuProblemeLineaireRampesThermiques(problemeHebdo, true); +} \ No newline at end of file diff --git a/src/solver/optimisation/opt_gestion_des_bornes_cas_lineaire.cpp b/src/solver/optimisation/opt_gestion_des_bornes_cas_lineaire.cpp index ef2a8cb706..0bdd762c1d 100644 --- a/src/solver/optimisation/opt_gestion_des_bornes_cas_lineaire.cpp +++ b/src/solver/optimisation/opt_gestion_des_bornes_cas_lineaire.cpp @@ -491,8 +491,8 @@ void OPT_InitialiserLesBornesDesVariablesDuProblemeLineaire(PROBLEME_HEBDO* prob if (problemeHebdo->OptimisationAvecCoutsDeDemarrage) { - OPT_InitialiserLesBornesDesVariablesDuProblemeLineaireCoutsDeDemarrage( - problemeHebdo, PremierPdtDeLIntervalle, DernierPdtDeLIntervalle); + OPT_InitialiserLesBornesDesVariablesDuProblemeLineaireCoutsDeDemarrage(problemeHebdo, PremierPdtDeLIntervalle, DernierPdtDeLIntervalle); + OPT_InitialiserLesBornesDesVariablesDuProblemeLineaireRampesThermiques(problemeHebdo, PremierPdtDeLIntervalle, DernierPdtDeLIntervalle); } return; diff --git a/src/solver/optimisation/opt_gestion_des_bornes_rampes_thermiques.cpp b/src/solver/optimisation/opt_gestion_des_bornes_rampes_thermiques.cpp new file mode 100644 index 0000000000..7ba6671393 --- /dev/null +++ b/src/solver/optimisation/opt_gestion_des_bornes_rampes_thermiques.cpp @@ -0,0 +1,74 @@ +/* +** Copyright 2007-2023 RTE +** Authors: Antares_Simulator Team +** +** This file is part of Antares_Simulator. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** There are special exceptions to the terms and conditions of the +** license as they are applied to this software. View the full text of +** the exceptions in file COPYING.txt in the directory of this software +** distribution +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Antares_Simulator. If not, see . +** +** SPDX-License-Identifier: licenceRef-GPL3_WITH_RTE-Exceptions +*/ + +#include "antares/solver/optimisation/opt_structure_probleme_a_resoudre.h" + +#include "variables/VariableManagement.h" +#include "variables/VariableManagerUtils.h" +#include "antares/solver/simulation/simulation.h" +#include "antares/solver/simulation/sim_structure_donnees.h" +#include "antares/solver/simulation/sim_structure_probleme_economique.h" +#include "antares/solver/optimisation/opt_fonctions.h" + + +void OPT_InitialiserLesBornesDesVariablesDuProblemeLineaireRampesThermiques(PROBLEME_HEBDO* problemeHebdo, const int PremierPdtDeLIntervalle, const int DernierPdtDeLIntervalle) +{ + const auto& ProblemeAResoudre = problemeHebdo->ProblemeAResoudre; + std::vector& Xmin = ProblemeAResoudre->Xmin; + std::vector& Xmax = ProblemeAResoudre->Xmax; + + for (int pdtHebdo = PremierPdtDeLIntervalle, pdtJour = 0; pdtHebdo < DernierPdtDeLIntervalle; + pdtHebdo++, pdtJour++) + { + const CORRESPONDANCES_DES_VARIABLES& CorrespondanceVarNativesVarOptim = problemeHebdo->CorrespondanceVarNativesVarOptim[pdtJour]; + + for (uint32_t pays = 0; pays < problemeHebdo->NombreDePays; pays++) + { + const PALIERS_THERMIQUES& PaliersThermiquesDuPays = problemeHebdo->PaliersThermiquesDuPays[pays]; + int maxThermalPlant = PaliersThermiquesDuPays.NombreDePaliersThermiques; + + for (int index = 0; index < maxThermalPlant; index++) + { + if (PaliersThermiquesDuPays.maxUpwardPowerRampingRate[index] >= 0) + { + const int palier = PaliersThermiquesDuPays + .NumeroDuPalierDansLEnsembleDesPaliersThermiques[index]; + + int var = CorrespondanceVarNativesVarOptim.powerRampingDecreaseIndex[palier]; + Xmin[var] = 0; + Xmax[var] = LINFINI_ANTARES; + + + var = CorrespondanceVarNativesVarOptim.powerRampingIncreaseIndex[palier]; + Xmin[var] = 0; + Xmax[var] = LINFINI_ANTARES; + + } + } + } + } +} \ No newline at end of file diff --git a/src/solver/optimisation/opt_gestion_des_couts_cas_lineaire.cpp b/src/solver/optimisation/opt_gestion_des_couts_cas_lineaire.cpp index 3595141031..096061464c 100644 --- a/src/solver/optimisation/opt_gestion_des_couts_cas_lineaire.cpp +++ b/src/solver/optimisation/opt_gestion_des_couts_cas_lineaire.cpp @@ -325,8 +325,8 @@ void OPT_InitialiserLesCoutsLineaire(PROBLEME_HEBDO* problemeHebdo, if (problemeHebdo->OptimisationAvecCoutsDeDemarrage) { - OPT_InitialiserLesCoutsLineaireCoutsDeDemarrage( - problemeHebdo, PremierPdtDeLIntervalle, DernierPdtDeLIntervalle); + OPT_InitialiserLesCoutsLineaireCoutsDeDemarrage(problemeHebdo, PremierPdtDeLIntervalle, DernierPdtDeLIntervalle); + OPT_InitialiserLesCoutsLineaireRampesThermiques(problemeHebdo, PremierPdtDeLIntervalle, DernierPdtDeLIntervalle); } return; diff --git a/src/solver/optimisation/opt_gestion_des_couts_rampes_thermiques.cpp b/src/solver/optimisation/opt_gestion_des_couts_rampes_thermiques.cpp new file mode 100644 index 0000000000..fe0942e215 --- /dev/null +++ b/src/solver/optimisation/opt_gestion_des_couts_rampes_thermiques.cpp @@ -0,0 +1,78 @@ +/* +** Copyright 2007-2023 RTE +** Authors: Antares_Simulator Team +** +** This file is part of Antares_Simulator. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** There are special exceptions to the terms and conditions of the +** license as they are applied to this software. View the full text of +** the exceptions in file COPYING.txt in the directory of this software +** distribution +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Antares_Simulator. If not, see . +** +** SPDX-License-Identifier: licenceRef-GPL3_WITH_RTE-Exceptions +*/ + +#include "antares/solver/optimisation/opt_structure_probleme_a_resoudre.h" + +#include "variables/VariableManagerUtils.h" +#include "antares/solver/simulation/simulation.h" +#include "antares/solver/simulation/sim_structure_donnees.h" +#include "antares/solver/simulation/sim_extern_variables_globales.h" + +#include "antares/solver/optimisation/opt_fonctions.h" + +void OPT_InitialiserLesCoutsLineaireRampesThermiques(PROBLEME_HEBDO* problemeHebdo, + const int PremierPdtDeLIntervalle, + const int DernierPdtDeLIntervalle) +{ + const auto& ProblemeAResoudre = problemeHebdo->ProblemeAResoudre; + + int pdtJour = 0; + + for (int pdtHebdo = PremierPdtDeLIntervalle, pdtJour = 0; pdtHebdo < DernierPdtDeLIntervalle; pdtHebdo++, pdtJour++) + { + const CORRESPONDANCES_DES_VARIABLES& CorrespondanceVarNativesVarOptim = problemeHebdo->CorrespondanceVarNativesVarOptim[pdtJour]; + + for (uint32_t pays = 0; pays < problemeHebdo->NombreDePays; ++pays) + { + const PALIERS_THERMIQUES& PaliersThermiquesDuPays = problemeHebdo->PaliersThermiquesDuPays[pays]; + int var; + + for (int index = 0; index < PaliersThermiquesDuPays.NombreDePaliersThermiques; index++) + { + if (PaliersThermiquesDuPays.maxUpwardPowerRampingRate[index] >= 0 ) + { + int palier = PaliersThermiquesDuPays + .NumeroDuPalierDansLEnsembleDesPaliersThermiques[index]; + + var = CorrespondanceVarNativesVarOptim.powerRampingDecreaseIndex[palier]; + if (var >= 0 && var < ProblemeAResoudre->NombreDeVariables) + { + ProblemeAResoudre->CoutLineaire[var] + = PaliersThermiquesDuPays.downwardRampingCost[index]; + } + + var = CorrespondanceVarNativesVarOptim.powerRampingIncreaseIndex[palier]; + if (var >= 0 && var < ProblemeAResoudre->NombreDeVariables) + { + ProblemeAResoudre->CoutLineaire[var] + = PaliersThermiquesDuPays.upwardRampingCost[index]; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/solver/optimisation/opt_gestion_second_membre_rampes_thermiques.cpp b/src/solver/optimisation/opt_gestion_second_membre_rampes_thermiques.cpp new file mode 100644 index 0000000000..e245b4cb3c --- /dev/null +++ b/src/solver/optimisation/opt_gestion_second_membre_rampes_thermiques.cpp @@ -0,0 +1,95 @@ +/* +** Copyright 2007-2023 RTE +** Authors: Antares_Simulator Team +** +** This file is part of Antares_Simulator. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** There are special exceptions to the terms and conditions of the +** license as they are applied to this software. View the full text of +** the exceptions in file COPYING.txt in the directory of this software +** distribution +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Antares_Simulator. If not, see . +** +** SPDX-License-Identifier: licenceRef-GPL3_WITH_RTE-Exceptions +*/ + +#include "opt_structure_probleme_a_resoudre.h" + +#include "../simulation/sim_structure_donnees.h" +#include "../simulation/sim_extern_variables_globales.h" + +#include "opt_fonctions.h" + +#include + +using namespace Antares; +using namespace Antares::Data; +using namespace Yuni; + +void OPT_InitialiserLeSecondMembreDuProblemeLineaireRampesThermiques(PROBLEME_HEBDO* problemeHebdo, + int PremierPdtDeLIntervalle, + int DernierPdtDeLIntervalle) +{ + logs.info() << "PremierPdtDeLIntervalle= " << PremierPdtDeLIntervalle; + + if (PremierPdtDeLIntervalle > 0) + { + const auto& ProblemeAResoudre = problemeHebdo->ProblemeAResoudre; + std::vector& SecondMembre = ProblemeAResoudre->SecondMembre; + + int NombreDePasDeTempsPourUneOptimisation + = problemeHebdo->NombreDePasDeTempsPourUneOptimisation; + + for (uint32_t pays = 0; pays < problemeHebdo->NombreDePays; pays++) + { + const PALIERS_THERMIQUES& PaliersThermiquesDuPays + = problemeHebdo->PaliersThermiquesDuPays[pays]; + + for (int index = 0; index < PaliersThermiquesDuPays.NombreDePaliersThermiques; index++) + { + const int palier + = PaliersThermiquesDuPays.NumeroDuPalierDansLEnsembleDesPaliersThermiques[index]; + + const CORRESPONDANCES_DES_CONTRAINTES& CorrespondanceCntNativesCntOptim + = problemeHebdo->CorrespondanceCntNativesCntOptim[0]; + int cnt = CorrespondanceCntNativesCntOptim.ConstraintIndexRampingIncrease[palier]; + if (cnt >= 0) + { + SecondMembre[cnt] = problemeHebdo->ResultatsHoraires[pays] + .ProductionThermique[PremierPdtDeLIntervalle - 1] + .ProductionThermiqueDuPalier[index]; + logs.info() << "second membre = " << SecondMembre[cnt]; + } + + cnt = CorrespondanceCntNativesCntOptim.ConstraintIndexRampingIncrease[palier]; + if (cnt >= 0) + { + SecondMembre[cnt] = problemeHebdo->ResultatsHoraires[pays] + .ProductionThermique[PremierPdtDeLIntervalle - 1] + .ProductionThermiqueDuPalier[index]; + } + + cnt = CorrespondanceCntNativesCntOptim.ConstraintIndexRampingIncrease[palier]; + if (cnt >= 0) + { + SecondMembre[cnt] = problemeHebdo->ResultatsHoraires[pays] + .ProductionThermique[PremierPdtDeLIntervalle - 1] + .ProductionThermiqueDuPalier[index]; + } + } + } + } + +} \ No newline at end of file diff --git a/src/solver/optimisation/opt_nombre_min_groupes_demarres_couts_demarrage.cpp b/src/solver/optimisation/opt_nombre_min_groupes_demarres_couts_demarrage.cpp index c29938a455..8240572326 100644 --- a/src/solver/optimisation/opt_nombre_min_groupes_demarres_couts_demarrage.cpp +++ b/src/solver/optimisation/opt_nombre_min_groupes_demarres_couts_demarrage.cpp @@ -168,6 +168,9 @@ void OPT_AjusterLeNombreMinDeGroupesDemarresCoutsDeDemarrage(PROBLEME_HEBDO* pro } NombreMinDeGroupesEnMarcheDuPalierThermique[pdtHebdo] = (int)ceil(X); + /* logs.info() + << " Nombre de groupe en marche = " + << NombreMinDeGroupesEnMarcheDuPalierThermique[pdtHebdo] << " avant = " << X;*/ } if (!problemeHebdo->OptimisationAvecVariablesEntieres) @@ -175,10 +178,15 @@ void OPT_AjusterLeNombreMinDeGroupesDemarresCoutsDeDemarrage(PROBLEME_HEBDO* pro OPT_PbLineairePourAjusterLeNombreMinDeGroupesDemarresCoutsDeDemarrage( problemeHebdo, NombreMinDeGroupesEnMarcheDuPalierThermique, pays, index); + int ramp = PaliersThermiquesDuPays.maxUpwardPowerRampingRate[index]; + + for (int pdtHebdo = 0; pdtHebdo < NombreDePasDeTempsProblemeHebdo; pdtHebdo++) { + // When using the ramping model, we must ensure that the NODU don't change during the 2nd optimization. + // Without this, the solver may bypass the ramping constraints by partially starting/stopping units. if (NombreMaxDeGroupesEnMarcheDuPalierThermique[pdtHebdo] - < NombreMinDeGroupesEnMarcheDuPalierThermique[pdtHebdo]) + < NombreMinDeGroupesEnMarcheDuPalierThermique[pdtHebdo] || ramp >= 0 ) NombreMaxDeGroupesEnMarcheDuPalierThermique[pdtHebdo] = NombreMinDeGroupesEnMarcheDuPalierThermique[pdtHebdo]; diff --git a/src/solver/optimisation/opt_rename_problem.cpp b/src/solver/optimisation/opt_rename_problem.cpp index 4f8e77bd64..e3a3e96bc2 100644 --- a/src/solver/optimisation/opt_rename_problem.cpp +++ b/src/solver/optimisation/opt_rename_problem.cpp @@ -94,6 +94,18 @@ void VariableNamer::DispatchableProduction(unsigned int variable, const std::str SetThermalClusterElementName(variable, "DispatchableProduction", clusterName); } +void VariableNamer::ProductionIncreaseAboveMin(unsigned int variable, const std::string& clusterName) +{ + SetThermalClusterElementName(variable, "ProductionIncreaseAboveMin", clusterName); +} + +void VariableNamer::ProductionDecreaseAboveMin(unsigned int variable, + const std::string& clusterName) +{ + SetThermalClusterElementName(variable, "ProductionDecreaseAboveMin", clusterName); +} + + void VariableNamer::NODU(unsigned int variable, const std::string& clusterName) { SetThermalClusterElementName(variable, "NODU", clusterName); @@ -348,6 +360,23 @@ void ConstraintNamer::PMinDispatchableGeneration(unsigned int constraint, SetThermalClusterElementName(constraint, "PMinDispatchableGeneration", clusterName); } +void ConstraintNamer::RampingIncreaseRate(unsigned int constraint, + const std::string& clusterName) +{ + SetThermalClusterElementName(constraint, "RampingIncreaseRate", clusterName); +} + +void ConstraintNamer::RampingDecreaseRate(unsigned int constraint, const std::string& clusterName) +{ + SetThermalClusterElementName(constraint, "RampingDecreaseRate", clusterName); +} + +void ConstraintNamer::ProductionOutputVariation(unsigned int constraint, + const std::string& clusterName) +{ + SetThermalClusterElementName(constraint, "ProductionOutputVariation", clusterName); +} + void ConstraintNamer::ConsistenceNODU(unsigned int constraint, const std::string& clusterName) { SetThermalClusterElementName(constraint, "ConsistenceNODU", clusterName); diff --git a/src/solver/optimisation/variables/VariableManagement.cpp b/src/solver/optimisation/variables/VariableManagement.cpp index de7a18388f..558fc366de 100644 --- a/src/solver/optimisation/variables/VariableManagement.cpp +++ b/src/solver/optimisation/variables/VariableManagement.cpp @@ -43,6 +43,25 @@ int& VariableManager::DispatchableProduction(unsigned int index, return CorrespondanceVarNativesVarOptim_[pdt].NumeroDeVariableDuPalierThermique[index]; } +int& VariableManager::ProductionIncreaseAboveMin(unsigned int index, + unsigned int hourInWeek, + int offset, + int delta) +{ + auto pdt = GetShiftedTimeStep(offset, delta, hourInWeek); + return CorrespondanceVarNativesVarOptim_[pdt].powerRampingIncreaseIndex[index]; +} + +int& VariableManager::ProductionDecreaseAboveMin(unsigned int index, + unsigned int hourInWeek, + int offset, + int delta) +{ + auto pdt = GetShiftedTimeStep(offset, delta, hourInWeek); + return CorrespondanceVarNativesVarOptim_[pdt].powerRampingDecreaseIndex[index]; +} + + int& VariableManager::NumberOfDispatchableUnits(unsigned int index, unsigned int hourInWeek, int offset, diff --git a/src/solver/optimisation/variables/VariableManagement.h b/src/solver/optimisation/variables/VariableManagement.h index d989e59539..19c42cd7ba 100644 --- a/src/solver/optimisation/variables/VariableManagement.h +++ b/src/solver/optimisation/variables/VariableManagement.h @@ -22,6 +22,16 @@ class VariableManager int offset = 0, int delta = 0); + int& ProductionIncreaseAboveMin(unsigned int index, + unsigned int hourInWeek, + int offset = 0, + int delta = 0); + + int& ProductionDecreaseAboveMin(unsigned int index, + unsigned int hourInWeek, + int offset = 0, + int delta = 0); + int& NumberOfDispatchableUnits(unsigned int index, unsigned int hourInWeek, int offset = 0, diff --git a/src/solver/simulation/include/antares/solver/simulation/sim_structure_probleme_economique.h b/src/solver/simulation/include/antares/solver/simulation/sim_structure_probleme_economique.h index 11ace389d9..660660ecac 100644 --- a/src/solver/simulation/include/antares/solver/simulation/sim_structure_probleme_economique.h +++ b/src/solver/simulation/include/antares/solver/simulation/sim_structure_probleme_economique.h @@ -44,6 +44,12 @@ struct CORRESPONDANCES_DES_VARIABLES std::vector NumeroDeVariableCoutExtremiteVersOrigineDeLInterconnexion; std::vector NumeroDeVariableDuPalierThermique; + //! index of the variables for the power output hourly increases (MW above P_min,between t and t+1) + // for the thermal clusters + std::vector powerRampingIncreaseIndex; + //! index of the variables for the power output hourly dereases (MW above P_min,between t and t+1) + // for the thermal clusters + std::vector powerRampingDecreaseIndex; std::vector NumeroDeVariablesDeLaProdHyd; @@ -259,7 +265,7 @@ struct PDISP_ET_COUTS_HORAIRES_PAR_PALIER struct PALIERS_THERMIQUES { - int NombreDePaliersThermiques; + int NombreDePaliersThermiques; std::vector minUpDownTime; @@ -277,6 +283,15 @@ struct PALIERS_THERMIQUES std::vector DureeMinimaleDeMarcheDUnGroupeDuPalierThermique; std::vector DureeMinimaleDArretDUnGroupeDuPalierThermique; std::vector NomsDesPaliersThermiques; + + //! maximum hourly upward power ramping rate for a thermal unit (MW/hour) + std::vector maxUpwardPowerRampingRate; + //! maximum hourly downward power ramping rate for a thermal unit (MW/hour) + std::vector maxDownwardPowerRampingRate; + //! cost of 1 MW power increase for the thermal cluster (above minimum stable level) + std::vector upwardRampingCost; + //! cost of 1 MW power decrease for the thermal cluster + std::vector downwardRampingCost; }; struct ENERGIES_ET_PUISSANCES_HYDRAULIQUES @@ -522,6 +537,7 @@ struct PROBLEME_HEBDO /* Optimization problem */ uint32_t NbTermesContraintesPourLesCoutsDeDemarrage = 0; + uint32_t NbTermesContraintesPourLesRampes = 0; std::vector DefaillanceNegativeUtiliserPMinThermique; std::vector DefaillanceNegativeUtiliserHydro; std::vector DefaillanceNegativeUtiliserConsoAbattue; diff --git a/src/solver/simulation/sim_alloc_probleme_hebdo.cpp b/src/solver/simulation/sim_alloc_probleme_hebdo.cpp index fecb3af268..60359f8874 100644 --- a/src/solver/simulation/sim_alloc_probleme_hebdo.cpp +++ b/src/solver/simulation/sim_alloc_probleme_hebdo.cpp @@ -173,9 +173,13 @@ void SIM_AllocationProblemePasDeTemps(PROBLEME_HEBDO& problem, .assign(linkCount, 0); variablesMapping.NumeroDeVariableCoutExtremiteVersOrigineDeLInterconnexion .assign(linkCount, 0); - - variablesMapping.NumeroDeVariableDuPalierThermique + variablesMapping.NumeroDeVariableDuPalierThermique.assign( + study.runtime->thermalPlantTotalCount, 0); + variablesMapping.powerRampingIncreaseIndex + .assign(study.runtime->thermalPlantTotalCount, 0); + variablesMapping.powerRampingDecreaseIndex .assign(study.runtime->thermalPlantTotalCount, 0); + variablesMapping.NumeroDeVariablesDeLaProdHyd .assign(nbPays, 0); variablesMapping.NumeroDeVariablesDePompage @@ -346,6 +350,15 @@ void SIM_AllocateAreas(PROBLEME_HEBDO& problem, { const uint nbPaliers = study.areas.byIndex[k]->thermal.list.enabledAndNotMustRunCount(); + // count clusters with ramping enabled + uint nRampingClusters = 0; + for (uint clusterIndex = 0; clusterIndex != nbPaliers; ++clusterIndex) + { + auto& cluster = *(study.areas.byIndex[k]->thermal.list[clusterIndex]); + if (cluster.ramping) + nRampingClusters++; + } + problem.PaliersThermiquesDuPays[k].minUpDownTime.assign(nbPaliers, 0); problem.PaliersThermiquesDuPays[k].PminDuPalierThermiquePendantUneHeure .assign(nbPaliers, 0.); @@ -372,6 +385,11 @@ void SIM_AllocateAreas(PROBLEME_HEBDO& problem, .assign(nbPaliers, 0); problem.PaliersThermiquesDuPays[k].NomsDesPaliersThermiques.resize(nbPaliers); + problem.PaliersThermiquesDuPays[k].downwardRampingCost.assign(nbPaliers, -1); + problem.PaliersThermiquesDuPays[k].upwardRampingCost.assign(nbPaliers, -1); + problem.PaliersThermiquesDuPays[k].maxUpwardPowerRampingRate.assign(nbPaliers, -1); + problem.PaliersThermiquesDuPays[k].maxDownwardPowerRampingRate.assign(nbPaliers, -1); + problem.CaracteristiquesHydrauliques[k].CntEnergieH2OParIntervalleOptimise .assign(7, 0.); problem.CaracteristiquesHydrauliques[k].CntEnergieH2OParJour diff --git a/src/solver/simulation/sim_calcul_economique.cpp b/src/solver/simulation/sim_calcul_economique.cpp index 98c27037cc..d40f7fb41d 100644 --- a/src/solver/simulation/sim_calcul_economique.cpp +++ b/src/solver/simulation/sim_calcul_economique.cpp @@ -284,6 +284,7 @@ void SIM_InitialisationProblemeHebdo(Data::Study& study, unsigned int clusterCount = area.thermal.list.enabledAndNotMustRunCount(); pbPalier.NombreDePaliersThermiques = clusterCount; + for (const auto& cluster : area.thermal.list.each_enabled_and_not_mustrun()) { pbPalier.NumeroDuPalierDansLEnsembleDesPaliersThermiques[cluster->index] @@ -302,6 +303,19 @@ void SIM_InitialisationProblemeHebdo(Data::Study& study, pbPalier.DureeMinimaleDArretDUnGroupeDuPalierThermique[cluster->index] = cluster->minDownTime; + // ramping (if enabled) + if (cluster->ramping) + { + pbPalier.upwardRampingCost[cluster->index] + = cluster->ramping.value().powerIncreaseCost; + pbPalier.downwardRampingCost[cluster->index] + = cluster->ramping.value().powerDecreaseCost; + pbPalier.maxDownwardPowerRampingRate[cluster->index] + = cluster->ramping.value().maxDownwardPowerRampingRate; + pbPalier.maxUpwardPowerRampingRate[cluster->index] + = cluster->ramping.value().maxUpwardPowerRampingRate; + } + pbPalier.PmaxDUnGroupeDuPalierThermique[cluster->index] = cluster->nominalCapacityWithSpinning; pbPalier.pminDUnGroupeDuPalierThermique[cluster->index] diff --git a/src/solver/variable/CMakeLists.txt b/src/solver/variable/CMakeLists.txt index c8e027546e..19083be8b7 100644 --- a/src/solver/variable/CMakeLists.txt +++ b/src/solver/variable/CMakeLists.txt @@ -127,6 +127,8 @@ set(SRC_VARIABLE_ECONOMY include/antares/solver/variable/economy/lolp.h include/antares/solver/variable/economy/avail-dispatchable-generation.h include/antares/solver/variable/economy/dispatchable-generation-margin.h + include/antares/solver/variable/economy/rampingCosts.h + include/antares/solver/variable/economy/rampingCostByDispatchablePlant.h # Links include/antares/solver/variable/economy/links/flowLinear.h diff --git a/src/solver/variable/include/antares/solver/variable/economy/all.h b/src/solver/variable/include/antares/solver/variable/economy/all.h index 1a46c130f1..a8f4a05b69 100644 --- a/src/solver/variable/include/antares/solver/variable/economy/all.h +++ b/src/solver/variable/include/antares/solver/variable/economy/all.h @@ -59,6 +59,7 @@ #include "spilledEnergyAfterCSR.h" #include "dtgMarginAfterCsr.h" #include "spilledEnergy.h" +#include "rampingCosts.h" #include "lold.h" #include "lolp.h" @@ -72,6 +73,7 @@ #include "npCostByDispatchablePlant.h" #include "nbOfDispatchedUnitsByPlant.h" #include "profitByPlant.h" +#include "rampingCostByDispatchablePlant.h" // By RES plant #include "productionByRenewablePlant.h" @@ -168,10 +170,12 @@ typedef // Prices >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> VariablesPerArea; /*! @@ -259,12 +263,15 @@ typedef // Prices // Number Of Dispatched Units Common::SpatialAggregate< - NbOfDispatchedUnits // MBO + NbOfDispatchedUnits, // MBO // 25/02/2016 // - // refs: // #55 - >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + Common::SpatialAggregate< + RampingCost + >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + VariablesPerSetOfAreas; typedef BindingConstMarginCost< // Marginal cost for a binding constraint diff --git a/src/solver/variable/include/antares/solver/variable/economy/rampingCostByDispatchablePlant.h b/src/solver/variable/include/antares/solver/variable/economy/rampingCostByDispatchablePlant.h new file mode 100644 index 0000000000..f0444326d2 --- /dev/null +++ b/src/solver/variable/include/antares/solver/variable/economy/rampingCostByDispatchablePlant.h @@ -0,0 +1,339 @@ +/* +** Copyright 2007-2023 RTE +** Authors: Antares_Simulator Team +** +** This file is part of Antares_Simulator. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** There are special exceptions to the terms and conditions of the +** license as they are applied to this software. View the full text of +** the exceptions in file COPYING.txt in the directory of this software +** distribution +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Antares_Simulator. If not, see . +** +** SPDX-License-Identifier: licenceRef-GPL3_WITH_RTE-Exceptions +*/ +#ifndef __SOLVER_VARIABLE_ECONOMY_RampingCostByDispatchablePlant_H__ +#define __SOLVER_VARIABLE_ECONOMY_RampingCostByDispatchablePlant_H__ + +#include "../variable.h" + +namespace Antares +{ +namespace Solver +{ +namespace Variable +{ +namespace Economy +{ +struct VCardRampingCostByDispatchablePlant +{ + //! Caption + static std::string Caption() + { + return "RAMP COST BY PLANT"; + } + //! Unit + static std::string Unit() + { + return "Ramping Cost - Euro"; + } + + //! The short description of the variable + static std::string Description() + { + return "Ramping costs by all the clusters"; + } + + //! The expected results + typedef Results> + ResultsType; + + //! The VCard to look for for calculating spatial aggregates + typedef VCardRampingCostByDispatchablePlant VCardForSpatialAggregate; + + enum + { + //! Data Level + categoryDataLevel = Category::area, + //! File level (provided by the type of the results) + categoryFileLevel = ResultsType::categoryFile & (Category::de), + //! Precision (views) + precision = Category::all, + //! Indentation (GUI) + nodeDepthForGUI = +0, + //! Decimal precision + decimal = 0, + //! Number of columns used by the variable + columnCount = Category::dynamicColumns, + //! The Spatial aggregation + spatialAggregate = Category::spatialAggregateSum, + spatialAggregateMode = Category::spatialAggregateEachYear, + spatialAggregatePostProcessing = 0, + //! Intermediate values + hasIntermediateValues = 1, + //! Can this variable be non applicable (0 : no, 1 : yes) + isPossiblyNonApplicable = 0, + }; + + typedef IntermediateValues IntermediateValuesDeepType; + typedef IntermediateValues* IntermediateValuesBaseType; + typedef IntermediateValuesBaseType* IntermediateValuesType; + + // typedef IntermediateValues IntermediateValuesType; + +}; // class VCard + +/*! +** \brief C02 Average value of the overrall OperatingCost emissions expected from all +** the thermal dispatchable clusters +*/ +template +class RampingCostByDispatchablePlant + : public Variable::IVariable, + NextT, + VCardRampingCostByDispatchablePlant> +{ +public: + //! Type of the next static variable + typedef NextT NextType; + //! VCard + typedef VCardRampingCostByDispatchablePlant VCardType; + //! Ancestor + typedef Variable::IVariable, NextT, VCardType> + AncestorType; + + //! List of expected results + typedef typename VCardType::ResultsType ResultsType; + + typedef VariableAccessor VariableAccessorType; + + enum + { + //! How many items have we got + count = 1 + NextT::count, + }; + + template + struct Statistics + { + enum + { + count + = ((VCardType::categoryDataLevel & CDataLevel && VCardType::categoryFileLevel & CFile) + ? (NextType::template Statistics::count + + VCardType::columnCount * ResultsType::count) + : NextType::template Statistics::count), + }; + }; + +public: + RampingCostByDispatchablePlant() : pValuesForTheCurrentYear(NULL), pSize(0) + { + } + + ~RampingCostByDispatchablePlant() + { + for (unsigned int numSpace = 0; numSpace < pNbYearsParallel; numSpace++) + delete[] pValuesForTheCurrentYear[numSpace]; + delete[] pValuesForTheCurrentYear; + } + + void initializeFromStudy(Data::Study& study) + { + // Next + NextType::initializeFromStudy(study); + } + + void initializeFromArea(Data::Study* study, Data::Area* area) + { + // Get the number of years in parallel + pNbYearsParallel = study->maxNbYearsInParallel; + pValuesForTheCurrentYear = new VCardType::IntermediateValuesBaseType[pNbYearsParallel]; + + // Get the area + pSize = area->thermal.list.enabledCount(); + if (pSize) + { + AncestorType::pResults.resize(pSize); + for (unsigned int numSpace = 0; numSpace < pNbYearsParallel; numSpace++) + pValuesForTheCurrentYear[numSpace] + = new VCardType::IntermediateValuesDeepType[pSize]; + + for (unsigned int numSpace = 0; numSpace < pNbYearsParallel; numSpace++) + for (unsigned int i = 0; i != pSize; ++i) + pValuesForTheCurrentYear[numSpace][i].initializeFromStudy(*study); + + for (unsigned int i = 0; i != pSize; ++i) + { + AncestorType::pResults[i].initializeFromStudy(*study); + AncestorType::pResults[i].reset(); + } + } + else + { + for (unsigned int numSpace = 0; numSpace < pNbYearsParallel; numSpace++) + pValuesForTheCurrentYear[numSpace] = nullptr; + + AncestorType::pResults.clear(); + } + + // Next + NextType::initializeFromArea(study, area); + } + + size_t getMaxNumberColumns() const + { + return pSize * ResultsType::count; + } + + void initializeFromLink(Data::Study* study, Data::AreaLink* link) + { + // Next + NextType::initializeFromAreaLink(study, link); + } + + void simulationBegin() + { + // Next + NextType::simulationBegin(); + } + + void simulationEnd() + { + NextType::simulationEnd(); + } + + void yearBegin(unsigned int year, unsigned int numSpace) + { + // Reset the values for the current year + for (unsigned int i = 0; i != pSize; ++i) + pValuesForTheCurrentYear[numSpace][i].reset(); + + // Next variable + NextType::yearBegin(year, numSpace); + } + + void yearEndBuildForEachThermalCluster(State& state, uint year, unsigned int numSpace) + { + // Get end year calculations + for (unsigned int i = state.study.runtime->rangeLimits.hour[Data::rangeBegin]; + i <= state.study.runtime->rangeLimits.hour[Data::rangeEnd]; + ++i) + { + pValuesForTheCurrentYear[numSpace][state.thermalCluster->areaWideIndex].hour[i] + = state.thermalClusterRampingCostForYear[i]; + } + + // Next variable + NextType::yearEndBuildForEachThermalCluster(state, year, numSpace); + } + + void yearEndBuild(State& state, unsigned int year) + { + // Next variable + NextType::yearEndBuild(state, year); + } + + void yearEnd(unsigned int year, unsigned int numSpace) + { + // Merge all results for all thermal clusters + { + for (unsigned int i = 0; i < pSize; ++i) + { + // Compute all statistics for the current year (daily,weekly,monthly) + pValuesForTheCurrentYear[numSpace][i].computeStatisticsForTheCurrentYear(); + } + } + // Next variable + NextType::yearEnd(year, numSpace); + } + + void computeSummary(std::map& numSpaceToYear, + unsigned int nbYearsForCurrentSummary) + { + for (unsigned int numSpace = 0; numSpace < nbYearsForCurrentSummary; ++numSpace) + { + for (unsigned int i = 0; i < pSize; ++i) + { + // Merge all those values with the global results + AncestorType::pResults[i].merge(numSpaceToYear[numSpace], + pValuesForTheCurrentYear[numSpace][i]); + } + } + + // Next variable + NextType::computeSummary(numSpaceToYear, nbYearsForCurrentSummary); + } + + void hourBegin(unsigned int hourInTheYear) + { + // Next variable + NextType::hourBegin(hourInTheYear); + } + + void hourForEachArea(State& state, unsigned int numSpace) + { + // Next variable + NextType::hourForEachArea(state, numSpace); + } + + Antares::Memory::Stored::ConstReturnType retrieveRawHourlyValuesForCurrentYear( + unsigned int, + unsigned int numSpace) const + { + return pValuesForTheCurrentYear[numSpace]->hour; + } + + void localBuildAnnualSurveyReport(SurveyResults& results, + int fileLevel, + int precision, + unsigned int numSpace) const + { + // Initializing external pointer on current variable non applicable status + results.isCurrentVarNA = AncestorType::isNonApplicable; + + if (AncestorType::isPrinted[0]) + { + assert(NULL != results.data.area); + const auto& thermal = results.data.area->thermal; + + // Write the data for the current year + for (auto& cluster : thermal.list.each_enabled()) + { + // Write the data for the current year + results.variableCaption = cluster->name(); // VCardType::Caption(); + results.variableUnit = VCardType::Unit(); + pValuesForTheCurrentYear[numSpace][cluster->areaWideIndex] + .template buildAnnualSurveyReport( + results, fileLevel, precision); + } + } + } + +private: + //! Intermediate values for each year + typename VCardType::IntermediateValuesType pValuesForTheCurrentYear; + size_t pSize; + unsigned int pNbYearsParallel; + +}; // class RampingCostByDispatchablePlant + +} // namespace Economy +} // namespace Variable +} // namespace Solver +} // namespace Antares + +#endif // __SOLVER_VARIABLE_ECONOMY_RampingCostByDispatchablePlant_H__ diff --git a/src/solver/variable/include/antares/solver/variable/economy/rampingCosts.h b/src/solver/variable/include/antares/solver/variable/economy/rampingCosts.h new file mode 100644 index 0000000000..6c08859bea --- /dev/null +++ b/src/solver/variable/include/antares/solver/variable/economy/rampingCosts.h @@ -0,0 +1,295 @@ +/* +** Copyright 2007-2023 RTE +** Authors: Antares_Simulator Team +** +** This file is part of Antares_Simulator. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** There are special exceptions to the terms and conditions of the +** license as they are applied to this software. View the full text of +** the exceptions in file COPYING.txt in the directory of this software +** distribution +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Antares_Simulator. If not, see . +** +** SPDX-License-Identifier: licenceRef-GPL3_WITH_RTE-Exceptions +*/ +#ifndef __SOLVER_VARIABLE_ECONOMY_RampingCost_H__ +#define __SOLVER_VARIABLE_ECONOMY_RampingCost_H__ + +#include "../variable.h" + +namespace Antares +{ +namespace Solver +{ +namespace Variable +{ +namespace Economy +{ +struct VCardRampingCost +{ + //! Caption + static std::string Caption() + { + return "RAMP COST"; + } + //! Unit + static std::string Unit() + { + return "Euro"; + } + + //! The short description of the variable + static std::string Description() + { + return "Ramping Cost throughout all MC years, of all the thermal dispatchable " + "clusters"; + } + + //! The expecte results + typedef Results>>>, + R::AllYears::Average // The + > + ResultsType; + + //! The VCard to look for for calculating spatial aggregates + typedef VCardRampingCost VCardForSpatialAggregate; + + enum + { + //! Data Level + categoryDataLevel = Category::area, + //! File level (provided by the type of the results) + categoryFileLevel = ResultsType::categoryFile & (Category::id | Category::va), + //! Precision (views) + precision = Category::all, + //! Indentation (GUI) + nodeDepthForGUI = +0, + //! Decimal precision + decimal = 0, + //! Number of columns used by the variable (One ResultsType per column) + columnCount = 1, + //! The Spatial aggregation + spatialAggregate = Category::spatialAggregateSum, + spatialAggregateMode = Category::spatialAggregateEachYear, + spatialAggregatePostProcessing = 0, + //! Intermediate values + hasIntermediateValues = 1, + //! Can this variable be non applicable (0 : no, 1 : yes) + isPossiblyNonApplicable = 0, + }; + + typedef IntermediateValues IntermediateValuesBaseType; + typedef IntermediateValues* IntermediateValuesType; + + typedef IntermediateValuesBaseType* IntermediateValuesTypeForSpatialAg; + +}; // class VCard + +/*! +** \brief C02 Average value of the overrall OperatingCost emissions expected from all +** the thermal dispatchable clusters +*/ +template +class RampingCost + : public Variable::IVariable, NextT, VCardRampingCost> +{ +public: + //! Type of the next static variable + typedef NextT NextType; + //! VCard + typedef VCardRampingCost VCardType; + //! Ancestor + typedef Variable::IVariable, NextT, VCardType> AncestorType; + + //! List of expected results + typedef typename VCardType::ResultsType ResultsType; + + typedef VariableAccessor VariableAccessorType; + + enum + { + //! How many items have we got + count = 1 + NextT::count, + }; + + template + struct Statistics + { + enum + { + count + = ((VCardType::categoryDataLevel & CDataLevel && VCardType::categoryFileLevel & CFile) + ? (NextType::template Statistics::count + + VCardType::columnCount * ResultsType::count) + : NextType::template Statistics::count), + }; + }; + +public: + ~RampingCost() + { + delete[] pValuesForTheCurrentYear; + } + + void initializeFromStudy(Data::Study& study) + { + pNbYearsParallel = study.maxNbYearsInParallel; + + InitializeResultsFromStudy(AncestorType::pResults, study); + + pValuesForTheCurrentYear = new VCardType::IntermediateValuesBaseType[pNbYearsParallel]; + for (unsigned int numSpace = 0; numSpace < pNbYearsParallel; numSpace++) + pValuesForTheCurrentYear[numSpace].initializeFromStudy(study); + + // Next + NextType::initializeFromStudy(study); + } + + template + static void InitializeResultsFromStudy(R& results, Data::Study& study) + { + VariableAccessorType::InitializeAndReset(results, study); + } + + void initializeFromArea(Data::Study* study, Data::Area* area) + { + // Next + NextType::initializeFromArea(study, area); + } + + void initializeFromLink(Data::Study* study, Data::AreaLink* link) + { + // Next + NextType::initializeFromAreaLink(study, link); + } + + void simulationBegin() + { + // Next + NextType::simulationBegin(); + } + + void simulationEnd() + { + NextType::simulationEnd(); + } + + void yearBegin(unsigned int year, unsigned int numSpace) + { + // Reset the values for the current year + pValuesForTheCurrentYear[numSpace].reset(); + // Next variable + NextType::yearBegin(year, numSpace); + } + + void yearEndBuildForEachThermalCluster(State& state, uint year, unsigned int numSpace) + { + // Get end year calculations + for (unsigned int i = state.study.runtime->rangeLimits.hour[Data::rangeBegin]; + i <= state.study.runtime->rangeLimits.hour[Data::rangeEnd]; + ++i) + { + pValuesForTheCurrentYear[numSpace][i] + += state.thermalClusterRampingCostForYear[i]; + } + + // Next variable + NextType::yearEndBuildForEachThermalCluster(state, year, numSpace); + } + + void yearEndBuild(State& state, unsigned int year) + { + // Next variable + NextType::yearEndBuild(state, year); + } + + void yearEnd(unsigned int year, unsigned int numSpace) + { + // Compute all statistics for the current year (daily,weekly,monthly) + pValuesForTheCurrentYear[numSpace].computeStatisticsForTheCurrentYear(); + + // Next variable + NextType::yearEnd(year, numSpace); + } + + void computeSummary(std::map& numSpaceToYear, + unsigned int nbYearsForCurrentSummary) + { + for (unsigned int numSpace = 0; numSpace < nbYearsForCurrentSummary; ++numSpace) + { + // Merge all those values with the global results + AncestorType::pResults.merge(numSpaceToYear[numSpace] /*year*/, + pValuesForTheCurrentYear[numSpace]); + } + + // Next variable + NextType::computeSummary(numSpaceToYear, nbYearsForCurrentSummary); + } + + void hourBegin(unsigned int hourInTheYear) + { + // Next variable + NextType::hourBegin(hourInTheYear); + } + + void hourForEachArea(State& state, unsigned int numSpace) + { + // Next variable + NextType::hourForEachArea(state, numSpace); + } + + Antares::Memory::Stored::ConstReturnType retrieveRawHourlyValuesForCurrentYear( + unsigned int, + unsigned int numSpace) const + { + return pValuesForTheCurrentYear[numSpace].hour; + } + + void localBuildAnnualSurveyReport(SurveyResults& results, + int fileLevel, + int precision, + unsigned int numSpace) const + { + // Initializing external pointer on current variable non applicable status + results.isCurrentVarNA = AncestorType::isNonApplicable; + + if (AncestorType::isPrinted[0]) + { + // Write the data for the current year + results.variableCaption = VCardType::Caption(); + results.variableUnit = VCardType::Unit(); + pValuesForTheCurrentYear[numSpace].template buildAnnualSurveyReport( + results, fileLevel, precision); + } + } + +private: + //! Intermediate values for each year + typename VCardType::IntermediateValuesType pValuesForTheCurrentYear; + unsigned int pNbYearsParallel; + +}; // class RampingCost + +} // namespace Economy +} // namespace Variable +} // namespace Solver +} // namespace Antares + +#endif // __SOLVER_VARIABLE_ECONOMY_RampingCost_H__ diff --git a/src/solver/variable/include/antares/solver/variable/state.h b/src/solver/variable/include/antares/solver/variable/state.h index f1f16f9618..8a8553155b 100644 --- a/src/solver/variable/include/antares/solver/variable/state.h +++ b/src/solver/variable/include/antares/solver/variable/state.h @@ -100,6 +100,7 @@ class State void yearEndBuildFromThermalClusterIndex(const unsigned int areaWideIndex); + private: /*! ** \brief Initialize some variable according a thermal cluster index @@ -116,6 +117,12 @@ class State const std::array& ON_opt, const Data::ThermalCluster* currentCluster); + void yearEndBuildCalculateRampingCosts( + const uint& maxDurationON, + const std::array& ON_min, + const std::array& ON_opt, + const Data::ThermalCluster* currentCluster); + std::array computeEconomicallyOptimalNbClustersONforEachHour( const uint& maxDurationON, const std::array& ON_min, @@ -190,6 +197,8 @@ class State double thermalClusterNonProportionalCostForYear[Variable::maxHoursInAYear]; //! Minimum power of the cluster for the whole year double thermalClusterPMinOfTheClusterForYear[Variable::maxHoursInAYear]; + //! Ramping cost of the thermal cluster for the whole year + double thermalClusterRampingCostForYear[Variable::maxHoursInAYear]; double renewableClusterProduction; diff --git a/src/solver/variable/include/antares/solver/variable/state.hxx b/src/solver/variable/include/antares/solver/variable/state.hxx index a6649f52ec..471cf58b9c 100644 --- a/src/solver/variable/include/antares/solver/variable/state.hxx +++ b/src/solver/variable/include/antares/solver/variable/state.hxx @@ -41,6 +41,9 @@ inline void State::startANewYear() 0, sizeof(thermalClusterDispatchedUnitsCountForYear)); + //if (unitCommitmentMode == Antares::Data::UnitCommitmentMode::ucHeuristicAccurate) + memset(thermalClusterRampingCostForYear, 0, sizeof(thermalClusterRampingCostForYear)); + // Re-initializing annual costs (to be printed in output into separate files) annualSystemCost = 0.; optimalSolutionCost1 = 0.; @@ -61,6 +64,7 @@ inline void State::yearEndResetThermal() memset(thermalClusterDispatchedUnitsCountForYear, 0, sizeof(thermalClusterDispatchedUnitsCountForYear)); + memset(thermalClusterRampingCostForYear, 0, sizeof(thermalClusterRampingCostForYear)); } inline void State::initFromAreaIndex(const unsigned int areaIndex, uint numSpace) diff --git a/src/solver/variable/state.cpp b/src/solver/variable/state.cpp index 6a37a2b5e7..79364458e2 100644 --- a/src/solver/variable/state.cpp +++ b/src/solver/variable/state.cpp @@ -199,6 +199,34 @@ void State::initFromThermalClusterIndexProduction(const uint clusterAreaWideInde thermal[area->index].thermalClustersOperatingCost[clusterAreaWideIndex] += thermalCluster->fixedCost * newUnitCount; + // ramping + if (unitCommitmentMode == Antares::Data::UnitCommitmentMode::ucHeuristicAccurate + && thermalCluster->ramping) + { + double startingStoppingProduction + = (newUnitCount - previousUnitCount) + * thermalCluster->nominalCapacityWithSpinning; + + double startingProduction = std::max(0., startingStoppingProduction); + double stoppingProduction = std::max(0., -startingStoppingProduction); + + double rampingIncrease + = std::max(p - thermal[area->index].productionLastHour[clusterAreaWideIndex] + - startingProduction, + 0.); + + double rampingDecrease + = std::max(thermal[area->index].productionLastHour[clusterAreaWideIndex] - p + - stoppingProduction, + 0.); + + thermal[area->index].thermalClustersOperatingCost[clusterAreaWideIndex] + += rampingIncrease * thermalCluster->ramping->powerIncreaseCost; + + thermal[area->index].thermalClustersOperatingCost[clusterAreaWideIndex] + += rampingDecrease * thermalCluster->ramping->powerDecreaseCost; + } + // Storing the new unit count for the next hour thermal[area->index].unitCountLastHour[clusterAreaWideIndex] = newUnitCount; thermal[area->index].productionLastHour[clusterAreaWideIndex] = p; @@ -340,8 +368,89 @@ void State::yearEndBuildFromThermalClusterIndex(const uint clusterAreaWideIndex) // Calculation of non linear and startup costs yearEndBuildThermalClusterCalculateStartupCosts(maxDurationON, ON_min, ON_opt, currentCluster); + // Calculation of ramping costs + yearEndBuildCalculateRampingCosts(maxDurationON, ON_min, ON_opt, currentCluster); +} + + +void State::yearEndBuildCalculateRampingCosts( + const uint& maxDurationON, + const std::array& ON_min, + const std::array& ON_opt, + const Data::ThermalCluster* currentCluster) +{ + if (unitCommitmentMode == Antares::Data::UnitCommitmentMode::ucHeuristicAccurate + && currentCluster->ramping) + { + uint startHourForCurrentYear = study.runtime->rangeLimits.hour[Data::rangeBegin]; + uint endHourForCurrentYear + = startHourForCurrentYear + study.runtime->rangeLimits.hour[Data::rangeCount]; + // min, and max unit ON calculation + const auto& availableProduction = currentCluster->series.getColumn(this->year); + for (uint h = startHourForCurrentYear; h < endHourForCurrentYear; ++h) + { + // Getting available production from cluster data + double thermalClusterAvailableProduction = availableProduction[h]; + uint last_index = (h > startHourForCurrentYear) ? h - 1 : startHourForCurrentYear; + double thermalClusterLastAvailableProduction = availableProduction[last_index]; + double thermalClusterProduction = 0.; + double thermalClusterLastProduction = 0.; + if (currentCluster->mustrun) + { + // When the cluster is in must-run mode, the production value + // directly comes from the time-series + thermalClusterProduction + = thermalClusterAvailableProduction; // in mustrun, production==available + thermalClusterLastProduction = thermalClusterLastAvailableProduction; + // production + } + else + { + // otherwise from the solver results (most of the time) + thermalClusterProduction = thermalClusterProductionForYear[h]; + thermalClusterLastProduction = thermalClusterProductionForYear[last_index]; + } + + double startingStoppingProduction + = 0.; + + if (h >= startHourForCurrentYear + 1) // starting hour +1 (fron start hour) + { + // nombre de groupes démarrés à l'heure h + int delta + = (maxDurationON == 0) ? ON_min[h] - ON_min[h - 1] : ON_opt[h] - ON_opt[h - 1]; + + startingStoppingProduction = delta + * currentCluster->nominalCapacityWithSpinning; + + } + double startingProduction = std::max(0., startingStoppingProduction); + double stoppingProduction = std::max(0., -startingStoppingProduction); + + double rampingIncrease = std::max( + thermalClusterProduction - thermalClusterLastProduction + - startingProduction, + 0.); + + double rampingDecrease = std::max( + thermalClusterLastProduction - thermalClusterProduction + - stoppingProduction, + 0.); + + thermalClusterRampingCostForYear[h] + = rampingIncrease * currentCluster->ramping->powerIncreaseCost; + + thermalClusterRampingCostForYear[h] + += rampingDecrease * currentCluster->ramping->powerDecreaseCost; + + thermalClusterOperatingCostForYear[h] += thermalClusterRampingCostForYear[h]; + } + } + } + + void State::yearEndBuildThermalClusterCalculateStartupCosts(const uint& maxDurationON, const std::array& ON_min, const std::array& ON_opt, diff --git a/src/solver/variable/surveyresults/surveyresults.cpp b/src/solver/variable/surveyresults/surveyresults.cpp index bd814412e8..11256b4de2 100644 --- a/src/solver/variable/surveyresults/surveyresults.cpp +++ b/src/solver/variable/surveyresults/surveyresults.cpp @@ -27,6 +27,7 @@ #include "antares/solver/variable/surveyresults/surveyresults.h" #include + using namespace Yuni; using namespace Antares; @@ -111,7 +112,8 @@ static void ExportGridInfosAreas(const Data::Study& study, outLinks << "upstream\tdownstream\n"; outThermal << "area id\tid\tname\tgroup\tunit count\tnominal capacity\t" "min stable power\tmin up/down time\tspinning\tco2\t" - "marginal cost\tfixed cost\tstartup cost\tmarket bid cost\tspread cost\n"; + "marginal cost\tfixed cost\tstartup cost\tmarket bid cost\tspread cost\t" + "power increase cost`\rpower decrease cost\t max power upward rate\t max power downward rate\n "; study.areas.each([&](const Data::Area& area) { out << area.id << '\t'; @@ -143,10 +145,18 @@ static void ExportGridInfosAreas(const Data::Study& study, outThermal << cluster->startupCost << '\t'; outThermal << cluster->marketBidCost << '\t'; outThermal << cluster->spreadCost << '\n'; + if (cluster->ramping) + { + outThermal << cluster->ramping.value().powerIncreaseCost << '\t'; + outThermal << cluster->ramping.value().powerDecreaseCost << '\t'; + outThermal << cluster->ramping.value().maxUpwardPowerRampingRate << '\t'; + outThermal << cluster->ramping.value().maxDownwardPowerRampingRate << '\n'; + } } // each thermal cluster }); // each area - auto add = [&writer, &originalOutput](const YString& filename, Clob&& buffer) { + // buffer must be copied since addEntryFromBuffer has no std::string&& variant, unfortunately + auto add = [&writer, &originalOutput](const YString& filename, std::string buffer) { YString path; path << originalOutput << SEP << "grid" << SEP << filename; writer.addEntryFromBuffer(path.c_str(), buffer);