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

Draft
wants to merge 26 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 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
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
299 changes: 299 additions & 0 deletions src/solver/simulation/hydro-remix-new.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
#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

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

{
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 isLessThan(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 isLessThan(const std::vector<double>& v, const double c)
{
return std::ranges::all_of(v, [&c](const double& e) { return e <= c; });
}

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

Choose a reason for hiding this comment

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

Use operator overloading, I wrote this example

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


static void checkInputCorrectness(const std::vector<double>& G,
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)
Comment on lines +77 to +89
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::adjacent_find(sizes, std::not_equal_to()) != sizes.end())
Copy link
Member

@flomnes flomnes Dec 20, 2024

Choose a reason for hiding this comment

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

Check all sizes against 1st one

Suggested change
if (std::ranges::adjacent_find(sizes, std::not_equal_to()) != sizes.end())
if (!std::ranges::all_of(sizes, [&](std::size_t s){ s == sizes.front();})

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

{
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 (!isLessThan(H, P_max))
{
throw std::invalid_argument(msg_prefix + "H not smaller than Pmax everywhere");
}
// Hydro production > Pmin
if (!isLessThan(P_min, H))
{
throw std::invalid_argument(msg_prefix + "H not greater than Pmin everywhere");
}

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

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 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())
{
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
Loading
Loading