Antares Simulator
Power System Simulator
solver.hxx
1 /*
2  * Copyright 2007-2025, RTE (https://www.rte-france.com)
3  * See AUTHORS.txt
4  * SPDX-License-Identifier: MPL-2.0
5  * This file is part of Antares-Simulator,
6  * Adequacy and Performance assessment for interconnected energy networks.
7  *
8  * Antares_Simulator is free software: you can redistribute it and/or modify
9  * it under the terms of the Mozilla Public Licence 2.0 as published by
10  * the Mozilla Foundation, either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * Antares_Simulator is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * Mozilla Public Licence 2.0 for more details.
17  *
18  * You should have received a copy of the Mozilla Public Licence 2.0
19  * along with Antares_Simulator. If not, see <https://opensource.org/license/mpl-2-0/>.
20  */
21 #ifndef __SOLVER_SIMULATION_SOLVER_HXX__
22 #define __SOLVER_SIMULATION_SOLVER_HXX__
23 
24 #include <antares/antares/fatal-error.h>
25 #include <antares/date/date.h>
26 #include <antares/exception/InitializationError.hpp>
27 #include <antares/logs/logs.h>
28 #include "antares/concurrency/concurrency.h"
29 #include "antares/io/outputs/SimulationTableCsv.h"
30 #include "antares/solver/hydro/management/HydroInputsChecker.h"
31 #include "antares/solver/hydro/management/management.h"
32 #include "antares/solver/simulation/common-eco-adq.h"
33 #include "antares/solver/simulation/numspace_manager.h"
34 #include "antares/solver/simulation/opt_time_writer.h"
35 #include "antares/solver/simulation/random.h"
36 #include "antares/solver/simulation/regenerate_timeseries.h"
37 #include "antares/solver/simulation/timeseries-numbers.h"
38 #include "antares/solver/ts-generator/generator.h"
39 #include "antares/solver/variable/print.h"
40 
41 namespace Antares::Solver::Simulation
42 {
43 
44 template<class Impl>
45 class yearJob
46 {
47 public:
48  yearJob(ISimulation<Impl>* simulation,
49  unsigned int pY,
50  std::map<uint, bool>& pYearsFailed,
51  NumSpaceManager& numspaceManager,
52  randomNumbers& pRandomForParallelYears,
53  Data::Study& pStudy,
54  std::vector<Variable::State>& pStates,
55  bool pYearByYear,
56  Benchmarking::DurationCollector& durationCollector,
57  IResultWriter& resultWriter,
58  ISimulationObserver& simulationObserver,
59  std::mutex& aggregationMutex):
60  simulation_(simulation),
61  y(pY),
62  yearsFailed(pYearsFailed),
63  numspaceManager(numspaceManager),
64  randomForParallelYears(pRandomForParallelYears),
65  study(pStudy),
66  states(pStates),
67  yearByYear(pYearByYear),
68  pDurationCollector(durationCollector),
69  pResultWriter(resultWriter),
70  simulationObserver_(simulationObserver),
71  hydroManagement(study.areas, study.parameters, study.calendar, resultWriter),
72  aggregationMutex(aggregationMutex)
73  {
74  }
75 
76  yearJob(const yearJob&) = delete;
77  yearJob& operator=(const yearJob&) = delete;
78  ~yearJob() = default;
79 
80 private:
81  ISimulation<Impl>* simulation_;
82  unsigned int y;
83  std::map<uint, bool>& yearsFailed;
84  NumSpaceManager& numspaceManager;
85  randomNumbers& randomForParallelYears;
86  Data::Study& study;
87  std::vector<Variable::State>& states;
88  bool yearByYear;
89  Benchmarking::DurationCollector& pDurationCollector;
90  IResultWriter& pResultWriter;
91  ISimulationObserver& simulationObserver_;
92  HydroManagement hydroManagement;
93  std::mutex& aggregationMutex;
94 
95 private:
96  /*
97  ** \brief Log failed week
98  **
99  ** \param y MC Year
100  ** \param study The Antares study
101  ** \param failedWeek List of failing week
102  */
103  void logFailedWeek(int y, const Data::Study& study, const std::list<uint>& failedWeekList)
104  {
105  if (!failedWeekList.empty())
106  {
107  std::stringstream failedWeekStr;
108  std::ranges::copy(failedWeekList, std::ostream_iterator<int>(failedWeekStr, " "));
109 
110  std::string s = failedWeekStr.str();
111  s = s.substr(0, s.length() - 1); // get rid of the trailing space
112 
113  std::string failedStr = failedWeekList.size() != 1 ? " failed at weeks "
114  : " failed at week ";
115 
116  logs.info(); // empty line
117 
118  if (Data::stopSimulation(study.parameters.include.unfeasibleProblemBehavior))
119  {
120  logs.fatal() << "Year " << y + 1 << failedStr << s << ".";
121  }
122  else
123  {
124  logs.warning() << "Year " << y + 1 << failedStr << s << ".";
125  }
126  }
127  }
128 
129 public:
130  void operator()()
131  {
132  Progression::Task progression(study, y, Solver::Progression::sectYear);
133 
134  // Index of the current year in the list of structures
135  uint indexYear = randomForParallelYears.yearNumberToIndex[y];
136 
137  // Getting random tables for this year
138  yearRandomNumbers& randomForCurrentYear = randomForParallelYears.pYears[indexYear];
139 
140  // 1 - Applying random levels for current year
141  auto randomReservoirLevel = randomForCurrentYear.pReservoirLevels;
142 
143  // 2 - Getting the numpspace and scratchMap associated to the current year
144  unsigned numSpace = numspaceManager.getAvailableNumSpace();
145  Yuni::Logs::threadNumber() = numSpace;
146  logs.info() << "Year " << y + 1 << " started";
147 
148  Antares::Data::Area::ScratchMap scratchmap = study.areas.buildScratchMap(numSpace);
149 
150  // 3 - Preparing data related to Clusters in 'must-run' mode
151  prepareClustersInMustRunMode(study, scratchmap, y, Impl::mode);
152 
153  // 4 - Hydraulic ventilation
154  pDurationCollector("hydro_ventilation") << [this, &scratchmap, &randomReservoirLevel]
155  { hydroManagement.makeVentilation(randomReservoirLevel, y, scratchmap); };
156 
157  // Updating the state
158  auto& state = states[numSpace];
159  state.year = y;
160 
161  // 5 - Resetting all variables for the output
162  simulation_->variables.yearBegin(y, numSpace);
163 
164  // 6 - The Solver itself
165  std::list<uint> failedWeekList;
166 
167  OptimizationStatisticsWriter optWriter(pResultWriter, y);
168  bool yearFailed = !simulation_->year(progression,
169  state,
170  numSpace,
171  randomForCurrentYear,
172  failedWeekList,
173  hydroManagement.ventilationResults(),
174  optWriter,
175  scratchmap);
176  if (!study.parameters.noOutput)
177  {
178  auto& simTable = simulation_->getSimulationTable(numSpace);
179 
180  auto buffers = simTable.moveBuffers();
181 
182  simulation_->storeYearBuffers(y, std::move(buffers.first), std::move(buffers.second));
183  }
184 
185  // Log failing weeks
186  logFailedWeek(y, study, failedWeekList);
187 
188  simulation_->variables.yearEndBuild(state, y, numSpace);
189 
190  // 7 - End of the year, this is the last stade where the variables can retrieve
191  // their data for this year.
192  simulation_->variables.yearEnd(y, numSpace);
193 
194  // 8 - Spatial clusters
195  // Notifying all variables to perform spatial aggregates.
196  // This must be done only when all variables have finished to compute their
197  // data for the year.
198  simulation_->variables.yearEndSpatialAggregates(simulation_->variables, y, numSpace);
199 
200  // 9 - Write results for the current year
201  if (yearByYear)
202  {
203  pDurationCollector("yby_export") << [this, &numSpace]
204  {
205  // Before writing, some variable may require minor modifications
206  simulation_->variables.beforeYearByYearExport(y, numSpace);
207  // writing the results for the current year into the output
208  simulation_->writeResults(false, y, numSpace); // false for synthesis
209  };
210  }
211 
212  // 10 - Synthesis results
213  // Computing the summary : adding the contribution of MC years
214  // previously computed in parallel
215  aggregationMutex.lock();
216  yearsFailed[y] = yearFailed;
217 
218  simulation_->variables.computeSummary(y, numSpace);
219 
220  // Computing summary of spatial aggregations
221  simulation_->variables.computeSpatialAggregatesSummary(simulation_->variables, y, numSpace);
222 
223  // Computes statistics on annual (system and solution) costs, to be printed in output
224  // into separate files
225  simulation_->computeAnnualCostsStatistics(state);
226 
227  logs.debug() << "year " << y + 1 << " ended and returned numSpace " << numSpace;
228  numspaceManager.freeNumSpace(numSpace);
229  simulation_->incrementProgression(progression);
230 
231  aggregationMutex.unlock();
232 
233  } // End of onExecute() method
234 };
235 
236 template<class ImplementationType>
238  Data::Study& study,
239  const ::Settings& settings,
240  Benchmarking::DurationCollector& duration_collector,
241  IResultWriter& resultWriter,
242  Simulation::ISimulationObserver& simulationObserver):
243  ImplementationType(study, resultWriter, simulationObserver),
244  study(study),
245  settings(settings),
246  pNbMaxPerformedYearsInParallel(0),
247  pYearByYear(study.parameters.yearByYear),
248  pDurationCollector(duration_collector),
249  pQueueService(study.pQueueService),
250  pResultWriter(resultWriter),
251  simulationObserver_(simulationObserver)
252 {
253  // Ask to the interface to show the messages
254  logs.info();
255  logs.info() << LOG_UI_DISPLAY_MESSAGES_ON;
256 
257  // Running !
258  logs.checkpoint() << "Running the simulation (" << ImplementationType::Name() << ')';
259  logs.info() << "Allocating resources...";
260 
261  if (pYearByYear && (settings.noOutput || settings.tsGeneratorsOnly))
262  {
263  pYearByYear = false;
264  }
265 }
266 
267 template<class ImplementationType>
269 {
270  // The zip writer needs a queue service (async mutexed write)
271  if (!pQueueService && pResultWriter.needsTheJobQueue())
272  {
274  }
275 }
276 
277 template<class ImplementationType>
279 {
280 }
281 
282 template<class ImplementationType>
284 {
285  pNbMaxPerformedYearsInParallel = study.maxNbYearsInParallel;
286 
287  // Initialize all data
288  ImplementationType::variables.initializeFromStudy(study);
289  // Computing the max number columns a report of any kind can contain.
290  study.parameters.variablesPrintInfo.computeMaxColumnsCountInReports();
291 
292  logs.info() << "Allocating resources...";
293 
294  // Memory usage
295  {
297  ImplementationType::variables.template provideInformations<Variable::PrintInfosStdCout>(c);
298  }
299 
300  ImplementationType::setNbPerformedYearsInParallel(pNbMaxPerformedYearsInParallel);
301 
302  if (settings.tsGeneratorsOnly)
303  {
304  // Only the preprocessors can be used
305  // We only have to regenerate time-series according the settings
306  // in general data of the study.
307  logs.info() << " Only the preprocessors are enabled.";
308 
309  regenerateTimeSeries(study, pResultWriter, pDurationCollector);
310 
311  // Destroy the TS Generators if any
312  // It will export the time-series into the output at the same time
313  TSGenerator::DestroyAll(study);
314  }
315  else
316  {
317  // Export ts-numbers into output
318  TimeSeriesNumbers::StoreTimeSeriesNumbersIntoOuput(study, pResultWriter);
319 
320  if (not ImplementationType::simulationBegin())
321  {
322  return;
323  }
324  // Allocating the memory
325  ImplementationType::variables.simulationBegin();
326 
327  // For beauty
328  logs.info();
329 
330  // Launching the simulation for all years
331  logs.info() << "MC-Years : [" << (study.runtime.rangeLimits.year[Data::rangeBegin] + 1)
332  << " .. " << (1 + study.runtime.rangeLimits.year[Data::rangeEnd])
333  << "], total: " << study.runtime.rangeLimits.year[Data::rangeCount];
334 
335  // Current state
336  std::vector<Variable::State> state(pNbMaxPerformedYearsInParallel, Variable::State(study));
337  // Initializing states for parallel actually performed years
338  for (uint numSpace = 0; numSpace != pNbMaxPerformedYearsInParallel; ++numSpace)
339  {
340  ImplementationType::initializeState(state[numSpace], numSpace);
341  }
342 
343  uint finalYear = 1 + study.runtime.rangeLimits.year[Data::rangeEnd];
344  {
345  pDurationCollector("mc_years")
346  << [finalYear, &state, this] { loopThroughYears(0, finalYear, state); };
347  }
348  // Destroy the TS Generators if any
349  // It will export the time-series into the output in the same time
350  TSGenerator::DestroyAll(study);
351 
352  // Post operations
353  pDurationCollector("post_processing") << [this] { ImplementationType::simulationEnd(); };
354 
355  ImplementationType::variables.simulationEnd();
356 
357  // Spatial clusters
358  // Notifying all variables to perform the final spatial clusters.
359  // This must be done only when all variables have finished to compute their
360  // own data.
361  ImplementationType::variables.simulationEndSpatialAggregates(ImplementationType::variables);
362  }
363 }
364 
365 template<class ImplementationType>
366 void ISimulation<ImplementationType>::writeResults(bool synthesis, uint year, uint numSpace)
367 {
368  using namespace Yuni;
369 
370  // The writer might need the job queue, after it's been stopped
371  // this is the case e.g if synthesis == true (writing mc-all)
372  // Don't restart the queue if the writer doesn't need it
373 
374  assert(!settings.noOutput);
375  assert(!settings.tsGeneratorsOnly);
376 
377  if (!pNbYearsReallyPerformed)
378  {
379  logs.info();
380  logs.info() << "The writing of the output results is disabled.";
381  logs.info();
382  }
383  else
384  {
385  if (synthesis)
386  {
387  const auto& parameters = study.parameters;
388  if (not parameters.synthesis) // disabled by parameters
389  {
390  logs.info() << "The simulation synthesis is disabled.";
391  return;
392  }
393  }
394 
395  // The target folder
396  String newPath;
397  newPath << ImplementationType::Name() << IO::Separator;
398  if (synthesis)
399  {
400  newPath << "mc-all";
401  }
402  else
403  {
404  CString<10, false> tmp;
405  tmp = (year + 1);
406  newPath << "mc-ind" << IO::Separator << "00000";
407  newPath.overwriteRight(tmp);
408  }
409 
410  // Dumping
411  ImplementationType::variables.exportSurveyResults(synthesis,
412  newPath,
413  numSpace,
414  pResultWriter);
415  }
416 }
417 
418 template<class ImplementationType>
420 {
421  pAnnualStatistics.systemCost.addCost(s.annualSystemCost);
422  pAnnualStatistics.criterionCost1.addCost(s.optimalSolutionCost1);
423  pAnnualStatistics.criterionCost2.addCost(s.optimalSolutionCost2);
424  pAnnualStatistics.optimizationTime1.addCost(s.averageOptimizationTime1);
425  pAnnualStatistics.optimizationTime2.addCost(s.averageOptimizationTime2);
426  pAnnualStatistics.updateTime.addCost(s.averageUpdateTime);
427 }
428 
429 template<class ImplementationType>
430 void ISimulation<ImplementationType>::loopThroughYears(uint firstYear,
431  uint endYear,
432  std::vector<Variable::State>& state)
433 {
434  assert(endYear <= study.parameters.nbYears);
435 
436  // Init random hydro
437  MersenneTwister randomHydroGenerator;
438  randomHydroGenerator.reset(study.parameters.seed[Data::seedHydroManagement]);
439 
440  // List of parallel years sets
441  std::vector<setOfParallelYears> setsOfParallelYears;
442 
443  // Number of threads to perform the jobs waiting in the queue
444  pQueueService->maximumThreadCount(pNbMaxPerformedYearsInParallel);
445 
446  regenerateTimeSeries(study, pResultWriter, pDurationCollector);
447  HydroInputsChecker hydroInputsChecker(study);
448  logs.info() << " Doing hydro validation";
449 
450  // Selecting the years to be performed
451  std::map<unsigned, bool> yearsFailed;
452  std::map<unsigned, bool> isYearPerformed;
453  pNbYearsReallyPerformed = 0;
454  for (uint year = firstYear; year < endYear; ++year)
455  {
456  isYearPerformed[year] = study.parameters.yearsFilter[year];
457  if (study.parameters.yearsFilter[year])
458  {
459  ++pNbYearsReallyPerformed;
460  }
461  else
462  {
463  logs.debug() << "Ignoring year " << year + 1 << ": not in the playlist";
464  yearsFailed[year] = false;
465  }
466  }
467 
468  if (pNbYearsReallyPerformed != endYear)
469  {
470  logs.info() << "Playlist detected, " << pNbYearsReallyPerformed
471  << " years to be performed out of " << endYear << " years.";
472  }
473 
474  logs.info() << " Starting the simulation";
475 
476  // Related to annual costs statistics (printed in output into separate files)
477  pAnnualStatistics.setNbPerformedYears(pNbYearsReallyPerformed);
478 
479  // Container for random numbers of parallel years (to be executed or not)
480  randomNumbers randomForParallelYears(pNbYearsReallyPerformed,
481  study.parameters.power.fluctuations);
482 
483  randomForParallelYears.allocate(study);
484  randomForParallelYears.compute(study, endYear, isYearPerformed, randomHydroGenerator);
485 
486  // hydro checks
487  for (uint year = firstYear; year < endYear; ++year)
488  {
489  if (study.parameters.yearsFilter[year])
490  {
491  hydroInputsChecker.Execute(year);
492  }
493  }
494  hydroInputsChecker.CheckForErrors();
495 
496  NumSpaceManager numspaceManager(pNbMaxPerformedYearsInParallel);
497 
498  Concurrency::FutureSet results;
499  std::mutex aggregationMutex;
500  for (uint year = firstYear; year < endYear; ++year)
501  {
502  if (study.parameters.yearsFilter[year])
503  {
504  // If the year has not to be rerun, we skip the computation of the year.
505  // Note that, when we enter for the first time in the "for" loop, all years of the set
506  // have to be rerun (meaning : they must be run once). if(!batch.yearsFailed[y])
507  // continue;
508 
509  auto task = std::make_shared<yearJob<ImplementationType>>(this,
510  year,
511  yearsFailed,
512  numspaceManager,
513  randomForParallelYears,
514  study,
515  state,
516  pYearByYear,
517  pDurationCollector,
518  pResultWriter,
519  simulationObserver_.get(),
520  aggregationMutex);
521  results.add(Concurrency::AddTask(*pQueueService, task));
522  }
523  }
524 
525  pQueueService->start();
526 
527  pQueueService->wait(Yuni::qseIdle);
528  pQueueService->stop();
529  if (!study.parameters.noOutput)
530  {
531  aggregateAndWriteSimulationTables();
532  }
533  results.join();
534  pResultWriter.flush();
535  // On regarde si au moins une année du lot n'a pas trouvé de solution
536  for (auto& [year, failed]: yearsFailed)
537  {
538  // Si une année du lot d'années n'a pas trouvé de solution, on arrête tout
539  if (failed)
540  {
541  std::ostringstream msg;
542  msg << "Year " << year + 1 << " has failed in the previous set of parallel year.";
543  throw FatalError(msg.str());
544  }
545  }
546 
547  // Set to zero the random numbers of all parallel years
548  randomForParallelYears.reset();
549 
550  // Writing annual costs statistics
551  pAnnualStatistics.endStandardDeviations();
552  pAnnualStatistics.writeToOutput(pResultWriter);
553 }
554 
555 template<class ImplementationType>
556 void ISimulation<ImplementationType>::storeYearBuffers(uint year,
557  std::string&& firstBuffer,
558  std::string&& secondBuffer)
559 {
560  std::lock_guard lock(buffersMutex_);
561  yearSimulationBuffers_.emplace(year,
562  std::pair{std::move(firstBuffer), std::move(secondBuffer)});
563 }
564 
565 template<class ImplementationType>
566 void ISimulation<ImplementationType>::aggregateAndWriteSimulationTables()
567 {
568  std::lock_guard lock(buffersMutex_);
569  std::string globalFirstBuffer;
570  std::string globalSecondBuffer;
571  // dans l'ordre des années
572  for (uint year = 0; year < study.parameters.nbYears; ++year)
573  {
574  auto it = yearSimulationBuffers_.find(year);
575  if (it != yearSimulationBuffers_.end())
576  {
577  globalFirstBuffer += it->second.first;
578  globalSecondBuffer += it->second.second;
579  }
580  }
581  const auto header = ImplementationType::getSimulationTableHeader() + "\n";
582  if (!globalFirstBuffer.empty())
583  {
584  std::string writerEntry = header + std::move(globalFirstBuffer);
585  pResultWriter.addEntryFromBuffer("simulation_table--optim-nb-1.csv", writerEntry);
586  }
587  if (!globalSecondBuffer.empty())
588  {
589  std::string writerEntry = header + std::move(globalSecondBuffer);
590  pResultWriter.addEntryFromBuffer("simulation_table--optim-nb-2.csv", writerEntry);
591  }
592 
593  yearSimulationBuffers_.clear();
594 }
595 
596 template<class ImplementationType>
597 OptimisationsSimulationTable& ISimulation<ImplementationType>::getSimulationTable(uint numSpace)
598 {
599  // Cette méthode doit être implémentée dans la classe dérivée (Economy)
600  // pour retourner la table spécifique à l'espace numérique
601  return ImplementationType::getSimulationTable(numSpace);
602 }
603 } // namespace Antares::Solver::Simulation
604 
605 #endif // __SOLVER_SIMULATION_SOLVER_HXX__
Utility class to gather futures to wait for.
Definition: concurrency.h:61
void add(TaskFuture &&f)
Adds one future to be monitored by this set.
Definition: concurrency.cpp:71
void join()
Waits for completion of all added futures.
Definition: concurrency.cpp:77
Definition: study.h:57
A generic exception for errors that should end the program.
Definition: fatal-error.h:32
Definition: HydroInputsChecker.h:33
Definition: management.h:62
void makeVentilation(const std::vector< double > &randomReservoirLevel, uint y, Antares::Data::Area::ScratchMap &scratchmap)
Perform the hydro ventilation.
Definition: management.cpp:269
MersenneTwister Pseudo random number generator.
Definition: mersenne-twister.h:41
void reset()
Reset the generator.
Definition: mersenne-twister.cpp:126
Definition: i_writer.h:32
Definition: InitializationError.hpp:29
Definition: progression.h:89
The ISimulationObserver class is an interface for observing the simulation.
Definition: ISimulationObserver.h:36
const ::Settings & settings
The global settings.
Definition: solver.h:85
void writeResults(bool synthesis, uint year=0, uint numSpace=9999)
Export the results to disk.
Definition: solver.hxx:366
ISimulation(Data::Study &study, const ::Settings &settings, Benchmarking::DurationCollector &duration_collector, IResultWriter &resultWriter, Simulation::ISimulationObserver &simulationObserver)
Constructor (with a given study)
Definition: solver.hxx:237
void run()
Run the simulation.
Definition: solver.hxx:283
~ISimulation()
Destructor.
Definition: solver.hxx:278
Definition: numspace_manager.h:30
Definition: solver.hxx:46
Definition: state.h:71
double optimalSolutionCost1
Sum of the weekly optimal costs over the year (first optimisation step)
Definition: state.h:227
double averageOptimizationTime2
Average time spent in second optimization over the year (ms)
Definition: state.h:233
double averageUpdateTime
Average time spent updating the problem over the year (ms)
Definition: state.h:235
double optimalSolutionCost2
Sum of the weekly optimal costs over the year (second optimisation step)
Definition: state.h:229
double averageOptimizationTime1
Average time spent in first optimization over the year (ms)
Definition: state.h:231
Definition: DurationCollector.h:36
Definition: OptimisationsSimulationTable.h:31
Definition: opt_time_writer.h:30
bool tsGeneratorsOnly
Run the TS generator only.
Definition: options.h:56
bool noOutput
True to disable the writing in the output folder.
Definition: options.h:59