Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ANT-992] Add ramping for thermal clusters #1817

Draft
wants to merge 26 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a64625a
core implementation of the ramping model
bencamus Dec 5, 2023
b9c4f2b
add export/import features for the thermal ramping attributes
bencamus Dec 7, 2023
624f2ce
make the ramping constraints cyclical as the evolution of the product…
bencamus Dec 18, 2023
915e0c7
remove reference to Yuni in the ramping model code
bencamus Dec 18, 2023
f2d469e
removing of a useless header file
bencamus Dec 18, 2023
e78c034
improve readability of ramping model methods by naming their paramete…
bencamus Dec 18, 2023
b9801fe
add subclass to store and check thermal cluster ramping attributes
bencamus Dec 18, 2023
09a2ca9
Small code improvement for ramping
flomnes Dec 18, 2023
5ce4531
Solve build errors with a friend
flomnes Dec 19, 2023
77daa36
add the possibility to enable/disable the ramping model for each cluster
bencamus Dec 20, 2023
8133150
improvement of the implementation of the ON/OFF ramping model features
bencamus Dec 20, 2023
134ad57
merge commit
bencamus Dec 20, 2023
54ef194
add ramping costs to the output operational costs of the cluster
bencamus Jan 15, 2024
d8ca6aa
add dedicated output log for ramping costs
bencamus Jan 16, 2024
d1f983c
implementation of a new version of the ramping equations
bencamus Feb 29, 2024
70ba583
fixing a bug in the definition of the ramping variables bounds + free…
bencamus Mar 6, 2024
7387013
Supression of clusterRampingVariablesIndex and standardisation of the…
sylvmara Mar 7, 2024
ab1b701
Harmonisation of ramp checking
sylvmara Mar 7, 2024
8b6ce0b
merge with Antares master
bencamus Apr 12, 2024
3b17545
merge last version of ramping model with last version of Antares
bencamus Apr 12, 2024
50f7a48
removing ramping files that are useless with the last version of Antares
bencamus Apr 12, 2024
554455d
changing constraint equation related to power output variation of the…
bencamus Apr 16, 2024
3625bd2
Temporarily enable build for CentOS7
flomnes May 3, 2024
44f722d
changing the variable caption from RAMP COST by plant to RAMP COST BY…
bencamus May 15, 2024
e41d0b8
update the ramping constraint 18ter to remove breaking down thermal u…
bencamus Aug 26, 2024
34f2531
Merge remote-tracking branch 'origin/feature/ramping_model_scalian' i…
bencamus Aug 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion src/libs/antares/study/parts/thermal/cluster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,11 @@ Data::ThermalCluster::ThermalCluster(Area* parent) :
forcedLaw(thermalLawUniform),
plannedLaw(thermalLawUniform),
PthetaInf(HOURS_PER_YEAR, 0),
costsTimeSeries(1, CostsTimeSeries())
costsTimeSeries(1, CostsTimeSeries()),
maxUpwardPowerRampingRate(0.),
maxDownwardPowerRampingRate(0.),
powerIncreaseCost(0.),
powerDecreaseCost(0.)
flomnes marked this conversation as resolved.
Show resolved Hide resolved
{
// assert
assert(parent and "A parent for a thermal dispatchable cluster can not be null");
Expand Down Expand Up @@ -182,6 +186,12 @@ void Data::ThermalCluster::copyFrom(const ThermalCluster& cluster)
minUpTime = cluster.minUpTime;
minDownTime = cluster.minDownTime;

// ramping
maxUpwardPowerRampingRate = cluster.maxUpwardPowerRampingRate;
maxDownwardPowerRampingRate = cluster.maxDownwardPowerRampingRate;
powerDecreaseCost = cluster.powerDecreaseCost;
powerIncreaseCost = cluster.powerIncreaseCost;
flomnes marked this conversation as resolved.
Show resolved Hide resolved

// spinning
spinning = cluster.spinning;

Expand Down Expand Up @@ -479,6 +489,11 @@ void Data::ThermalCluster::reset()
marketBidCost = 0.;
variableomcost = 0.;
costsTimeSeries.resize(1, CostsTimeSeries());
powerIncreaseCost = 0;
powerDecreaseCost = 0;

maxUpwardPowerRampingRate = 0;
maxDownwardPowerRampingRate = 0;
flomnes marked this conversation as resolved.
Show resolved Hide resolved

// modulation
modulation.resize(thermalModulationMax, HOURS_PER_YEAR);
Expand Down Expand Up @@ -608,6 +623,36 @@ bool Data::ThermalCluster::integrityCheck()
ret = false;
}*/

// ramping
if (maxUpwardPowerRampingRate <= 0)
{
logs.error() << "Thermal cluster: " << parentArea->name << '/' << pName
<< ": The maximum upward power ramping rate must greater than zero.";
maxUpwardPowerRampingRate = 1.;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would be helpful to print the fallback value;
also it seems that the return value is never used

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, with commit 8133150, the ramping is disabled in this case, and this information is printed.

ret = false;
}
if (maxDownwardPowerRampingRate <= 0)
{
logs.error() << "Thermal cluster: " << parentArea->name << '/' << pName
<< ": The maximum downward power ramping rate must greater than zero.";
maxDownwardPowerRampingRate = 1.;
ret = false;
}
if (powerIncreaseCost < 0)
{
logs.error() << "Thermal cluster: " << parentArea->name << '/' << pName
<< ": The ramping power increase cost must be positive or null.";
powerIncreaseCost = 0.;
ret = false;
}
if (powerDecreaseCost < 0)
{
logs.error() << "Thermal cluster: " << parentArea->name << '/' << pName
<< ": The ramping power decrease cost must be positive or null.";
powerDecreaseCost = 0.;
ret = false;
}
flomnes marked this conversation as resolved.
Show resolved Hide resolved

return ret;
}

Expand Down
10 changes: 10 additions & 0 deletions src/libs/antares/study/parts/thermal/cluster.h
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,16 @@ class ThermalCluster final : public Cluster, public std::enable_shared_from_this
double marketBidCost = 0;
//! Variable O&M cost (euros/MWh)
double variableomcost = 0;
//! Cost of power increase (euros/MW)
double powerIncreaseCost = 0;
//! Cost of power decrease (euros/MW)
double powerDecreaseCost = 0;

//! Maximum hourly upward power ramping rate (MW/hour)
double maxUpwardPowerRampingRate = 0;
//! Maximum hourly downward power ramping rate (MW/hour)
double maxDownwardPowerRampingRate = 0;

//@}

/*!
Expand Down
18 changes: 18 additions & 0 deletions src/libs/antares/study/parts/thermal/cluster_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,15 @@ static bool ThermalClusterLoadFromProperty(ThermalCluster& cluster, const IniFil
if (p->key == "startup-cost")
return p->value.to<double>(cluster.startupCost);

if (p->key == "power-increase-cost")
return p->value.to<double>(cluster.powerIncreaseCost);
if (p->key == "power-decrease-cost")
return p->value.to<double>(cluster.powerDecreaseCost);
if (p->key == "max-upward-power-ramping-rate")
return p->value.to<double>(cluster.maxUpwardPowerRampingRate);
if (p->key == "max-downward-power-ramping-rate")
return p->value.to<double>(cluster.maxDownwardPowerRampingRate);

if (p->key == "unitcount")
return p->value.to<uint>(cluster.unitCount);
if (p->key == "volatility.planned")
Expand Down Expand Up @@ -405,6 +414,15 @@ bool ThermalClusterList::saveToFolder(const AnyString& folder) const
if (!Math::Zero(c.variableomcost))
s->add("variableomcost", Math::Round(c.variableomcost,3));

// ramping
if (not Math::Zero(c.powerIncreaseCost))
s->add("power-increase-cost", Math::Round(c.powerIncreaseCost, 3));
if (not Math::Zero(c.powerDecreaseCost))
s->add("power-decrease-cost", Math::Round(c.powerDecreaseCost, 3));
if (not Math::Zero(c.maxUpwardPowerRampingRate))
s->add("max-upward-power-ramping-rate", Math::Round(c.maxUpwardPowerRampingRate, 3));
if (not Math::Zero(c.maxDownwardPowerRampingRate))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Math is part of Yuni, which is deprecated. Please use STL functions instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed by commit 915e0c7

s->add("max-downward-power-ramping-rate", Math::Round(c.maxDownwardPowerRampingRate, 3));

//pollutant factor
for (auto const& [key, val] : Pollutant::namesToEnum)
Expand Down
12 changes: 11 additions & 1 deletion src/solver/optimisation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ set(RTESOLVER_OPT
opt_decompte_variables_et_contraintes_couts_demarrage.cpp
opt_init_minmax_groupes_couts_demarrage.cpp
opt_nombre_min_groupes_demarres_couts_demarrage.cpp
opt_construction_contraintes_rampes_thermiques.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
opt_export_structure.h
opt_export_structure.cpp
base_weekly_optimization.h
Expand Down Expand Up @@ -119,7 +124,12 @@ set(RTESOLVER_OPT
constraints/NbDispUnitsMinBoundSinceMinUpTime.cpp
constraints/MinDownTime.h
constraints/MinDownTime.cpp

constraints/RampingIncreaseRate.h
constraints/RampingIncreaseRate.cpp
constraints/RampingDecreaseRate.h
constraints/RampingDecreaseRate.cpp
constraints/PowerOutputVariation.h
constraints/PowerOutputVariation.cpp
)


Expand Down
19 changes: 19 additions & 0 deletions src/solver/optimisation/constraints/ConstraintBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,25 @@ ConstraintBuilder& ConstraintBuilder::DispatchableProduction(unsigned int index,
int delta)
{
AddVariable(GetVariableManager(offset, delta).DispatchableProduction(index), coeff);
// logs.info() << "dispatchable production idx " << GetVariableManager(offset, delta).DispatchableProduction(index);
return *this;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove debug comments

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in commit 624f2ce

}

ConstraintBuilder& ConstraintBuilder::ProductionDecreaseAboveMin(unsigned int index,
double coeff,
int offset,
int delta)
{
AddVariable(GetVariableManager(offset, delta).ProductionDecreaseAboveMin(index), coeff);
return *this;
}

ConstraintBuilder& ConstraintBuilder::ProductionIncreaseAboveMin(unsigned int index,
double coeff,
int offset,
int delta)
{
AddVariable(GetVariableManager(offset, delta).ProductionIncreaseAboveMin(index), coeff);
return *this;
}

Expand Down
20 changes: 20 additions & 0 deletions src/solver/optimisation/constraints/ConstraintBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ class VariableManager
return nativeOptimVar.NumeroDeVariableDuPalierThermique[index];
}

int ProductionIncreaseAboveMin(unsigned int index) const
{
return nativeOptimVar.powerRampingIncreaseIndex[index];
}

int ProductionDecreaseAboveMin(unsigned int index) const
{
return nativeOptimVar.powerRampingDecreaseIndex[index];
}

int NumberOfDispatchableUnits(unsigned int index) const
{
return nativeOptimVar.NumeroDeVariableDuNombreDeGroupesEnMarcheDuPalierThermique[index];
Expand Down Expand Up @@ -183,6 +193,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,
int offset = 0,
Expand Down
47 changes: 47 additions & 0 deletions src/solver/optimisation/constraints/PowerOutputVariation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include "PowerOutputVariation.h"

void PowerOutputVariation::add(int pays, int cluster, int clusterIndex, int pdt, bool Simulation)
{
if (!Simulation)
{
const PALIERS_THERMIQUES& PaliersThermiquesDuPays = problemeHebdo->PaliersThermiquesDuPays[pays];
double maxUpwardPowerRampingRate = PaliersThermiquesDuPays.maxUpwardPowerRampingRate[clusterIndex];
double pminDUnGroupeDuPalierThermique = PaliersThermiquesDuPays.pminDUnGroupeDuPalierThermique[clusterIndex];
// constraint : P(t) - P(t-1) - l * M^+(t) - P^+ + P^- = 0
if (pdt > 0)
{
builder.updateHourWithinWeek(pdt)
.DispatchableProduction(cluster, 1.0)
.DispatchableProduction(cluster, -1.0, -1, problemeHebdo->NombreDePasDeTempsPourUneOptimisation)
.NumberStartingDispatchableUnits(cluster, -pminDUnGroupeDuPalierThermique)
.ProductionIncreaseAboveMin(cluster, -1.0)
.ProductionDecreaseAboveMin(cluster, 1.0)
.equalTo();
}
else
{
builder.updateHourWithinWeek(pdt)
.DispatchableProduction(cluster, 1.0)
.NumberStartingDispatchableUnits(cluster, -pminDUnGroupeDuPalierThermique)
.ProductionIncreaseAboveMin(cluster, -1.0)
.ProductionDecreaseAboveMin(cluster, 1.0)
.equalTo();
}
if (builder.NumberOfVariables() > 0)
{
ConstraintNamer namer(problemeHebdo->ProblemeAResoudre->NomDesContraintes);

namer.UpdateTimeStep(problemeHebdo->weekInTheYear * 168 + pdt);
namer.UpdateArea(problemeHebdo->NomsDesPays[pays]);

namer.ProductionOutputVariation(problemeHebdo->ProblemeAResoudre->NombreDeContraintes, PaliersThermiquesDuPays.NomsDesPaliersThermiques[clusterIndex]);
}
builder.build();
}
else
{
int add = (pdt == 0) ? 4 : 5;
flomnes marked this conversation as resolved.
Show resolved Hide resolved
problemeHebdo->NbTermesContraintesPourLesRampes += add;
problemeHebdo->ProblemeAResoudre->NombreDeContraintes++;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be checked carefully

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with commit 624f2ce, the ramping constraints become cyclical (the evolution of the production for the first hour is constrained the production of the last hour). Then, we don't need to use a constraint for the first time step that is different from the other time steps anymore. This code is now removed.

}
20 changes: 20 additions & 0 deletions src/solver/optimisation/constraints/PowerOutputVariation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once
#include "ConstraintBuilder.h"

/*!
* represent 'RampingIncreaseRate' Constraint type
*/
class PowerOutputVariation : private ConstraintFactory
{
public:
using ConstraintFactory::ConstraintFactory;

/*!
* @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 cluster, int clusterIndex, int pdt, bool Simulation);
};
48 changes: 48 additions & 0 deletions src/solver/optimisation/constraints/RampingDecreaseRate.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "RampingDecreaseRate.h"

void RampingDecreaseRate::add(int pays, int cluster, int clusterIndex, int pdt, bool Simulation)
{
if (!Simulation)
{
const PALIERS_THERMIQUES& PaliersThermiquesDuPays = problemeHebdo->PaliersThermiquesDuPays[pays];
double maxDownwardPowerRampingRate = PaliersThermiquesDuPays.maxDownwardPowerRampingRate[clusterIndex];
double pmaxDUnGroupeDuPalierThermique = PaliersThermiquesDuPays.PmaxDUnGroupeDuPalierThermique[clusterIndex];
// constraint : P(t) - P(t-1) + R^- * M(t) + u * M^-(t) + u * M^--(t) > 0
flomnes marked this conversation as resolved.
Show resolved Hide resolved
if (pdt > 0)
{
builder.updateHourWithinWeek(pdt)
.DispatchableProduction(cluster, 1.0)
.DispatchableProduction(cluster, -1.0, -1, problemeHebdo->NombreDePasDeTempsPourUneOptimisation)
.NumberOfDispatchableUnits(cluster, maxDownwardPowerRampingRate)
.NumberStoppingDispatchableUnits(cluster, pmaxDUnGroupeDuPalierThermique)
.NumberBreakingDownDispatchableUnits(cluster, pmaxDUnGroupeDuPalierThermique)
.greaterThan();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need this if..else since DispatchableProduction has arguments to take this into account.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, commit 624f2ce fixed this.

else
{
builder.updateHourWithinWeek(pdt)
.DispatchableProduction(cluster, 1.0)
.NumberOfDispatchableUnits(cluster, maxDownwardPowerRampingRate)
.NumberStoppingDispatchableUnits(cluster, pmaxDUnGroupeDuPalierThermique)
.NumberBreakingDownDispatchableUnits(cluster, pmaxDUnGroupeDuPalierThermique)
.greaterThan();
}
if (builder.NumberOfVariables() > 0)
{
ConstraintNamer namer(problemeHebdo->ProblemeAResoudre->NomDesContraintes);

namer.UpdateTimeStep(problemeHebdo->weekInTheYear * 168 + pdt);
namer.UpdateArea(problemeHebdo->NomsDesPays[pays]);

namer.RampingDecreaseRate(problemeHebdo->ProblemeAResoudre->NombreDeContraintes,
PaliersThermiquesDuPays.NomsDesPaliersThermiques[clusterIndex]);
}
builder.build();
}
else
{
int add = (pdt == 0) ? 4 : 5;
problemeHebdo->NbTermesContraintesPourLesRampes += add;
problemeHebdo->ProblemeAResoudre->NombreDeContraintes++;
}
}
20 changes: 20 additions & 0 deletions src/solver/optimisation/constraints/RampingDecreaseRate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once
#include "ConstraintBuilder.h"

/*!
* represent 'RampingIncreaseRate' Constraint type
*/
class RampingDecreaseRate : private ConstraintFactory
{
public:
using ConstraintFactory::ConstraintFactory;

/*!
* @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 cluster, int clusterIndex, int pdt, bool Simulation);
};
47 changes: 47 additions & 0 deletions src/solver/optimisation/constraints/RampingIncreaseRate.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include "RampingIncreaseRate.h"

void RampingIncreaseRate::add(int pays, int cluster, int clusterIndex, int pdt, bool Simulation)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may define a class both for RampingIncreaseRate and RampingDecreaseRate. With a pure virtual method for behaviors that differ, e.g

-.NumberOfDispatchableUnits(cluster, -maxUpwardPowerRampingRate)
-.NumberStartingDispatchableUnits(cluster, -pminDUnGroupeDuPalierThermique)
+.NumberStoppingDispatchableUnits(cluster, pmaxDUnGroupeDuPalierThermique)
+.NumberBreakingDownDispatchableUnits(cluster, pmaxDUnGroupeDuPalierThermique)

See https://refactoring.guru/design-patterns/template-method

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pending until the constraint equations are validated

{
if (!Simulation)
{
const PALIERS_THERMIQUES& PaliersThermiquesDuPays = problemeHebdo->PaliersThermiquesDuPays[pays];
double maxUpwardPowerRampingRate = PaliersThermiquesDuPays.maxUpwardPowerRampingRate[clusterIndex];
double pminDUnGroupeDuPalierThermique = PaliersThermiquesDuPays.pminDUnGroupeDuPalierThermique[clusterIndex];
// constraint : P(t) - P(t-1) - R^+ * M(t) - l * M^+(t) < 0
// logs.info() << "add up ramping constraint for cluster " << cluster << " pdt " << pdt;
flomnes marked this conversation as resolved.
Show resolved Hide resolved

if (pdt > 0)
{
builder.updateHourWithinWeek(pdt)
.DispatchableProduction(cluster, 1.0)
.DispatchableProduction(
cluster, -1.0, -1, problemeHebdo->NombreDePasDeTempsPourUneOptimisation)
.NumberOfDispatchableUnits(cluster, -maxUpwardPowerRampingRate)
.NumberStartingDispatchableUnits(cluster, -pminDUnGroupeDuPalierThermique)
.lessThan();
}
else
{
builder.updateHourWithinWeek(pdt)
.DispatchableProduction(cluster, 1.0)
.NumberOfDispatchableUnits(cluster, -maxUpwardPowerRampingRate)
.NumberStartingDispatchableUnits(cluster, -pminDUnGroupeDuPalierThermique)
.lessThan();
}
if (builder.NumberOfVariables() > 0)
{
ConstraintNamer namer(problemeHebdo->ProblemeAResoudre->NomDesContraintes);
namer.UpdateTimeStep(problemeHebdo->weekInTheYear * 168 + pdt);
namer.UpdateArea(problemeHebdo->NomsDesPays[pays]);
namer.RampingIncreaseRate(problemeHebdo->ProblemeAResoudre->NombreDeContraintes,
PaliersThermiquesDuPays.NomsDesPaliersThermiques[clusterIndex]);
}
builder.build();
}
else
{
int add = (pdt == 0) ? 3 : 4;
problemeHebdo->NbTermesContraintesPourLesRampes += add;
problemeHebdo->ProblemeAResoudre->NombreDeContraintes++;
}
}
Loading