Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

New hydro remix algorithm #2532

Open
wants to merge 44 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
6871e98
New hydro remix : setting unit tests environment
guilpier-code Dec 12, 2024
33a96ea
New hydro remix : some cleaning
guilpier-code Dec 12, 2024
e40b32f
New hydro remix : unit tests on input data
guilpier-code Dec 13, 2024
34f0ed7
New hydro remix : add unit test > pmax is supposed to limit hydro gen…
guilpier-code Dec 13, 2024
cba01ee
New hydro remix : adding tests on Pmax respect
guilpier-code Dec 16, 2024
9f41dc1
New hydro remix : Hydro must respect condition H <= Pmax in input of …
guilpier-code Dec 16, 2024
a52aa7b
New hydro remix : adding a test on influence of Pmax
guilpier-code Dec 16, 2024
52b04e0
New hydro remix : adding a test on influence of Pmin
guilpier-code Dec 16, 2024
56ab22d
New hydro remix : Pmin & Pmax unit tests enhancement
guilpier-code Dec 17, 2024
548fc1f
New hydro remix : change the algorithm due to new specifications
guilpier-code Dec 17, 2024
f3708e2
New hydro remix : adding 2 unit tests on levels computation
guilpier-code Dec 17, 2024
4c8d407
New hydro remix : improve comparison between 2 std::vector<double>
guilpier-code Dec 18, 2024
88dd5ae
New hydro remix : make a test rightfully fail
guilpier-code Dec 18, 2024
d3a24ef
New hydro remix : adding unit tests on levels computed from input data
guilpier-code Dec 18, 2024
cd35bdb
New hydro remix : adding a failing test about of impact reservoir cap…
guilpier-code Dec 19, 2024
f7757b1
New hydro remix : change algorithm to try to fix a test failure
guilpier-code Dec 19, 2024
ea7e23f
New hydro remix : oops, tiny fix
guilpier-code Dec 19, 2024
6ed6f62
New hydro remix : fix the previous fix
guilpier-code Dec 19, 2024
adb89a7
New hydro remix : adding a unit test on getting a sub optimal solutio…
guilpier-code Dec 19, 2024
72f75e4
New hydro remix : adding a unit test about the effect of lowering ini…
guilpier-code Dec 19, 2024
08bbd70
New hydro remix : adding test that case where initial level has no in…
guilpier-code Dec 19, 2024
f0854dd
New hydro remix : creating a header for remix hydro algorithm
guilpier-code Dec 20, 2024
4685abb
New hydro remix : define and use proper comparison operator when chec…
guilpier-code Dec 20, 2024
957cd5b
[skip ci] New hydro remix : replace use of std::adjacent_find with us…
guilpier-code Dec 20, 2024
6157f34
[skip ci] New hydro remix : make code comment more clear
guilpier-code Dec 20, 2024
889b982
New hydro remix : improve unit tests by use of a fixture
guilpier-code Dec 20, 2024
da27cb9
New hydro remix : adding test on invariance where S + DTG_MRG > 0
guilpier-code Dec 27, 2024
91e0a0a
New hydro remix : renaming algorithm input data
guilpier-code Jan 6, 2025
c02210a
New hydro remix : renaming algorithm input data (part 2)
guilpier-code Jan 6, 2025
7d71f04
New hydro remix : unit tests - renaming algorithm input data
guilpier-code Jan 6, 2025
237940e
New hydro remix : unit tests - renaming algorithm input data (part 2)
guilpier-code Jan 6, 2025
47af157
Merge remote-tracking branch 'remotes/origin/develop' into feature/ne…
guilpier-code Jan 7, 2025
8ce9f25
New hydro remix : some more renaming
guilpier-code Jan 7, 2025
65102e2
fix vcpkg
flomnes Jan 7, 2025
41a811a
New hydro remix : more renaming
guilpier-code Jan 7, 2025
bd8d62d
New hydro remix : small correction on algorithm initialization
guilpier-code Jan 7, 2025
511435a
New hydro remix : renaming the algorithm itself
guilpier-code Jan 7, 2025
37bb2be
New hydro remix : create a cmake target for remix hydro algorithm
guilpier-code Jan 7, 2025
a4a68ac
New hydro remix : renaming locally in algorithm
guilpier-code Jan 7, 2025
2309099
New hydro remix : more algorithm local renaming
guilpier-code Jan 7, 2025
1e51fa2
New hydro remix : renaming
guilpier-code Jan 8, 2025
b7107fa
[skip ci] New hydro remix : tiny correction due to review
guilpier-code Jan 8, 2025
d06eeae
New hydro remix : adding a test for comparison with python script
guilpier-code Jan 9, 2025
42352d1
[skip ci] New hydro remix : renaming mainly
guilpier-code Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
295 changes: 295 additions & 0 deletions src/solver/simulation/hydro-remix-new.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
#include "include/antares/solver/simulation/hydro-remix-new.h"

#include <algorithm>
#include <ranges>
#include <stdexcept>
#include <vector>

namespace Antares::Solver::Simulation
{

int find_min_index(const std::vector<double>& G_plus_H,
const std::vector<double>& new_D,
const std::vector<double>& new_H,
const std::vector<int>& tried_creux,
Copy link
Member

Choose a reason for hiding this comment

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

Unclear naming

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

const std::vector<double>& P_max,
const std::vector<bool>& filter_hours_remix,
double top)
Copy link
Member

Choose a reason for hiding this comment

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

Unclear naming

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

{
double min_val = top;
int min_idx = -1;
for (int i = 0; i < G_plus_H.size(); ++i)
{
if (new_D[i] > 0 && new_H[i] < P_max[i] && tried_creux[i] == 0 && filter_hours_remix[i])
{
if (G_plus_H[i] < min_val)
{
min_val = G_plus_H[i];
min_idx = i;
}
}
}
return min_idx;
}

int find_max_index(const std::vector<double>& G_plus_H,
const std::vector<double>& new_H,
const std::vector<int>& tried_pic,
const std::vector<double>& P_min,
const std::vector<bool>& filter_hours_remix,
double ref_value,
double eps)
{
double max_val = 0;
int max_idx = -1;
for (int i = 0; i < G_plus_H.size(); ++i)
{
if (new_H[i] > P_min[i] && G_plus_H[i] >= ref_value + eps && tried_pic[i] == 0
&& filter_hours_remix[i])
{
if (G_plus_H[i] > max_val)
{
max_val = G_plus_H[i];
max_idx = i;
}
}
}
return max_idx;
}

static bool operator<=(const std::vector<double>& a, const std::vector<double>& b)
{
std::vector<double> a_minus_b;
std::ranges::transform(a, b, std::back_inserter(a_minus_b), std::minus<double>());
return std::ranges::all_of(a_minus_b, [](const double& e) { return e <= 0.; });
}

static bool operator<=(const std::vector<double>& v, const double c)
{
return std::ranges::all_of(v, [&c](const double& e) { return e <= c; });
}

static bool operator>=(const std::vector<double>& v, const double c)
{
return std::ranges::all_of(v, [&c](const double& e) { return e >= c; });
}

static void checkInputCorrectness(const std::vector<double>& G,
Copy link
Contributor

Choose a reason for hiding this comment

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

Several functions have a lots of args, maybe creating a struct to encapsulate the data will be simpler ?

Copy link
Contributor Author

@guilpier-code guilpier-code Jan 6, 2025

Choose a reason for hiding this comment

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

I don't know what its better : have many and detailed input arguments, or have a unique input struct argument encapsulating a list of many input data.
Having an input struct as argument may have a disadvantage :
we first have to pack input data into the struct, and then, inside the algorithm, we have to unpack the arguments, meaning we have to call input.someData or input.someOtherData, etc.
It may require more lines of code to do that.
I'm not sure it worth it to have a struct as input

const std::vector<double>& H,
const std::vector<double>& D,
const std::vector<double>& levels,
const std::vector<double>& P_max,
const std::vector<double>& P_min,
double initial_level,
double capacity,
const std::vector<double>& inflows,
const std::vector<double>& overflow,
const std::vector<double>& pump,
const std::vector<double>& S,
const std::vector<double>& DTG_MRG)
Copy link
Member

Choose a reason for hiding this comment

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

  1. I don't really agree with having this function static, it might be public to ease testing
  2. Please avoid single character variable names

Copy link
Contributor Author

@guilpier-code guilpier-code Dec 20, 2024

Choose a reason for hiding this comment

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

  1. If this function was a method of a class, it would be private.
    Question is : do we expose it to the outside world ?
    Well literature says : people should test classes / objects behaviors, not their methods, and especially their private methods (see here for instance). We can't make every private methods public just to test them. So here, we test how the algorithm reacts to incorrect input data.
  2. I do agree

{
std::string msg_prefix = "Remix hydro input : ";

// Initial level smaller than capacity
if (initial_level > capacity)
{
throw std::invalid_argument(msg_prefix + "initial level > reservoir capacity");
}
// Arrays sizes must be identical
std::vector<size_t> sizes = {G.size(),
H.size(),
D.size(),
levels.size(),
P_max.size(),
P_min.size(),
inflows.size(),
overflow.size(),
pump.size(),
S.size(),
DTG_MRG.size()};

if (!std::ranges::all_of(sizes, [&sizes](const size_t s) { return s == sizes.front(); }))
{
throw std::invalid_argument(msg_prefix + "arrays of different sizes");
}

// Arrays are of size 0
if (!G.size())
{
throw std::invalid_argument(msg_prefix + "all arrays of sizes 0");
}

// Hydro production < Pmax
if (!(H <= P_max))
{
throw std::invalid_argument(msg_prefix + "H not smaller than Pmax everywhere");
}

// Hydro production > Pmin
if (!(P_min <= H))
{
throw std::invalid_argument(msg_prefix + "H not greater than Pmin everywhere");
}

if (!(levels <= capacity) || !(levels >= 0.))
{
throw std::invalid_argument(msg_prefix
+ "levels computed from input don't respect reservoir bounds");
}
}

RemixHydroOutput new_remix_hydro(const std::vector<double>& G,
const std::vector<double>& H,
const std::vector<double>& D,
const std::vector<double>& P_max,
const std::vector<double>& P_min,
double initial_level,
double capa,
const std::vector<double>& inflows,
const std::vector<double>& overflow,
const std::vector<double>& pump,
const std::vector<double>& S,
const std::vector<double>& DTG_MRG)
{
std::vector<double> levels(G.size());
if (levels.size())
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (levels.size())
if (!levels.empty())

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

{
levels[0] = initial_level + inflows[0] - overflow[0] + pump[0] - H[0];
for (size_t i = 1; i < levels.size(); ++i)
{
levels[i] = levels[i - 1] + inflows[i] - overflow[i] + pump[i] - H[i];
}
}

checkInputCorrectness(G,
H,
D,
levels,
P_max,
P_min,
initial_level,
capa,
inflows,
overflow,
pump,
S,
DTG_MRG);

std::vector<double> new_H = H;
std::vector<double> new_D = D;

int loop = 1000;
double eps = 1e-3;
double top = *std::max_element(G.begin(), G.end()) + *std::max_element(H.begin(), H.end())
+ *std::max_element(D.begin(), D.end()) + 1;

std::vector<bool> filter_hours_remix(G.size(), false);
for (unsigned int h = 0; h < filter_hours_remix.size(); h++)
{
if (S[h] + DTG_MRG[h] == 0. && H[h] + D[h] > 0.)
{
filter_hours_remix[h] = true;
}
}

std::vector<double> G_plus_H(G.size());
std::transform(G.begin(), G.end(), new_H.begin(), G_plus_H.begin(), std::plus<>());

while (loop-- > 0)
{
std::vector<int> tried_creux(G.size(), 0);
double delta = 0;

while (true)
{
int idx_creux = find_min_index(G_plus_H,
new_D,
new_H,
tried_creux,
P_max,
filter_hours_remix,
top);
if (idx_creux == -1)
{
break;
}

std::vector<int> tried_pic(G.size(), 0);
while (true)
{
int idx_pic = find_max_index(G_plus_H,
new_H,
tried_pic,
P_min,
filter_hours_remix,
G_plus_H[idx_creux],
eps);
if (idx_pic == -1)
{
break;
}

std::vector<double> intermediate_level(levels.begin()
+ std::min(idx_creux, idx_pic),
levels.begin()
+ std::max(idx_creux, idx_pic));
double max_pic, max_creux;
if (idx_creux < idx_pic)
{
max_pic = capa;
max_creux = *std::min_element(intermediate_level.begin(),
intermediate_level.end());
}
else
{
max_pic = capa
- *std::max_element(intermediate_level.begin(),
intermediate_level.end());
max_creux = capa;
}

max_pic = std::min(new_H[idx_pic] - P_min[idx_pic], max_pic);
max_creux = std::min(
{P_max[idx_creux] - new_H[idx_creux], new_D[idx_creux], max_creux});

double dif_pic_creux = std::max(G_plus_H[idx_pic] - G_plus_H[idx_creux], 0.);

delta = std::max(std::min({max_pic, max_creux, dif_pic_creux / 2.}), 0.);

if (delta > 0)
{
new_H[idx_pic] -= delta;
new_H[idx_creux] += delta;
new_D[idx_pic] = H[idx_pic] + D[idx_pic] - new_H[idx_pic];
new_D[idx_creux] = H[idx_creux] + D[idx_creux] - new_H[idx_creux];
break;
}
else
{
tried_pic[idx_pic] = 1;
}
}

if (delta > 0)
{
break;
}
tried_creux[idx_creux] = 1;
}

if (delta == 0)
{
break;
}

std::transform(G.begin(), G.end(), new_H.begin(), G_plus_H.begin(), std::plus<>());
levels[0] = initial_level + inflows[0] - overflow[0] + pump[0] - new_H[0];
for (size_t i = 1; i < levels.size(); ++i)
{
levels[i] = levels[i - 1] + inflows[i] - overflow[i] + pump[i] - new_H[i];
}
}
return {new_H, new_D, levels};
}

} // End namespace Antares::Solver::Simulation
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

#pragma once

#include <vector>

namespace Antares::Solver::Simulation
{

struct RemixHydroOutput
{
std::vector<double> new_H;
std::vector<double> new_D;
std::vector<double> levels;
};

RemixHydroOutput new_remix_hydro(const std::vector<double>& G,
const std::vector<double>& H,
const std::vector<double>& D,
const std::vector<double>& P_max,
const std::vector<double>& P_min,
double init_level,
double capacity,
const std::vector<double>& inflow,
const std::vector<double>& overflow,
const std::vector<double>& pump,
const std::vector<double>& S,
const std::vector<double>& DTG_MRG);

} // namespace Antares::Solver::Simulation
Loading
Loading