diff --git a/src/solver/modeler/loadFiles/readLibraries.cpp b/src/solver/modeler/loadFiles/readLibraries.cpp index 41200dbf55..86b83cec5a 100644 --- a/src/solver/modeler/loadFiles/readLibraries.cpp +++ b/src/solver/modeler/loadFiles/readLibraries.cpp @@ -65,7 +65,8 @@ static Study::SystemModel::Library loadSingleLibrary(const fs::path& filePath) } catch (const std::runtime_error& e) { - logs.error() << "Error while converting this library yaml: " << filePath; + logs.error() << "Error while converting this library yaml: " << filePath << ": " + << e.what(); throw ErrorLoadingYaml(e.what()); } } diff --git a/src/solver/modeler/main.cpp b/src/solver/modeler/main.cpp index 2f58a2d224..1a42ff5842 100644 --- a/src/solver/modeler/main.cpp +++ b/src/solver/modeler/main.cpp @@ -157,8 +157,9 @@ int main(int argc, const char** argv) case MipStatus::FEASIBLE: if (!parameters.noOutput) { - logs.info() << "Writing variables..."; + logs.info() << "Writing objective & variable values..."; std::ofstream sol_out(outputPath / "solution.csv"); + sol_out << "objective " << solution->getObjectiveValue() << std::endl; for (const auto& [name, value]: solution->getOptimalValues()) { sol_out << name << " " << value << std::endl; diff --git a/src/solver/variable/include/antares/solver/variable/economy/lold.h b/src/solver/variable/include/antares/solver/variable/economy/lold.h index 394cf0adab..ee1f5ab104 100644 --- a/src/solver/variable/include/antares/solver/variable/economy/lold.h +++ b/src/solver/variable/include/antares/solver/variable/economy/lold.h @@ -74,7 +74,7 @@ struct VCardLOLD //! Indentation (GUI) static constexpr uint8_t nodeDepthForGUI = +0; //! Decimal precision - static constexpr uint8_t decimal = 2; + static constexpr uint8_t decimal = 4; //! Number of columns used by the variable (One ResultsType per column) static constexpr int columnCount = 1; //! The Spatial aggregation diff --git a/src/solver/variable/include/antares/solver/variable/economy/loldCsr.h b/src/solver/variable/include/antares/solver/variable/economy/loldCsr.h index c752a90a21..9f242ab583 100644 --- a/src/solver/variable/include/antares/solver/variable/economy/loldCsr.h +++ b/src/solver/variable/include/antares/solver/variable/economy/loldCsr.h @@ -69,7 +69,7 @@ struct VCardLOLD_CSR //! Indentation (GUI) static constexpr uint8_t nodeDepthForGUI = +0; //! Decimal precision - static constexpr uint8_t decimal = 2; + static constexpr uint8_t decimal = 4; //! Number of columns used by the variable (One ResultsType per column) static constexpr int columnCount = 1; //! The Spatial aggregation diff --git a/src/solver/variable/include/antares/solver/variable/economy/price.h b/src/solver/variable/include/antares/solver/variable/economy/price.h index 7ac93069bf..f14c65ee1b 100644 --- a/src/solver/variable/include/antares/solver/variable/economy/price.h +++ b/src/solver/variable/include/antares/solver/variable/economy/price.h @@ -73,7 +73,7 @@ struct VCardPrice //! Indentation (GUI) static constexpr uint8_t nodeDepthForGUI = +0; //! Decimal precision - static constexpr uint8_t decimal = 2; + static constexpr uint8_t decimal = 4; //! Number of columns used by the variable (One ResultsType per column) static constexpr int columnCount = 1; //! The Spatial aggregation diff --git a/src/solver/variable/include/antares/solver/variable/economy/priceCSR.h b/src/solver/variable/include/antares/solver/variable/economy/priceCSR.h index d5209b6171..c720b17fec 100644 --- a/src/solver/variable/include/antares/solver/variable/economy/priceCSR.h +++ b/src/solver/variable/include/antares/solver/variable/economy/priceCSR.h @@ -74,7 +74,7 @@ struct VCardPriceCSR //! Indentation (GUI) static constexpr uint8_t nodeDepthForGUI = +0; //! Decimal precision - static constexpr uint8_t decimal = 2; + static constexpr uint8_t decimal = 4; //! Number of columns used by the variable (One ResultsType per column) static constexpr int columnCount = 1; //! The Spatial aggregation diff --git a/src/tests/cucumber/features/modeler-features/epic2/us2.5.feature b/src/tests/cucumber/features/modeler-features/epic2/us2.5.feature index 18581f18a7..1ea96d1ca0 100644 --- a/src/tests/cucumber/features/modeler-features/epic2/us2.5.feature +++ b/src/tests/cucumber/features/modeler-features/epic2/us2.5.feature @@ -1,36 +1,43 @@ Feature: 2.5 - Pure modeler simple studies, with no ports and no timeseries - Scenario: 2.5.1: One model with one load and two generators, one timestamp + Scenario: 2.5.1: One model with one load and two generators, one timestep Given the study path is "modeler/epic2/us2.5/study_2.5.1" When I run antares modeler Then the simulation succeeds + And the objective value is 160 And the optimal value of variable node1.gen1_p_0 is 80 And the optimal value of variable node1.gen2_p_0 is 20 - Scenario: 2.5.2: One model with one load and two generators (minP), three timestamps + Scenario: 2.5.2: One model with one load and two generators (minP), three timesteps Given the study path is "modeler/epic2/us2.5/study_2.5.2" When I run antares modeler Then the simulation succeeds - And the optimal value of variable node1.gen1_up_0 is 1 - And the optimal value of variable node1.gen1_up_1 is 1 - And the optimal value of variable node1.gen1_up_2 is 1 - And the optimal value of variable node1.gen1_p_0 is 60 - And the optimal value of variable node1.gen1_p_1 is 60 - And the optimal value of variable node1.gen1_p_2 is 60 - And the optimal value of variable node1.gen2_up_0 is 1 - And the optimal value of variable node1.gen2_up_1 is 1 - And the optimal value of variable node1.gen2_up_2 is 1 - And the optimal value of variable node1.gen2_p_0 is 40 - And the optimal value of variable node1.gen2_p_1 is 40 - And the optimal value of variable node1.gen2_p_2 is 40 + And the objective value is 810 + And the optimal values of the variables are + | component | variable | timestep | value | + | node1 | gen1_up | 0-2 | 1 | + | node1 | gen1_p | 0-2 | 60 | + | node1 | gen1_up | 0-2 | 1 | + | node1 | gen2_p | 0-2 | 40 | - Scenario: 2.5.3: Two libs, one timestamp + Scenario: 2.5.3: Two libs, one timestep Given the study path is "modeler/epic2/us2.5/study_2.5.3" When I run antares modeler Then the simulation succeeds - And the optimal value of variable node1.gen1_p_0 is 0 - And the optimal value of variable node1.gen2_p_0 is 100 - And the optimal value of variable node2.gen1_p_0 is 500 - And the optimal value of variable node2.gen1_up_0 is 1 - And the optimal value of variable node2.gen2_p_0 is 500 - And the optimal value of variable node2.gen2_up_0 is 1 \ No newline at end of file + And the objective value is 15600 + And the optimal values of the variables are + | component | variable | timestep | value | + | node1 | gen1_p | 0 | 0 | + | node1 | gen2_p | 0 | 100 | + | node2 | gen1_p | 0 | 500 | + | node2 | gen1_up | 0 | 1 | + | node2 | gen2_p | 0 | 500 | + | node2 | gen2_up | 0 | 1 | + + Scenario: 2.5.4: Test with integer variable + Given the study path is "modeler/epic2/us2.5/study_2.5.4" + When I run antares modeler + Then the simulation succeeds + And the objective value is 540 + And the optimal value of variable node1.gen_total_p_0 is 1000 + And the optimal value of variable node1.gen_n_on_0 is 4 \ No newline at end of file diff --git a/src/tests/cucumber/features/steps/common_steps/steps.py b/src/tests/cucumber/features/steps/common_steps/steps.py index 030a40051b..0668ee329b 100644 --- a/src/tests/cucumber/features/steps/common_steps/steps.py +++ b/src/tests/cucumber/features/steps/common_steps/steps.py @@ -4,10 +4,9 @@ import pathlib from behave import * - from common_steps.assertions import * -from common_steps.simulator_utils import run_simulation from common_steps.modeler_utils import run_modeler +from common_steps.simulator_utils import run_simulation from features.steps.common_steps.assertions import assert_double_close @@ -133,4 +132,19 @@ def run_antares_modeler(context): @step('the optimal value of variable {var} is {value:g}') def modeler_var_optimal_value(context, var, value): - assert_double_close(value, context.moh.get_optimal_value(var), 1e-6) \ No newline at end of file + assert_double_close(value, context.moh.get_optimal_value(var), 1e-6) + + +@step('the objective value is {value:g}') +def modeler_obj_value(context, value): + assert_double_close(value, context.moh.get_optimal_value("objective"), 1e-6) + +@step('the optimal values of the variables are') +def modeler_var_optimal_value(context): + for row in context.table: + ts_array = row["timestep"].split("-") + ts_start = int(ts_array[0]) + ts_end = int(ts_array[1]) if len(ts_array) == 2 else ts_start + for ts in range(ts_start, ts_end + 1): + var_id = row["component"] + "." + row["variable"] + "_" + str(ts) + assert_double_close(float(row["value"]), context.moh.get_optimal_value(var_id), 1e-6) \ No newline at end of file diff --git a/src/tests/resources/modeler/epic2/us2.5/study_2.5.1/input/system.yml b/src/tests/resources/modeler/epic2/us2.5/study_2.5.1/input/system.yml index e3431532fd..59dd3cd791 100644 --- a/src/tests/resources/modeler/epic2/us2.5/study_2.5.1/input/system.yml +++ b/src/tests/resources/modeler/epic2/us2.5/study_2.5.1/input/system.yml @@ -1,6 +1,7 @@ system: id: sys_example1 - description: test system + description: one load of 100 MW, one cheap generator gen1 (max_p=80 MW, cost=0.5€/MWh), one expensive generator gen2 + (max_p=200 MW, cost=6€/MWh). Thus, gen1 must be used fully (80) and gen2 must complete (20), objective=80*0.5+20*6=160 model-libraries: lib_example1 components: diff --git a/src/tests/resources/modeler/epic2/us2.5/study_2.5.2/input/system.yml b/src/tests/resources/modeler/epic2/us2.5/study_2.5.2/input/system.yml index 1d53821ef9..20ae8fe82f 100644 --- a/src/tests/resources/modeler/epic2/us2.5/study_2.5.2/input/system.yml +++ b/src/tests/resources/modeler/epic2/us2.5/study_2.5.2/input/system.yml @@ -1,6 +1,8 @@ system: id: sys_example2 - description: test system + description: one load of 100 MW, one cheap generator gen1 (max_p=80 MW, cost=0.5€/MWh), one expensive generator gen2 + (min_p=40 MW, cost=6€/MWh). Thus, gen1 does not suffice, gen2 must be used at its min_p (40), and gen_1 must complete + (60); on the 3 timestamps. Objective = 3 * (60 * 0.5 + 40 * 6) = 810 model-libraries: lib_example2 components: diff --git a/src/tests/resources/modeler/epic2/us2.5/study_2.5.3/input/system.yml b/src/tests/resources/modeler/epic2/us2.5/study_2.5.3/input/system.yml index 8ec294015c..aed39a3a05 100644 --- a/src/tests/resources/modeler/epic2/us2.5/study_2.5.3/input/system.yml +++ b/src/tests/resources/modeler/epic2/us2.5/study_2.5.3/input/system.yml @@ -1,6 +1,11 @@ system: id: sys_example3 - description: test system + description: in node1, gen is enough to fulfill load and is less expensive than gen1; thus gen1 must not be used (0) + and gen2 must be used at load level (100). Objective = 100 * 6 = 600 for node1. + In node2, load is 1000 MW and gen1 is cheap but not enough (max_p=650, cost=10€/MWh), and gen2 is large but more + expensive (min_p=500, cost=20 €/MWh). Thus gen2 must be used at its minimum power (500) and gen1 should complete + (500). Objective = 500 * 10 + 500 * 20 = 15000 for node2. + Total objective = 15600 for both nodes. model-libraries: lib_example1, lib_example2 components: diff --git a/src/tests/resources/modeler/epic2/us2.5/study_2.5.4/input/model-libraries/lib_example3.yml b/src/tests/resources/modeler/epic2/us2.5/study_2.5.4/input/model-libraries/lib_example3.yml new file mode 100644 index 0000000000..4535c346bc --- /dev/null +++ b/src/tests/resources/modeler/epic2/us2.5/study_2.5.4/input/model-libraries/lib_example3.yml @@ -0,0 +1,43 @@ +library: + id: lib_example3 + description: test model library + + models: + - id: node_genCluster_oneLoad + description: A simple node with a generator cluster and one load, to test integer variables + parameters: + - id: load + time-dependent: false + scenario-dependent: false + - id: gen_max_p + time-dependent: false + scenario-dependent: false + - id: gen_min_p + time-dependent: false + scenario-dependent: false + - id: gen_cluster_size + time-dependent: false + scenario-dependent: false + - id: gen_prop_cost + time-dependent: false + scenario-dependent: false + - id: gen_fixed_cost + time-dependent: false + scenario-dependent: false + variables: + - id: gen_total_p + lower-bound: 0 + upper-bound: gen_max_p * gen_cluster_size + variable-type: continuous + - id: gen_n_on + lower-bound: 0 + upper-bound: gen_cluster_size + variable-type: integer + constraints: + - id: respect_min_p + expression: gen_total_p >= gen_n_on * gen_min_p + - id: respect_max_p + expression: gen_total_p <= gen_n_on * gen_max_p + - id: balance + expression: gen_total_p = load + objective: gen_total_p * gen_prop_cost + gen_n_on * gen_fixed_cost diff --git a/src/tests/resources/modeler/epic2/us2.5/study_2.5.4/input/system.yml b/src/tests/resources/modeler/epic2/us2.5/study_2.5.4/input/system.yml new file mode 100644 index 0000000000..ff4210e6fe --- /dev/null +++ b/src/tests/resources/modeler/epic2/us2.5/study_2.5.4/input/system.yml @@ -0,0 +1,29 @@ +system: + id: sys_example4 + description: a node with up to 10 similar generators (min_p=150, max_p=300, cost = 0.5 €/MWh + 10€/hour of up time). + Load is of 1000MW; thus 4 generators should be used. Objective = 1000*0.5 + 4*10 = 540. + model-libraries: lib_example3 + + components: + - id: node1 + model: lib_example3.node_genCluster_oneLoad + scenario-group: sg + parameters: + - id: load + type: constant + value: 1000 + - id: gen_min_p + type: constant + value: 150 + - id: gen_max_p + type: constant + value: 300 + - id: gen_cluster_size + type: constant + value: 10 + - id: gen_prop_cost + type: constant + value: 0.5 + - id: gen_fixed_cost + type: constant + value: 10 diff --git a/src/tests/resources/modeler/epic2/us2.5/study_2.5.4/output/problem.lp b/src/tests/resources/modeler/epic2/us2.5/study_2.5.4/output/problem.lp new file mode 100644 index 0000000000..0f743a55e0 --- /dev/null +++ b/src/tests/resources/modeler/epic2/us2.5/study_2.5.4/output/problem.lp @@ -0,0 +1,20 @@ +\ Generated by MPModelProtoExporter +\ Name : +\ Format : Free +\ Constraints : 3 +\ Variables : 2 +\ Binary : 0 +\ Integer : 1 +\ Continuous : 1 +Minimize + Obj: +10 node1.gen_n_on_0 +0.5 node1.gen_total_p_0 +Subject to + node1.balance_0: +1 node1.gen_total_p_0 = 1000 + node1.respect_max_p_0: -300 node1.gen_n_on_0 +1 node1.gen_total_p_0 <= -0 + node1.respect_min_p_0: -150 node1.gen_n_on_0 +1 node1.gen_total_p_0 >= -0 +Bounds + 0 <= node1.gen_n_on_0 <= 10 + 0 <= node1.gen_total_p_0 <= 3000 +Generals + node1.gen_n_on_0 +End diff --git a/src/tests/resources/modeler/epic2/us2.5/study_2.5.4/parameters.yml b/src/tests/resources/modeler/epic2/us2.5/study_2.5.4/parameters.yml new file mode 100644 index 0000000000..a7117d4a05 --- /dev/null +++ b/src/tests/resources/modeler/epic2/us2.5/study_2.5.4/parameters.yml @@ -0,0 +1,6 @@ +solver: scip +solver-logs: false +solver-parameters: +no-output: false +first-time-step: 0 +last-time-step: 0