diff --git a/src/solver/optim/api/LinearProblemBuilder.cpp b/src/solver/optim/api/LinearProblemBuilder.cpp index 1c0d703389..bccd8aacb0 100644 --- a/src/solver/optim/api/LinearProblemBuilder.cpp +++ b/src/solver/optim/api/LinearProblemBuilder.cpp @@ -11,29 +11,33 @@ void LinearProblemBuilder::addFiller(std::shared_ptr filler fillers_.push_back(filler); } -void LinearProblemBuilder::build(const LinearProblemData& data) { - if (built) { +void LinearProblemBuilder::build(const LinearProblemData& data, const BuildContext& buildCtx) +{ + if (built) + { // TODO throw; } for (auto filler : fillers_) { - filler->addVariables(*linearProblem_, data); + filler->addVariables(*linearProblem_, data, buildCtx); } for (auto filler : fillers_) { - filler->addConstraints(*linearProblem_, data); + filler->addConstraints(*linearProblem_, data, buildCtx); } for (auto filler : fillers_) { - filler->addObjective(*linearProblem_, data); + filler->addObjective(*linearProblem_, data, buildCtx); } built = true; } -void LinearProblemBuilder::update(const LinearProblemData& data) const { +void LinearProblemBuilder::update(const LinearProblemData& data) const +{ // TODO : throw if timestamps have changed ? - if (!built) { + if (!built) + { // TODO throw; } @@ -46,7 +50,8 @@ void LinearProblemBuilder::update(const LinearProblemData& data) const { MipSolution LinearProblemBuilder::solve(const operations_research::MPSolverParameters& param) { // TODO : move to new interface LinearProblemSolver ?? - if (!built) { + if (!built) + { // TODO throw; } diff --git a/src/solver/optim/api/include/antares/optim/api/LinearProblem.h b/src/solver/optim/api/include/antares/optim/api/LinearProblem.h index 3b069c493d..9ce725947b 100644 --- a/src/solver/optim/api/include/antares/optim/api/LinearProblem.h +++ b/src/solver/optim/api/include/antares/optim/api/LinearProblem.h @@ -44,8 +44,10 @@ namespace Antares::optim::api virtual operations_research::MPConstraint& addBalanceConstraint(std::string name, double bound, std::string nodeName, int timestep) = 0; virtual operations_research::MPConstraint& getConstraint(std::string name) = 0; virtual void setObjectiveCoefficient(const operations_research::MPVariable& variable, double coefficient) = 0; + virtual double getObjectiveCoefficient(const operations_research::MPVariable& variable) = 0; virtual void setMinimization(bool isMinim) = 0; virtual MipSolution solve(const operations_research::MPSolverParameters& param) = 0; + virtual double infinity() = 0; virtual ~LinearProblem() = default; }; } diff --git a/src/solver/optim/api/include/antares/optim/api/LinearProblemBuilder.h b/src/solver/optim/api/include/antares/optim/api/LinearProblemBuilder.h index b77611872c..15b863fb04 100644 --- a/src/solver/optim/api/include/antares/optim/api/LinearProblemBuilder.h +++ b/src/solver/optim/api/include/antares/optim/api/LinearProblemBuilder.h @@ -26,22 +26,23 @@ */ #pragma once +#include #include "LinearProblemFiller.h" -#include "vector" namespace Antares::optim::api { - class LinearProblemBuilder final - { - private: - LinearProblem* linearProblem_; - std::vector> fillers_{}; - bool built = false; - public: - explicit LinearProblemBuilder(LinearProblem& linearProblem) : linearProblem_(&linearProblem) {}; - void addFiller(std::shared_ptr filler); - void build(const LinearProblemData& data); - void update(const LinearProblemData& data) const; - MipSolution solve(const operations_research::MPSolverParameters& param); - }; -} +class LinearProblemBuilder final +{ +private: + LinearProblem* linearProblem_; + std::vector> fillers_{}; + bool built = false; + +public: + explicit LinearProblemBuilder(LinearProblem& linearProblem) : linearProblem_(&linearProblem){}; + void addFiller(std::shared_ptr filler); + void build(const LinearProblemData& data, const BuildContext& ctx); + void update(const LinearProblemData& data) const; + MipSolution solve(const operations_research::MPSolverParameters& param); +}; +} // namespace Antares::optim::api diff --git a/src/solver/optim/api/include/antares/optim/api/LinearProblemData.h b/src/solver/optim/api/include/antares/optim/api/LinearProblemData.h index 3b077cd12e..de56741474 100644 --- a/src/solver/optim/api/include/antares/optim/api/LinearProblemData.h +++ b/src/solver/optim/api/include/antares/optim/api/LinearProblemData.h @@ -27,45 +27,96 @@ #pragma once #include +#include #include "antares/solver/simulation/sim_structure_probleme_economique.h" -#include "vector" - namespace Antares::optim::api { - class LinearProblemData final +class LinearProblemData final +{ +private: + // TODO : timestamps or timesteps? + int timeResolutionInMinutes_; + std::map> scalarData_; + std::map>> timedData_; + // TODO : handle scenarios, and data vectorized on scenarios, on time, or on both +public: + explicit LinearProblemData( + int timeResolutionInMinutes, + const std::map>& scalarData, + const std::map>>& timedData) : + timeResolutionInMinutes_(timeResolutionInMinutes), + scalarData_(scalarData), + timedData_(timedData){ + // TODO: some coherence check on data + // for example, check that timed data are all of same size = size of timeStamps_ + }; + [[nodiscard]] int getTimeResolutionInMinutes() const + { + return timeResolutionInMinutes_; + } + [[nodiscard]] bool hasScalarData(const std::string& key) const + { + return scalarData_.contains(key); + } + [[nodiscard]] double getScalarData(const std::string& key, unsigned int scenario) const { - private: - // TODO : timestamps or timesteps? - std::vector timeStamps_; - int timeResolutionInMinutes_; - std::map scalarData_; - std::map> timedData_; - // TODO : handle scenarios, and data vectorized on scenarios, on time, or on both - public: - explicit LinearProblemData(const std::vector &timeStamps, int timeResolutionInMinutes, - const std::map &scalarData, - const std::map> &timedData) : - timeStamps_(timeStamps), timeResolutionInMinutes_(timeResolutionInMinutes), - scalarData_(scalarData), timedData_(timedData) + switch (scalarData_.at(key).size()) { - // TODO: some coherence check on data - // for example, check that timed data are all of same size = size of timeStamps_ - }; - [[nodiscard]] std::vector getTimeStamps() const { return timeStamps_; } - [[nodiscard]] int getTimeResolutionInMinutes() const { return timeResolutionInMinutes_; } - [[nodiscard]] bool hasScalarData(const std::string& key) const { return scalarData_.contains(key); } - [[nodiscard]] double getScalarData(const std::string& key) const { return scalarData_.at(key); } - [[nodiscard]] bool hasTimedData(const std::string& key) const { return timedData_.contains(key); } - [[nodiscard]] const std::vector& getTimedData(const std::string& key) const { return timedData_.at(key); } + case 1: + return scalarData_.at(key)[0]; + default: + return scalarData_.at(key)[scenario]; + } + } + [[nodiscard]] bool hasTimedData(const std::string& key) const + { + return timedData_.contains(key); + } + [[nodiscard]] const std::vector& getTimedData(const std::string& key, + unsigned int scenario) const + { + switch (timedData_.at(key).size()) + { + case 1: + return timedData_.at(key)[0]; + default: + return timedData_.at(key)[scenario]; + } + } - // TODO: remove this when legacy support is dropped - // TODO: meanwhile, instead of having a nested struct, create a daughter class? - struct Legacy { - const std::vector* constraintMapping; - const std::vector* areaNames; - }; - Legacy legacy; + // TODO: remove this when legacy support is dropped + // TODO: meanwhile, instead of having a nested struct, create a daughter class? + struct Legacy + { + const std::vector* constraintMapping; + const std::vector* areaNames; }; + Legacy legacy; +}; // namespace Antares::optim::api + +// TODO[FOM] Move to dedicated header file +struct BuildContext final +{ + using ScenarioID = unsigned int; + using TimeStampID = int; + + [[nodiscard]] std::vector getTimeStamps() const + { + return timeStamps; + } + + [[nodiscard]] std::vector getScenarios() const + { + return scenarios; + } + + BuildContext(std::vector scenarios, std::vector timeStamps) : + scenarios(scenarios), timeStamps(timeStamps) + { + } + const std::vector scenarios; + const std::vector timeStamps; +}; -} +} // namespace Antares::optim::api diff --git a/src/solver/optim/api/include/antares/optim/api/LinearProblemFiller.h b/src/solver/optim/api/include/antares/optim/api/LinearProblemFiller.h index 00fbda55ff..4d1c66f6a4 100644 --- a/src/solver/optim/api/include/antares/optim/api/LinearProblemFiller.h +++ b/src/solver/optim/api/include/antares/optim/api/LinearProblemFiller.h @@ -31,20 +31,31 @@ namespace Antares::optim::api { - class LinearProblemFiller - { - public: - virtual void addVariables(LinearProblem& problem, const LinearProblemData& data) = 0; - virtual void addConstraints(LinearProblem& problem, const LinearProblemData& data) = 0; - virtual void addObjective(LinearProblem& problem, const LinearProblemData& data) = 0; - virtual void update(LinearProblem& problem, const LinearProblemData& data) = 0; - // TODO : see if update is really needed in target solution - // Currently used to update the MIP from week to week by only changing LB/UB & coefs - // (see sim_structure_contrainte_economique.h & ApportNaturelHoraire) - // This may be dropped in the target solution (thus we'll have to re-create the MIP) for 2 reasons: - // - we may have to add/remove variables & constraints - // - OR-Tools does not allow changing the names of variables & constraints, which is necessary if we want the - // variables & constraints to be indexed by the number of the week in the year - virtual ~LinearProblemFiller() = default; - }; -} +class LinearProblemFiller +{ +public: + virtual void addVariables(LinearProblem& problem, + const LinearProblemData& data, + const BuildContext&) + = 0; + virtual void addConstraints(LinearProblem& problem, + const LinearProblemData& data, + const BuildContext&) + = 0; + virtual void addObjective(LinearProblem& problem, + const LinearProblemData& data, + const BuildContext&) + = 0; + virtual void update(LinearProblem& problem, const LinearProblemData& data) = 0; + // TODO : see if update is really needed in target solution + // Currently used to update the MIP from week to week by only changing LB/UB & coefs + // (see sim_structure_contrainte_economique.h & ApportNaturelHoraire) + // This may be dropped in the target solution (thus we'll have to re-create the MIP) for 2 + // reasons: + // - we may have to add/remove variables & constraints + // - OR-Tools does not allow changing the names of variables & constraints, which is necessary + // if we want the + // variables & constraints to be indexed by the number of the week in the year + virtual ~LinearProblemFiller() = default; +}; +} // namespace Antares::optim::api diff --git a/src/solver/optim/api/include/antares/optim/api/MipSolution.h b/src/solver/optim/api/include/antares/optim/api/MipSolution.h index c9d4b42ef5..97fd56845a 100644 --- a/src/solver/optim/api/include/antares/optim/api/MipSolution.h +++ b/src/solver/optim/api/include/antares/optim/api/MipSolution.h @@ -32,40 +32,54 @@ namespace Antares::optim::api { - class MipSolution final +class MipSolution final +{ + // TODO: improve this by removing dependency to OR-Tools +private: + operations_research::MPSolver::ResultStatus responseStatus_; + std::map solution_; + double objectiveValue_; + +public: + MipSolution(operations_research::MPSolver::ResultStatus responseStatus, + const std::map& solution, + double objectiveValue) : + responseStatus_(responseStatus), objectiveValue_(objectiveValue) { - // TODO: improve this by removing dependency to OR-Tools - private: - operations_research::MPSolver::ResultStatus responseStatus_; - std::map solution_; - public: - MipSolution(operations_research::MPSolver::ResultStatus responseStatus, const std::map& solution) : responseStatus_(responseStatus) + // Only store non-zero values + for (const auto& varAndValue : solution) { - // Only store non-zero values - for (const auto& varAndValue : solution) + if (abs(varAndValue.second) > 1e-6) // TODO: is this tolerance OK? { - if (abs(varAndValue.second) > 1e-6) // TODO: is this tolerance OK? - { - solution_.insert(varAndValue); - } + solution_.insert(varAndValue); } } + } - operations_research::MPSolver::ResultStatus getStatus() { return responseStatus_; } + operations_research::MPSolver::ResultStatus getStatus() + { + return responseStatus_; + } - double getOptimalValue(const std::string& variableName) - { - return solution_.contains(variableName) ? solution_.at(variableName) : 0; - } + double getOptimalValue(const std::string& variableName) + { + return solution_.contains(variableName) ? solution_.at(variableName) : 0; + } - std::vector getOptimalValues(const std::vector& variableNames) + std::vector getOptimalValues(const std::vector& variableNames) + { + std::vector solution; + solution.reserve(variableNames.size()); + for (const auto& varName : variableNames) { - std::vector solution; - solution.reserve(variableNames.size()); - for (const auto& varName : variableNames) { - solution.push_back(getOptimalValue(varName)); - } - return solution; + solution.push_back(getOptimalValue(varName)); } - }; -} + return solution; + } + + double getObjectiveValue() + { + return objectiveValue_; + } +}; +} // namespace Antares::optim::api diff --git a/src/solver/optim/impl/LinearProblemImpl.cpp b/src/solver/optim/impl/LinearProblemImpl.cpp index 2bad8062a2..f7a6dfe5c9 100644 --- a/src/solver/optim/impl/LinearProblemImpl.cpp +++ b/src/solver/optim/impl/LinearProblemImpl.cpp @@ -12,18 +12,17 @@ LinearProblemImpl::LinearProblemImpl(bool isMip, const std::string& solverName) this->mpSolver = MPSolverFactory(isMip, solverName); } - MPVariable& LinearProblemImpl::addNumVariable(string name, double lb, double ub) { - // TODO: OR-Tools does not seem to care if you try to add two different variables / constraints with the same name - // This is pretty dangerous, so we have to forbid it ourselves + // TODO: OR-Tools does not seem to care if you try to add two different variables / constraints + // with the same name This is pretty dangerous, so we have to forbid it ourselves return *mpSolver->MakeNumVar(lb, ub, name); } MPVariable& LinearProblemImpl::addIntVariable(string name, double lb, double ub) { - // TODO: OR-Tools does not seem to care if you try to add two different variables / constraints with the same name - // This is pretty dangerous, so we have to forbid it ourselves + // TODO: OR-Tools does not seem to care if you try to add two different variables / constraints + // with the same name This is pretty dangerous, so we have to forbid it ourselves return *mpSolver->MakeIntVar(lb, ub, name); } @@ -34,15 +33,18 @@ MPVariable& LinearProblemImpl::getVariable(string name) MPConstraint& LinearProblemImpl::addConstraint(string name, double lb, double ub) { - // TODO: OR-Tools does not seem to care if you try to add two different variables / constraints with the same name - // This is pretty dangerous, so we have to forbid it ourselves + // TODO: OR-Tools does not seem to care if you try to add two different variables / constraints + // with the same name This is pretty dangerous, so we have to forbid it ourselves return *mpSolver->MakeRowConstraint(lb, ub, name); } -MPConstraint& LinearProblemImpl::addBalanceConstraint(string name, double bound, string nodeName, int timestep) +MPConstraint& LinearProblemImpl::addBalanceConstraint(string name, + double bound, + string nodeName, + int timestep) { - // TODO: OR-Tools does not seem to care if you try to add two different variables / constraints with the same name - // This is pretty dangerous, so we have to forbid it ourselves + // TODO: OR-Tools does not seem to care if you try to add two different variables / constraints + // with the same name This is pretty dangerous, so we have to forbid it ourselves // TODO: log ignored arguments return this->addConstraint(name, bound, bound); } @@ -61,14 +63,24 @@ void LinearProblemImpl::setObjectiveCoefficient(const MPVariable& variable, doub mpSolver->MutableObjective()->SetCoefficient(&variable, coefficient); } +double LinearProblemImpl::getObjectiveCoefficient(const MPVariable& variable) +{ + return mpSolver->MutableObjective()->GetCoefficient(&variable); +} + void LinearProblemImpl::setMinimization(bool isMinim) { - isMinim ? mpSolver->MutableObjective()->SetMinimization() : mpSolver->MutableObjective()->SetMaximization(); + isMinim ? mpSolver->MutableObjective()->SetMinimization() + : mpSolver->MutableObjective()->SetMaximization(); } MipSolution LinearProblemImpl::solve(const operations_research::MPSolverParameters& param) { - mpSolver->EnableOutput(); + // mpSolver->EnableOutput(); + // std::string model; + // std::ofstream m("/tmp/model.lp"); + // if (m && mpSolver->ExportModelAsLpFormat(false, &model)) + // m << model; auto status = mpSolver->Solve(param); // TODO remove this // std::string str; @@ -78,7 +90,12 @@ MipSolution LinearProblemImpl::solve(const operations_research::MPSolverParamete { solution.insert({var->name(), var->solution_value()}); } - return {status, solution}; + return {status, solution, mpSolver->Objective().Value()}; +} + +double LinearProblemImpl::infinity() +{ + return mpSolver->solver_infinity(); } LinearProblemImpl::~LinearProblemImpl() = default; diff --git a/src/solver/optim/impl/include/antares/optim/impl/LinearProblemImpl.h b/src/solver/optim/impl/include/antares/optim/impl/LinearProblemImpl.h index 2b6bb30a08..a9cb6e2dd6 100644 --- a/src/solver/optim/impl/include/antares/optim/impl/LinearProblemImpl.h +++ b/src/solver/optim/impl/include/antares/optim/impl/LinearProblemImpl.h @@ -36,16 +36,31 @@ class LinearProblemImpl : public Antares::optim::api::LinearProblem operations_research::MPSolver* mpSolver{}; // TODO: remove this constructor when legacy support is dropped LinearProblemImpl(); -public : + std::set constraintNames_; + +public: LinearProblemImpl(bool isMip, const std::string& solverName); - operations_research::MPVariable& addNumVariable(std::string name, double lb, double ub) override; - operations_research::MPVariable& addIntVariable(std::string name, double lb, double ub) override; + operations_research::MPVariable& addNumVariable(std::string name, + double lb, + double ub) override; + operations_research::MPVariable& addIntVariable(std::string name, + double lb, + double ub) override; operations_research::MPVariable& getVariable(std::string name) override; - operations_research::MPConstraint& addConstraint(std::string name, double lb, double ub) override; - operations_research::MPConstraint& addBalanceConstraint(std::string name, double bound, std::string nodeName, int timestep) override; + operations_research::MPConstraint& addConstraint(std::string name, + double lb, + double ub) override; + operations_research::MPConstraint& addBalanceConstraint(std::string name, + double bound, + std::string nodeName, + int timestep) override; operations_research::MPConstraint& getConstraint(std::string name) override; - void setObjectiveCoefficient(const operations_research::MPVariable& variable, double coefficient) override; + void setObjectiveCoefficient(const operations_research::MPVariable& variable, + double coefficient) override; + double getObjectiveCoefficient(const operations_research::MPVariable& variable) override; void setMinimization(bool isMinim) override; - Antares::optim::api::MipSolution solve(const operations_research::MPSolverParameters& param) override; + Antares::optim::api::MipSolution solve( + const operations_research::MPSolverParameters& param) override; + double infinity() override; virtual ~LinearProblemImpl() override; }; diff --git a/src/solver/optimisation/LegacyLinearProblemFillerImpl.cpp b/src/solver/optimisation/LegacyLinearProblemFillerImpl.cpp index 8db3b59b7a..3d9963d17f 100644 --- a/src/solver/optimisation/LegacyLinearProblemFillerImpl.cpp +++ b/src/solver/optimisation/LegacyLinearProblemFillerImpl.cpp @@ -30,57 +30,66 @@ using namespace Antares::optim::api; -void LegacyLinearProblemFillerImpl::addVariables(LinearProblem& problem, const LinearProblemData& data) +void LegacyLinearProblemFillerImpl::addVariables(LinearProblem& problem, + const LinearProblemData& data, + const BuildContext& ctx) { - // For now, only one "fill" method is developed in order for the legacy problem to fill the ORTools matrix - // It is called here since addVariables is called first in problem builder - // If needed, we shall split it up in three functions - // Also, no "fill(linearProblem)" has been developed yet, so we are casting the linearProblem and fetching its - // underlying MPSolver object in order to fill it. If needed, we should change this in the future. + // For now, only one "fill" method is developed in order for the legacy problem to fill the + // ORTools matrix It is called here since addVariables is called first in problem builder If + // needed, we shall split it up in three functions Also, no "fill(linearProblem)" has been + // developed yet, so we are casting the linearProblem and fetching its underlying MPSolver + // object in order to fill it. If needed, we should change this in the future. Antares::Optimization::ProblemSimplexeNommeConverter converter("mock", legacyProblem_); - if (auto *legacyLinearProblem = dynamic_cast(&problem)) { + if (auto* legacyLinearProblem = dynamic_cast(&problem)) + { converter.Fill(legacyLinearProblem->getMpSolver()); declareBalanceConstraints(legacyLinearProblem, data.legacy); - } else { + } + else + { // throw } }; -void LegacyLinearProblemFillerImpl::addConstraints(LinearProblem& problem, const LinearProblemData& data) -{ -// nothing to do: everything has been done in addVariables +void LegacyLinearProblemFillerImpl::addConstraints(LinearProblem& problem, + const LinearProblemData& data, + const BuildContext& ctx){ + // nothing to do: everything has been done in addVariables }; -void LegacyLinearProblemFillerImpl::addObjective(LinearProblem& problem, const LinearProblemData& data) +void LegacyLinearProblemFillerImpl::addObjective(LinearProblem& problem, + const LinearProblemData& data, + const BuildContext& ctx) { -// nothing to do: everything has been done in addVariables + // nothing to do: everything has been done in addVariables } void LegacyLinearProblemFillerImpl::update(LinearProblem& problem, const LinearProblemData& data) { -// TODO + // TODO } -// TODO move to class LegacyLinearProblem? Maybe simpler if we don't need the "update" method after all +// TODO move to class LegacyLinearProblem? Maybe simpler if we don't need the "update" method after +// all -// Tell the LegacyLinearProblem what the balance constraints are, in order to be able to add new models to existing nodes -void LegacyLinearProblemFillerImpl::declareBalanceConstraints(LegacyLinearProblemImpl *legacyLinearProblem, - const LinearProblemData::Legacy& legacy) +// Tell the LegacyLinearProblem what the balance constraints are, in order to be able to add new +// models to existing nodes +void LegacyLinearProblemFillerImpl::declareBalanceConstraints( + LegacyLinearProblemImpl* legacyLinearProblem, + const LinearProblemData::Legacy& legacy) { - const auto* solver = legacyLinearProblem->getMpSolver(); - auto* constraintMapping = legacy.constraintMapping; + const auto* solver = legacyLinearProblem->getMpSolver(); + auto* constraintMapping = legacy.constraintMapping; - for (unsigned int timestamp = 0; timestamp < constraintMapping->size(); timestamp++) - { - const auto& BalanceAtT = constraintMapping->at(timestamp).NumeroDeContrainteDesBilansPays; - for (unsigned areaIndex = 0; areaIndex < BalanceAtT.size(); areaIndex++) - { - int cnt = BalanceAtT[areaIndex]; - operations_research::MPConstraint* constraint = solver->constraint(cnt); - legacyLinearProblem->declareBalanceConstraint(std::string(legacy.areaNames->at(areaIndex)), timestamp, constraint); - } - } + for (unsigned int timestamp = 0; timestamp < constraintMapping->size(); timestamp++) + { + const auto& BalanceAtT = constraintMapping->at(timestamp).NumeroDeContrainteDesBilansPays; + for (unsigned areaIndex = 0; areaIndex < BalanceAtT.size(); areaIndex++) + { + int cnt = BalanceAtT[areaIndex]; + operations_research::MPConstraint* constraint = solver->constraint(cnt); + legacyLinearProblem->declareBalanceConstraint( + std::string(legacy.areaNames->at(areaIndex)), timestamp, constraint); + } + } } - - - diff --git a/src/solver/optimisation/LegacyLinearProblemFillerImpl.h b/src/solver/optimisation/LegacyLinearProblemFillerImpl.h index 1a196b15d6..faf36a30a8 100644 --- a/src/solver/optimisation/LegacyLinearProblemFillerImpl.h +++ b/src/solver/optimisation/LegacyLinearProblemFillerImpl.h @@ -34,11 +34,22 @@ class LegacyLinearProblemFillerImpl final : public Antares::optim::api::LinearPr { private: const Antares::Optimization::PROBLEME_SIMPLEXE_NOMME* legacyProblem_; - static void declareBalanceConstraints(LegacyLinearProblemImpl* legacyLinearProblem, const LinearProblemData::Legacy& legacy); + static void declareBalanceConstraints(LegacyLinearProblemImpl* legacyLinearProblem, + const LinearProblemData::Legacy& legacy); + public: - explicit LegacyLinearProblemFillerImpl(const Antares::Optimization::PROBLEME_SIMPLEXE_NOMME* legacyProblem) : legacyProblem_(legacyProblem) {}; - void addVariables(Antares::optim::api::LinearProblem& problem, const Antares::optim::api::LinearProblemData& data) override; - void addConstraints(Antares::optim::api::LinearProblem& problem, const Antares::optim::api::LinearProblemData& data) override; - void addObjective(Antares::optim::api::LinearProblem& problem, const Antares::optim::api::LinearProblemData& data) override; - void update(Antares::optim::api::LinearProblem& problem, const Antares::optim::api::LinearProblemData& data) override; + explicit LegacyLinearProblemFillerImpl( + const Antares::Optimization::PROBLEME_SIMPLEXE_NOMME* legacyProblem) : + legacyProblem_(legacyProblem){}; + void addVariables(Antares::optim::api::LinearProblem& problem, + const Antares::optim::api::LinearProblemData& data, + const BuildContext&) override; + void addConstraints(Antares::optim::api::LinearProblem& problem, + const Antares::optim::api::LinearProblemData& data, + const BuildContext&) override; + void addObjective(Antares::optim::api::LinearProblem& problem, + const Antares::optim::api::LinearProblemData& data, + const BuildContext&) override; + void update(Antares::optim::api::LinearProblem& problem, + const Antares::optim::api::LinearProblemData& data) override; }; diff --git a/src/solver/optimisation/LegacyLinearProblemImpl.cpp b/src/solver/optimisation/LegacyLinearProblemImpl.cpp index cfffa2ac42..77acc03611 100644 --- a/src/solver/optimisation/LegacyLinearProblemImpl.cpp +++ b/src/solver/optimisation/LegacyLinearProblemImpl.cpp @@ -9,8 +9,10 @@ using namespace Antares::optim::api; using namespace std; -LegacyLinearProblemImpl::LegacyLinearProblemImpl(const Antares::Optimization::PROBLEME_SIMPLEXE_NOMME *legacyProblem, - const std::string &solverName) : LinearProblemImpl() +LegacyLinearProblemImpl::LegacyLinearProblemImpl( + const Antares::Optimization::PROBLEME_SIMPLEXE_NOMME* legacyProblem, + const std::string& solverName) : + LinearProblemImpl() { this->mpSolver = MPSolverFactory(legacyProblem, solverName); Antares::Optimization::ProblemSimplexeNommeConverter converter(solverName, legacyProblem); @@ -18,38 +20,51 @@ LegacyLinearProblemImpl::LegacyLinearProblemImpl(const Antares::Optimization::PR mpSolver->MutableObjective()->SetMinimization(); } -MPConstraint& LegacyLinearProblemImpl::addBalanceConstraint(string name, double bound, string nodeName, int timestamp) +MPConstraint& LegacyLinearProblemImpl::addBalanceConstraint(string name, + double bound, + string nodeName, + int timestamp) { string key = getBalanceConstraintKey(nodeName, timestamp); - if (balanceConstraintPerNodeName.contains(key)) { + if (balanceConstraintPerNodeName.contains(key)) + { // add new name declared by filler to list of aliases of the existing constraint balanceConstraintPerNodeName.at(key).second.push_back(name); // return the existing constraint return *balanceConstraintPerNodeName.at(key).first; } - MPConstraint *constraint = mpSolver->MakeRowConstraint(bound, bound, name); + MPConstraint* constraint = mpSolver->MakeRowConstraint(bound, bound, name); balanceConstraintPerNodeName.insert({key, {constraint, {name}}}); return *constraint; } void LegacyLinearProblemImpl::setMinimization(bool isMinim) { - if (!isMinim) { + if (!isMinim) + { // TODO: improve exception throw; - } else { + } + else + { mpSolver->MutableObjective()->SetMinimization(); } } -void LegacyLinearProblemImpl::declareBalanceConstraint(const string &nodeName, int timestamp, MPConstraint *constraint) +void LegacyLinearProblemImpl::declareBalanceConstraint(const string& nodeName, + int timestamp, + MPConstraint* constraint) { - if (balanceConstraintPerNodeName.contains(nodeName)) { + if (balanceConstraintPerNodeName.contains(nodeName)) + { // TODO: improve exception throw; - } else { + } + else + { auto name = constraint->name(); - balanceConstraintPerNodeName.insert({getBalanceConstraintKey(nodeName, timestamp), {constraint, {name}}}); + balanceConstraintPerNodeName.insert( + {getBalanceConstraintKey(nodeName, timestamp), {constraint, {name}}}); } } diff --git a/src/solver/optimisation/opt_appel_solveur_lineaire.cpp b/src/solver/optimisation/opt_appel_solveur_lineaire.cpp index eb67b505a1..4a5630fbcf 100644 --- a/src/solver/optimisation/opt_appel_solveur_lineaire.cpp +++ b/src/solver/optimisation/opt_appel_solveur_lineaire.cpp @@ -96,19 +96,17 @@ struct SimplexResult mpsWriterFactory mps_writer_factory; }; -static SimplexResult OPT_TryToCallSimplex( - const OptimizationOptions& options, - PROBLEME_HEBDO* problemeHebdo, - Optimization::PROBLEME_SIMPLEXE_NOMME& Probleme, - const int NumIntervalle, - const int optimizationNumber, - const OptPeriodStringGenerator& optPeriodStringGenerator, - bool PremierPassage, - IResultWriter& writer) +static SimplexResult OPT_TryToCallSimplex(const OptimizationOptions& options, + PROBLEME_HEBDO* problemeHebdo, + Optimization::PROBLEME_SIMPLEXE_NOMME& Probleme, + const int NumIntervalle, + const int optimizationNumber, + const OptPeriodStringGenerator& optPeriodStringGenerator, + bool PremierPassage, + IResultWriter& writer) { const auto& ProblemeAResoudre = problemeHebdo->ProblemeAResoudre; - auto ProbSpx - = (PROBLEME_SPX*)(ProblemeAResoudre->ProblemesSpx[(int)NumIntervalle]); + auto ProbSpx = (PROBLEME_SPX*)(ProblemeAResoudre->ProblemesSpx[(int)NumIntervalle]); auto solver = (MPSolver*)(ProblemeAResoudre->ProblemesSpx[(int)NumIntervalle]); const int opt = optimizationNumber - 1; @@ -154,8 +152,9 @@ static SimplexResult OPT_TryToCallSimplex( if (options.useOrtools) { // TODO comment gérer le update ??? => ajouter une méthode update à LinearProblem ? - ORTOOLS_ModifierLeVecteurCouts( - solver, ProblemeAResoudre->CoutLineaire.data(), ProblemeAResoudre->NombreDeVariables); + ORTOOLS_ModifierLeVecteurCouts(solver, + ProblemeAResoudre->CoutLineaire.data(), + ProblemeAResoudre->NombreDeVariables); ORTOOLS_ModifierLeVecteurSecondMembre(solver, ProblemeAResoudre->SecondMembre.data(), ProblemeAResoudre->Sens.data(), @@ -168,8 +167,9 @@ static SimplexResult OPT_TryToCallSimplex( } else { - SPX_ModifierLeVecteurCouts( - ProbSpx, ProblemeAResoudre->CoutLineaire.data(), ProblemeAResoudre->NombreDeVariables); + SPX_ModifierLeVecteurCouts(ProbSpx, + ProblemeAResoudre->CoutLineaire.data(), + ProblemeAResoudre->NombreDeVariables); SPX_ModifierLeVecteurSecondMembre(ProbSpx, ProblemeAResoudre->SecondMembre.data(), ProblemeAResoudre->Sens.data(), @@ -196,7 +196,7 @@ static SimplexResult OPT_TryToCallSimplex( Probleme.NombreDeTermesDesLignes = ProblemeAResoudre->NombreDeTermesDesLignes.data(); Probleme.IndicesColonnes = ProblemeAResoudre->IndicesColonnes.data(); Probleme.CoefficientsDeLaMatriceDesContraintes - = ProblemeAResoudre->CoefficientsDeLaMatriceDesContraintes.data(); + = ProblemeAResoudre->CoefficientsDeLaMatriceDesContraintes.data(); Probleme.Sens = ProblemeAResoudre->Sens.data(); Probleme.SecondMembre = ProblemeAResoudre->SecondMembre.data(); @@ -204,7 +204,7 @@ static SimplexResult OPT_TryToCallSimplex( Probleme.TypeDePricing = PRICING_STEEPEST_EDGE; - Probleme.FaireDuScaling = ( PremierPassage ? OUI_SPX : NON_SPX ); + Probleme.FaireDuScaling = (PremierPassage ? OUI_SPX : NON_SPX); Probleme.StrategieAntiDegenerescence = AGRESSIF; @@ -226,7 +226,8 @@ static SimplexResult OPT_TryToCallSimplex( LinearProblemBuilder linearProblemBuilder(legacyLinearProblem); if (options.useOrtools) { - auto filler = std::make_shared(&Probleme); // TODO: merge this with LegacyLinearProblemImpl ? + auto filler = std::make_shared( + &Probleme); // TODO: merge this with LegacyLinearProblemImpl ? linearProblemBuilder.addFiller(filler); // TODO: we can add extra fillers here for (const auto& additionalFiller : gAdditionalFillers) @@ -234,13 +235,19 @@ static SimplexResult OPT_TryToCallSimplex( // sinon renvoyer le builder ou le problem à une autre classe // Required for the balance constraint indices - gLinearProblemData.legacy.constraintMapping = &problemeHebdo->CorrespondanceCntNativesCntOptim; + gLinearProblemData.legacy.constraintMapping + = &problemeHebdo->CorrespondanceCntNativesCntOptim; gLinearProblemData.legacy.areaNames = &problemeHebdo->NomsDesPays; // TODO : add data here - linearProblemBuilder.build(gLinearProblemData); + std::vector timeStamps(168); + std::iota(timeStamps.begin(), timeStamps.end(), 0); + + BuildContext buildCtx({0}, timeStamps); + linearProblemBuilder.build(gLinearProblemData, buildCtx); solver = legacyLinearProblem.getMpSolver(); - // TODO: because of LinearProblemImpl's destructor, when we exit this scope, the MPSolver instance is destroyed - // We have to work around this in order for the current "update" methods to work + // TODO: because of LinearProblemImpl's destructor, when we exit this scope, the MPSolver + // instance is destroyed We have to work around this in order for the current "update" + // methods to work } const std::string filename = createMPSfilename(optPeriodStringGenerator, optimizationNumber); @@ -258,10 +265,7 @@ static SimplexResult OPT_TryToCallSimplex( if (options.useOrtools) { const bool keepBasis = (optimizationNumber == PREMIERE_OPTIMISATION); - solver = ORTOOLS_Simplexe(&Probleme, - solver, - linearProblemBuilder, - keepBasis); + solver = ORTOOLS_Simplexe(&Probleme, solver, linearProblemBuilder, keepBasis); if (solver != nullptr) { ProblemeAResoudre->ProblemesSpx[NumIntervalle] = (void*)solver; @@ -300,8 +304,9 @@ static SimplexResult OPT_TryToCallSimplex( { logs.info() << " solver: resetting"; } - return {.success=false, .timeMeasure=timeMeasure, - .mps_writer_factory=mps_writer_factory}; + return {.success = false, + .timeMeasure = timeMeasure, + .mps_writer_factory = mps_writer_factory}; } else @@ -309,8 +314,7 @@ static SimplexResult OPT_TryToCallSimplex( throw FatalError("Internal error: insufficient memory"); } } - return {.success=true, .timeMeasure=timeMeasure, - .mps_writer_factory=mps_writer_factory}; + return {.success = true, .timeMeasure = timeMeasure, .mps_writer_factory = mps_writer_factory}; } bool OPT_AppelDuSimplexe(const OptimizationOptions& options, @@ -331,16 +335,27 @@ bool OPT_AppelDuSimplexe(const OptimizationOptions& options, bool PremierPassage = true; - SimplexResult simplexResult = - OPT_TryToCallSimplex(options, problemeHebdo, Probleme, NumIntervalle, optimizationNumber, - optPeriodStringGenerator, PremierPassage, writer); + SimplexResult simplexResult = OPT_TryToCallSimplex(options, + problemeHebdo, + Probleme, + NumIntervalle, + optimizationNumber, + optPeriodStringGenerator, + PremierPassage, + writer); if (!simplexResult.success) { // TODO : why ?? PremierPassage = false; - simplexResult = OPT_TryToCallSimplex(options, problemeHebdo, Probleme, NumIntervalle, optimizationNumber, - optPeriodStringGenerator, PremierPassage, writer); + simplexResult = OPT_TryToCallSimplex(options, + problemeHebdo, + Probleme, + NumIntervalle, + optimizationNumber, + optPeriodStringGenerator, + PremierPassage, + writer); } if (ProblemeAResoudre->ExistenceDUneSolution == OUI_SPX) @@ -399,14 +414,16 @@ bool OPT_AppelDuSimplexe(const OptimizationOptions& options, Probleme.SetUseNamedProblems(true); // Analyse d'infaisa en clonant le MPSolver existant => on peut ignorer ça pour l'instant - auto MPproblem = std::shared_ptr(ProblemSimplexeNommeConverter(options.solverName, &Probleme).Convert()); + auto MPproblem = std::shared_ptr( + ProblemSimplexeNommeConverter(options.solverName, &Probleme).Convert()); auto analyzer = makeUnfeasiblePbAnalyzer(); analyzer->run(MPproblem.get()); analyzer->printReport(); auto mps_writer_on_error = simplexResult.mps_writer_factory.createOnOptimizationError(); - const std::string filename = createMPSfilename(optPeriodStringGenerator, optimizationNumber); + const std::string filename + = createMPSfilename(optPeriodStringGenerator, optimizationNumber); mps_writer_on_error->runIfNeeded(writer, filename); return false; diff --git a/src/solver/optimisation/opt_global.cpp b/src/solver/optimisation/opt_global.cpp index 0d92ed451b..200b563f9c 100644 --- a/src/solver/optimisation/opt_global.cpp +++ b/src/solver/optimisation/opt_global.cpp @@ -2,6 +2,6 @@ // TODO one object per thread (numSpace) // TODO eliminate global variables -Antares::optim::api::LinearProblemData gLinearProblemData({}, 0, {}, {}); -Antares::optim::api::MipSolution gMipSolution(operations_research::MPSolver::NOT_SOLVED, {}); +Antares::optim::api::LinearProblemData gLinearProblemData(0, {}, {}); +Antares::optim::api::MipSolution gMipSolution(operations_research::MPSolver::NOT_SOLVED, {}, 0.); std::vector> gAdditionalFillers; diff --git a/src/tests/poc/CMakeLists.txt b/src/tests/poc/CMakeLists.txt index 3cc82d789e..970d2ad077 100644 --- a/src/tests/poc/CMakeLists.txt +++ b/src/tests/poc/CMakeLists.txt @@ -1,2 +1,2 @@ -add_subdirectory(new-workflow) add_subdirectory(legacy-workflow) +add_subdirectory(new-workflow) diff --git a/src/tests/poc/include/simple/Balance.h b/src/tests/poc/include/simple/Balance.h deleted file mode 100644 index 59ac0d1b26..0000000000 --- a/src/tests/poc/include/simple/Balance.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include - -#include "antares/optim/api/LinearProblemFiller.h" -#include "Battery.h" -#include "Thermal.h" - -using namespace Antares::optim::api; -using namespace std; - -class Balance : public LinearProblemFiller -{ -private: - string nodeName_; - vector> batteries_; // sera remplacé par la notion de ports - vector> thermals_; // sera remplacé par la notion de ports -public: - Balance(string nodeName, vector> batteries, vector> thermals) : - nodeName_(std::move(nodeName)), batteries_(std::move(batteries)), thermals_(std::move(thermals)) {}; - void addVariables(LinearProblem& problem, const LinearProblemData& data) override; - void addConstraints(LinearProblem& problem, const LinearProblemData& data) override; - void addObjective(LinearProblem& problem, const LinearProblemData& data) override; - void update(LinearProblem& problem, const LinearProblemData& data) override; -}; - -void Balance::addVariables(LinearProblem& problem, const LinearProblemData& data) -{ - // nothing to do -} - -void Balance::addConstraints(LinearProblem& problem, const LinearProblemData& data) -{ - if (!data.hasTimedData("consumption_" + nodeName_)) { - throw; - } - auto consumption = data.getTimedData("consumption_" + nodeName_); - // IMPORTANT : we have to use the convention -production = -consumption, in order to be compatible - // with the legacy code's balance constraint - for (auto ts : data.getTimeStamps()) { - auto balanceConstraint = - &problem.addBalanceConstraint("Balance_" + nodeName_ + "_" + to_string(ts), -consumption[ts], nodeName_, ts); - - for (const auto& battery : batteries_) { - auto p = &problem.getVariable(battery->getPVarName(ts)); - balanceConstraint->SetCoefficient(p, -1); - } - for (const auto& thermal : thermals_) { - auto p = &problem.getVariable(thermal->getPVarName(ts)); - balanceConstraint->SetCoefficient(p, -1); - } - } -} - -void Balance::addObjective(Antares::optim::api::LinearProblem& problem, const LinearProblemData& data) -{ - // nothing to do -} - -void Balance::update(Antares::optim::api::LinearProblem& problem, const LinearProblemData& data) -{ - if (!data.hasTimedData("consumption_" + nodeName_)) { - throw; - } - auto consumption = data.getTimedData("consumption_" + nodeName_); - for (auto ts : data.getTimeStamps()) { - auto balanceConstraint = &problem.getConstraint("Balance_" + nodeName_ + "_" + to_string(ts)); - balanceConstraint->SetLB(consumption[ts]); - balanceConstraint->SetUB(consumption[ts]); - } -} diff --git a/src/tests/poc/include/simple/Battery.h b/src/tests/poc/include/simple/Battery.h deleted file mode 100644 index 618bdbae79..0000000000 --- a/src/tests/poc/include/simple/Battery.h +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -#include - -#include "vector" -#include "antares/optim/api/LinearProblemFiller.h" - -using namespace Antares::optim::api; -using namespace std; - -class Battery : public LinearProblemFiller -{ -private: - string id_; - double maxP_; - double maxStock_; - vector pVarNames; - vector stockVarNames; -public: - Battery(string id, double maxP, double maxStock) : id_(std::move(id)), maxP_(maxP), maxStock_(maxStock) - { - - }; - void addVariables(LinearProblem& problem, const LinearProblemData& data) override; - void addConstraints(LinearProblem& problem, const LinearProblemData& data) override; - void addObjective(LinearProblem& problem, const LinearProblemData& data) override; - void update(LinearProblem& problem, const LinearProblemData& data) override; - string getPVarName(int ts); // sera remplacé par la notion de ports -}; - -void Battery::addVariables(LinearProblem& problem, const LinearProblemData& data) -{ - auto timestamps = data.getTimeStamps(); - pVarNames.reserve(timestamps.size()); - stockVarNames.reserve(timestamps.size()); - for (auto ts : timestamps) { - string pVarName = "P_" + id_ + "_" + to_string(ts); - problem.addNumVariable(pVarName, -maxP_, maxP_); - // - charge - // + décharge - pVarNames.push_back(pVarName); - - string stockVarName = "Stock_" + id_ + "_" + to_string(ts); - problem.addNumVariable(stockVarName, 0, maxStock_); - stockVarNames.push_back(stockVarName); - } -} - -void Battery::addConstraints(LinearProblem& problem, const LinearProblemData& data) -{ - if (!data.hasScalarData("initialStock_" + id_)) { - throw; - } - double initialStock = data.getScalarData("initialStock_" + id_); - for (auto ts : data.getTimeStamps()) { - auto p = &problem.getVariable(pVarNames[ts]); - auto e = &problem.getVariable(stockVarNames[ts]); - - // Stock(t) = Stock(t-T) - T/60 * P(t) - auto stockConstraint = &problem.addConstraint("Stock_constr_" + to_string(ts), 0, 0); - stockConstraint->SetCoefficient(e, 1); - stockConstraint->SetCoefficient(p, data.getTimeResolutionInMinutes() * 1.0 / 60.0); - if (ts > 0) - { - auto previousE = &problem.getVariable(stockVarNames[ts - 1]); - stockConstraint->SetCoefficient(previousE, -1); - } - else - { - stockConstraint->SetLB(initialStock); - stockConstraint->SetUB(initialStock); - } - } -} - -void Battery::addObjective(Antares::optim::api::LinearProblem& problem, const LinearProblemData& data) -{ - // nothing to do -} - -void Battery::update(Antares::optim::api::LinearProblem& problem, const LinearProblemData& data) -{ - // nothing to do -} - -string Battery::getPVarName(int ts) -{ - return pVarNames[ts]; -} diff --git a/src/tests/poc/include/simple/ProductionPriceMinimization.h b/src/tests/poc/include/simple/ProductionPriceMinimization.h deleted file mode 100644 index dac518bdf1..0000000000 --- a/src/tests/poc/include/simple/ProductionPriceMinimization.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include - -#include "antares/optim/api/LinearProblemFiller.h" -#include "Thermal.h" - -using namespace Antares::optim::api; -using namespace std; - -class ProductionPriceMinimization : public LinearProblemFiller -{ -private: - vector> thermals_; // sera remplacé par la notion de ports -public: - explicit ProductionPriceMinimization(vector> thermals) : thermals_(std::move(thermals)) {}; - void addVariables(LinearProblem& problem, const LinearProblemData& data) override; - void addConstraints(LinearProblem& problem, const LinearProblemData& data) override; - void addObjective(LinearProblem& problem, const LinearProblemData& data) override; - void update(LinearProblem& problem, const LinearProblemData& data) override; -}; - -void ProductionPriceMinimization::addVariables(LinearProblem& problem, const LinearProblemData& data) -{ - // nothing to do -} - -void ProductionPriceMinimization::addConstraints(LinearProblem& problem, const LinearProblemData& data) -{ - // nothing to do -} - -void ProductionPriceMinimization::addObjective(Antares::optim::api::LinearProblem& problem, const LinearProblemData& data) -{ - problem.setMinimization(true); - for (auto ts : data.getTimeStamps()) { - for (const auto& thermal : thermals_) { - auto* p = &problem.getVariable(thermal->getPVarName(ts)); - problem.setObjectiveCoefficient(*p, thermal->getPCost(ts)); - } - } -} - -void ProductionPriceMinimization::update(Antares::optim::api::LinearProblem& problem, const LinearProblemData& data) -{ - // nothing to do -} diff --git a/src/tests/poc/include/simple/Thermal.h b/src/tests/poc/include/simple/Thermal.h deleted file mode 100644 index 00290c9858..0000000000 --- a/src/tests/poc/include/simple/Thermal.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include - -#include "vector" -#include "antares/optim/api/LinearProblemFiller.h" - -using namespace Antares::optim::api; -using namespace std; - -class Thermal : public LinearProblemFiller -{ -private: - string id_; - double maxP_; - vector pCost_; // TODO : put in LinearProblemData ? - vector pVarNames; -public: - Thermal(string id, double maxP) : id_(std::move(id)), maxP_(maxP) - {} - void addVariables(LinearProblem& problem, const LinearProblemData& data) override; - void addConstraints(LinearProblem& problem, const LinearProblemData& data) override; - void addObjective(LinearProblem& problem, const LinearProblemData& data) override; - void update(LinearProblem& problem, const LinearProblemData& data) override; - - string getPVarName(int ts); // sera remplacé par la notion de ports - double getPCost(int ts); // sera remplacé par la notion de ports -}; - -void Thermal::addVariables(LinearProblem& problem, const LinearProblemData& data) -{ - pVarNames.reserve(data.getTimeStamps().size()); - for (auto ts : data.getTimeStamps()) { - string pVarName = "P_" + id_ + "_" + to_string(ts); - problem.addNumVariable(pVarName, 0, maxP_); - pVarNames.push_back(pVarName); - } - // keep cost data for later (will be replaced with ports) - if (!data.hasTimedData("cost_" + id_)) { - throw; - } - pCost_ = data.getTimedData("cost_" + id_); -} - -void Thermal::addConstraints(LinearProblem& problem, const LinearProblemData& data) -{ - // nothing to do -} - -void Thermal::addObjective(Antares::optim::api::LinearProblem& problem, const LinearProblemData& data) -{ - // nothing to do -} - -void Thermal::update(Antares::optim::api::LinearProblem& problem, const LinearProblemData& data) -{ - // nothing to do -} - -string Thermal::getPVarName(int ts) -{ - return pVarNames[ts]; -} - -double Thermal::getPCost(int ts) -{ - return pCost_[ts]; -} - diff --git a/src/tests/poc/include/standard/Component.h b/src/tests/poc/include/standard/Component.h index 354e6035f2..e4ef49fd87 100644 --- a/src/tests/poc/include/standard/Component.h +++ b/src/tests/poc/include/standard/Component.h @@ -9,6 +9,7 @@ enum Model { THERMAL, BATTERY, + BATTERY_WITH_VARIABLE_SIZING, BALANCE, PRICE_MINIM }; @@ -19,14 +20,32 @@ class Component Model model_; map doubleParameterValues_; map stringParameterValues_; + public: - Component(string id, Model model, map doubleParameterValues, - map stringParameterValues) : id_(std::move(id)), model_(model), - doubleParameterValues_(std::move(doubleParameterValues)), - stringParameterValues_(std::move(stringParameterValues)) - {} - [[nodiscard]] string getId() const { return id_; } - [[nodiscard]] Model getModel() const { return model_; } - [[nodiscard]] double getDoubleParameterValue(const string& key) const { return doubleParameterValues_.at(key); } - [[nodiscard]] string getStringParameterValue(const string& key) const { return stringParameterValues_.at(key); } -}; \ No newline at end of file + Component(string id, + Model model, + map doubleParameterValues, + map stringParameterValues) : + id_(std::move(id)), + model_(model), + doubleParameterValues_(std::move(doubleParameterValues)), + stringParameterValues_(std::move(stringParameterValues)) + { + } + [[nodiscard]] string getId() const + { + return id_; + } + [[nodiscard]] Model getModel() const + { + return model_; + } + [[nodiscard]] double getDoubleParameterValue(const string& key) const + { + return doubleParameterValues_.at(key); + } + [[nodiscard]] string getStringParameterValue(const string& key) const + { + return stringParameterValues_.at(key); + } +}; diff --git a/src/tests/poc/include/standard/ComponentFiller.h b/src/tests/poc/include/standard/ComponentFiller.h index e78ee7ed1b..9986bfd26e 100644 --- a/src/tests/poc/include/standard/ComponentFiller.h +++ b/src/tests/poc/include/standard/ComponentFiller.h @@ -4,6 +4,7 @@ #include "antares/optim/api/LinearProblemFiller.h" #include "Component.h" #include "PortConnection.h" +#include using namespace Antares::optim::api; using namespace std; @@ -13,139 +14,275 @@ class ComponentFiller : public LinearProblemFiller private: PortConnectionsManager* portConnectionsManager_; Component component_; - [[nodiscard]] map getPortPin(string name, int timestamp, const LinearProblemData& linearProblemData) const; + [[nodiscard]] map getPortPin(string name, + int timestamp, + const LinearProblemData& linearProblemData, + BuildContext::ScenarioID scenario) const; + public: - ComponentFiller(Component component, PortConnectionsManager &portConnectionsManager) : - portConnectionsManager_(&portConnectionsManager), component_(std::move(component)) - {} - void addVariables(LinearProblem &problem, const LinearProblemData &data) override; - void addConstraints(LinearProblem &problem, const LinearProblemData &data) override; - void addObjective(LinearProblem &problem, const LinearProblemData &data) override; - void update(LinearProblem &problem, const LinearProblemData &data) override; + ComponentFiller(Component component, PortConnectionsManager& portConnectionsManager) : + portConnectionsManager_(&portConnectionsManager), component_(std::move(component)) + { + } + void addVariables(LinearProblem& problem, + const LinearProblemData& data, + const BuildContext& ctx) override; + void addConstraints(LinearProblem& problem, + const LinearProblemData& data, + const BuildContext& ctx) override; + void addObjective(LinearProblem& problem, + const LinearProblemData& data, + const BuildContext& ctx) override; + void update(LinearProblem& problem, const LinearProblemData& data) override; }; -void ComponentFiller::addVariables(LinearProblem& problem, const LinearProblemData& data) +void ComponentFiller::addVariables(LinearProblem& problem, + const LinearProblemData& data, + const BuildContext& ctx) { - // TODO : remplacer par des AST - if (component_.getModel() == THERMAL) + // TODO : replace with AST + for (auto scenario : ctx.getScenarios()) { - for (auto ts : data.getTimeStamps()) { - string pVarName = "P_" + component_.getId() + "_" + to_string(ts); - double maxP = component_.getDoubleParameterValue("maxP"); - problem.addNumVariable(pVarName, 0, maxP); + if (component_.getModel() == THERMAL) + { + for (auto ts : ctx.getTimeStamps()) + { + string pVarName + = "P_" + component_.getId() + "_" + to_string(ts) + "_" + to_string(scenario); + double maxP = component_.getDoubleParameterValue("maxP"); + problem.addNumVariable(pVarName, 0, maxP); + } + } + else if (component_.getModel() == BATTERY + || component_.getModel() == BATTERY_WITH_VARIABLE_SIZING) + { + auto timestamps = ctx.getTimeStamps(); + for (auto ts : timestamps) + { + // P < 0 : charge + // P > 0 : décharge + string pVarName + = "P_" + component_.getId() + "_" + to_string(ts) + "_" + to_string(scenario); + double maxP = component_.getDoubleParameterValue("maxP"); + problem.addNumVariable(pVarName, -maxP, maxP); + + string stockVarName + = "Stock_" + component_.getId() + "_" + to_string(ts) + "_" + to_string(scenario); + double maxStock = component_.getDoubleParameterValue("maxStock"); + problem.addNumVariable(stockVarName, 0, maxStock); + } } } - else if (component_.getModel() == BATTERY) + if (component_.getModel() == BATTERY_WITH_VARIABLE_SIZING) { - auto timestamps = data.getTimeStamps(); - for (auto ts : timestamps) { - // P < 0 : charge - // P > 0 : décharge - string pVarName = "P_" + component_.getId() + "_" + to_string(ts); - double maxP = component_.getDoubleParameterValue("maxP"); - problem.addNumVariable(pVarName, -maxP, maxP); - - string stockVarName = "Stock_" + component_.getId() + "_" + to_string(ts); - double maxStock = component_.getDoubleParameterValue("maxStock"); - problem.addNumVariable(stockVarName, 0, maxStock); - } + string maxPVarName = "maxP_" + component_.getId(); + double maxP = component_.getDoubleParameterValue("maxP"); + problem.addNumVariable(maxPVarName, 0, maxP); + + string maxStockVarName = "maxStock_" + component_.getId(); + double maxStock = component_.getDoubleParameterValue("maxStock"); + problem.addNumVariable(maxStockVarName, 0, maxStock); } } -void ComponentFiller::addConstraints(LinearProblem& problem, const LinearProblemData& data) +void ComponentFiller::addConstraints(LinearProblem& problem, + const LinearProblemData& data, + const BuildContext& ctx) { - // TODO : remplacer par des AST - if (component_.getModel() == BATTERY) { - if (!data.hasScalarData("initialStock_" + component_.getId())) { - throw; - } - double initialStock = data.getScalarData("initialStock_" + component_.getId()); - for (auto ts : data.getTimeStamps()) { - string pVarName = "P_" + component_.getId() + "_" + to_string(ts); - auto p = &problem.getVariable(pVarName); - string stockVarName = "Stock_" + component_.getId() + "_" + to_string(ts); - auto e = &problem.getVariable(stockVarName); - - // Stock(t) = Stock(t-T) - T/60 * P(t) - auto stockConstraint = &problem.addConstraint("Stock_constr_" + to_string(ts), 0, 0); - stockConstraint->SetCoefficient(e, 1); - stockConstraint->SetCoefficient(p, data.getTimeResolutionInMinutes() * 1.0 / 60.0); - if (ts > 0) + // TODO : replace with AST + if (component_.getModel() == BATTERY || component_.getModel() == BATTERY_WITH_VARIABLE_SIZING) + { + for (auto scenario : ctx.getScenarios()) + { + for (auto ts : ctx.getTimeStamps()) { - string stockVarNameTm1 = "Stock_" + component_.getId() + "_" + to_string(ts - 1); + string pVarName + = "P_" + component_.getId() + "_" + to_string(ts) + "_" + to_string(scenario); + auto p = &problem.getVariable(pVarName); + string stockVarName + = "Stock_" + component_.getId() + "_" + to_string(ts) + "_" + to_string(scenario); + auto stock = &problem.getVariable(stockVarName); + + // Stock(t) = Stock(t-T) - T/60 * P(t) + auto stockConstraint = &problem.addConstraint( + "Stock_constr_" + to_string(ts) + "_" + to_string(scenario), 0, 0); + stockConstraint->SetCoefficient(stock, 1); + stockConstraint->SetCoefficient(p, data.getTimeResolutionInMinutes() * 1.0 / 60.0); + + int previous_stock_ts; + if (ts > 0) + { + previous_stock_ts = ts - 1; + } + else + { + // We consider that the battery has to cycle, ie Stock(0-T) = Stock(final t) + previous_stock_ts = ctx.getTimeStamps()[ctx.getTimeStamps().size() - 1]; + } + string stockVarNameTm1 = "Stock_" + component_.getId() + "_" + + to_string(previous_stock_ts) + "_" + to_string(scenario); auto previousE = &problem.getVariable(stockVarNameTm1); stockConstraint->SetCoefficient(previousE, -1); + if (component_.getModel() == BATTERY_WITH_VARIABLE_SIZING) + { + // Sizing constraints + auto maxP = &problem.getVariable("maxP_" + component_.getId()); + auto maxStock = &problem.getVariable("maxStock_" + component_.getId()); + // P >= -maxP + auto maxPConstraintLB = &problem.addConstraint( + "maxStock_constr_" + to_string(ts) + "_" + to_string(scenario), + 0, + problem.infinity()); + maxPConstraintLB->SetCoefficient(p, 1); + maxPConstraintLB->SetCoefficient(maxP, 1); + // P <= maxP + auto maxPConstraintUB = &problem.addConstraint( + "maxStock_constr_" + to_string(ts) + "_" + to_string(scenario), + 0, + problem.infinity()); + maxPConstraintUB->SetCoefficient(p, -1); + maxPConstraintUB->SetCoefficient(maxP, 1); + // Stock <= maxStock + auto maxStockConstraint = &problem.addConstraint( + "maxStock_constr_" + to_string(ts) + "_" + to_string(scenario), + 0, + problem.infinity()); + maxStockConstraint->SetCoefficient(stock, -1); + maxStockConstraint->SetCoefficient(maxStock, 1); + } } - else + if (data.hasScalarData("initialStock_" + component_.getId())) { - stockConstraint->SetLB(initialStock); - stockConstraint->SetUB(initialStock); + // If the user has defined in the input data the initial stock strategy, force the + // model to reach that stock at the beginning & end of the horizon + double initialStock + = data.getScalarData("initialStock_" + component_.getId(), scenario); + int final_ts = ctx.getTimeStamps()[ctx.getTimeStamps().size() - 1]; + string finalStockVarName = "Stock_" + component_.getId() + "_" + to_string(final_ts) + + "_" + to_string(scenario); + auto finalStockVar = &problem.getVariable(finalStockVarName); + finalStockVar->SetLB(initialStock); + finalStockVar->SetUB(initialStock); } } } - else if (component_.getModel() == BALANCE) { + else if (component_.getModel() == BALANCE) + { string nodeName = component_.getStringParameterValue("nodeName"); - if (!data.hasTimedData("consumption_" + nodeName)) { + if (!data.hasTimedData("consumption_" + nodeName)) + { throw; } - auto consumption = data.getTimedData("consumption_" + nodeName); - for (auto ts : data.getTimeStamps()) { - // IMPORTANT : we have to use the convention -production = -consumption, in order to be compatible - // with the legacy code's balance constraint - auto balanceConstraint = - &problem.addBalanceConstraint("Balance_" + nodeName + "_" + to_string(ts), -consumption[ts], nodeName, ts); - for (const auto& connection : portConnectionsManager_->getConectionsTo(this, "P")) { - for (const auto& varAndCoeff : connection.componentFiller->getPortPin(connection.portName, ts, data)) + for (auto scenario : ctx.getScenarios()) + { + auto consumption = data.getTimedData("consumption_" + nodeName, scenario); + for (auto ts : ctx.getTimeStamps()) + { + // IMPORTANT : we have to use the convention -production = -consumption, in + // order to be compatible with the legacy code's balance constraint + auto balanceConstraint = &problem.addBalanceConstraint( + "Balance_" + nodeName + "_" + to_string(ts) + "_" + to_string(scenario), + -consumption[ts], + nodeName, + ts); + for (const auto& connection : portConnectionsManager_->getConectionsTo(this, "P")) { - auto p = &problem.getVariable(varAndCoeff.first); - balanceConstraint->SetCoefficient(p, varAndCoeff.second * -1.0); + for (const auto& [varName, coeff] : connection.componentFiller->getPortPin( + connection.portName, ts, data, scenario)) + { + auto p = &problem.getVariable(varName); + balanceConstraint->SetCoefficient(p, coeff * -1.0); + } } } } } } -void ComponentFiller::addObjective(Antares::optim::api::LinearProblem& problem, const LinearProblemData& data) +void ComponentFiller::addObjective(Antares::optim::api::LinearProblem& problem, + const LinearProblemData& data, + const BuildContext& ctx) { - // TODO : remplacer par des AST - if (component_.getModel() == PRICE_MINIM) { + // TODO : replace with AST + if (component_.getModel() == PRICE_MINIM) + { problem.setMinimization(true); - for (auto ts : data.getTimeStamps()) { - for (const auto& connection : portConnectionsManager_->getConectionsTo(this, "cost")) { - for (const auto& varAndCoeff : connection.componentFiller->getPortPin(connection.portName, ts, data)) + for (auto scenario : ctx.getScenarios()) + { + for (auto ts : ctx.getTimeStamps()) + { + for (const auto& connection : + portConnectionsManager_->getConectionsTo(this, "cost")) { - auto variable = &problem.getVariable(varAndCoeff.first); - problem.setObjectiveCoefficient(*variable, varAndCoeff.second); + for (const auto& [varName, coeff] : connection.componentFiller->getPortPin( + connection.portName, ts, data, scenario)) + { + auto variable = &problem.getVariable(varName); + problem.setObjectiveCoefficient( + *variable, + coeff / ctx.getScenarios().size() + + problem.getObjectiveCoefficient(*variable)); + } } } } } } -void ComponentFiller::update(Antares::optim::api::LinearProblem& problem, const LinearProblemData& data) +void ComponentFiller::update(Antares::optim::api::LinearProblem& problem, + const LinearProblemData& data) { // ? } -map ComponentFiller::getPortPin(string name, int timestamp, const LinearProblemData& data) const +map ComponentFiller::getPortPin(string name, + int timestamp, + const LinearProblemData& data, + BuildContext::ScenarioID scenario) const { - // TODO : remplacer par des AST - if (component_.getModel() == THERMAL) { - string pVarName = "P_" + component_.getId() + "_" + to_string(timestamp); - if (name == "P") { - return {{pVarName, 1.0}}; - } else if (name == "cost") { - if (!data.hasTimedData("cost_" + component_.getId())) { - throw; + // TODO : replace with AST + const std::string pVarName + = "P_" + component_.getId() + "_" + to_string(timestamp) + "_" + to_string(scenario); + + // P BATTERY/THERMAL + if (name == "P") + { + return {{pVarName, 1.0}}; + } + + // cost THERMAL + if (name == "cost") + { + if (component_.getModel() == BATTERY_WITH_VARIABLE_SIZING) + { + std::string maxPVarName = "maxP_" + component_.getId(); + std::string maxStockVarName = "maxStock_" + component_.getId(); + + // TODO : not very clean since scenario is ignored, which works because + // the PRICE_MINIM filler does not add costs for the scenarios + double maxPLifeTimeCost = component_.getDoubleParameterValue("maxPLifeTimeCost"); + double maxStockLifeTimeCost + = component_.getDoubleParameterValue("maxStockLifeTimeCost"); + double lifeTimeInYears = component_.getDoubleParameterValue("lifeTimeInYears"); + double nTsInLifeTime + = lifeTimeInYears * 365.25 * 1440 / (1.0 * data.getTimeResolutionInMinutes()); + double maxPCostPerTs = maxPLifeTimeCost / nTsInLifeTime; + double maxStockCostPerTs = maxStockLifeTimeCost / nTsInLifeTime; + + return {{maxPVarName, maxPCostPerTs}, {maxStockVarName, maxStockCostPerTs}}; + // TODO: we can add variable cost on P here too, instead of if/else + } + else + { + if (!data.hasTimedData("cost_" + component_.getId())) + { + return {}; } - double cost = data.getTimedData("cost_" + component_.getId()).at(timestamp); + // TODO BuildContext + double cost = data.getTimedData("cost_" + component_.getId(), scenario).at(timestamp); return {{pVarName, cost}}; } - } else if (component_.getModel() == BATTERY) { - if (name == "P") { - string pVarName = "P_" + component_.getId() + "_" + to_string(timestamp); - return {{pVarName, 1.0}}; - } } + return {}; } diff --git a/src/tests/poc/include/standard/PortConnection.h b/src/tests/poc/include/standard/PortConnection.h index 92bcde7176..af4ecc3336 100644 --- a/src/tests/poc/include/standard/PortConnection.h +++ b/src/tests/poc/include/standard/PortConnection.h @@ -7,7 +7,8 @@ using namespace std; class ComponentFiller; -struct ComponentAndPort { +struct ComponentAndPort +{ shared_ptr componentFiller; string portName; }; @@ -17,32 +18,45 @@ class PortConnection private: ComponentAndPort componentAndPort1; // TODO : à terme, ne pas référencer directement des fillers ComponentAndPort componentAndPort2; + public: - PortConnection(ComponentAndPort componentAndPort1, ComponentAndPort componentAndPort2) - : componentAndPort1(std::move(componentAndPort1)), componentAndPort2(std::move(componentAndPort2)) - {} - [[nodiscard]] ComponentAndPort getComponentAndPort1() const { return componentAndPort1; } - [[nodiscard]] ComponentAndPort getComponentAndPort2() const { return componentAndPort2; } + PortConnection(ComponentAndPort componentAndPort1, ComponentAndPort componentAndPort2) : + componentAndPort1(componentAndPort1), componentAndPort2(componentAndPort2) + { + } + [[nodiscard]] const ComponentAndPort& getComponentAndPort1() const + { + return componentAndPort1; + } + [[nodiscard]] const ComponentAndPort& getComponentAndPort2() const + { + return componentAndPort2; + } }; class PortConnectionsManager { private: vector connections; + public: void addConnection(ComponentAndPort componentAndPort1, ComponentAndPort componentAndPort2) { - connections.emplace_back(std::move(componentAndPort1), std::move(componentAndPort2)); + connections.emplace_back(componentAndPort1, componentAndPort2); } vector getConectionsTo(ComponentFiller* componentFiller, const string& portId) { // TODO: implement a better search algorithm vector connectedComponents; - for (const auto& c: connections) { + for (const auto& c : connections) + { if (c.getComponentAndPort1().componentFiller.get() == componentFiller - && c.getComponentAndPort1().portName == portId) { + && c.getComponentAndPort1().portName == portId) + { connectedComponents.emplace_back(c.getComponentAndPort2()); - } else if (c.getComponentAndPort2().componentFiller.get() == componentFiller - && c.getComponentAndPort2().portName == portId) { + } + else if (c.getComponentAndPort2().componentFiller.get() == componentFiller + && c.getComponentAndPort2().portName == portId) + { connectedComponents.emplace_back(c.getComponentAndPort1()); } } diff --git a/src/tests/poc/legacy-workflow/simple-study.cpp b/src/tests/poc/legacy-workflow/simple-study.cpp index f69eac2922..b16834e62c 100644 --- a/src/tests/poc/legacy-workflow/simple-study.cpp +++ b/src/tests/poc/legacy-workflow/simple-study.cpp @@ -4,8 +4,7 @@ #include #include "../../../solver/optimisation/opt_global.h" -#include "../include/simple/Thermal.h" -#include "../include/simple/Balance.h" +#include "../include/standard/ComponentFiller.h" #include "utils.h" @@ -15,37 +14,38 @@ namespace tt = boost::test_tools; using namespace Antares::Data; // ================================= -// Basic fixture +// Basic fixture // ================================= struct StudyFixture : public StudyBuilder { - using StudyBuilder::StudyBuilder; - StudyFixture(); - - // Data members - std::shared_ptr cluster; - Area* area = nullptr; - double loadInArea = 0.; - TimeSeriesConfigurer loadTSconfig; + using StudyBuilder::StudyBuilder; + StudyFixture(); + + // Data members + std::shared_ptr cluster; + Area* area = nullptr; + double loadInArea = 0.; + TimeSeriesConfigurer loadTSconfig; }; StudyFixture::StudyFixture() { - simulationBetweenDays(0, 7); - area = addAreaToStudy("some_area"); + simulationBetweenDays(0, 7); + area = addAreaToStudy("some_area"); - loadInArea = 7.0; - loadTSconfig = TimeSeriesConfigurer(area->load.series.timeSeries); - loadTSconfig.setColumnCount(1) - .fillColumnWith(0, loadInArea); + loadInArea = 7.0; + loadTSconfig = TimeSeriesConfigurer(area->load.series.timeSeries); + loadTSconfig.setColumnCount(1) + .fillColumnWith(0, loadInArea); }; BOOST_FIXTURE_TEST_SUITE(ONE_AREA__ONE_THERMAL_CLUSTER, StudyFixture) BOOST_AUTO_TEST_CASE(thermal_cluster_fullfills_area_demand) -{ - setNumberMCyears(1); +{ + setNumberMCyears(1); + PortConnectionsManager portConnectionsManager; std::vector timeStamps(168); std::iota(timeStamps.begin(), @@ -54,19 +54,20 @@ BOOST_AUTO_TEST_CASE(thermal_cluster_fullfills_area_demand) { std::vector costThermal1(168, 1.); std::vector consumption1(168, 100); - gLinearProblemData = Antares::optim::api::LinearProblemData(timeStamps, - 60, // timeResolutionInMinutes - {}, // scalarData - {{"cost_thermal1", std::move(costThermal1)}, - {"consumption_some_area", consumption1}}); - - auto thermal = std::make_shared("thermal1", 100); - gAdditionalFillers.push_back(thermal); - // For some reason, this doesn't compile with g++-10 - // gAdditionalFillers.push_back(std::make_shared("some_area", {}, {thermal})); - // brace-init seems to be the issue, so we need to be more explicit with types - auto balance = std::make_shared("some_area", std::vector>{}, std::vector>({thermal})); - gAdditionalFillers.push_back(balance); + gLinearProblemData = Antares::optim::api::LinearProblemData( + 60, // timeResolutionInMinutes + {}, // scalarData + {{"cost_thermal1", {std::move(costThermal1)}}, {"consumption_some_area", {consumption1}}}); + + Component thermal("thermal1", THERMAL, {{"maxP", 100}}, {}); + auto thermalFiller = make_shared(thermal, portConnectionsManager); + Component balance("balanceA", BALANCE, {}, {{"nodeName", "some_area"}}); + auto balanceAFiller = make_shared(balance, portConnectionsManager); + + portConnectionsManager.addConnection({balanceAFiller, "P"}, {thermalFiller, "P"}); + + gAdditionalFillers.push_back(thermalFiller); + gAdditionalFillers.push_back(balanceAFiller); } area->thermal.unsuppliedEnergyCost = 1.e3; @@ -83,8 +84,9 @@ BOOST_AUTO_TEST_CASE(thermal_cluster_fullfills_area_demand) std::vector expectedThermalP(168, loadInArea); std::vector query; + int scenario = 0; // defaults to 0 in legacy problems for (int timeStamp : timeStamps) - query.push_back("P_thermal1_"+std::to_string(timeStamp)); + query.push_back("P_thermal1_"+std::to_string(timeStamp)+"_"+std::to_string(scenario)); auto actualThermalP = gMipSolution.getOptimalValues(query); // Test no unsupplied energy at h=0 @@ -93,6 +95,3 @@ BOOST_AUTO_TEST_CASE(thermal_cluster_fullfills_area_demand) BOOST_TEST(actualThermalP == expectedThermalP, tt::per_element()); // TODO add tolerance } BOOST_AUTO_TEST_SUITE_END() - - - diff --git a/src/tests/poc/new-workflow/CMakeLists.txt b/src/tests/poc/new-workflow/CMakeLists.txt index c5bd2ff8c0..00409522e5 100644 --- a/src/tests/poc/new-workflow/CMakeLists.txt +++ b/src/tests/poc/new-workflow/CMakeLists.txt @@ -1,20 +1,17 @@ -add_executable(simple-test simple-test.cpp -) -target_link_libraries(simple-test +add_executable(standard-test standard-test.cpp) +target_link_libraries(standard-test Antares::optim_api Antares::optim_impl ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) -add_executable(standard-test standard-test.cpp -) -target_link_libraries(standard-test +add_executable(scaling-test scaling-test.cpp) +target_link_libraries(scaling-test Antares::optim_api Antares::optim_impl ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) -add_executable(scaling-test scaling-test.cpp -) -target_link_libraries(scaling-test +add_executable(multi-scenario-test multi-scenario-test.cpp) +target_link_libraries(multi-scenario-test Antares::optim_api Antares::optim_impl - ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) \ No newline at end of file + ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) diff --git a/src/tests/poc/new-workflow/multi-scenario-test.cpp b/src/tests/poc/new-workflow/multi-scenario-test.cpp new file mode 100644 index 0000000000..cc66795f05 --- /dev/null +++ b/src/tests/poc/new-workflow/multi-scenario-test.cpp @@ -0,0 +1,164 @@ +#define BOOST_TEST_MODULE poc tests +#define BOOST_TEST_DYN_LINK + +#include +#include +#include +#include "antares/optim/api/LinearProblemBuilder.h" +#include "antares/optim/impl/LinearProblemImpl.h" +#include "../include/standard/ComponentFiller.h" + +namespace tt = boost::test_tools; +namespace bdata = boost::unit_test::data; +using namespace Antares::optim::api; + +static constexpr std::array solverNames = {"xpress", "sirius", "coin", "scip"}; + +MipSolution runBatterySizingLP(string solverName, + vector timeStamps, + int timeResolution, + vector scenarios, + int batteryLifeTime, + vector> consumptionScenarios, + vector> thermal1CostScenarios, + vector> thermal2CostScenarios) +{ + LinearProblemImpl linearProblem(false, solverName); + LinearProblemBuilder linearProblemBuilder(linearProblem); + PortConnectionsManager portConnectionsManager; + + Component balance("balanceA", BALANCE, {}, {{"nodeName", "nodeA"}}); + auto balanceAFiller = make_shared(balance, portConnectionsManager); + Component priceMinim("priceMinim", PRICE_MINIM, {}, {}); + auto priceMinimFiller = make_shared(priceMinim, portConnectionsManager); + Component thermal1("thermal1", THERMAL, {{"maxP", 100}}, {}); + auto thermal1Filler = make_shared(thermal1, portConnectionsManager); + Component thermal2("thermal2", THERMAL, {{"maxP", 100}}, {}); + auto thermal2Filler = make_shared(thermal2, portConnectionsManager); + Component battery("battery1", + BATTERY_WITH_VARIABLE_SIZING, + {{"maxP", 9999}, + {"maxStock", 9999}, + {"lifeTimeInYears", batteryLifeTime}, + {"maxStockLifeTimeCost", 150e3}, + {"maxPLifeTimeCost", 200e3}}, + {}); + auto batteryFiller = make_shared(battery, portConnectionsManager); + + linearProblemBuilder.addFiller(thermal1Filler); + linearProblemBuilder.addFiller(thermal2Filler); + linearProblemBuilder.addFiller(batteryFiller); + linearProblemBuilder.addFiller(balanceAFiller); + linearProblemBuilder.addFiller(priceMinimFiller); + + portConnectionsManager.addConnection({balanceAFiller, "P"}, {thermal1Filler, "P"}); + portConnectionsManager.addConnection({balanceAFiller, "P"}, {thermal2Filler, "P"}); + portConnectionsManager.addConnection({balanceAFiller, "P"}, {batteryFiller, "P"}); + portConnectionsManager.addConnection({priceMinimFiller, "cost"}, {thermal1Filler, "cost"}); + portConnectionsManager.addConnection({priceMinimFiller, "cost"}, {thermal2Filler, "cost"}); + portConnectionsManager.addConnection({priceMinimFiller, "cost"}, {batteryFiller, "cost"}); + + LinearProblemData linearProblemData(timeResolution, + {{"initialStock_battery1", {0}}}, + {{"consumption_nodeA", consumptionScenarios}, + {"cost_thermal1", thermal1CostScenarios}, + {"cost_thermal2", thermal2CostScenarios}}); + + BuildContext ctx(scenarios, timeStamps); + linearProblemBuilder.build(linearProblemData, ctx); + + auto solution = linearProblemBuilder.solve({}); + + return solution; +} + +BOOST_DATA_TEST_CASE(test_battery_sizing_one_scenario, bdata::make(solverNames), solverName) +{ + // In this test, we will try to find the best investment in a battery. + // This is a copy of test in "standard-test" named + // "test_std_oneWeek_oneNode_oneBattery_twoThermals", where the battery size (maxP, maxStock) is + // an optimization variable. The battery costs 150k€/MWh (on maxStock) and 200k€/MW (on maxP), + // and lives 15 years. Under these circumstances, the optimal sizing for the one scenario copied + // from the existing test should be: maxP = 50 MW, maxStock = 100 MWh, for a total price of + // 3210.5€ (tested iteratively) + + auto solution = runBatterySizingLP(solverName, + {0, 1, 2, 3}, + 60, + {0}, + 15, + {{0, 150, 150, 150}}, + {{1, 4, 8, 11}}, + {{2, 3, 10, 9}}); + + BOOST_REQUIRE_EQUAL(solution.getOptimalValue("maxP_battery1"), 50); + BOOST_REQUIRE_EQUAL(solution.getOptimalValue("maxStock_battery1"), 100); + BOOST_TEST(solution.getObjectiveValue() == 3210.51, tt::tolerance(0.01)); +} + +BOOST_DATA_TEST_CASE(test_battery_sizing_three_same_scenarios, bdata::make(solverNames), solverName) +{ + // Copy of previous test, with the same scenario repeated thrice + // (consumption & thermal prices have multiple scenarios) + // Expected result is the same as the previous test + + vector> consumptionScenarios + = {{0, 150, 150, 150}, {0, 150, 150, 150}, {0, 150, 150, 150}}; + vector> t1CostScenarios = {{1, 4, 8, 11}, {1, 4, 8, 11}, {1, 4, 8, 11}}; + vector> t2CostScenarios = {{2, 3, 10, 9}, {2, 3, 10, 9}, {2, 3, 10, 9}}; + + auto solution = runBatterySizingLP(solverName, + {0, 1, 2, 3}, + 60, + {0, 1, 2}, + 15, + consumptionScenarios, + t1CostScenarios, + t2CostScenarios); + + BOOST_REQUIRE_EQUAL(solution.getOptimalValue("maxP_battery1"), 50); + BOOST_REQUIRE_EQUAL(solution.getOptimalValue("maxStock_battery1"), 100); + BOOST_TEST(solution.getObjectiveValue() == 3210.51, tt::tolerance(0.01)); +} + +BOOST_DATA_TEST_CASE(test_battery_sizing_multiple_scenarios, bdata::make(solverNames), solverName) +{ + // In this test, we will try to find the best investment in a battery + // We will study 5 7-hour long scenarios (1-hour step), of a system with 2 thermal units + // (100 MW each), to which we would like to add a battery. + // The changing parameters between weeks is the consumption and the thermal costs (because of + // fuel price fluctuations, for example) + // The decision variables are: the capacity of the battery (150k€/MWh), its maximum power + // output/input (200k€/MW). + // The battery can live 30 years + // The optimal battery should have 20 MW and 20 MWh (verified iteratively) + + vector> consumptionScenarios = {{0, 150, 150, 150, 0, 30, 200}, + {200, 100, 30, 150, 100, 150, 180}, + {10, 10, 20, 30, 50, 20, 15}, + {200, 190, 200, 200, 180, 190, 200}, + {200, 100, 200, 100, 200, 100, 200}}; + vector> t1CostScenarios = {{1, 4, 8, 11, 8, 4, 1}, + {1, 4, 8, 11, 8, 4, 1}, + {1, 4, 8, 11, 8, 4, 1}, + {10, 10, 11, 10, 10, 11, 10}, + {15, 0, 15, 0, 15, 0, 15}}; + vector> t2CostScenarios = {{2, 3, 10, 9, 10, 3, 2}, + {10, 0, 0, 8, 0, 0, 10}, + {1, 1, 0, 0, 0, 1, 1}, + {6, 6, 6, 6, 6, 6, 6}, + {9, 9, 9, 9, 9, 9, 9}}; + + auto solution = runBatterySizingLP(solverName, + {0, 1, 2, 3, 4, 5, 6}, + 60, + {0, 1, 2, 3, 4}, + 30, + consumptionScenarios, + t1CostScenarios, + t2CostScenarios); + + BOOST_REQUIRE_EQUAL(solution.getOptimalValue("maxP_battery1"), 20); + BOOST_REQUIRE_EQUAL(solution.getOptimalValue("maxStock_battery1"), 20); + BOOST_TEST(solution.getObjectiveValue() == 5563.33, tt::tolerance(0.01)); +} \ No newline at end of file diff --git a/src/tests/poc/new-workflow/scaling-test.cpp b/src/tests/poc/new-workflow/scaling-test.cpp index 4a35688258..9475c276e2 100644 --- a/src/tests/poc/new-workflow/scaling-test.cpp +++ b/src/tests/poc/new-workflow/scaling-test.cpp @@ -13,23 +13,24 @@ namespace bdata = boost::unit_test::data; using namespace Antares::optim::api; using namespace std; -static constexpr std::array solverNames = - { - "xpress", - "sirius", - "coin", - //"scip" // TODO activate this after adding tolerance - }; +static constexpr std::array solverNames = { + "xpress", + "sirius", + "coin", + //"scip" // TODO activate this after adding tolerance +}; -static constexpr std::array timestepNumbers = - { - 24, // 1 day - 168, // 1 week - 744, // 1 month - 4380 // 6 months - }; +static constexpr std::array timestepNumbers = { + 24, // 1 day + 168, // 1 week + 744, // 1 month + 4380 // 6 months +}; -BOOST_DATA_TEST_CASE(test_scaling_simple_problem, bdata::make(solverNames)*bdata::make(timestepNumbers), solverName, nTimesteps) +BOOST_DATA_TEST_CASE(test_scaling_simple_problem, + bdata::make(solverNames) * bdata::make(timestepNumbers), + solverName, + nTimesteps) { int timeResolution = 60; @@ -37,55 +38,63 @@ BOOST_DATA_TEST_CASE(test_scaling_simple_problem, bdata::make(solverNames)*bdata std::vector timeStamps(nTimesteps); std::iota(timeStamps.begin(), timeStamps.end(), 0); - std::map> timedData; + std::map>> timedData; - // Build increasing consumption vector beginning at 0 MW, and increasing by 1 MW at every timestep + // Build increasing consumption vector beginning at 0 MW, and increasing by 1 MW at every + // timestep std::vector consumption(nTimesteps); std::iota(consumption.begin(), consumption.end(), 1); - timedData.insert({"consumption_nodeA", consumption}); + timedData.insert({"consumption_nodeA", {consumption}}); LinearProblemImpl linearProblem(false, solverName); LinearProblemBuilder linearProblemBuilder(linearProblem); PortConnectionsManager portConnectionsManager; Component balance("balanceA", BALANCE, {}, {{"nodeName", "nodeA"}}); - shared_ptr balanceAFiller = make_shared(balance, portConnectionsManager); + shared_ptr balanceAFiller + = make_shared(balance, portConnectionsManager); linearProblemBuilder.addFiller(balanceAFiller); Component priceMinim("priceMinim", PRICE_MINIM, {}, {}); - shared_ptr priceMinimFiller = make_shared(priceMinim, portConnectionsManager); + shared_ptr priceMinimFiller + = make_shared(priceMinim, portConnectionsManager); linearProblemBuilder.addFiller(priceMinimFiller); // Create thermal production units // Every unit can produce up to 10 MW // Price is stable in time but increase for every unit int nUnits = ceil(1.0 * (nTimesteps - 1) / 10.0); - for (int i = 1; i <= nUnits; ++i) { + for (int i = 1; i <= nUnits; ++i) + { string id = "thermal" + to_string(i); Component thermal(id, THERMAL, {{"maxP", 10}}, {}); - shared_ptr thermalFiller = make_shared(thermal, portConnectionsManager); + shared_ptr thermalFiller + = make_shared(thermal, portConnectionsManager); vector cost(nTimesteps, 1.0 * i); - timedData.insert({"cost_" + id, cost}); + timedData.insert({"cost_" + id, {cost}}); linearProblemBuilder.addFiller(thermalFiller); portConnectionsManager.addConnection({balanceAFiller, "P"}, {thermalFiller, "P"}); portConnectionsManager.addConnection({priceMinimFiller, "cost"}, {thermalFiller, "cost"}); } - LinearProblemData linearProblemData(timeStamps, timeResolution, {}, timedData); - linearProblemBuilder.build(linearProblemData); + LinearProblemData linearProblemData(timeResolution, {{}}, timedData); + BuildContext buildCtx({0}, timeStamps); + linearProblemBuilder.build(linearProblemData, buildCtx); auto solution = linearProblemBuilder.solve({}); - for (int i = 1; i <= nUnits; ++i) { - string pVarNamePrefix = "P_thermal" + to_string(i) + "_"; + for (int i = 1; i <= nUnits; ++i) + { + string pVarNamePrefix = "P_thermal" + to_string(i) + "_"; vector pVarNames; pVarNames.reserve(nTimesteps); vector expectedP; expectedP.reserve(nTimesteps); - for (int ts: timeStamps) { - pVarNames.push_back(pVarNamePrefix + to_string(ts)); + for (int ts : timeStamps) + { + pVarNames.push_back(pVarNamePrefix + to_string(ts) + "_0"); expectedP.push_back(min(10.0, max(0.0, consumption[ts] - (i - 1) * 10))); } vector actualP = solution.getOptimalValues(pVarNames); BOOST_TEST(actualP == expectedP, tt::per_element()); // TODO add tolerance } -} \ No newline at end of file +} diff --git a/src/tests/poc/new-workflow/simple-test.cpp b/src/tests/poc/new-workflow/simple-test.cpp deleted file mode 100644 index 67563dc7a2..0000000000 --- a/src/tests/poc/new-workflow/simple-test.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#define BOOST_TEST_MODULE poc tests -#define BOOST_TEST_DYN_LINK - -#include -#include -#include -#include "antares/optim/api/LinearProblemBuilder.h" -#include "antares/optim/impl/LinearProblemImpl.h" -#include "../include/simple/Battery.h" -#include "../include/simple/Thermal.h" -#include "../include/simple/Balance.h" -#include "../include/simple/ProductionPriceMinimization.h" -#include "../include/standard/PortConnection.h" - -namespace tt = boost::test_tools; -namespace bdata = boost::unit_test::data; -using namespace Antares::optim::api; - -static constexpr std::array solverNames = - { - "xpress", - "sirius", - "coin", - //"scip" // TODO activate this after adding tolerance - }; - -BOOST_DATA_TEST_CASE(test_oneWeek_oneNode_oneBattery_oneThermal, - bdata::make(solverNames), solverName) -{ - vector timeStamps{0, 1, 2, 3}; - int timeResolution = 60; - - LinearProblemImpl linearProblem(false, solverName); - LinearProblemBuilder linearProblemBuilder(linearProblem); - - auto battery = make_shared("battery1", 100, 1000); - auto thermal = make_shared("thermal1", 100); - vector> batteries{battery}; - vector> thermals{thermal}; - auto balance = make_shared("nodeA", batteries, thermals); - auto objective = make_shared(thermals); - - linearProblemBuilder.addFiller(battery); - linearProblemBuilder.addFiller(thermal); - linearProblemBuilder.addFiller(balance); - linearProblemBuilder.addFiller(objective); - - LinearProblemData linearProblemData( - timeStamps, // TODO : move to LinearProblem ? - timeResolution, // TODO : move to LinearProblem ? - { - {"initialStock_battery1", 0} - }, - { - {"consumption_nodeA", {50, 50, 150, 120}}, - {"cost_thermal1", {1, 3, 10, 8}} - }); - linearProblemBuilder.build(linearProblemData); - auto solution = linearProblemBuilder.solve({}); - - // Consumption is greater than thermal maximum production in TS 2 & 3 - // So, the battery has to charge during tS 0 & 1 - // Moreover, production cost is big during TS 2 & 3, so the battery has to charge up to its maximum during 0 & 1 - // Consumption in 0 & 1 is 50 MW, so the battery can charge 50 MW x 2 - // It must then discharge in 2 & 3, but mostly in 2 because thermal production cost is higher, while keeping 20 MW - // to achieve balance in 3 (consumption = 120, thermal production <= 100) - // Thermal production must complete the rest. Thus, the expected power plans are: - // Thermal : 100, 100, 70, 100 - // Battery : -50, -50, 80, 20 - - vector actualThermalP = solution.getOptimalValues({"P_thermal1_0", "P_thermal1_1", "P_thermal1_2", "P_thermal1_3"}); - vector expectedThermalP({100., 100., 70., 100.}); - BOOST_TEST(actualThermalP == expectedThermalP, tt::per_element()); // TODO add tolerance - - vector actualBatteryP = solution.getOptimalValues({"P_battery1_0", "P_battery1_1", "P_battery1_2", "P_battery1_3"}); - vector expectedBatteryP({-50, -50, 80, 20}); - BOOST_TEST(actualBatteryP == expectedBatteryP, tt::per_element()); // TODO add tolerance -} - -BOOST_DATA_TEST_CASE(test_oneWeek_oneNode_oneBattery_twoThermals, - bdata::make(solverNames), solverName) -{ - vector timeStamps{0, 1, 2, 3}; - int timeResolution = 60; - - LinearProblemImpl linearProblem(false, solverName); - LinearProblemBuilder linearProblemBuilder(linearProblem); - - auto battery1 = make_shared("battery1", 180, 200); - auto thermal1 = make_shared("thermal1", 100); - auto thermal2 = make_shared("thermal2", 100); - vector> batteries{battery1}; - vector> thermals{thermal1, thermal2}; - auto balance = make_shared("nodeA", batteries, thermals); - auto objective = make_shared(thermals); - - linearProblemBuilder.addFiller(battery1); - linearProblemBuilder.addFiller(thermal1); - linearProblemBuilder.addFiller(thermal2); - linearProblemBuilder.addFiller(balance); - linearProblemBuilder.addFiller(objective); - - LinearProblemData linearProblemData( - timeStamps, // TODO : move to LinearProblem ? - timeResolution, // TODO : move to LinearProblem ? - { - {"initialStock_battery1", 0} - }, - { - {"consumption_nodeA", {0, 150, 150, 150}}, - {"cost_thermal1", {1, 4, 8, 11}}, - {"cost_thermal2", {2, 3, 10, 9}} - }); - linearProblemBuilder.build(linearProblemData); - auto solution = linearProblemBuilder.solve({}); - - // Thermal production is cheap in TS 0, then very expensive. - // Battery must charge up to its max during TS 0, then discharge mostly when thermal production is most - // expensive (mostly in TS 3, then in TS 2). It is limited in power and stock, it can charge 180 MW in TS 0 and - // 20 MW in TS 2. - // Thermal production will complete the rest, following merit order imposed by their respective costs. - // Expected power plans are: - // - Battery: -180, -20, 50, 150 - // - Thermal1: 100, 70, 100, 0 - // - Thermal2: 80, 100, 0, 0 - - vector actualBatteryP = solution.getOptimalValues({"P_battery1_0", "P_battery1_1", "P_battery1_2", "P_battery1_3"}); - vector expectedBatteryP({-180, -20, 50, 150}); - BOOST_TEST(actualBatteryP == expectedBatteryP, tt::per_element()); // TODO add tolerance - - vector actualThermal1P = solution.getOptimalValues({"P_thermal1_0", "P_thermal1_1", "P_thermal1_2", "P_thermal1_3"}); - vector expectedThermal1P({100, 70, 100, 0}); - BOOST_TEST(actualThermal1P == expectedThermal1P, tt::per_element()); // TODO add tolerance - vector actualThermal2P = solution.getOptimalValues({"P_thermal2_0", "P_thermal2_1", "P_thermal2_2", "P_thermal2_3"}); - vector expectedThermal2P({80, 100, 0, 0}); - BOOST_TEST(actualThermal2P == expectedThermal2P, tt::per_element()); // TODO add tolerance -} diff --git a/src/tests/poc/new-workflow/standard-test.cpp b/src/tests/poc/new-workflow/standard-test.cpp index d5e76989f6..5598bb6de9 100644 --- a/src/tests/poc/new-workflow/standard-test.cpp +++ b/src/tests/poc/new-workflow/standard-test.cpp @@ -12,16 +12,16 @@ namespace tt = boost::test_tools; namespace bdata = boost::unit_test::data; using namespace Antares::optim::api; -static constexpr std::array solverNames = - { - "xpress", - "sirius", - "coin", - //"scip" // TODO activate this after adding tolerance - }; +static constexpr std::array solverNames = { + "xpress", + "sirius", + "coin", + //"scip" // TODO activate this after adding tolerance +}; BOOST_DATA_TEST_CASE(test_std_oneWeek_oneNode_oneBattery_oneThermal, - bdata::make(solverNames), solverName) + bdata::make(solverNames), + solverName) { vector timeStamps{0, 1, 2, 3}; int timeResolution = 60; @@ -50,39 +50,37 @@ BOOST_DATA_TEST_CASE(test_std_oneWeek_oneNode_oneBattery_oneThermal, portConnectionsManager.addConnection({priceMinimFiller, "cost"}, {batteryFiller, "cost"}); LinearProblemData linearProblemData( - timeStamps, // TODO : move to LinearProblem ? - timeResolution, // TODO : move to LinearProblem ? - { - {"initialStock_battery1", 0} - }, - { - {"consumption_nodeA", {50, 50, 150, 120}}, - {"cost_thermal1", {1, 3, 10, 8}} - }); - linearProblemBuilder.build(linearProblemData); + timeResolution, // TODO : move to LinearProblem ? + {{"initialStock_battery1", {0}}}, + {{"consumption_nodeA", {{50, 50, 150, 120}}}, {"cost_thermal1", {{1, 3, 10, 8}}}}); + BuildContext ctx({0}, timeStamps); + linearProblemBuilder.build(linearProblemData, ctx); auto solution = linearProblemBuilder.solve({}); // Consumption is greater than thermal maximum production in TS 2 & 3 // So, the battery has to charge during tS 0 & 1 - // Moreover, production cost is big during TS 2 & 3, so the battery has to charge up to its maximum during 0 & 1 - // Consumption in 0 & 1 is 50 MW, so the battery can charge 50 MW x 2 - // It must then discharge in 2 & 3, but mostly in 2 because thermal production cost is higher, while keeping 20 MW - // to achieve balance in 3 (consumption = 120, thermal production <= 100) + // Moreover, production cost is big during TS 2 & 3, so the battery has to charge up to its + // maximum during 0 & 1 Consumption in 0 & 1 is 50 MW, so the battery can charge 50 MW x 2 It + // must then discharge in 2 & 3, but mostly in 2 because thermal production cost is higher, + // while keeping 20 MW to achieve balance in 3 (consumption = 120, thermal production <= 100) // Thermal production must complete the rest. Thus, the expected power plans are: // Thermal : 100, 100, 70, 100 // Battery : -50, -50, 80, 20 - vector actualThermalP = solution.getOptimalValues({"P_thermal1_0", "P_thermal1_1", "P_thermal1_2", "P_thermal1_3"}); + vector actualThermalP = solution.getOptimalValues( + {"P_thermal1_0_0", "P_thermal1_1_0", "P_thermal1_2_0", "P_thermal1_3_0"}); vector expectedThermalP({100., 100., 70., 100.}); BOOST_TEST(actualThermalP == expectedThermalP, tt::per_element()); // TODO add tolerance - vector actualBatteryP = solution.getOptimalValues({"P_battery1_0", "P_battery1_1", "P_battery1_2", "P_battery1_3"}); + vector actualBatteryP = solution.getOptimalValues( + {"P_battery1_0_0", "P_battery1_1_0", "P_battery1_2_0", "P_battery1_3_0"}); vector expectedBatteryP({-50, -50, 80, 20}); BOOST_TEST(actualBatteryP == expectedBatteryP, tt::per_element()); // TODO add tolerance } BOOST_DATA_TEST_CASE(test_std_oneWeek_oneNode_oneBattery_twoThermals, - bdata::make(solverNames), solverName) + bdata::make(solverNames), + solverName) { vector timeStamps{0, 1, 2, 3}; int timeResolution = 60; @@ -115,40 +113,41 @@ BOOST_DATA_TEST_CASE(test_std_oneWeek_oneNode_oneBattery_twoThermals, portConnectionsManager.addConnection({priceMinimFiller, "cost"}, {thermal2Filler, "cost"}); portConnectionsManager.addConnection({priceMinimFiller, "cost"}, {batteryFiller, "cost"}); - LinearProblemData linearProblemData( - timeStamps, // TODO : move to LinearProblem ? - timeResolution, // TODO : move to LinearProblem ? - { - {"initialStock_battery1", 0} - }, - { - {"consumption_nodeA", {0, 150, 150, 150}}, - {"cost_thermal1", {1, 4, 8, 11}}, - {"cost_thermal2", {2, 3, 10, 9}} - }); - linearProblemBuilder.build(linearProblemData); + LinearProblemData linearProblemData(timeResolution, // TODO : move to LinearProblem ? + {{"initialStock_battery1", {0}}}, + {{"consumption_nodeA", {{0, 150, 150, 150}}}, + {"cost_thermal1", {{1, 4, 8, 11}}}, + {"cost_thermal2", {{2, 3, 10, 9}}}}); + + BuildContext ctx({0}, timeStamps); + linearProblemBuilder.build(linearProblemData, ctx); + auto solution = linearProblemBuilder.solve({}); // Thermal production is cheap in TS 0, then very expensive. - // Battery must charge up to its max during TS 0, then discharge mostly when thermal production is most - // expensive (mostly in TS 3, then in TS 2). It is limited in power and stock, it can charge 180 MW in TS 0 and - // 20 MW in TS 2. - // Thermal production will complete the rest, following merit order imposed by their respective costs. - // Expected power plans are: + // Battery must charge up to its max during TS 0, then discharge mostly when thermal + // production is most expensive (mostly in TS 3, then in TS 2). It is limited in power and + // stock, it can charge 180 MW in TS 0 and 20 MW in TS 2. Thermal production will complete + // the rest, following merit order imposed by their respective costs. Expected power plans + // are: // - Battery: -180, -20, 50, 150 // - Thermal1: 100, 70, 100, 0 // - Thermal2: 80, 100, 0, 0 - vector actualBatteryP = solution.getOptimalValues({"P_battery1_0", "P_battery1_1", "P_battery1_2", "P_battery1_3"}); + vector actualBatteryP = solution.getOptimalValues( + {"P_battery1_0_0", "P_battery1_1_0", "P_battery1_2_0", "P_battery1_3_0"}); vector expectedBatteryP({-180, -20, 50, 150}); BOOST_TEST(actualBatteryP == expectedBatteryP, tt::per_element()); // TODO add tolerance with boost version >= 1.73.0 - //BOOST_TEST(actualBatteryP == expectedBatteryP, tt::tolerance( 1e-3 ) << "comparison to ground truth failed" << tt::per_element()); + // BOOST_TEST(actualBatteryP == expectedBatteryP, tt::tolerance( 1e-3 ) << "comparison to + // ground truth failed" << tt::per_element()); - vector actualThermal1P = solution.getOptimalValues({"P_thermal1_0", "P_thermal1_1", "P_thermal1_2", "P_thermal1_3"}); + vector actualThermal1P = solution.getOptimalValues( + {"P_thermal1_0_0", "P_thermal1_1_0", "P_thermal1_2_0", "P_thermal1_3_0"}); vector expectedThermal1P({100, 70, 100, 0}); BOOST_TEST(actualThermal1P == expectedThermal1P, tt::per_element()); // TODO add tolerance - vector actualThermal2P = solution.getOptimalValues({"P_thermal2_0", "P_thermal2_1", "P_thermal2_2", "P_thermal2_3"}); + vector actualThermal2P = solution.getOptimalValues( + {"P_thermal2_0_0", "P_thermal2_1_0", "P_thermal2_2_0", "P_thermal2_3_0"}); vector expectedThermal2P({80, 100, 0, 0}); BOOST_TEST(actualThermal2P == expectedThermal2P, tt::per_element()); // TODO add tolerance }