From 5b72bebc9b424f00c88793527fa199caec5e7541 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Mon, 26 Feb 2024 10:44:23 +0100 Subject: [PATCH 01/29] test Quota CO2 --- tests/andromede/test_quota_co2.py | 194 ++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 tests/andromede/test_quota_co2.py diff --git a/tests/andromede/test_quota_co2.py b/tests/andromede/test_quota_co2.py new file mode 100644 index 00000000..67eac53c --- /dev/null +++ b/tests/andromede/test_quota_co2.py @@ -0,0 +1,194 @@ +from andromede.expression import literal, param, var +from andromede.expression.expression import port_field +from andromede.libs.standard import CONSTANT, TIME_AND_SCENARIO_FREE +from andromede.model import ( + Constraint, + ModelPort, + PortField, + PortType, + float_parameter, + float_variable, + model, +) +from andromede.model.model import PortFieldDefinition, PortFieldId +from andromede.simulation import TimeBlock, build_problem, OutputValues +from andromede.study import ( + ConstantData, + DataBase, + Network, + Node, + PortRef, + create_component, +) + +""" +Power flow port +""" +FLOW_PORT = PortType(id="flow_port", fields=[PortField("F")]) + +""" +CO² emmission port +""" +EMISSION_PORT = PortType(id="emission_port", fields=[PortField("Q")]) + +""" +Simple node that manage flow interconnections. +""" +NODE_MODEL = model( + id="Noeud", + ports=[ModelPort(port_type=FLOW_PORT, port_name="FlowN")], + binding_constraints=[ + Constraint( + name="Balance", + expression=port_field("FlowN", "F").sum_connections() == literal(0), + ) + ], +) + +""" +Model of a simple power generator that takes account of CO² emissions related to the production. +The power production p is bounded between p_min and p_max. +An emission factor is used to determine the CO² emission according to the production. +""" +C02_POWER_MODEL = model( + id='CO2 power', + parameters=[float_parameter("p_min", CONSTANT), + float_parameter("p_max", CONSTANT), + float_parameter("cost", CONSTANT), + float_parameter("taux_emission", CONSTANT)], + variables=[float_variable("p", lower_bound=param("p_min"), upper_bound=param("p_max"))], + ports=[ModelPort(port_type=FLOW_PORT, port_name="FlowP"), + ModelPort(port_type=EMISSION_PORT, port_name="OutCO2")], + port_fields_definitions=[ + PortFieldDefinition( + port_field=PortFieldId("FlowP", "F"), + definition=var("p"), + ), + PortFieldDefinition( + port_field=PortFieldId("OutCO2", "Q"), + definition=var("p") * param("taux_emission"), + ) + ], + objective_contribution=(param("cost") * var("p")).sum().expec(), +) + + +""" +Basic energy consumption model. +It consume a fixed amount of energy "d" each hour. +""" +DEMAND_MODEL = model( + id='Demand model', + parameters=[float_parameter("d", CONSTANT)], + ports=[ModelPort(port_type=FLOW_PORT, port_name="FlowD")], + port_fields_definitions=[ + PortFieldDefinition( + port_field=PortFieldId("FlowD", "F"), + definition=-param("d"), + ) + ] +) + +""" +Model of the CO² quota. +It takes a set a CO² emissions as input. It forces the sum of those emissions to be smaller than a predefined quota. +""" +QUOTA_CO2_MODEL = model( + id='QuotaCO2', + parameters=[float_parameter("quota", CONSTANT)], + ports=[ModelPort(port_type=EMISSION_PORT, port_name="emissionCO2")], + constraints=[Constraint(name='Bound CO2', expression=port_field("emissionCO2", "Q").sum_connections() <= param("quota"))] +) + +""" +Link model to interconnect nodes. +""" +LINK_MODEL = model( + id="LINK", + parameters=[float_parameter("f_max", TIME_AND_SCENARIO_FREE)], + variables=[ + float_variable("flow", lower_bound=-param("f_max"), upper_bound=param("f_max")) + ], + ports=[ + ModelPort(port_type=FLOW_PORT, port_name="port_from"), + ModelPort(port_type=FLOW_PORT, port_name="port_to"), + ], + port_fields_definitions=[ + PortFieldDefinition( + port_field=PortFieldId("port_from", "F"), + definition=-var("flow"), + ), + PortFieldDefinition( + port_field=PortFieldId("port_to", "F"), + definition=var("flow"), + ) + ] +) + + +""" +build the quota CO² test system. + + N1 -----N2----Demand ^ + | | + Oil1 Coal1 + | | + --------- + | + MonQuotaCO2 + +""" +def test_quota_co2(): + n1 = Node(model=NODE_MODEL, id="N1") + n2 = Node(model=NODE_MODEL, id="N2") + oil1 = create_component(model=C02_POWER_MODEL, id="Oil1") + coal1 = create_component(model=C02_POWER_MODEL, id="Coal1") + l12 = create_component(model=LINK_MODEL, id='L12') + demand = create_component(model=DEMAND_MODEL, id='Demand') + monQuotaCO2 = create_component(model=QUOTA_CO2_MODEL, id='MonQuotaCO2') + + network = Network("test") + network.add_node(n1) + network.add_node(n2) + network.add_component(oil1) + network.add_component(coal1) + network.add_component(l12) + network.add_component(demand) + network.add_component(monQuotaCO2) + + network.connect(PortRef(demand, "FlowD"), PortRef(n2, "FlowN")) + network.connect(PortRef(n2, "FlowN"), PortRef(l12, "port_from")) + network.connect(PortRef(l12, "port_to"), PortRef(n1, "FlowN")) + network.connect(PortRef(n1, "FlowN"), PortRef(oil1, "FlowP")) + network.connect(PortRef(n2, 'FlowN'), PortRef(coal1, "FlowP")) + network.connect(PortRef(oil1, "OutCO2"), PortRef(monQuotaCO2, "emissionCO2")) + network.connect(PortRef(coal1, "OutCO2"), PortRef(monQuotaCO2, "emissionCO2")) + + database = DataBase() + database.add_data("Demand", "d", ConstantData(100)) + database.add_data("Coal1", "p_min", ConstantData(0)) + database.add_data("Oil1", "p_min", ConstantData(0)) + database.add_data("Coal1", "p_max", ConstantData(100)) + database.add_data("Oil1", "p_max", ConstantData(100)) + database.add_data("Coal1", "taux_emission", ConstantData(2)) + database.add_data("Oil1", "taux_emission", ConstantData(1)) + database.add_data("Coal1", "cost", ConstantData(10)) + database.add_data("Oil1", "cost", ConstantData(100)) + database.add_data("L12", "f_max", ConstantData(100)) + # when the bug in the port is fixed, the quota should be 150 + database.add_data("MonQuotaCO2", "quota", ConstantData(150)) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + output = OutputValues(problem) + oil1_p = output.component('Oil1').var('p').value + coal1_p = output.component('Coal1').var('p').value + l12_flow = output.component('L12').var('flow').value + + assert status == problem.solver.OPTIMAL + assert problem.solver.Objective().Value() == 5500 + assert oil1_p == 50 + assert coal1_p == 50 + assert l12_flow == -50 \ No newline at end of file From 7465997e2d306a13f3e18b2e2023a215ea7ce6b5 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Mon, 4 Mar 2024 14:21:14 +0100 Subject: [PATCH 02/29] reformatage du fichier test_quota_co2 --- tests/andromede/test_quota_co2.py | 62 +++++++++++++++++++------------ 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/tests/andromede/test_quota_co2.py b/tests/andromede/test_quota_co2.py index 67eac53c..043de3cb 100644 --- a/tests/andromede/test_quota_co2.py +++ b/tests/andromede/test_quota_co2.py @@ -11,7 +11,7 @@ model, ) from andromede.model.model import PortFieldDefinition, PortFieldId -from andromede.simulation import TimeBlock, build_problem, OutputValues +from andromede.simulation import OutputValues, TimeBlock, build_problem from andromede.study import ( ConstantData, DataBase, @@ -51,14 +51,20 @@ An emission factor is used to determine the CO² emission according to the production. """ C02_POWER_MODEL = model( - id='CO2 power', - parameters=[float_parameter("p_min", CONSTANT), - float_parameter("p_max", CONSTANT), - float_parameter("cost", CONSTANT), - float_parameter("taux_emission", CONSTANT)], - variables=[float_variable("p", lower_bound=param("p_min"), upper_bound=param("p_max"))], - ports=[ModelPort(port_type=FLOW_PORT, port_name="FlowP"), - ModelPort(port_type=EMISSION_PORT, port_name="OutCO2")], + id="CO2 power", + parameters=[ + float_parameter("p_min", CONSTANT), + float_parameter("p_max", CONSTANT), + float_parameter("cost", CONSTANT), + float_parameter("taux_emission", CONSTANT), + ], + variables=[ + float_variable("p", lower_bound=param("p_min"), upper_bound=param("p_max")) + ], + ports=[ + ModelPort(port_type=FLOW_PORT, port_name="FlowP"), + ModelPort(port_type=EMISSION_PORT, port_name="OutCO2"), + ], port_fields_definitions=[ PortFieldDefinition( port_field=PortFieldId("FlowP", "F"), @@ -67,7 +73,7 @@ PortFieldDefinition( port_field=PortFieldId("OutCO2", "Q"), definition=var("p") * param("taux_emission"), - ) + ), ], objective_contribution=(param("cost") * var("p")).sum().expec(), ) @@ -78,7 +84,7 @@ It consume a fixed amount of energy "d" each hour. """ DEMAND_MODEL = model( - id='Demand model', + id="Demand model", parameters=[float_parameter("d", CONSTANT)], ports=[ModelPort(port_type=FLOW_PORT, port_name="FlowD")], port_fields_definitions=[ @@ -86,7 +92,7 @@ port_field=PortFieldId("FlowD", "F"), definition=-param("d"), ) - ] + ], ) """ @@ -94,10 +100,16 @@ It takes a set a CO² emissions as input. It forces the sum of those emissions to be smaller than a predefined quota. """ QUOTA_CO2_MODEL = model( - id='QuotaCO2', + id="QuotaCO2", parameters=[float_parameter("quota", CONSTANT)], ports=[ModelPort(port_type=EMISSION_PORT, port_name="emissionCO2")], - constraints=[Constraint(name='Bound CO2', expression=port_field("emissionCO2", "Q").sum_connections() <= param("quota"))] + constraints=[ + Constraint( + name="Bound CO2", + expression=port_field("emissionCO2", "Q").sum_connections() + <= param("quota"), + ) + ], ) """ @@ -121,8 +133,8 @@ PortFieldDefinition( port_field=PortFieldId("port_to", "F"), definition=var("flow"), - ) - ] + ), + ], ) @@ -138,14 +150,16 @@ MonQuotaCO2 """ + + def test_quota_co2(): n1 = Node(model=NODE_MODEL, id="N1") n2 = Node(model=NODE_MODEL, id="N2") oil1 = create_component(model=C02_POWER_MODEL, id="Oil1") coal1 = create_component(model=C02_POWER_MODEL, id="Coal1") - l12 = create_component(model=LINK_MODEL, id='L12') - demand = create_component(model=DEMAND_MODEL, id='Demand') - monQuotaCO2 = create_component(model=QUOTA_CO2_MODEL, id='MonQuotaCO2') + l12 = create_component(model=LINK_MODEL, id="L12") + demand = create_component(model=DEMAND_MODEL, id="Demand") + monQuotaCO2 = create_component(model=QUOTA_CO2_MODEL, id="MonQuotaCO2") network = Network("test") network.add_node(n1) @@ -160,7 +174,7 @@ def test_quota_co2(): network.connect(PortRef(n2, "FlowN"), PortRef(l12, "port_from")) network.connect(PortRef(l12, "port_to"), PortRef(n1, "FlowN")) network.connect(PortRef(n1, "FlowN"), PortRef(oil1, "FlowP")) - network.connect(PortRef(n2, 'FlowN'), PortRef(coal1, "FlowP")) + network.connect(PortRef(n2, "FlowN"), PortRef(coal1, "FlowP")) network.connect(PortRef(oil1, "OutCO2"), PortRef(monQuotaCO2, "emissionCO2")) network.connect(PortRef(coal1, "OutCO2"), PortRef(monQuotaCO2, "emissionCO2")) @@ -183,12 +197,12 @@ def test_quota_co2(): status = problem.solver.Solve() output = OutputValues(problem) - oil1_p = output.component('Oil1').var('p').value - coal1_p = output.component('Coal1').var('p').value - l12_flow = output.component('L12').var('flow').value + oil1_p = output.component("Oil1").var("p").value + coal1_p = output.component("Coal1").var("p").value + l12_flow = output.component("L12").var("flow").value assert status == problem.solver.OPTIMAL assert problem.solver.Objective().Value() == 5500 assert oil1_p == 50 assert coal1_p == 50 - assert l12_flow == -50 \ No newline at end of file + assert l12_flow == -50 From 8b687c60e306cff476a5e599c694dd8cda3246a0 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Mon, 4 Mar 2024 14:21:54 +0100 Subject: [PATCH 03/29] Ajout de Short term storage complex dans les modeles standard --- src/andromede/libs/standard.py | 45 +++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/andromede/libs/standard.py b/src/andromede/libs/standard.py index 856cbcb5..b24a79a7 100644 --- a/src/andromede/libs/standard.py +++ b/src/andromede/libs/standard.py @@ -205,7 +205,7 @@ .shift(ExpressionRange(-param("d_min_down") + 1, literal(0))) .sum() <= param("nb_units_max").shift(-param("d_min_down")) - var("nb_on"), - ) + ), # It also works by writing ExpressionRange(-param("d_min_down") + 1, 0) as ExpressionRange's __post_init__ wraps integers to literal nodes. However, MyPy does not seem to infer that ExpressionRange's attributes are necessarily of ExpressionNode type and raises an error if the arguments in the constructor are integer (whereas it runs correctly), this why we specify it here with literal(0) instead of 0. ], objective_contribution=(param("cost") * var("generation")).sum().expec(), @@ -359,3 +359,46 @@ ], objective_contribution=literal(0), # Implcitement nul ? ) + +SHORT_TERM_STORAGE_COMPLEX = model( + id="STS_COMPLEX", + parameters=[ + float_parameter("p_max_injection"), + float_parameter("p_max_withdrawal"), + float_parameter("level_min"), + float_parameter("level_max"), + float_parameter("inflows"), + float_parameter( + "efficiency" + ), # Should be constant, but time-dependent values should work as well + ], + variables=[ + float_variable( + "injection", lower_bound=literal(0), upper_bound=param("p_max_injection") + ), + float_variable( + "withdrawal", lower_bound=literal(0), upper_bound=param("p_max_withdrawal") + ), + float_variable( + "level", lower_bound=param("level_min"), upper_bound=param("level_max") + ), + ], + ports=[ModelPort(port_type=BALANCE_PORT_TYPE, port_name="balance_port")], + port_fields_definitions=[ + PortFieldDefinition( + port_field=PortFieldId("balance_port", "flow"), + definition=var("withdrawal") - var("injection"), + ) + ], + constraints=[ + Constraint( + name="Level", + expression=var("level") + - var("level").shift(-1) + - param("efficiency") * var("injection") + + var("withdrawal") + == param("inflows"), + ), + ], + objective_contribution=literal(0), # Implcitement nul ? +) From 927cc47a0ed18c027d8e3f1ed747effeff28ea0c Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Mon, 4 Mar 2024 17:26:40 +0100 Subject: [PATCH 04/29] Ajout du test electrolyzer n inputs --- src/andromede/libs/standard.py | 101 ++++--- src/andromede/libs/standard_sc.py | 197 ++++++++++++++ tests/andromede/test_electrolyzer_n_inputs.py | 250 ++++++++++++++++++ 3 files changed, 492 insertions(+), 56 deletions(-) create mode 100644 src/andromede/libs/standard_sc.py create mode 100644 tests/andromede/test_electrolyzer_n_inputs.py diff --git a/src/andromede/libs/standard.py b/src/andromede/libs/standard.py index b24a79a7..b33c1cfc 100644 --- a/src/andromede/libs/standard.py +++ b/src/andromede/libs/standard.py @@ -41,6 +41,28 @@ ], ) +NODE_WITH_SPILL_AND_ENS_MODEL = model( + id="NODE_WITH_SPILL_AND_ENS_MODEL", + parameters=[float_parameter("spillage_cost"), float_parameter("ens_cost")], + variables=[ + float_variable("spillage", lower_bound=literal(0)), + float_variable("unsupplied_energy", lower_bound=literal(0)), + ], + ports=[ModelPort(port_type=BALANCE_PORT_TYPE, port_name="balance_port")], + binding_constraints=[ + Constraint( + name="Balance", + expression=port_field("balance_port", "flow").sum_connections() + == var("spillage") - var("unsupplied_energy"), + ) + ], + objective_operational_contribution=( + param("spillage_cost") * var("spillage") + + param("ens_cost") * var("unsupplied_energy") + ) + .sum() + .expec(), +) """ A standard model for a linear cost generation, limited by a maximum generation. """ @@ -63,7 +85,9 @@ name="Max generation", expression=var("generation") <= param("p_max") ), ], - objective_contribution=(param("cost") * var("generation")).sum().expec(), + objective_operational_contribution=(param("cost") * var("generation")) + .sum() + .expec(), ) """ @@ -133,7 +157,9 @@ lower_bound=literal(0), ), # To test both ways of setting constraints ], - objective_contribution=(param("cost") * var("generation")).sum().expec(), + objective_operational_contribution=(param("cost") * var("generation")) + .sum() + .expec(), ) # For now, no starting cost @@ -159,17 +185,17 @@ "nb_on", lower_bound=literal(0), upper_bound=param("nb_units_max"), - structural_type=ANTICIPATIVE_TIME_VARYING, + structure=ANTICIPATIVE_TIME_VARYING, ), int_variable( "nb_stop", lower_bound=literal(0), - structural_type=ANTICIPATIVE_TIME_VARYING, + structure=ANTICIPATIVE_TIME_VARYING, ), int_variable( "nb_start", lower_bound=literal(0), - structural_type=ANTICIPATIVE_TIME_VARYING, + structure=ANTICIPATIVE_TIME_VARYING, ), ], ports=[ModelPort(port_type=BALANCE_PORT_TYPE, port_name="balance_port")], @@ -208,7 +234,9 @@ ), # It also works by writing ExpressionRange(-param("d_min_down") + 1, 0) as ExpressionRange's __post_init__ wraps integers to literal nodes. However, MyPy does not seem to infer that ExpressionRange's attributes are necessarily of ExpressionNode type and raises an error if the arguments in the constructor are integer (whereas it runs correctly), this why we specify it here with literal(0) instead of 0. ], - objective_contribution=(param("cost") * var("generation")).sum().expec(), + objective_operational_contribution=(param("cost") * var("generation")) + .sum() + .expec(), ) # Same model as previous one, except that starting/stopping variables are now non anticipative @@ -234,17 +262,17 @@ "nb_on", lower_bound=literal(0), upper_bound=param("nb_units_max"), - structural_type=NON_ANTICIPATIVE_TIME_VARYING, + structure=NON_ANTICIPATIVE_TIME_VARYING, ), int_variable( "nb_stop", lower_bound=literal(0), - structural_type=NON_ANTICIPATIVE_TIME_VARYING, + structure=NON_ANTICIPATIVE_TIME_VARYING, ), int_variable( "nb_start", lower_bound=literal(0), - structural_type=NON_ANTICIPATIVE_TIME_VARYING, + structure=NON_ANTICIPATIVE_TIME_VARYING, ), ], ports=[ModelPort(port_type=BALANCE_PORT_TYPE, port_name="balance_port")], @@ -282,7 +310,9 @@ <= param("nb_units_max").shift(-param("d_min_down")) - var("nb_on"), ), ], - objective_contribution=(param("cost") * var("generation")).sum().expec(), + objective_operational_contribution=(param("cost") * var("generation")) + .sum() + .expec(), ) SPILLAGE_MODEL = model( @@ -296,7 +326,7 @@ definition=-var("spillage"), ) ], - objective_contribution=(param("cost") * var("spillage")).sum().expec(), + objective_operational_contribution=(param("cost") * var("spillage")).sum().expec(), ) UNSUPPLIED_ENERGY_MODEL = model( @@ -310,7 +340,9 @@ definition=var("unsupplied_energy"), ) ], - objective_contribution=(param("cost") * var("unsupplied_energy")).sum().expec(), + objective_operational_contribution=(param("cost") * var("unsupplied_energy")) + .sum() + .expec(), ) # Simplified model @@ -357,48 +389,5 @@ == param("inflows"), ), ], - objective_contribution=literal(0), # Implcitement nul ? -) - -SHORT_TERM_STORAGE_COMPLEX = model( - id="STS_COMPLEX", - parameters=[ - float_parameter("p_max_injection"), - float_parameter("p_max_withdrawal"), - float_parameter("level_min"), - float_parameter("level_max"), - float_parameter("inflows"), - float_parameter( - "efficiency" - ), # Should be constant, but time-dependent values should work as well - ], - variables=[ - float_variable( - "injection", lower_bound=literal(0), upper_bound=param("p_max_injection") - ), - float_variable( - "withdrawal", lower_bound=literal(0), upper_bound=param("p_max_withdrawal") - ), - float_variable( - "level", lower_bound=param("level_min"), upper_bound=param("level_max") - ), - ], - ports=[ModelPort(port_type=BALANCE_PORT_TYPE, port_name="balance_port")], - port_fields_definitions=[ - PortFieldDefinition( - port_field=PortFieldId("balance_port", "flow"), - definition=var("withdrawal") - var("injection"), - ) - ], - constraints=[ - Constraint( - name="Level", - expression=var("level") - - var("level").shift(-1) - - param("efficiency") * var("injection") - + var("withdrawal") - == param("inflows"), - ), - ], - objective_contribution=literal(0), # Implcitement nul ? + objective_operational_contribution=literal(0), # Implcitement nul ? ) diff --git a/src/andromede/libs/standard_sc.py b/src/andromede/libs/standard_sc.py new file mode 100644 index 00000000..c849d85b --- /dev/null +++ b/src/andromede/libs/standard_sc.py @@ -0,0 +1,197 @@ +from andromede.expression import literal, param, var +from andromede.expression.expression import port_field +from andromede.libs.standard import CONSTANT, TIME_AND_SCENARIO_FREE +from andromede.model import ( + Constraint, + ModelPort, + PortField, + PortType, + float_parameter, + float_variable, + model, +) +from andromede.model.model import PortFieldDefinition, PortFieldId + +""" +Flow port +""" +FLOW_PORT = PortType(id="flow_port", fields=[PortField("flow")]) + +""" +Simple node that manage flow interconnections. +""" +NODE_MODEL = model( + id="Noeud", + ports=[ModelPort(port_type=FLOW_PORT, port_name="FlowN")], + binding_constraints=[ + Constraint( + name="Balance", + expression=port_field("FlowN", "flow").sum_connections() == literal(0), + ) + ], +) + +""" +Basic consumption model. +It consume a fixed amount of energy "d" each hour. +""" +DEMAND_MODEL = model( + id="Energy Demand model", + parameters=[float_parameter("demand", CONSTANT)], + ports=[ModelPort(port_type=FLOW_PORT, port_name="FlowD")], + port_fields_definitions=[ + PortFieldDefinition( + port_field=PortFieldId("FlowD", "flow"), + definition=-param("demand"), + ) + ], +) + +""" +Model of a power generator. +The power production p is bounded between p_min and p_max. +An emission factor is used to determine the CO² emission according to the production. +""" +PROD_MODEL = model( + id="Production", + parameters=[float_parameter("p_max", CONSTANT), float_parameter("cost", CONSTANT)], + variables=[ + float_variable("prod", lower_bound=literal(0), upper_bound=param("p_max")) + ], + ports=[ModelPort(port_type=FLOW_PORT, port_name="FlowP")], + port_fields_definitions=[ + PortFieldDefinition( + port_field=PortFieldId("FlowP", "flow"), + definition=var("prod"), + ) + ], + objective_operational_contribution=(param("cost") * var("prod")).sum().expec(), +) + +""" +Simple Convertor model. +""" +CONVERTOR_MODEL = model( + id="Convertor model", + parameters=[float_parameter("alpha")], + variables=[ + float_variable("input", lower_bound=literal(0)), + float_variable("output"), + ], + ports=[ + ModelPort(port_type=FLOW_PORT, port_name="FlowDI"), + ModelPort(port_type=FLOW_PORT, port_name="FlowDO"), + ], + port_fields_definitions=[ + PortFieldDefinition( + port_field=PortFieldId("FlowDI", "flow"), + definition=-var("input"), + ), + PortFieldDefinition( + port_field=PortFieldId("FlowDO", "flow"), + definition=var("output"), + ), + ], + constraints=[ + Constraint( + name="Conversion", + expression=var("output") == var("input") * param("alpha"), + ) + ], +) + +""" +Two inputs Convertor model. +""" +TWO_INPUTS_CONVERTOR_MODEL = model( + id="Convertor model", + parameters=[float_parameter("alpha1"), float_parameter("alpha2")], + variables=[ + float_variable("input1", lower_bound=literal(0)), + float_variable("input2", lower_bound=literal(0)), + float_variable("output"), + ], + ports=[ + ModelPort(port_type=FLOW_PORT, port_name="FlowDI1"), + ModelPort(port_type=FLOW_PORT, port_name="FlowDI2"), + ModelPort(port_type=FLOW_PORT, port_name="FlowDO"), + ], + port_fields_definitions=[ + PortFieldDefinition( + port_field=PortFieldId("FlowDI1", "flow"), + definition=-var("input1"), + ), + PortFieldDefinition( + port_field=PortFieldId("FlowDI2", "flow"), + definition=-var("input2"), + ), + PortFieldDefinition( + port_field=PortFieldId("FlowDO", "flow"), + definition=var("output"), + ), + ], + constraints=[ + Constraint( + name="Conversion", + expression=var("output") + == var("input1") * param("alpha1") + var("input2") * param("alpha2"), + ) + ], +) + +DECOMPOSE_1_FLOW_INTO_2_FLOW = model( + id="Consumption electrolyzer model", + variables=[ + float_variable("input1", lower_bound=literal(0)), + float_variable("input2", lower_bound=literal(0)), + float_variable("output"), + ], + ports=[ + ModelPort(port_type=FLOW_PORT, port_name="FlowDI1"), + ModelPort(port_type=FLOW_PORT, port_name="FlowDI2"), + ModelPort(port_type=FLOW_PORT, port_name="FlowDO"), + ], + port_fields_definitions=[ + PortFieldDefinition( + port_field=PortFieldId("FlowDI1", "flow"), + definition=-var("input1"), + ), + PortFieldDefinition( + port_field=PortFieldId("FlowDI2", "flow"), + definition=-var("input2"), + ), + PortFieldDefinition( + port_field=PortFieldId("FlowDO", "flow"), definition=var("output") + ), + ], + constraints=[ + Constraint( + name="output", + expression=var("output") == var("input1") + var("input2"), + ), + ], +) + +CONVERTOR_MODEL_MOD = model( + id="Convertor model", + parameters=[float_parameter("alpha")], + variables=[ + float_variable("input", lower_bound=literal(0)), + ], + ports=[ + ModelPort(port_type=FLOW_PORT, port_name="FlowDI"), + ModelPort(port_type=FLOW_PORT, port_name="FlowDO"), + ], + port_fields_definitions=[ + PortFieldDefinition( + port_field=PortFieldId("FlowDO", "flow"), + definition=var("input") * param("alpha"), + ), + ], + constraints=[ + Constraint( + name="Conversion", + expression=var("input") == port_field("FlowDI", "flow").sum_connections(), + ) + ], +) diff --git a/tests/andromede/test_electrolyzer_n_inputs.py b/tests/andromede/test_electrolyzer_n_inputs.py new file mode 100644 index 00000000..7e521f9c --- /dev/null +++ b/tests/andromede/test_electrolyzer_n_inputs.py @@ -0,0 +1,250 @@ +from andromede.expression import literal, param, var +from andromede.expression.expression import port_field +from andromede.libs.standard_sc import ( + CONVERTOR_MODEL, + CONVERTOR_MODEL_MOD, + DECOMPOSE_1_FLOW_INTO_2_FLOW, + DEMAND_MODEL, + FLOW_PORT, + NODE_MODEL, + PROD_MODEL, + TWO_INPUTS_CONVERTOR_MODEL, +) +from andromede.model import ( + Constraint, + ModelPort, + PortField, + PortType, + float_parameter, + float_variable, + model, +) +from andromede.model.model import PortFieldDefinition, PortFieldId +from andromede.simulation import TimeBlock, build_problem +from andromede.study import ( + ConstantData, + DataBase, + Network, + Node, + PortRef, + create_component, +) + + +def test_electrolyzer_n_inputs_1(): + elec_node_1 = Node(model=NODE_MODEL, id="e1") + electric_prod_1 = create_component(model=PROD_MODEL, id="ep1") + electrolyzer1 = create_component(model=CONVERTOR_MODEL, id="ez1") + + elec_node_2 = Node(model=NODE_MODEL, id="e2") + electric_prod_2 = create_component(model=PROD_MODEL, id="ep2") + electrolyzer2 = create_component(model=CONVERTOR_MODEL, id="ez2") + + gaz_node = Node(model=NODE_MODEL, id="g") + gaz_prod = create_component(model=PROD_MODEL, id="gp") + gaz_demand = create_component(model=DEMAND_MODEL, id="gd") + + database = DataBase() + + database.add_data("ep1", "p_max", ConstantData(100)) + database.add_data("ep1", "cost", ConstantData(30)) + database.add_data("ez1", "alpha", ConstantData(0.7)) + + database.add_data("ep2", "p_max", ConstantData(100)) + database.add_data("ep2", "cost", ConstantData(30)) + database.add_data("ez2", "alpha", ConstantData(0.7)) + + database.add_data("gd", "demand", ConstantData(70)) + database.add_data("gp", "p_max", ConstantData(10)) + database.add_data("gp", "cost", ConstantData(40)) + + network = Network("test") + network.add_node(elec_node_1) + network.add_component(electric_prod_1) + network.add_component(electrolyzer1) + network.add_node(elec_node_2) + network.add_component(electric_prod_2) + network.add_component(electrolyzer2) + network.add_node(gaz_node) + network.add_component(gaz_prod) + network.add_component(gaz_demand) + + network.connect(PortRef(electric_prod_1, "FlowP"), PortRef(elec_node_1, "FlowN")) + network.connect(PortRef(elec_node_1, "FlowN"), PortRef(electrolyzer1, "FlowDI")) + network.connect(PortRef(electrolyzer1, "FlowDO"), PortRef(gaz_node, "FlowN")) + network.connect(PortRef(electric_prod_2, "FlowP"), PortRef(elec_node_2, "FlowN")) + network.connect(PortRef(elec_node_2, "FlowN"), PortRef(electrolyzer2, "FlowDI")) + network.connect(PortRef(electrolyzer2, "FlowDO"), PortRef(gaz_node, "FlowN")) + network.connect(PortRef(gaz_node, "FlowN"), PortRef(gaz_demand, "FlowD")) + network.connect(PortRef(gaz_prod, "FlowP"), PortRef(gaz_node, "FlowN")) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + assert status == problem.solver.OPTIMAL + assert problem.solver.Objective().Value() == 2971.4285714285716 + + +def test_electrolyzer_n_inputs_2(): + """ + Test avec un electrolyzer qui prend 2 input + """ + + elec_node_1 = Node(model=NODE_MODEL, id="e1") + elec_node_2 = Node(model=NODE_MODEL, id="e2") + gaz_node = Node(model=NODE_MODEL, id="g") + + electric_prod_1 = create_component(model=PROD_MODEL, id="ep1") + electric_prod_2 = create_component(model=PROD_MODEL, id="ep2") + + gaz_prod = create_component(model=PROD_MODEL, id="gp") + gaz_demand = create_component(model=DEMAND_MODEL, id="gd") + + electrolyzer = create_component(model=TWO_INPUTS_CONVERTOR_MODEL, id="ez") + + database = DataBase() + database.add_data("gd", "demand", ConstantData(70)) + database.add_data("ep1", "p_max", ConstantData(100)) + database.add_data("ep2", "p_max", ConstantData(100)) + database.add_data("ep1", "cost", ConstantData(30)) + database.add_data("ep2", "cost", ConstantData(30)) + database.add_data("ez", "alpha1", ConstantData(0.7)) + database.add_data("ez", "alpha2", ConstantData(0.5)) + database.add_data("gp", "p_max", ConstantData(10)) + database.add_data("gp", "cost", ConstantData(40)) + + network = Network("test") + network.add_node(elec_node_1) + network.add_node(elec_node_2) + network.add_node(gaz_node) + network.add_component(electric_prod_1) + network.add_component(electric_prod_2) + network.add_component(gaz_prod) + network.add_component(gaz_demand) + network.add_component(electrolyzer) + + network.connect(PortRef(electric_prod_1, "FlowP"), PortRef(elec_node_1, "FlowN")) + network.connect(PortRef(elec_node_1, "FlowN"), PortRef(electrolyzer, "FlowDI1")) + network.connect(PortRef(electric_prod_2, "FlowP"), PortRef(elec_node_2, "FlowN")) + network.connect(PortRef(elec_node_2, "FlowN"), PortRef(electrolyzer, "FlowDI2")) + network.connect(PortRef(electrolyzer, "FlowDO"), PortRef(gaz_node, "FlowN")) + network.connect(PortRef(gaz_node, "FlowN"), PortRef(gaz_demand, "FlowD")) + network.connect(PortRef(gaz_prod, "FlowP"), PortRef(gaz_node, "FlowN")) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + assert status == problem.solver.OPTIMAL + assert problem.solver.Objective().Value() == 2971.4285714285716 + + +def test_electrolyzer_n_inputs_3(): + + elec_node_1 = Node(model=NODE_MODEL, id="e1") + elec_node_2 = Node(model=NODE_MODEL, id="e2") + gaz_node = Node(model=NODE_MODEL, id="g") + + electric_prod_1 = create_component(model=PROD_MODEL, id="ep1") + electric_prod_2 = create_component(model=PROD_MODEL, id="ep2") + + gaz_prod = create_component(model=PROD_MODEL, id="gp") + gaz_demand = create_component(model=DEMAND_MODEL, id="gd") + + electrolyzer = create_component(model=CONVERTOR_MODEL_MOD, id="ez") + consumption_electrolyzer = create_component( + model=DECOMPOSE_1_FLOW_INTO_2_FLOW, id="ce" + ) + + database = DataBase() + database.add_data("gd", "demand", ConstantData(70)) + database.add_data("ep1", "p_max", ConstantData(100)) + database.add_data("ep2", "p_max", ConstantData(100)) + database.add_data("ep1", "cost", ConstantData(30)) + database.add_data("ep2", "cost", ConstantData(30)) + database.add_data("ez", "alpha", ConstantData(0.7)) + database.add_data("gp", "p_max", ConstantData(10)) + database.add_data("gp", "cost", ConstantData(40)) + + network = Network("test") + network.add_node(elec_node_1) + network.add_node(elec_node_2) + network.add_node(gaz_node) + network.add_component(electric_prod_1) + network.add_component(electric_prod_2) + network.add_component(gaz_prod) + network.add_component(gaz_demand) + network.add_component(electrolyzer) + network.add_component(consumption_electrolyzer) + + network.connect(PortRef(electric_prod_1, "FlowP"), PortRef(elec_node_1, "FlowN")) + network.connect( + PortRef(elec_node_1, "FlowN"), PortRef(consumption_electrolyzer, "FlowDI1") + ) + network.connect(PortRef(electric_prod_2, "FlowP"), PortRef(elec_node_2, "FlowN")) + network.connect( + PortRef(elec_node_2, "FlowN"), PortRef(consumption_electrolyzer, "FlowDI2") + ) + network.connect( + PortRef(consumption_electrolyzer, "FlowDO"), PortRef(electrolyzer, "FlowDI") + ) + network.connect(PortRef(electrolyzer, "FlowDO"), PortRef(gaz_node, "FlowN")) + network.connect(PortRef(gaz_node, "FlowN"), PortRef(gaz_demand, "FlowD")) + network.connect(PortRef(gaz_prod, "FlowP"), PortRef(gaz_node, "FlowN")) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + assert status == problem.solver.OPTIMAL + assert problem.solver.Objective().Value() == 2971.4285714285716 + + +def test_electrolyzer_n_inputs_4(): + elec_node_1 = Node(model=NODE_MODEL, id="e1") + elec_node_2 = Node(model=NODE_MODEL, id="e2") + gaz_node = Node(model=NODE_MODEL, id="g") + + electric_prod_1 = create_component(model=PROD_MODEL, id="ep1") + electric_prod_2 = create_component(model=PROD_MODEL, id="ep2") + + gaz_prod = create_component(model=PROD_MODEL, id="gp") + gaz_demand = create_component(model=DEMAND_MODEL, id="gd") + + electrolyzer = create_component(model=CONVERTOR_MODEL, id="ez") + + database = DataBase() + database.add_data("gd", "demand", ConstantData(70)) + database.add_data("ep1", "p_max", ConstantData(100)) + database.add_data("ep2", "p_max", ConstantData(100)) + database.add_data("ep1", "cost", ConstantData(30)) + database.add_data("ep2", "cost", ConstantData(30)) + database.add_data("ez", "alpha", ConstantData(0.7)) + database.add_data("gp", "p_max", ConstantData(10)) + database.add_data("gp", "cost", ConstantData(40)) + + network = Network("test") + network.add_node(elec_node_1) + network.add_node(elec_node_2) + network.add_node(gaz_node) + network.add_component(electric_prod_1) + network.add_component(electric_prod_2) + network.add_component(gaz_prod) + network.add_component(gaz_demand) + network.add_component(electrolyzer) + + network.connect(PortRef(electric_prod_1, "FlowP"), PortRef(elec_node_1, "FlowN")) + network.connect(PortRef(elec_node_1, "FlowN"), PortRef(electrolyzer, "FlowDI")) + network.connect(PortRef(electric_prod_2, "FlowP"), PortRef(elec_node_2, "FlowN")) + network.connect(PortRef(elec_node_2, "FlowN"), PortRef(electrolyzer, "FlowDI")) + network.connect(PortRef(electrolyzer, "FlowDO"), PortRef(gaz_node, "FlowN")) + network.connect(PortRef(gaz_node, "FlowN"), PortRef(gaz_demand, "FlowD")) + network.connect(PortRef(gaz_prod, "FlowP"), PortRef(gaz_node, "FlowN")) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + assert status == problem.solver.OPTIMAL + assert problem.solver.Objective().Value() == 2971.4285714285716 From 18a4b1f2dbc3550fe21fc5269586786790fdefe9 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Mon, 4 Mar 2024 18:07:01 +0100 Subject: [PATCH 05/29] fix taking in account the comments on the pull request --- AUTHORS.txt | 1 + src/andromede/libs/standard_sc.py | 126 +++++++----- tests/andromede/test_electrolyzer_n_inputs.py | 193 +++++++++++------- tests/unittests/test_quota_co2.py | 158 +++----------- 4 files changed, 229 insertions(+), 249 deletions(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index 4ddcc5b2..30c2bacf 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -8,3 +8,4 @@ pet-mit sylvlecl tbittar vargastat +Yann-Temudjin \ No newline at end of file diff --git a/src/andromede/libs/standard_sc.py b/src/andromede/libs/standard_sc.py index c849d85b..3b16789a 100644 --- a/src/andromede/libs/standard_sc.py +++ b/src/andromede/libs/standard_sc.py @@ -1,6 +1,18 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + from andromede.expression import literal, param, var from andromede.expression.expression import port_field -from andromede.libs.standard import CONSTANT, TIME_AND_SCENARIO_FREE +from andromede.libs.standard import BALANCE_PORT_TYPE, CONSTANT, TIME_AND_SCENARIO_FREE from andromede.model import ( Constraint, ModelPort, @@ -12,41 +24,6 @@ ) from andromede.model.model import PortFieldDefinition, PortFieldId -""" -Flow port -""" -FLOW_PORT = PortType(id="flow_port", fields=[PortField("flow")]) - -""" -Simple node that manage flow interconnections. -""" -NODE_MODEL = model( - id="Noeud", - ports=[ModelPort(port_type=FLOW_PORT, port_name="FlowN")], - binding_constraints=[ - Constraint( - name="Balance", - expression=port_field("FlowN", "flow").sum_connections() == literal(0), - ) - ], -) - -""" -Basic consumption model. -It consume a fixed amount of energy "d" each hour. -""" -DEMAND_MODEL = model( - id="Energy Demand model", - parameters=[float_parameter("demand", CONSTANT)], - ports=[ModelPort(port_type=FLOW_PORT, port_name="FlowD")], - port_fields_definitions=[ - PortFieldDefinition( - port_field=PortFieldId("FlowD", "flow"), - definition=-param("demand"), - ) - ], -) - """ Model of a power generator. The power production p is bounded between p_min and p_max. @@ -58,7 +35,7 @@ variables=[ float_variable("prod", lower_bound=literal(0), upper_bound=param("p_max")) ], - ports=[ModelPort(port_type=FLOW_PORT, port_name="FlowP")], + ports=[ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowP")], port_fields_definitions=[ PortFieldDefinition( port_field=PortFieldId("FlowP", "flow"), @@ -79,8 +56,8 @@ float_variable("output"), ], ports=[ - ModelPort(port_type=FLOW_PORT, port_name="FlowDI"), - ModelPort(port_type=FLOW_PORT, port_name="FlowDO"), + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowDI"), + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowDO"), ], port_fields_definitions=[ PortFieldDefinition( @@ -112,9 +89,9 @@ float_variable("output"), ], ports=[ - ModelPort(port_type=FLOW_PORT, port_name="FlowDI1"), - ModelPort(port_type=FLOW_PORT, port_name="FlowDI2"), - ModelPort(port_type=FLOW_PORT, port_name="FlowDO"), + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowDI1"), + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowDI2"), + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowDO"), ], port_fields_definitions=[ PortFieldDefinition( @@ -147,9 +124,9 @@ float_variable("output"), ], ports=[ - ModelPort(port_type=FLOW_PORT, port_name="FlowDI1"), - ModelPort(port_type=FLOW_PORT, port_name="FlowDI2"), - ModelPort(port_type=FLOW_PORT, port_name="FlowDO"), + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowDI1"), + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowDI2"), + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowDO"), ], port_fields_definitions=[ PortFieldDefinition( @@ -179,8 +156,8 @@ float_variable("input", lower_bound=literal(0)), ], ports=[ - ModelPort(port_type=FLOW_PORT, port_name="FlowDI"), - ModelPort(port_type=FLOW_PORT, port_name="FlowDO"), + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowDI"), + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowDO"), ], port_fields_definitions=[ PortFieldDefinition( @@ -195,3 +172,58 @@ ) ], ) + +""" +CO² emmission port +""" +EMISSION_PORT = PortType(id="emission_port", fields=[PortField("Q")]) + +""" +Model of a simple power generator that takes account of CO² emissions related to the production. +The power production p is bounded between p_min and p_max. +An emission factor is used to determine the CO² emission according to the production. +""" +C02_POWER_MODEL = model( + id="CO2 power", + parameters=[ + float_parameter("p_min", CONSTANT), + float_parameter("p_max", CONSTANT), + float_parameter("cost", CONSTANT), + float_parameter("emission_rate", CONSTANT), + ], + variables=[ + float_variable("p", lower_bound=param("p_min"), upper_bound=param("p_max")) + ], + ports=[ + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowP"), + ModelPort(port_type=EMISSION_PORT, port_name="OutCO2"), + ], + port_fields_definitions=[ + PortFieldDefinition( + port_field=PortFieldId("FlowP", "flow"), + definition=var("p"), + ), + PortFieldDefinition( + port_field=PortFieldId("OutCO2", "Q"), + definition=var("p") * param("emission_rate"), + ), + ], + objective_operational_contribution=(param("cost") * var("p")).sum().expec(), +) + +""" +Model of the CO² quota. +It takes a set a CO² emissions as input. It forces the sum of those emissions to be smaller than a predefined quota. +""" +QUOTA_CO2_MODEL = model( + id="QuotaCO2", + parameters=[float_parameter("quota", CONSTANT)], + ports=[ModelPort(port_type=EMISSION_PORT, port_name="emissionCO2")], + binding_constraints=[ + Constraint( + name="Bound CO2", + expression=port_field("emissionCO2", "Q").sum_connections() + <= param("quota"), + ) + ], +) diff --git a/tests/andromede/test_electrolyzer_n_inputs.py b/tests/andromede/test_electrolyzer_n_inputs.py index 7e521f9c..cd018970 100644 --- a/tests/andromede/test_electrolyzer_n_inputs.py +++ b/tests/andromede/test_electrolyzer_n_inputs.py @@ -1,25 +1,22 @@ -from andromede.expression import literal, param, var -from andromede.expression.expression import port_field +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +from andromede.libs.standard import DEMAND_MODEL, GENERATOR_MODEL, NODE_BALANCE_MODEL from andromede.libs.standard_sc import ( CONVERTOR_MODEL, CONVERTOR_MODEL_MOD, DECOMPOSE_1_FLOW_INTO_2_FLOW, - DEMAND_MODEL, - FLOW_PORT, - NODE_MODEL, - PROD_MODEL, TWO_INPUTS_CONVERTOR_MODEL, ) -from andromede.model import ( - Constraint, - ModelPort, - PortField, - PortType, - float_parameter, - float_variable, - model, -) -from andromede.model.model import PortFieldDefinition, PortFieldId from andromede.simulation import TimeBlock, build_problem from andromede.study import ( ConstantData, @@ -32,16 +29,19 @@ def test_electrolyzer_n_inputs_1(): - elec_node_1 = Node(model=NODE_MODEL, id="e1") - electric_prod_1 = create_component(model=PROD_MODEL, id="ep1") + """ + Test with an electrolyzer for each inputs + """ + elec_node_1 = Node(model=NODE_BALANCE_MODEL, id="e1") + electric_prod_1 = create_component(model=GENERATOR_MODEL, id="ep1") electrolyzer1 = create_component(model=CONVERTOR_MODEL, id="ez1") - elec_node_2 = Node(model=NODE_MODEL, id="e2") - electric_prod_2 = create_component(model=PROD_MODEL, id="ep2") + elec_node_2 = Node(model=NODE_BALANCE_MODEL, id="e2") + electric_prod_2 = create_component(model=GENERATOR_MODEL, id="ep2") electrolyzer2 = create_component(model=CONVERTOR_MODEL, id="ez2") - gaz_node = Node(model=NODE_MODEL, id="g") - gaz_prod = create_component(model=PROD_MODEL, id="gp") + gaz_node = Node(model=NODE_BALANCE_MODEL, id="g") + gaz_prod = create_component(model=GENERATOR_MODEL, id="gp") gaz_demand = create_component(model=DEMAND_MODEL, id="gd") database = DataBase() @@ -69,14 +69,26 @@ def test_electrolyzer_n_inputs_1(): network.add_component(gaz_prod) network.add_component(gaz_demand) - network.connect(PortRef(electric_prod_1, "FlowP"), PortRef(elec_node_1, "FlowN")) - network.connect(PortRef(elec_node_1, "FlowN"), PortRef(electrolyzer1, "FlowDI")) - network.connect(PortRef(electrolyzer1, "FlowDO"), PortRef(gaz_node, "FlowN")) - network.connect(PortRef(electric_prod_2, "FlowP"), PortRef(elec_node_2, "FlowN")) - network.connect(PortRef(elec_node_2, "FlowN"), PortRef(electrolyzer2, "FlowDI")) - network.connect(PortRef(electrolyzer2, "FlowDO"), PortRef(gaz_node, "FlowN")) - network.connect(PortRef(gaz_node, "FlowN"), PortRef(gaz_demand, "FlowD")) - network.connect(PortRef(gaz_prod, "FlowP"), PortRef(gaz_node, "FlowN")) + network.connect( + PortRef(electric_prod_1, "balance_port"), PortRef(elec_node_1, "balance_port") + ) + network.connect( + PortRef(elec_node_1, "balance_port"), PortRef(electrolyzer1, "FlowDI") + ) + network.connect(PortRef(electrolyzer1, "FlowDO"), PortRef(gaz_node, "balance_port")) + network.connect( + PortRef(electric_prod_2, "balance_port"), PortRef(elec_node_2, "balance_port") + ) + network.connect( + PortRef(elec_node_2, "balance_port"), PortRef(electrolyzer2, "FlowDI") + ) + network.connect(PortRef(electrolyzer2, "FlowDO"), PortRef(gaz_node, "balance_port")) + network.connect( + PortRef(gaz_node, "balance_port"), PortRef(gaz_demand, "balance_port") + ) + network.connect( + PortRef(gaz_prod, "balance_port"), PortRef(gaz_node, "balance_port") + ) scenarios = 1 problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) @@ -88,17 +100,17 @@ def test_electrolyzer_n_inputs_1(): def test_electrolyzer_n_inputs_2(): """ - Test avec un electrolyzer qui prend 2 input + Test with one electrolyzer that has two inputs """ - elec_node_1 = Node(model=NODE_MODEL, id="e1") - elec_node_2 = Node(model=NODE_MODEL, id="e2") - gaz_node = Node(model=NODE_MODEL, id="g") + elec_node_1 = Node(model=NODE_BALANCE_MODEL, id="e1") + elec_node_2 = Node(model=NODE_BALANCE_MODEL, id="e2") + gaz_node = Node(model=NODE_BALANCE_MODEL, id="g") - electric_prod_1 = create_component(model=PROD_MODEL, id="ep1") - electric_prod_2 = create_component(model=PROD_MODEL, id="ep2") + electric_prod_1 = create_component(model=GENERATOR_MODEL, id="ep1") + electric_prod_2 = create_component(model=GENERATOR_MODEL, id="ep2") - gaz_prod = create_component(model=PROD_MODEL, id="gp") + gaz_prod = create_component(model=GENERATOR_MODEL, id="gp") gaz_demand = create_component(model=DEMAND_MODEL, id="gd") electrolyzer = create_component(model=TWO_INPUTS_CONVERTOR_MODEL, id="ez") @@ -124,13 +136,25 @@ def test_electrolyzer_n_inputs_2(): network.add_component(gaz_demand) network.add_component(electrolyzer) - network.connect(PortRef(electric_prod_1, "FlowP"), PortRef(elec_node_1, "FlowN")) - network.connect(PortRef(elec_node_1, "FlowN"), PortRef(electrolyzer, "FlowDI1")) - network.connect(PortRef(electric_prod_2, "FlowP"), PortRef(elec_node_2, "FlowN")) - network.connect(PortRef(elec_node_2, "FlowN"), PortRef(electrolyzer, "FlowDI2")) - network.connect(PortRef(electrolyzer, "FlowDO"), PortRef(gaz_node, "FlowN")) - network.connect(PortRef(gaz_node, "FlowN"), PortRef(gaz_demand, "FlowD")) - network.connect(PortRef(gaz_prod, "FlowP"), PortRef(gaz_node, "FlowN")) + network.connect( + PortRef(electric_prod_1, "balance_port"), PortRef(elec_node_1, "balance_port") + ) + network.connect( + PortRef(elec_node_1, "balance_port"), PortRef(electrolyzer, "FlowDI1") + ) + network.connect( + PortRef(electric_prod_2, "balance_port"), PortRef(elec_node_2, "balance_port") + ) + network.connect( + PortRef(elec_node_2, "balance_port"), PortRef(electrolyzer, "FlowDI2") + ) + network.connect(PortRef(electrolyzer, "FlowDO"), PortRef(gaz_node, "balance_port")) + network.connect( + PortRef(gaz_node, "balance_port"), PortRef(gaz_demand, "balance_port") + ) + network.connect( + PortRef(gaz_prod, "balance_port"), PortRef(gaz_node, "balance_port") + ) scenarios = 1 problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) @@ -141,15 +165,17 @@ def test_electrolyzer_n_inputs_2(): def test_electrolyzer_n_inputs_3(): + """ + Test with a consumption_electrolyzer with two inputs + """ + elec_node_1 = Node(model=NODE_BALANCE_MODEL, id="e1") + elec_node_2 = Node(model=NODE_BALANCE_MODEL, id="e2") + gaz_node = Node(model=NODE_BALANCE_MODEL, id="g") - elec_node_1 = Node(model=NODE_MODEL, id="e1") - elec_node_2 = Node(model=NODE_MODEL, id="e2") - gaz_node = Node(model=NODE_MODEL, id="g") - - electric_prod_1 = create_component(model=PROD_MODEL, id="ep1") - electric_prod_2 = create_component(model=PROD_MODEL, id="ep2") + electric_prod_1 = create_component(model=GENERATOR_MODEL, id="ep1") + electric_prod_2 = create_component(model=GENERATOR_MODEL, id="ep2") - gaz_prod = create_component(model=PROD_MODEL, id="gp") + gaz_prod = create_component(model=GENERATOR_MODEL, id="gp") gaz_demand = create_component(model=DEMAND_MODEL, id="gd") electrolyzer = create_component(model=CONVERTOR_MODEL_MOD, id="ez") @@ -178,20 +204,30 @@ def test_electrolyzer_n_inputs_3(): network.add_component(electrolyzer) network.add_component(consumption_electrolyzer) - network.connect(PortRef(electric_prod_1, "FlowP"), PortRef(elec_node_1, "FlowN")) network.connect( - PortRef(elec_node_1, "FlowN"), PortRef(consumption_electrolyzer, "FlowDI1") + PortRef(electric_prod_1, "balance_port"), PortRef(elec_node_1, "balance_port") ) - network.connect(PortRef(electric_prod_2, "FlowP"), PortRef(elec_node_2, "FlowN")) network.connect( - PortRef(elec_node_2, "FlowN"), PortRef(consumption_electrolyzer, "FlowDI2") + PortRef(elec_node_1, "balance_port"), + PortRef(consumption_electrolyzer, "FlowDI1"), + ) + network.connect( + PortRef(electric_prod_2, "balance_port"), PortRef(elec_node_2, "balance_port") + ) + network.connect( + PortRef(elec_node_2, "balance_port"), + PortRef(consumption_electrolyzer, "FlowDI2"), ) network.connect( PortRef(consumption_electrolyzer, "FlowDO"), PortRef(electrolyzer, "FlowDI") ) - network.connect(PortRef(electrolyzer, "FlowDO"), PortRef(gaz_node, "FlowN")) - network.connect(PortRef(gaz_node, "FlowN"), PortRef(gaz_demand, "FlowD")) - network.connect(PortRef(gaz_prod, "FlowP"), PortRef(gaz_node, "FlowN")) + network.connect(PortRef(electrolyzer, "FlowDO"), PortRef(gaz_node, "balance_port")) + network.connect( + PortRef(gaz_node, "balance_port"), PortRef(gaz_demand, "balance_port") + ) + network.connect( + PortRef(gaz_prod, "balance_port"), PortRef(gaz_node, "balance_port") + ) scenarios = 1 problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) @@ -202,14 +238,17 @@ def test_electrolyzer_n_inputs_3(): def test_electrolyzer_n_inputs_4(): - elec_node_1 = Node(model=NODE_MODEL, id="e1") - elec_node_2 = Node(model=NODE_MODEL, id="e2") - gaz_node = Node(model=NODE_MODEL, id="g") + """ + Test with one electrolyzer with one input that takes every inputs + """ + elec_node_1 = Node(model=NODE_BALANCE_MODEL, id="e1") + elec_node_2 = Node(model=NODE_BALANCE_MODEL, id="e2") + gaz_node = Node(model=NODE_BALANCE_MODEL, id="g") - electric_prod_1 = create_component(model=PROD_MODEL, id="ep1") - electric_prod_2 = create_component(model=PROD_MODEL, id="ep2") + electric_prod_1 = create_component(model=GENERATOR_MODEL, id="ep1") + electric_prod_2 = create_component(model=GENERATOR_MODEL, id="ep2") - gaz_prod = create_component(model=PROD_MODEL, id="gp") + gaz_prod = create_component(model=GENERATOR_MODEL, id="gp") gaz_demand = create_component(model=DEMAND_MODEL, id="gd") electrolyzer = create_component(model=CONVERTOR_MODEL, id="ez") @@ -234,13 +273,25 @@ def test_electrolyzer_n_inputs_4(): network.add_component(gaz_demand) network.add_component(electrolyzer) - network.connect(PortRef(electric_prod_1, "FlowP"), PortRef(elec_node_1, "FlowN")) - network.connect(PortRef(elec_node_1, "FlowN"), PortRef(electrolyzer, "FlowDI")) - network.connect(PortRef(electric_prod_2, "FlowP"), PortRef(elec_node_2, "FlowN")) - network.connect(PortRef(elec_node_2, "FlowN"), PortRef(electrolyzer, "FlowDI")) - network.connect(PortRef(electrolyzer, "FlowDO"), PortRef(gaz_node, "FlowN")) - network.connect(PortRef(gaz_node, "FlowN"), PortRef(gaz_demand, "FlowD")) - network.connect(PortRef(gaz_prod, "FlowP"), PortRef(gaz_node, "FlowN")) + network.connect( + PortRef(electric_prod_1, "balance_port"), PortRef(elec_node_1, "balance_port") + ) + network.connect( + PortRef(elec_node_1, "balance_port"), PortRef(electrolyzer, "FlowDI") + ) + network.connect( + PortRef(electric_prod_2, "balance_port"), PortRef(elec_node_2, "balance_port") + ) + network.connect( + PortRef(elec_node_2, "balance_port"), PortRef(electrolyzer, "FlowDI") + ) + network.connect(PortRef(electrolyzer, "FlowDO"), PortRef(gaz_node, "balance_port")) + network.connect( + PortRef(gaz_node, "balance_port"), PortRef(gaz_demand, "balance_port") + ) + network.connect( + PortRef(gaz_prod, "balance_port"), PortRef(gaz_node, "balance_port") + ) scenarios = 1 problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) diff --git a/tests/unittests/test_quota_co2.py b/tests/unittests/test_quota_co2.py index 043de3cb..d4824305 100644 --- a/tests/unittests/test_quota_co2.py +++ b/tests/unittests/test_quota_co2.py @@ -1,6 +1,19 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + from andromede.expression import literal, param, var from andromede.expression.expression import port_field -from andromede.libs.standard import CONSTANT, TIME_AND_SCENARIO_FREE +from andromede.libs.standard import DEMAND_MODEL, LINK_MODEL, NODE_BALANCE_MODEL +from andromede.libs.standard_sc import C02_POWER_MODEL, QUOTA_CO2_MODEL from andromede.model import ( Constraint, ModelPort, @@ -21,123 +34,6 @@ create_component, ) -""" -Power flow port -""" -FLOW_PORT = PortType(id="flow_port", fields=[PortField("F")]) - -""" -CO² emmission port -""" -EMISSION_PORT = PortType(id="emission_port", fields=[PortField("Q")]) - -""" -Simple node that manage flow interconnections. -""" -NODE_MODEL = model( - id="Noeud", - ports=[ModelPort(port_type=FLOW_PORT, port_name="FlowN")], - binding_constraints=[ - Constraint( - name="Balance", - expression=port_field("FlowN", "F").sum_connections() == literal(0), - ) - ], -) - -""" -Model of a simple power generator that takes account of CO² emissions related to the production. -The power production p is bounded between p_min and p_max. -An emission factor is used to determine the CO² emission according to the production. -""" -C02_POWER_MODEL = model( - id="CO2 power", - parameters=[ - float_parameter("p_min", CONSTANT), - float_parameter("p_max", CONSTANT), - float_parameter("cost", CONSTANT), - float_parameter("taux_emission", CONSTANT), - ], - variables=[ - float_variable("p", lower_bound=param("p_min"), upper_bound=param("p_max")) - ], - ports=[ - ModelPort(port_type=FLOW_PORT, port_name="FlowP"), - ModelPort(port_type=EMISSION_PORT, port_name="OutCO2"), - ], - port_fields_definitions=[ - PortFieldDefinition( - port_field=PortFieldId("FlowP", "F"), - definition=var("p"), - ), - PortFieldDefinition( - port_field=PortFieldId("OutCO2", "Q"), - definition=var("p") * param("taux_emission"), - ), - ], - objective_contribution=(param("cost") * var("p")).sum().expec(), -) - - -""" -Basic energy consumption model. -It consume a fixed amount of energy "d" each hour. -""" -DEMAND_MODEL = model( - id="Demand model", - parameters=[float_parameter("d", CONSTANT)], - ports=[ModelPort(port_type=FLOW_PORT, port_name="FlowD")], - port_fields_definitions=[ - PortFieldDefinition( - port_field=PortFieldId("FlowD", "F"), - definition=-param("d"), - ) - ], -) - -""" -Model of the CO² quota. -It takes a set a CO² emissions as input. It forces the sum of those emissions to be smaller than a predefined quota. -""" -QUOTA_CO2_MODEL = model( - id="QuotaCO2", - parameters=[float_parameter("quota", CONSTANT)], - ports=[ModelPort(port_type=EMISSION_PORT, port_name="emissionCO2")], - constraints=[ - Constraint( - name="Bound CO2", - expression=port_field("emissionCO2", "Q").sum_connections() - <= param("quota"), - ) - ], -) - -""" -Link model to interconnect nodes. -""" -LINK_MODEL = model( - id="LINK", - parameters=[float_parameter("f_max", TIME_AND_SCENARIO_FREE)], - variables=[ - float_variable("flow", lower_bound=-param("f_max"), upper_bound=param("f_max")) - ], - ports=[ - ModelPort(port_type=FLOW_PORT, port_name="port_from"), - ModelPort(port_type=FLOW_PORT, port_name="port_to"), - ], - port_fields_definitions=[ - PortFieldDefinition( - port_field=PortFieldId("port_from", "F"), - definition=-var("flow"), - ), - PortFieldDefinition( - port_field=PortFieldId("port_to", "F"), - definition=var("flow"), - ), - ], -) - - """ build the quota CO² test system. @@ -150,16 +46,17 @@ MonQuotaCO2 """ +""" Test of a generation of energy and co2 with a quota to limit the emission""" def test_quota_co2(): - n1 = Node(model=NODE_MODEL, id="N1") - n2 = Node(model=NODE_MODEL, id="N2") + n1 = Node(model=NODE_BALANCE_MODEL, id="N1") + n2 = Node(model=NODE_BALANCE_MODEL, id="N2") oil1 = create_component(model=C02_POWER_MODEL, id="Oil1") coal1 = create_component(model=C02_POWER_MODEL, id="Coal1") l12 = create_component(model=LINK_MODEL, id="L12") demand = create_component(model=DEMAND_MODEL, id="Demand") - monQuotaCO2 = create_component(model=QUOTA_CO2_MODEL, id="MonQuotaCO2") + monQuotaCO2 = create_component(model=QUOTA_CO2_MODEL, id="QuotaCO2") network = Network("test") network.add_node(n1) @@ -170,27 +67,26 @@ def test_quota_co2(): network.add_component(demand) network.add_component(monQuotaCO2) - network.connect(PortRef(demand, "FlowD"), PortRef(n2, "FlowN")) - network.connect(PortRef(n2, "FlowN"), PortRef(l12, "port_from")) - network.connect(PortRef(l12, "port_to"), PortRef(n1, "FlowN")) - network.connect(PortRef(n1, "FlowN"), PortRef(oil1, "FlowP")) - network.connect(PortRef(n2, "FlowN"), PortRef(coal1, "FlowP")) + network.connect(PortRef(demand, "balance_port"), PortRef(n2, "balance_port")) + network.connect(PortRef(n2, "balance_port"), PortRef(l12, "balance_port_from")) + network.connect(PortRef(l12, "balance_port_to"), PortRef(n1, "balance_port")) + network.connect(PortRef(n1, "balance_port"), PortRef(oil1, "FlowP")) + network.connect(PortRef(n2, "balance_port"), PortRef(coal1, "FlowP")) network.connect(PortRef(oil1, "OutCO2"), PortRef(monQuotaCO2, "emissionCO2")) network.connect(PortRef(coal1, "OutCO2"), PortRef(monQuotaCO2, "emissionCO2")) database = DataBase() - database.add_data("Demand", "d", ConstantData(100)) + database.add_data("Demand", "demand", ConstantData(100)) database.add_data("Coal1", "p_min", ConstantData(0)) database.add_data("Oil1", "p_min", ConstantData(0)) database.add_data("Coal1", "p_max", ConstantData(100)) database.add_data("Oil1", "p_max", ConstantData(100)) - database.add_data("Coal1", "taux_emission", ConstantData(2)) - database.add_data("Oil1", "taux_emission", ConstantData(1)) + database.add_data("Coal1", "emission_rate", ConstantData(2)) + database.add_data("Oil1", "emission_rate", ConstantData(1)) database.add_data("Coal1", "cost", ConstantData(10)) database.add_data("Oil1", "cost", ConstantData(100)) database.add_data("L12", "f_max", ConstantData(100)) - # when the bug in the port is fixed, the quota should be 150 - database.add_data("MonQuotaCO2", "quota", ConstantData(150)) + database.add_data("QuotaCO2", "quota", ConstantData(150)) scenarios = 1 problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) From d0e8e264e76d41bb9090ff5a7fd7546ad7290e26 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Wed, 13 Mar 2024 10:47:10 +0100 Subject: [PATCH 06/29] Ajout du isclose et modification des tests pour les electrolyzer n_inputs --- .../test_electrolyzer_n_inputs.py | 148 +++++++++++++----- tests/unittests/test_quota_co2.py | 22 +-- 2 files changed, 119 insertions(+), 51 deletions(-) rename tests/{andromede => unittests}/test_electrolyzer_n_inputs.py (69%) diff --git a/tests/andromede/test_electrolyzer_n_inputs.py b/tests/unittests/test_electrolyzer_n_inputs.py similarity index 69% rename from tests/andromede/test_electrolyzer_n_inputs.py rename to tests/unittests/test_electrolyzer_n_inputs.py index cd018970..e7c2ddc5 100644 --- a/tests/andromede/test_electrolyzer_n_inputs.py +++ b/tests/unittests/test_electrolyzer_n_inputs.py @@ -10,6 +10,8 @@ # # This file is part of the Antares project. +import math + from andromede.libs.standard import DEMAND_MODEL, GENERATOR_MODEL, NODE_BALANCE_MODEL from andromede.libs.standard_sc import ( CONVERTOR_MODEL, @@ -17,7 +19,7 @@ DECOMPOSE_1_FLOW_INTO_2_FLOW, TWO_INPUTS_CONVERTOR_MODEL, ) -from andromede.simulation import TimeBlock, build_problem +from andromede.simulation import OutputValues, TimeBlock, build_problem from andromede.study import ( ConstantData, DataBase, @@ -27,10 +29,32 @@ create_component, ) +""" +for every following test we have two electrical production with an electrolyzer converting to a gaz flow +we always have: + first electric production: + - p_max = 70 + - cost = 10 + second electric production: + - p_max = 80 + - cost = 20 + gaz production: + - p_max = 30 + - cost = 40 + first production conversion: + - alpha = 0.7 + second production conversion: + - alpha = 0.5 + for a gaz demand of 100 +""" + def test_electrolyzer_n_inputs_1(): """ Test with an electrolyzer for each inputs + + flow_ep1 * alpha_ez1 + flow_ep2 * alpha_ez2 + flow_gp + """ elec_node_1 = Node(model=NODE_BALANCE_MODEL, id="e1") electric_prod_1 = create_component(model=GENERATOR_MODEL, id="ep1") @@ -46,17 +70,17 @@ def test_electrolyzer_n_inputs_1(): database = DataBase() - database.add_data("ep1", "p_max", ConstantData(100)) - database.add_data("ep1", "cost", ConstantData(30)) + database.add_data("ep1", "p_max", ConstantData(70)) + database.add_data("ep1", "cost", ConstantData(10)) database.add_data("ez1", "alpha", ConstantData(0.7)) - database.add_data("ep2", "p_max", ConstantData(100)) - database.add_data("ep2", "cost", ConstantData(30)) - database.add_data("ez2", "alpha", ConstantData(0.7)) + database.add_data("ep2", "p_max", ConstantData(80)) + database.add_data("ep2", "cost", ConstantData(20)) + database.add_data("ez2", "alpha", ConstantData(0.5)) - database.add_data("gd", "demand", ConstantData(70)) - database.add_data("gp", "p_max", ConstantData(10)) - database.add_data("gp", "cost", ConstantData(40)) + database.add_data("gd", "demand", ConstantData(100)) + database.add_data("gp", "p_max", ConstantData(30)) + database.add_data("gp", "cost", ConstantData(15)) network = Network("test") network.add_node(elec_node_1) @@ -94,13 +118,27 @@ def test_electrolyzer_n_inputs_1(): problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) status = problem.solver.Solve() + output = OutputValues(problem) + ep1_gen = output.component("ep1").var("generation").value + ep2_gen = output.component("ep2").var("generation").value + gp_gen = output.component("gp").var("generation").value + print(ep1_gen) + print(ep2_gen) + print(gp_gen) + + assert math.isclose(ep1_gen, 70) + assert math.isclose(ep2_gen, 42) + assert math.isclose(gp_gen, 30) + assert status == problem.solver.OPTIMAL - assert problem.solver.Objective().Value() == 2971.4285714285716 + assert math.isclose(problem.solver.Objective().Value(), 1990) def test_electrolyzer_n_inputs_2(): """ Test with one electrolyzer that has two inputs + + flow_ep1 * alpha1_ez + flow_ep2 * alpha2_ez + flow_gp """ elec_node_1 = Node(model=NODE_BALANCE_MODEL, id="e1") @@ -116,15 +154,19 @@ def test_electrolyzer_n_inputs_2(): electrolyzer = create_component(model=TWO_INPUTS_CONVERTOR_MODEL, id="ez") database = DataBase() - database.add_data("gd", "demand", ConstantData(70)) - database.add_data("ep1", "p_max", ConstantData(100)) - database.add_data("ep2", "p_max", ConstantData(100)) - database.add_data("ep1", "cost", ConstantData(30)) - database.add_data("ep2", "cost", ConstantData(30)) + database.add_data("ez", "alpha1", ConstantData(0.7)) database.add_data("ez", "alpha2", ConstantData(0.5)) - database.add_data("gp", "p_max", ConstantData(10)) - database.add_data("gp", "cost", ConstantData(40)) + + database.add_data("ep1", "p_max", ConstantData(70)) + database.add_data("ep1", "cost", ConstantData(10)) + + database.add_data("ep2", "p_max", ConstantData(80)) + database.add_data("ep2", "cost", ConstantData(20)) + + database.add_data("gd", "demand", ConstantData(100)) + database.add_data("gp", "p_max", ConstantData(30)) + database.add_data("gp", "cost", ConstantData(15)) network = Network("test") network.add_node(elec_node_1) @@ -160,13 +202,29 @@ def test_electrolyzer_n_inputs_2(): problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) status = problem.solver.Solve() + output = OutputValues(problem) + ep1_gen = output.component("ep1").var("generation").value + ep2_gen = output.component("ep2").var("generation").value + gp_gen = output.component("gp").var("generation").value + print(ep1_gen) + print(ep2_gen) + print(gp_gen) + + assert math.isclose(ep1_gen, 70) + assert math.isclose(ep2_gen, 42) + assert math.isclose(gp_gen, 30) + assert status == problem.solver.OPTIMAL - assert problem.solver.Objective().Value() == 2971.4285714285716 + assert math.isclose(problem.solver.Objective().Value(), 1990) def test_electrolyzer_n_inputs_3(): """ Test with a consumption_electrolyzer with two inputs + + (flow_ep1 + flow_ep2) * alpha_ez + flow_gp + + The result is different since we only have one alpha at 0.7 """ elec_node_1 = Node(model=NODE_BALANCE_MODEL, id="e1") elec_node_2 = Node(model=NODE_BALANCE_MODEL, id="e2") @@ -184,14 +242,18 @@ def test_electrolyzer_n_inputs_3(): ) database = DataBase() - database.add_data("gd", "demand", ConstantData(70)) - database.add_data("ep1", "p_max", ConstantData(100)) - database.add_data("ep2", "p_max", ConstantData(100)) - database.add_data("ep1", "cost", ConstantData(30)) - database.add_data("ep2", "cost", ConstantData(30)) + database.add_data("ez", "alpha", ConstantData(0.7)) - database.add_data("gp", "p_max", ConstantData(10)) - database.add_data("gp", "cost", ConstantData(40)) + + database.add_data("ep1", "p_max", ConstantData(70)) + database.add_data("ep1", "cost", ConstantData(10)) + + database.add_data("ep2", "p_max", ConstantData(80)) + database.add_data("ep2", "cost", ConstantData(20)) + + database.add_data("gd", "demand", ConstantData(100)) + database.add_data("gp", "p_max", ConstantData(30)) + database.add_data("gp", "cost", ConstantData(15)) network = Network("test") network.add_node(elec_node_1) @@ -233,13 +295,26 @@ def test_electrolyzer_n_inputs_3(): problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) status = problem.solver.Solve() + output = OutputValues(problem) + ep1_gen = output.component("ep1").var("generation").value + ep2_gen = output.component("ep2").var("generation").value + gp_gen = output.component("gp").var("generation").value + + assert math.isclose(ep1_gen, 70) + assert math.isclose(ep2_gen, 30) + assert math.isclose(gp_gen, 30) + assert status == problem.solver.OPTIMAL - assert problem.solver.Objective().Value() == 2971.4285714285716 + assert math.isclose(problem.solver.Objective().Value(), 1750) def test_electrolyzer_n_inputs_4(): """ Test with one electrolyzer with one input that takes every inputs + + flow_ep1 + flow_ep2 * alpha_ez + flow_gp + + with the same values this one is infeasible since the electrolyzer model as in the drawio doesn't work with two inputs """ elec_node_1 = Node(model=NODE_BALANCE_MODEL, id="e1") elec_node_2 = Node(model=NODE_BALANCE_MODEL, id="e2") @@ -254,14 +329,18 @@ def test_electrolyzer_n_inputs_4(): electrolyzer = create_component(model=CONVERTOR_MODEL, id="ez") database = DataBase() - database.add_data("gd", "demand", ConstantData(70)) - database.add_data("ep1", "p_max", ConstantData(100)) - database.add_data("ep2", "p_max", ConstantData(100)) - database.add_data("ep1", "cost", ConstantData(30)) - database.add_data("ep2", "cost", ConstantData(30)) + database.add_data("ez", "alpha", ConstantData(0.7)) - database.add_data("gp", "p_max", ConstantData(10)) - database.add_data("gp", "cost", ConstantData(40)) + + database.add_data("ep1", "p_max", ConstantData(70)) + database.add_data("ep1", "cost", ConstantData(10)) + + database.add_data("ep2", "p_max", ConstantData(80)) + database.add_data("ep2", "cost", ConstantData(20)) + + database.add_data("gd", "demand", ConstantData(100)) + database.add_data("gp", "p_max", ConstantData(30)) + database.add_data("gp", "cost", ConstantData(15)) network = Network("test") network.add_node(elec_node_1) @@ -297,5 +376,4 @@ def test_electrolyzer_n_inputs_4(): problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) status = problem.solver.Solve() - assert status == problem.solver.OPTIMAL - assert problem.solver.Objective().Value() == 2971.4285714285716 + assert status == problem.solver.INFEASIBLE diff --git a/tests/unittests/test_quota_co2.py b/tests/unittests/test_quota_co2.py index d4824305..52ce8218 100644 --- a/tests/unittests/test_quota_co2.py +++ b/tests/unittests/test_quota_co2.py @@ -10,20 +10,10 @@ # # This file is part of the Antares project. -from andromede.expression import literal, param, var -from andromede.expression.expression import port_field +import math + from andromede.libs.standard import DEMAND_MODEL, LINK_MODEL, NODE_BALANCE_MODEL from andromede.libs.standard_sc import C02_POWER_MODEL, QUOTA_CO2_MODEL -from andromede.model import ( - Constraint, - ModelPort, - PortField, - PortType, - float_parameter, - float_variable, - model, -) -from andromede.model.model import PortFieldDefinition, PortFieldId from andromede.simulation import OutputValues, TimeBlock, build_problem from andromede.study import ( ConstantData, @@ -98,7 +88,7 @@ def test_quota_co2(): l12_flow = output.component("L12").var("flow").value assert status == problem.solver.OPTIMAL - assert problem.solver.Objective().Value() == 5500 - assert oil1_p == 50 - assert coal1_p == 50 - assert l12_flow == -50 + assert math.isclose(problem.solver.Objective().Value(), 5500) + assert math.isclose(oil1_p, 50) + assert math.isclose(coal1_p, 50) + assert math.isclose(l12_flow, -50) From 82efdad5280926e210d9520f19619f671b708e15 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Mon, 18 Mar 2024 11:49:56 +0100 Subject: [PATCH 07/29] Fixed test 3 and 4 of the electrolyzer_n_inputs --- src/andromede/libs/standard_sc.py | 90 ++++++++----------- tests/unittests/test_electrolyzer_n_inputs.py | 74 ++++++++++----- tests/unittests/test_quota_co2.py | 2 +- 3 files changed, 92 insertions(+), 74 deletions(-) diff --git a/src/andromede/libs/standard_sc.py b/src/andromede/libs/standard_sc.py index 3b16789a..038b6144 100644 --- a/src/andromede/libs/standard_sc.py +++ b/src/andromede/libs/standard_sc.py @@ -24,27 +24,6 @@ ) from andromede.model.model import PortFieldDefinition, PortFieldId -""" -Model of a power generator. -The power production p is bounded between p_min and p_max. -An emission factor is used to determine the CO² emission according to the production. -""" -PROD_MODEL = model( - id="Production", - parameters=[float_parameter("p_max", CONSTANT), float_parameter("cost", CONSTANT)], - variables=[ - float_variable("prod", lower_bound=literal(0), upper_bound=param("p_max")) - ], - ports=[ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowP")], - port_fields_definitions=[ - PortFieldDefinition( - port_field=PortFieldId("FlowP", "flow"), - definition=var("prod"), - ) - ], - objective_operational_contribution=(param("cost") * var("prod")).sum().expec(), -) - """ Simple Convertor model. """ @@ -53,7 +32,6 @@ parameters=[float_parameter("alpha")], variables=[ float_variable("input", lower_bound=literal(0)), - float_variable("output"), ], ports=[ ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowDI"), @@ -66,15 +44,9 @@ ), PortFieldDefinition( port_field=PortFieldId("FlowDO", "flow"), - definition=var("output"), + definition=var("input") * param("alpha"), ), ], - constraints=[ - Constraint( - name="Conversion", - expression=var("output") == var("input") * param("alpha"), - ) - ], ) """ @@ -86,7 +58,6 @@ variables=[ float_variable("input1", lower_bound=literal(0)), float_variable("input2", lower_bound=literal(0)), - float_variable("output"), ], ports=[ ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowDI1"), @@ -104,24 +75,17 @@ ), PortFieldDefinition( port_field=PortFieldId("FlowDO", "flow"), - definition=var("output"), + definition=var("input1") * param("alpha1") + + var("input2") * param("alpha2"), ), ], - constraints=[ - Constraint( - name="Conversion", - expression=var("output") - == var("input1") * param("alpha1") + var("input2") * param("alpha2"), - ) - ], ) DECOMPOSE_1_FLOW_INTO_2_FLOW = model( - id="Consumption electrolyzer model", + id="Consumption aggregation model", variables=[ - float_variable("input1", lower_bound=literal(0)), - float_variable("input2", lower_bound=literal(0)), - float_variable("output"), + float_variable("input1"), + float_variable("input2"), ], ports=[ ModelPort(port_type=BALANCE_PORT_TYPE, port_name="FlowDI1"), @@ -131,25 +95,23 @@ port_fields_definitions=[ PortFieldDefinition( port_field=PortFieldId("FlowDI1", "flow"), - definition=-var("input1"), + definition=var("input1"), ), PortFieldDefinition( port_field=PortFieldId("FlowDI2", "flow"), - definition=-var("input2"), - ), - PortFieldDefinition( - port_field=PortFieldId("FlowDO", "flow"), definition=var("output") + definition=var("input2"), ), ], - constraints=[ + binding_constraints=[ Constraint( - name="output", - expression=var("output") == var("input1") + var("input2"), - ), + name="Conversion", + expression=var("input1") + var("input2") + == port_field("FlowDO", "flow").sum_connections(), + ) ], ) -CONVERTOR_MODEL_MOD = model( +CONVERTOR_RECEIVE_IN = model( id="Convertor model", parameters=[float_parameter("alpha")], variables=[ @@ -165,7 +127,7 @@ definition=var("input") * param("alpha"), ), ], - constraints=[ + binding_constraints=[ Constraint( name="Conversion", expression=var("input") == port_field("FlowDI", "flow").sum_connections(), @@ -227,3 +189,25 @@ ) ], ) + +NODE_BALANCE_MODEL_MOD = model( + id="NODE_BALANCE_MODEL_MOD", + variables=[float_variable("p")], + ports=[ + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="balance_port_n"), + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="balance_port_e"), + ], + port_fields_definitions=[ + PortFieldDefinition( + port_field=PortFieldId("balance_port_e", "flow"), + definition=var("p"), + ) + ], + binding_constraints=[ + Constraint( + name="Balance", + expression=var("p") + == port_field("balance_port_n", "flow").sum_connections(), + ) + ], +) diff --git a/tests/unittests/test_electrolyzer_n_inputs.py b/tests/unittests/test_electrolyzer_n_inputs.py index e7c2ddc5..6c6f5be5 100644 --- a/tests/unittests/test_electrolyzer_n_inputs.py +++ b/tests/unittests/test_electrolyzer_n_inputs.py @@ -15,8 +15,9 @@ from andromede.libs.standard import DEMAND_MODEL, GENERATOR_MODEL, NODE_BALANCE_MODEL from andromede.libs.standard_sc import ( CONVERTOR_MODEL, - CONVERTOR_MODEL_MOD, + CONVERTOR_RECEIVE_IN, DECOMPOSE_1_FLOW_INTO_2_FLOW, + NODE_BALANCE_MODEL_MOD, TWO_INPUTS_CONVERTOR_MODEL, ) from andromede.simulation import OutputValues, TimeBlock, build_problem @@ -30,7 +31,7 @@ ) """ -for every following test we have two electrical production with an electrolyzer converting to a gaz flow +for every following test we have two electrical productions with an electrolyzer converting to a gaz flow we always have: first electric production: - p_max = 70 @@ -40,10 +41,10 @@ - cost = 20 gaz production: - p_max = 30 - - cost = 40 - first production conversion: + - cost = 15 + first conversion rate: - alpha = 0.7 - second production conversion: + second conversion rate: - alpha = 0.5 for a gaz demand of 100 """ @@ -51,9 +52,15 @@ def test_electrolyzer_n_inputs_1(): """ - Test with an electrolyzer for each inputs + Test with an electrolyzer for each input - flow_ep1 * alpha_ez1 + flow_ep2 * alpha_ez2 + flow_gp + ep1 = electric production 1 + ep2 = electric production 2 + ez1 = electrolyzer 1 + ez2 = electrolyzer 2 + gp = gaz production + + total gaz production = flow_ep1 * alpha_ez1 + flow_ep2 * alpha_ez2 + flow_gp """ elec_node_1 = Node(model=NODE_BALANCE_MODEL, id="e1") @@ -138,7 +145,12 @@ def test_electrolyzer_n_inputs_2(): """ Test with one electrolyzer that has two inputs - flow_ep1 * alpha1_ez + flow_ep2 * alpha2_ez + flow_gp + ep1 = electric production 1 + ep2 = electric production 2 + ez = electrolyzer + gp = gaz production + + total gaz production = flow_ep1 * alpha1_ez + flow_ep2 * alpha2_ez + flow_gp """ elec_node_1 = Node(model=NODE_BALANCE_MODEL, id="e1") @@ -222,7 +234,12 @@ def test_electrolyzer_n_inputs_3(): """ Test with a consumption_electrolyzer with two inputs - (flow_ep1 + flow_ep2) * alpha_ez + flow_gp + ep1 = electric production 1 + ep2 = electric production 2 + ez = electrolyzer + gp = gaz production + + total gaz production = (flow_ep1 + flow_ep2) * alpha_ez + flow_gp The result is different since we only have one alpha at 0.7 """ @@ -236,7 +253,7 @@ def test_electrolyzer_n_inputs_3(): gaz_prod = create_component(model=GENERATOR_MODEL, id="gp") gaz_demand = create_component(model=DEMAND_MODEL, id="gd") - electrolyzer = create_component(model=CONVERTOR_MODEL_MOD, id="ez") + electrolyzer = create_component(model=CONVERTOR_MODEL, id="ez") consumption_electrolyzer = create_component( model=DECOMPOSE_1_FLOW_INTO_2_FLOW, id="ce" ) @@ -312,12 +329,17 @@ def test_electrolyzer_n_inputs_4(): """ Test with one electrolyzer with one input that takes every inputs - flow_ep1 + flow_ep2 * alpha_ez + flow_gp + ep1 = electric production 1 + ep2 = electric production 2 + ez = electrolyzer + gp = gaz production + + total gaz production = (flow_ep1 + flow_ep2) * alpha_ez + flow_gp - with the same values this one is infeasible since the electrolyzer model as in the drawio doesn't work with two inputs + same as test 3, the result is different than the first two since we only have one alpha at 0.7 """ - elec_node_1 = Node(model=NODE_BALANCE_MODEL, id="e1") - elec_node_2 = Node(model=NODE_BALANCE_MODEL, id="e2") + elec_node_1 = Node(model=NODE_BALANCE_MODEL_MOD, id="e1") + elec_node_2 = Node(model=NODE_BALANCE_MODEL_MOD, id="e2") gaz_node = Node(model=NODE_BALANCE_MODEL, id="g") electric_prod_1 = create_component(model=GENERATOR_MODEL, id="ep1") @@ -326,7 +348,7 @@ def test_electrolyzer_n_inputs_4(): gaz_prod = create_component(model=GENERATOR_MODEL, id="gp") gaz_demand = create_component(model=DEMAND_MODEL, id="gd") - electrolyzer = create_component(model=CONVERTOR_MODEL, id="ez") + electrolyzer = create_component(model=CONVERTOR_RECEIVE_IN, id="ez") database = DataBase() @@ -353,16 +375,16 @@ def test_electrolyzer_n_inputs_4(): network.add_component(electrolyzer) network.connect( - PortRef(electric_prod_1, "balance_port"), PortRef(elec_node_1, "balance_port") + PortRef(electric_prod_1, "balance_port"), PortRef(elec_node_1, "balance_port_n") ) network.connect( - PortRef(elec_node_1, "balance_port"), PortRef(electrolyzer, "FlowDI") + PortRef(elec_node_1, "balance_port_e"), PortRef(electrolyzer, "FlowDI") ) network.connect( - PortRef(electric_prod_2, "balance_port"), PortRef(elec_node_2, "balance_port") + PortRef(electric_prod_2, "balance_port"), PortRef(elec_node_2, "balance_port_n") ) network.connect( - PortRef(elec_node_2, "balance_port"), PortRef(electrolyzer, "FlowDI") + PortRef(elec_node_2, "balance_port_e"), PortRef(electrolyzer, "FlowDI") ) network.connect(PortRef(electrolyzer, "FlowDO"), PortRef(gaz_node, "balance_port")) network.connect( @@ -376,4 +398,16 @@ def test_electrolyzer_n_inputs_4(): problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) status = problem.solver.Solve() - assert status == problem.solver.INFEASIBLE + assert status == problem.solver.OPTIMAL + + output = OutputValues(problem) + ep1_gen = output.component("ep1").var("generation").value + ep2_gen = output.component("ep2").var("generation").value + gp_gen = output.component("gp").var("generation").value + + assert math.isclose(ep1_gen, 70) + assert math.isclose(ep2_gen, 30) + assert math.isclose(gp_gen, 30) + + assert status == problem.solver.OPTIMAL + assert math.isclose(problem.solver.Objective().Value(), 1750) diff --git a/tests/unittests/test_quota_co2.py b/tests/unittests/test_quota_co2.py index 52ce8218..07f50e58 100644 --- a/tests/unittests/test_quota_co2.py +++ b/tests/unittests/test_quota_co2.py @@ -33,7 +33,7 @@ | | --------- | - MonQuotaCO2 + QuotaCO2 """ """ Test of a generation of energy and co2 with a quota to limit the emission""" From 51de9e41c09cd99adde3e6b4a3a9cee338ca9465 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Wed, 27 Mar 2024 12:00:22 +0100 Subject: [PATCH 08/29] test electrolyzer_n_inputs with yaml models --- src/andromede/libs/standard_sc.yml | 137 +++++ .../test_electrolyzer_n_inputs_yaml.py | 503 ++++++++++++++++++ 2 files changed, 640 insertions(+) create mode 100644 src/andromede/libs/standard_sc.yml create mode 100644 tests/unittests/test_electrolyzer_n_inputs_yaml.py diff --git a/src/andromede/libs/standard_sc.yml b/src/andromede/libs/standard_sc.yml new file mode 100644 index 00000000..d89d9aa1 --- /dev/null +++ b/src/andromede/libs/standard_sc.yml @@ -0,0 +1,137 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. +library: + id: basic + description: Basic library + + port-types: + - id: flow + description: A port which transfers power flow + fields: + - name: flow + + models: + - id: convertor + description: A basic convertor model + parameters: + - name: alpha + time-dependent: false + scenario-dependent: false + variables: + - name: input + lower-bound: 0 + ports: + - name: input_port + type: flow + - name: output_port + type: flow + port-field-definitions: + - port: input_port + field: flow + definition: -input + - port: output_port + field: flow + definition: input * alpha + + - id: two_input_convertor + description: Two input convertor model + parameters: + - name: alpha1 + time-dependent: false + scenario-dependent: false + - name: alpha2 + time-dependent: false + scenario-dependent: false + variables: + - name: input1 + lower-bound: 0 + - name: input2 + lower-bound: 0 + ports: + - name: input_port1 + type: flow + - name: input_port2 + type: flow + - name: output_port + type: flow + port-field-definitions: + - port: input_port1 + field: flow + definition: -input1 + - port: input_port2 + field: flow + definition: -input2 + - port: output_port + field: flow + definition: input1 * alpha1 + input2 * alpha2 + + - id: decompose_1_flow_into_2_flow + description: A Consumption aggregation model + variables: + - name: input1 + - name: input2 + ports: + - name: input_port1 + type: flow + - name: input_port2 + type: flow + - name: output_port + type: flow + port-field-definitions: + - port: input_port1 + field: flow + definition: input1 + - port: input_port2 + field: flow + definition: input2 + binding-constraints: + - name: Conversion + expression: sum_connections(output_port.flow) = input1 + input2 + + - id: convertor_receive_in + description: A convertor model + parameters: + - name: alpha + time-dependent: false + scenario-dependent: false + variables: + - name: input + lower-bound: 0 + ports: + - name: input_port + type: flow + - name: output_port + type: flow + port-field-definitions: + - port: output_port + field: flow + definition: input * alpha + binding-constraints: + - name: Conversion + expression: sum_connections(input_port.flow) = input + + - id: node_mod + description: A node model with two ports + variables: + - name: p + ports: + - name: injection_port_n + type: flow + - name: injection_port_e + type: flow + port-field-definitions: + - port: injection_port_e + field: flow + definition: p + binding-constraints: + - name: balance + expression: sum_connections(injection_port_n.flow) = p \ No newline at end of file diff --git a/tests/unittests/test_electrolyzer_n_inputs_yaml.py b/tests/unittests/test_electrolyzer_n_inputs_yaml.py new file mode 100644 index 00000000..565f7de5 --- /dev/null +++ b/tests/unittests/test_electrolyzer_n_inputs_yaml.py @@ -0,0 +1,503 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +import math +from pathlib import Path + +from andromede.libs.standard import DEMAND_MODEL, GENERATOR_MODEL, NODE_BALANCE_MODEL +from andromede.libs.standard_sc import ( + CONVERTOR_MODEL, + CONVERTOR_RECEIVE_IN, + DECOMPOSE_1_FLOW_INTO_2_FLOW, + NODE_BALANCE_MODEL_MOD, + TWO_INPUTS_CONVERTOR_MODEL, +) +from andromede.model.parsing import parse_yaml_library +from andromede.model.resolve_library import resolve_library +from andromede.simulation import OutputValues, TimeBlock, build_problem +from andromede.study import ( + ConstantData, + DataBase, + Network, + Node, + PortRef, + create_component, +) + +""" +for every following test we have two electrical productions with an electrolyzer converting to a gaz flow +we always have: + first electric production: + - p_max = 70 + - cost = 10 + second electric production: + - p_max = 80 + - cost = 20 + gaz production: + - p_max = 30 + - cost = 15 + first conversion rate: + - alpha = 0.7 + second conversion rate: + - alpha = 0.5 + for a gaz demand of 100 +""" + + +def test_electrolyzer_n_inputs_1(data_dir: Path): + """ + Test with an electrolyzer for each input + + ep1 = electric production 1 + ep2 = electric production 2 + ez1 = electrolyzer 1 + ez2 = electrolyzer 2 + gp = gaz production + + total gaz production = flow_ep1 * alpha_ez1 + flow_ep2 * alpha_ez2 + flow_gp + + """ + libs_path = Path("../../src/andromede/libs/") + lib_file = data_dir / "lib.yml" + lib_sc_file = libs_path / "standard_sc.yml" + + with lib_file.open() as f: + input_lib = parse_yaml_library(f) + with lib_sc_file.open() as f: + input_lib_sc = parse_yaml_library(f) + + lib = resolve_library(input_lib) + lib_sc = resolve_library(input_lib_sc) + + gen_model = lib.models["generator"] + node_model = lib.models["node"] + convertor_model = lib_sc.models["convertor"] + demand_model = lib.models["demand"] + + elec_node_1 = Node(model=node_model, id="e1") + electric_prod_1 = create_component(model=gen_model, id="ep1") + electrolyzer1 = create_component(model=convertor_model, id="ez1") + + elec_node_2 = Node(model=node_model, id="e2") + electric_prod_2 = create_component(model=gen_model, id="ep2") + electrolyzer2 = create_component(model=convertor_model, id="ez2") + + gaz_node = Node(model=node_model, id="g") + gaz_prod = create_component(model=gen_model, id="gp") + gaz_demand = create_component(model=demand_model, id="gd") + + database = DataBase() + + database.add_data("ep1", "p_max", ConstantData(70)) + database.add_data("ep1", "cost", ConstantData(10)) + database.add_data("ez1", "alpha", ConstantData(0.7)) + + database.add_data("ep2", "p_max", ConstantData(80)) + database.add_data("ep2", "cost", ConstantData(20)) + database.add_data("ez2", "alpha", ConstantData(0.5)) + + database.add_data("gd", "demand", ConstantData(100)) + database.add_data("gp", "p_max", ConstantData(30)) + database.add_data("gp", "cost", ConstantData(15)) + + network = Network("test") + network.add_node(elec_node_1) + network.add_component(electric_prod_1) + network.add_component(electrolyzer1) + network.add_node(elec_node_2) + network.add_component(electric_prod_2) + network.add_component(electrolyzer2) + network.add_node(gaz_node) + network.add_component(gaz_prod) + network.add_component(gaz_demand) + + network.connect( + PortRef(electric_prod_1, "injection_port"), + PortRef(elec_node_1, "injection_port"), + ) + network.connect( + PortRef(elec_node_1, "injection_port"), PortRef(electrolyzer1, "input_port") + ) + network.connect( + PortRef(electrolyzer1, "output_port"), PortRef(gaz_node, "injection_port") + ) + network.connect( + PortRef(electric_prod_2, "injection_port"), + PortRef(elec_node_2, "injection_port"), + ) + network.connect( + PortRef(elec_node_2, "injection_port"), PortRef(electrolyzer2, "input_port") + ) + network.connect( + PortRef(electrolyzer2, "output_port"), PortRef(gaz_node, "injection_port") + ) + network.connect( + PortRef(gaz_node, "injection_port"), PortRef(gaz_demand, "injection_port") + ) + network.connect( + PortRef(gaz_prod, "injection_port"), PortRef(gaz_node, "injection_port") + ) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + output = OutputValues(problem) + ep1_gen = output.component("ep1").var("generation").value + ep2_gen = output.component("ep2").var("generation").value + gp_gen = output.component("gp").var("generation").value + print(ep1_gen) + print(ep2_gen) + print(gp_gen) + + assert math.isclose(ep1_gen, 70) + assert math.isclose(ep2_gen, 42) + assert math.isclose(gp_gen, 30) + + assert status == problem.solver.OPTIMAL + assert math.isclose(problem.solver.Objective().Value(), 1990) + + +def test_electrolyzer_n_inputs_2(data_dir: Path): + """ + Test with one electrolyzer that has two inputs + + ep1 = electric production 1 + ep2 = electric production 2 + ez = electrolyzer + gp = gaz production + + total gaz production = flow_ep1 * alpha1_ez + flow_ep2 * alpha2_ez + flow_gp + """ + + libs_path = Path("../../src/andromede/libs/") + lib_file = data_dir / "lib.yml" + lib_sc_file = libs_path / "standard_sc.yml" + + with lib_file.open() as f: + input_lib = parse_yaml_library(f) + with lib_sc_file.open() as f: + input_lib_sc = parse_yaml_library(f) + + lib = resolve_library(input_lib) + lib_sc = resolve_library(input_lib_sc) + + gen_model = lib.models["generator"] + node_model = lib.models["node"] + convertor_model = lib_sc.models["two_input_convertor"] + demand_model = lib.models["demand"] + + elec_node_1 = Node(model=node_model, id="e1") + elec_node_2 = Node(model=node_model, id="e2") + gaz_node = Node(model=node_model, id="g") + + electric_prod_1 = create_component(model=gen_model, id="ep1") + electric_prod_2 = create_component(model=gen_model, id="ep2") + + gaz_prod = create_component(model=gen_model, id="gp") + gaz_demand = create_component(model=demand_model, id="gd") + + electrolyzer = create_component(model=convertor_model, id="ez") + + database = DataBase() + + database.add_data("ez", "alpha1", ConstantData(0.7)) + database.add_data("ez", "alpha2", ConstantData(0.5)) + + database.add_data("ep1", "p_max", ConstantData(70)) + database.add_data("ep1", "cost", ConstantData(10)) + + database.add_data("ep2", "p_max", ConstantData(80)) + database.add_data("ep2", "cost", ConstantData(20)) + + database.add_data("gd", "demand", ConstantData(100)) + database.add_data("gp", "p_max", ConstantData(30)) + database.add_data("gp", "cost", ConstantData(15)) + + network = Network("test") + network.add_node(elec_node_1) + network.add_node(elec_node_2) + network.add_node(gaz_node) + network.add_component(electric_prod_1) + network.add_component(electric_prod_2) + network.add_component(gaz_prod) + network.add_component(gaz_demand) + network.add_component(electrolyzer) + + network.connect( + PortRef(electric_prod_1, "injection_port"), + PortRef(elec_node_1, "injection_port"), + ) + network.connect( + PortRef(elec_node_1, "injection_port"), PortRef(electrolyzer, "input_port1") + ) + network.connect( + PortRef(electric_prod_2, "injection_port"), + PortRef(elec_node_2, "injection_port"), + ) + network.connect( + PortRef(elec_node_2, "injection_port"), PortRef(electrolyzer, "input_port2") + ) + network.connect( + PortRef(electrolyzer, "output_port"), PortRef(gaz_node, "injection_port") + ) + network.connect( + PortRef(gaz_node, "injection_port"), PortRef(gaz_demand, "injection_port") + ) + network.connect( + PortRef(gaz_prod, "injection_port"), PortRef(gaz_node, "injection_port") + ) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + output = OutputValues(problem) + ep1_gen = output.component("ep1").var("generation").value + ep2_gen = output.component("ep2").var("generation").value + gp_gen = output.component("gp").var("generation").value + print(ep1_gen) + print(ep2_gen) + print(gp_gen) + + assert math.isclose(ep1_gen, 70) + assert math.isclose(ep2_gen, 42) + assert math.isclose(gp_gen, 30) + + assert status == problem.solver.OPTIMAL + assert math.isclose(problem.solver.Objective().Value(), 1990) + + +def test_electrolyzer_n_inputs_3(data_dir: Path): + """ + Test with a consumption_electrolyzer with two inputs + + ep1 = electric production 1 + ep2 = electric production 2 + ez = electrolyzer + gp = gaz production + + total gaz production = (flow_ep1 + flow_ep2) * alpha_ez + flow_gp + + The result is different since we only have one alpha at 0.7 + """ + libs_path = Path("../../src/andromede/libs/") + lib_file = data_dir / "lib.yml" + lib_sc_file = libs_path / "standard_sc.yml" + + with lib_file.open() as f: + input_lib = parse_yaml_library(f) + with lib_sc_file.open() as f: + input_lib_sc = parse_yaml_library(f) + + lib = resolve_library(input_lib) + lib_sc = resolve_library(input_lib_sc) + + gen_model = lib.models["generator"] + node_model = lib.models["node"] + convertor_model = lib_sc.models["convertor"] + demand_model = lib.models["demand"] + decompose_flow_model = lib_sc.models["decompose_1_flow_into_2_flow"] + + elec_node_1 = Node(model=node_model, id="e1") + elec_node_2 = Node(model=node_model, id="e2") + gaz_node = Node(model=node_model, id="g") + + electric_prod_1 = create_component(model=gen_model, id="ep1") + electric_prod_2 = create_component(model=gen_model, id="ep2") + + gaz_prod = create_component(model=gen_model, id="gp") + gaz_demand = create_component(model=demand_model, id="gd") + + electrolyzer = create_component(model=convertor_model, id="ez") + consumption_electrolyzer = create_component(model=decompose_flow_model, id="ce") + + database = DataBase() + + database.add_data("ez", "alpha", ConstantData(0.7)) + + database.add_data("ep1", "p_max", ConstantData(70)) + database.add_data("ep1", "cost", ConstantData(10)) + + database.add_data("ep2", "p_max", ConstantData(80)) + database.add_data("ep2", "cost", ConstantData(20)) + + database.add_data("gd", "demand", ConstantData(100)) + database.add_data("gp", "p_max", ConstantData(30)) + database.add_data("gp", "cost", ConstantData(15)) + + network = Network("test") + network.add_node(elec_node_1) + network.add_node(elec_node_2) + network.add_node(gaz_node) + network.add_component(electric_prod_1) + network.add_component(electric_prod_2) + network.add_component(gaz_prod) + network.add_component(gaz_demand) + network.add_component(electrolyzer) + network.add_component(consumption_electrolyzer) + + network.connect( + PortRef(electric_prod_1, "injection_port"), + PortRef(elec_node_1, "injection_port"), + ) + network.connect( + PortRef(elec_node_1, "injection_port"), + PortRef(consumption_electrolyzer, "input_port1"), + ) + network.connect( + PortRef(electric_prod_2, "injection_port"), + PortRef(elec_node_2, "injection_port"), + ) + network.connect( + PortRef(elec_node_2, "injection_port"), + PortRef(consumption_electrolyzer, "input_port2"), + ) + network.connect( + PortRef(consumption_electrolyzer, "output_port"), + PortRef(electrolyzer, "input_port"), + ) + network.connect( + PortRef(electrolyzer, "output_port"), PortRef(gaz_node, "injection_port") + ) + network.connect( + PortRef(gaz_node, "injection_port"), PortRef(gaz_demand, "injection_port") + ) + network.connect( + PortRef(gaz_prod, "injection_port"), PortRef(gaz_node, "injection_port") + ) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + output = OutputValues(problem) + ep1_gen = output.component("ep1").var("generation").value + ep2_gen = output.component("ep2").var("generation").value + gp_gen = output.component("gp").var("generation").value + + assert math.isclose(ep1_gen, 70) + assert math.isclose(ep2_gen, 30) + assert math.isclose(gp_gen, 30) + + assert status == problem.solver.OPTIMAL + assert math.isclose(problem.solver.Objective().Value(), 1750) + + +def test_electrolyzer_n_inputs_4(data_dir: Path): + """ + Test with one electrolyzer with one input that takes every inputs + + ep1 = electric production 1 + ep2 = electric production 2 + ez = electrolyzer + gp = gaz production + + total gaz production = (flow_ep1 + flow_ep2) * alpha_ez + flow_gp + + same as test 3, the result is different than the first two since we only have one alpha at 0.7 + """ + libs_path = Path("../../src/andromede/libs/") + lib_file = data_dir / "lib.yml" + lib_sc_file = libs_path / "standard_sc.yml" + + with lib_file.open() as f: + input_lib = parse_yaml_library(f) + with lib_sc_file.open() as f: + input_lib_sc = parse_yaml_library(f) + + lib = resolve_library(input_lib) + lib_sc = resolve_library(input_lib_sc) + + gen_model = lib.models["generator"] + node_model = lib.models["node"] + node_mod_model = lib_sc.models["node_mod"] + convertor_model = lib_sc.models["convertor_receive_in"] + demand_model = lib.models["demand"] + + elec_node_1 = Node(model=node_mod_model, id="e1") + elec_node_2 = Node(model=node_mod_model, id="e2") + gaz_node = Node(model=node_model, id="g") + + electric_prod_1 = create_component(model=gen_model, id="ep1") + electric_prod_2 = create_component(model=gen_model, id="ep2") + + gaz_prod = create_component(model=gen_model, id="gp") + gaz_demand = create_component(model=demand_model, id="gd") + + electrolyzer = create_component(model=convertor_model, id="ez") + + database = DataBase() + + database.add_data("ez", "alpha", ConstantData(0.7)) + + database.add_data("ep1", "p_max", ConstantData(70)) + database.add_data("ep1", "cost", ConstantData(10)) + + database.add_data("ep2", "p_max", ConstantData(80)) + database.add_data("ep2", "cost", ConstantData(20)) + + database.add_data("gd", "demand", ConstantData(100)) + database.add_data("gp", "p_max", ConstantData(30)) + database.add_data("gp", "cost", ConstantData(15)) + + network = Network("test") + network.add_node(elec_node_1) + network.add_node(elec_node_2) + network.add_node(gaz_node) + network.add_component(electric_prod_1) + network.add_component(electric_prod_2) + network.add_component(gaz_prod) + network.add_component(gaz_demand) + network.add_component(electrolyzer) + + network.connect( + PortRef(electric_prod_1, "injection_port"), + PortRef(elec_node_1, "injection_port_n"), + ) + network.connect( + PortRef(elec_node_1, "injection_port_e"), PortRef(electrolyzer, "input_port") + ) + network.connect( + PortRef(electric_prod_2, "injection_port"), + PortRef(elec_node_2, "injection_port_n"), + ) + network.connect( + PortRef(elec_node_2, "injection_port_e"), PortRef(electrolyzer, "input_port") + ) + network.connect( + PortRef(electrolyzer, "output_port"), PortRef(gaz_node, "injection_port") + ) + network.connect( + PortRef(gaz_node, "injection_port"), PortRef(gaz_demand, "injection_port") + ) + network.connect( + PortRef(gaz_prod, "injection_port"), PortRef(gaz_node, "injection_port") + ) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + assert status == problem.solver.OPTIMAL + + output = OutputValues(problem) + ep1_gen = output.component("ep1").var("generation").value + ep2_gen = output.component("ep2").var("generation").value + gp_gen = output.component("gp").var("generation").value + + assert math.isclose(ep1_gen, 70) + assert math.isclose(ep2_gen, 30) + assert math.isclose(gp_gen, 30) + + assert status == problem.solver.OPTIMAL + assert math.isclose(problem.solver.Objective().Value(), 1750) From ffcc3dea63b57ee91915344c69aa0c102280322a Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Fri, 29 Mar 2024 10:43:47 +0100 Subject: [PATCH 09/29] quota C02 test with yaml model --- src/andromede/libs/standard_sc.yml | 75 +++++++++++++++- tests/unittests/test_quota_co2_yaml.py | 115 +++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 tests/unittests/test_quota_co2_yaml.py diff --git a/src/andromede/libs/standard_sc.yml b/src/andromede/libs/standard_sc.yml index d89d9aa1..7646df5d 100644 --- a/src/andromede/libs/standard_sc.yml +++ b/src/andromede/libs/standard_sc.yml @@ -18,6 +18,10 @@ library: description: A port which transfers power flow fields: - name: flow + - id: emission + description: A port which transfers co2 emission + fields: + - name: emission models: - id: convertor @@ -134,4 +138,73 @@ library: definition: p binding-constraints: - name: balance - expression: sum_connections(injection_port_n.flow) = p \ No newline at end of file + expression: sum_connections(injection_port_n.flow) = p + + - id: generator_with_co2 + description: generator model that emits CO2 + parameters: + - name: pmin + time-dependent: false + scenario-dependent: false + - name: pmax + time-dependent: false + scenario-dependent: false + - name: cost + time-dependent: false + scenario-dependent: false + - name: emission_rate + time-dependent: false + scenario-dependent: false + variables: + - name: p + lower-bound: pmin + upper-bound: pmax + ports: + - name: injection_port + type: flow + - name: co2_port + type: emission + port-field-definitions: + - port: injection_port + field: flow + definition: p + - port: co2_port + field: emission + definition: p * emission_rate + objective: expec(sum(cost * p)) + + - id: quota_co2 + description: A CO2 quota model + parameters: + - name: quota + time-dependent: false + scenario-dependent: false + ports: + - name: emission_port + type: emission + binding-constraints: + - name: bound_co2 + expression: sum_connections(emission_port.emission) <= quota + + - id: link + description: A link model + parameters: + - name: f_max + time-dependent: false + scenario-dependent: false + variables: + - name: flow + lower-bound: -f_max + upper-bound: f_max + ports: + - name: injection_port_from + type: flow + - name: injection_port_to + type: flow + port-field-definitions: + - port: injection_port_from + field: flow + definition: -flow + - port: injection_port_to + field: flow + definition: flow \ No newline at end of file diff --git a/tests/unittests/test_quota_co2_yaml.py b/tests/unittests/test_quota_co2_yaml.py new file mode 100644 index 00000000..4b623ed7 --- /dev/null +++ b/tests/unittests/test_quota_co2_yaml.py @@ -0,0 +1,115 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +import math +from pathlib import Path + +from andromede.libs.standard import DEMAND_MODEL, LINK_MODEL, NODE_BALANCE_MODEL +from andromede.libs.standard_sc import C02_POWER_MODEL, QUOTA_CO2_MODEL +from andromede.model.parsing import parse_yaml_library +from andromede.model.resolve_library import resolve_library +from andromede.simulation import OutputValues, TimeBlock, build_problem +from andromede.study import ( + ConstantData, + DataBase, + Network, + Node, + PortRef, + create_component, +) + +""" +build the quota CO² test system. + + N1 -----N2----Demand ^ + | | + Oil1 Coal1 + | | + --------- + | + QuotaCO2 + +""" +""" Test of a generation of energy and co2 with a quota to limit the emission""" + + +def test_quota_co2(data_dir: Path): + libs_path = Path("../../src/andromede/libs/") + lib_file = data_dir / "lib.yml" + lib_sc_file = libs_path / "standard_sc.yml" + + with lib_file.open() as f: + input_lib = parse_yaml_library(f) + with lib_sc_file.open() as f: + input_lib_sc = parse_yaml_library(f) + + lib = resolve_library(input_lib) + lib_sc = resolve_library(input_lib_sc) + + gen_model = lib_sc.models["generator_with_co2"] + node_model = lib.models["node"] + quota_co2_model = lib_sc.models["quota_co2"] + demand_model = lib.models["demand"] + link_model = lib_sc.models["link"] + + n1 = Node(model=node_model, id="N1") + n2 = Node(model=node_model, id="N2") + oil1 = create_component(model=gen_model, id="Oil1") + coal1 = create_component(model=gen_model, id="Coal1") + l12 = create_component(model=link_model, id="L12") + demand = create_component(model=demand_model, id="Demand") + monQuotaCO2 = create_component(model=quota_co2_model, id="QuotaCO2") + + network = Network("test") + network.add_node(n1) + network.add_node(n2) + network.add_component(oil1) + network.add_component(coal1) + network.add_component(l12) + network.add_component(demand) + network.add_component(monQuotaCO2) + + network.connect(PortRef(demand, "injection_port"), PortRef(n2, "injection_port")) + network.connect(PortRef(n2, "injection_port"), PortRef(l12, "injection_port_from")) + network.connect(PortRef(l12, "injection_port_to"), PortRef(n1, "injection_port")) + network.connect(PortRef(n1, "injection_port"), PortRef(oil1, "injection_port")) + network.connect(PortRef(n2, "injection_port"), PortRef(coal1, "injection_port")) + network.connect(PortRef(oil1, "co2_port"), PortRef(monQuotaCO2, "emission_port")) + network.connect(PortRef(coal1, "co2_port"), PortRef(monQuotaCO2, "emission_port")) + + database = DataBase() + database.add_data("Demand", "demand", ConstantData(100)) + database.add_data("Coal1", "pmin", ConstantData(0)) + database.add_data("Oil1", "pmin", ConstantData(0)) + database.add_data("Coal1", "pmax", ConstantData(100)) + database.add_data("Oil1", "pmax", ConstantData(100)) + database.add_data("Coal1", "emission_rate", ConstantData(2)) + database.add_data("Oil1", "emission_rate", ConstantData(1)) + database.add_data("Coal1", "cost", ConstantData(10)) + database.add_data("Oil1", "cost", ConstantData(100)) + database.add_data("L12", "f_max", ConstantData(100)) + database.add_data("QuotaCO2", "quota", ConstantData(150)) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + output = OutputValues(problem) + oil1_p = output.component("Oil1").var("p").value + coal1_p = output.component("Coal1").var("p").value + l12_flow = output.component("L12").var("flow").value + + assert status == problem.solver.OPTIMAL + assert math.isclose(problem.solver.Objective().Value(), 5500) + assert math.isclose(oil1_p, 50) + assert math.isclose(coal1_p, 50) + assert math.isclose(l12_flow, -50) From dc08c71c71b98c54c0d057ab02a8376684085408 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Tue, 2 Apr 2024 14:13:06 +0200 Subject: [PATCH 10/29] print to test the path --- tests/unittests/test_electrolyzer_n_inputs_yaml.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unittests/test_electrolyzer_n_inputs_yaml.py b/tests/unittests/test_electrolyzer_n_inputs_yaml.py index 565f7de5..fd1209af 100644 --- a/tests/unittests/test_electrolyzer_n_inputs_yaml.py +++ b/tests/unittests/test_electrolyzer_n_inputs_yaml.py @@ -68,6 +68,7 @@ def test_electrolyzer_n_inputs_1(data_dir: Path): """ libs_path = Path("../../src/andromede/libs/") lib_file = data_dir / "lib.yml" + print(lib_file) lib_sc_file = libs_path / "standard_sc.yml" with lib_file.open() as f: From f6218bdd58408e0aa160e39e7f113f211b7626cb Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Tue, 2 Apr 2024 14:53:44 +0200 Subject: [PATCH 11/29] fixed path for the first n_input_electrolyzer_yaml test --- tests/unittests/test_electrolyzer_n_inputs_yaml.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unittests/test_electrolyzer_n_inputs_yaml.py b/tests/unittests/test_electrolyzer_n_inputs_yaml.py index fd1209af..8813c143 100644 --- a/tests/unittests/test_electrolyzer_n_inputs_yaml.py +++ b/tests/unittests/test_electrolyzer_n_inputs_yaml.py @@ -11,6 +11,7 @@ # This file is part of the Antares project. import math +import os from pathlib import Path from andromede.libs.standard import DEMAND_MODEL, GENERATOR_MODEL, NODE_BALANCE_MODEL @@ -66,9 +67,8 @@ def test_electrolyzer_n_inputs_1(data_dir: Path): total gaz production = flow_ep1 * alpha_ez1 + flow_ep2 * alpha_ez2 + flow_gp """ - libs_path = Path("../../src/andromede/libs/") + libs_path = Path(os.path.join(os.getcwd(), "../../src/andromede/libs/")) lib_file = data_dir / "lib.yml" - print(lib_file) lib_sc_file = libs_path / "standard_sc.yml" with lib_file.open() as f: From 25b567fb81eec42f6278c15868ae786040786038 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Wed, 3 Apr 2024 09:36:06 +0200 Subject: [PATCH 12/29] model stock pipeline --- src/andromede/libs/standard_sc.py | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/andromede/libs/standard_sc.py b/src/andromede/libs/standard_sc.py index 038b6144..6b7fb907 100644 --- a/src/andromede/libs/standard_sc.py +++ b/src/andromede/libs/standard_sc.py @@ -211,3 +211,56 @@ ) ], ) + +LINK_WITH_STORAGE = model( + id="Link with storage model", + parameters=[ + float_parameter("f_from_max", CONSTANT), + float_parameter("f_to_max", CONSTANT), + float_parameter("capacity", CONSTANT), + float_parameter("initial_level", CONSTANT), + ], + variables=[ + float_variable("r", lower_bound=literal(0), upper_bound=param("capacity")), + float_variable( + "f_from", lower_bound=-param("f_from_max"), upper_bound=param("f_from_max") + ), + float_variable( + "f_to", lower_bound=-param("f_to_max"), upper_bound=param("f_to_max") + ), + float_variable("f_from+", lower_bound=literal(0)), + float_variable("f_from-", lower_bound=literal(0)), + float_variable("f_to+", lower_bound=literal(0)), + float_variable("f_to-", lower_bound=literal(0)), + ], + ports=[ + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="flow_from"), + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="flow_to"), + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="flow_from_pos"), + ModelPort(port_type=BALANCE_PORT_TYPE, port_name="flow_to_pos"), + ], + port_fields_definitions=[ + PortFieldDefinition( + port_field=PortFieldId("flow_from", "flow"), + definition=-var("f_from"), + ), + PortFieldDefinition( + port_field=PortFieldId("flow_to", "flow"), + definition=var("f_to"), + ), + PortFieldDefinition( + port_field=PortFieldId("flow_from_pos", "flow"), + definition=var("f_from+"), + ), + PortFieldDefinition( + port_field=PortFieldId("flow_to_pos", "flow"), + definition=var("f_to+"), + ), + ], + constraints=[ + Constraint( + name="Level", + expression=var("f_from") == (var("f_from+") - var("f_from-")), + ), + ], +) From f5a0eec6bd97ac6926d922149e9a574fb51ba4be Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Wed, 3 Apr 2024 15:42:33 +0200 Subject: [PATCH 13/29] moved test for models --- tests/unittests/{ => model}/test_electrolyzer_n_inputs.py | 0 tests/unittests/{ => model}/test_quota_co2.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/unittests/{ => model}/test_electrolyzer_n_inputs.py (100%) rename tests/unittests/{ => model}/test_quota_co2.py (100%) diff --git a/tests/unittests/test_electrolyzer_n_inputs.py b/tests/unittests/model/test_electrolyzer_n_inputs.py similarity index 100% rename from tests/unittests/test_electrolyzer_n_inputs.py rename to tests/unittests/model/test_electrolyzer_n_inputs.py diff --git a/tests/unittests/test_quota_co2.py b/tests/unittests/model/test_quota_co2.py similarity index 100% rename from tests/unittests/test_quota_co2.py rename to tests/unittests/model/test_quota_co2.py From d7ce0927357f62e6ebad4036419314671d3ab8a4 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Wed, 3 Apr 2024 15:43:44 +0200 Subject: [PATCH 14/29] added standard_sc.yml --- src/andromede/libs/standard_sc.yml | 80 ++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/andromede/libs/standard_sc.yml diff --git a/src/andromede/libs/standard_sc.yml b/src/andromede/libs/standard_sc.yml new file mode 100644 index 00000000..a9abb7e6 --- /dev/null +++ b/src/andromede/libs/standard_sc.yml @@ -0,0 +1,80 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. +library: + id: basic + description: Basic library + + port-types: + - id: flow + description: A port which transfers power flow + fields: + - name: flow + + models: + - id: link_with_storage + description: A link with energy storage + parameters: + - name: f_from_max + time-dependent: false + scenario-dependent: false + - name: f_to_max + time-dependent: false + scenario-dependent: false + - name: capacity + time-dependent: false + scenario-dependent: false + - name: initial_level + time-dependent: false + scenario-dependent: false + variables: + - name: r + lower-bound: 0 + upper-bound: capacity + - name: f_from + lower-bound: -f_from_max + upper-bound: f_from_max + - name: f_to + lower-bound: -f_to_max + upper-bound: f_to_max + - name: f_from_p + lower-bound: 0 + - name: f_from_m + lower-bound: 0 + - name: f_to_p + lower-bound: 0 + - name: f_to_m + lower-bound: 0 + ports: + - name: flow_from + type: flow + - name: flow_to + type: flow + - name: flow_from_pos + type: flow + - name: flow_to_pos + type: flow + port-field-definitions: + - port: flow_from + field: flow + definition: -f_from + - port: flow_to + field: flow + definition: f_to + - port: flow_from_pos + field: flow + definition: f_from_m + - port: flow_to_pos + field: flow + definition: f_to_p + constraints: + - name: Level + expression: f_from = f_from_p - f_from_m From fccf367a1fc7683818ad866c6a7a4664e1841e0a Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Wed, 3 Apr 2024 17:06:40 +0200 Subject: [PATCH 15/29] fix of libs path and move of the tests to models --- .../{ => model}/test_electrolyzer_n_inputs_yaml.py | 8 ++++---- tests/unittests/{ => model}/test_quota_co2_yaml.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename tests/unittests/{ => model}/test_electrolyzer_n_inputs_yaml.py (98%) rename tests/unittests/{ => model}/test_quota_co2_yaml.py (98%) diff --git a/tests/unittests/test_electrolyzer_n_inputs_yaml.py b/tests/unittests/model/test_electrolyzer_n_inputs_yaml.py similarity index 98% rename from tests/unittests/test_electrolyzer_n_inputs_yaml.py rename to tests/unittests/model/test_electrolyzer_n_inputs_yaml.py index 8813c143..ed111c80 100644 --- a/tests/unittests/test_electrolyzer_n_inputs_yaml.py +++ b/tests/unittests/model/test_electrolyzer_n_inputs_yaml.py @@ -67,7 +67,7 @@ def test_electrolyzer_n_inputs_1(data_dir: Path): total gaz production = flow_ep1 * alpha_ez1 + flow_ep2 * alpha_ez2 + flow_gp """ - libs_path = Path(os.path.join(os.getcwd(), "../../src/andromede/libs/")) + libs_path = Path(__file__).parents[3] / "src/andromede/libs/" lib_file = data_dir / "lib.yml" lib_sc_file = libs_path / "standard_sc.yml" @@ -180,7 +180,7 @@ def test_electrolyzer_n_inputs_2(data_dir: Path): total gaz production = flow_ep1 * alpha1_ez + flow_ep2 * alpha2_ez + flow_gp """ - libs_path = Path("../../src/andromede/libs/") + libs_path = Path(__file__).parents[3] / "src/andromede/libs/" lib_file = data_dir / "lib.yml" lib_sc_file = libs_path / "standard_sc.yml" @@ -291,7 +291,7 @@ def test_electrolyzer_n_inputs_3(data_dir: Path): The result is different since we only have one alpha at 0.7 """ - libs_path = Path("../../src/andromede/libs/") + libs_path = Path(__file__).parents[3] / "src/andromede/libs/" lib_file = data_dir / "lib.yml" lib_sc_file = libs_path / "standard_sc.yml" @@ -407,7 +407,7 @@ def test_electrolyzer_n_inputs_4(data_dir: Path): same as test 3, the result is different than the first two since we only have one alpha at 0.7 """ - libs_path = Path("../../src/andromede/libs/") + libs_path = Path(__file__).parents[3] / "src/andromede/libs/" lib_file = data_dir / "lib.yml" lib_sc_file = libs_path / "standard_sc.yml" diff --git a/tests/unittests/test_quota_co2_yaml.py b/tests/unittests/model/test_quota_co2_yaml.py similarity index 98% rename from tests/unittests/test_quota_co2_yaml.py rename to tests/unittests/model/test_quota_co2_yaml.py index 4b623ed7..f6ffbe93 100644 --- a/tests/unittests/test_quota_co2_yaml.py +++ b/tests/unittests/model/test_quota_co2_yaml.py @@ -43,7 +43,7 @@ def test_quota_co2(data_dir: Path): - libs_path = Path("../../src/andromede/libs/") + libs_path = Path(__file__).parents[3] / "src/andromede/libs/" lib_file = data_dir / "lib.yml" lib_sc_file = libs_path / "standard_sc.yml" From 006131bd15090e0bab7bfc97905fea8c70771656 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Fri, 5 Apr 2024 11:50:53 +0200 Subject: [PATCH 16/29] factorisation, removal of unused import and move of test files --- tests/unittests/conftest.py | 26 ++++++++ .../{ => model}/test_electrolyzer_n_inputs.py | 0 .../model/test_electrolyzer_n_inputs_yaml.py | 64 ++----------------- tests/unittests/{ => model}/test_quota_co2.py | 0 tests/unittests/model/test_quota_co2_yaml.py | 17 +---- 5 files changed, 31 insertions(+), 76 deletions(-) rename tests/unittests/{ => model}/test_electrolyzer_n_inputs.py (100%) rename tests/unittests/{ => model}/test_quota_co2.py (100%) diff --git a/tests/unittests/conftest.py b/tests/unittests/conftest.py index fe667438..afa153ea 100644 --- a/tests/unittests/conftest.py +++ b/tests/unittests/conftest.py @@ -13,7 +13,33 @@ import pytest +from andromede.model.parsing import parse_yaml_library +from andromede.model.resolve_library import resolve_library + @pytest.fixture(scope="session") def data_dir() -> Path: return Path(__file__).parent / "data" + + +@pytest.fixture(scope="session") +def lib(data_dir: Path): + lib_file = data_dir / "lib.yml" + + with lib_file.open() as f: + input_lib = parse_yaml_library(f) + + lib = resolve_library(input_lib) + return lib + + +@pytest.fixture(scope="session") +def lib_sc(data_dir: Path): + libs_path = Path(__file__).parents[2] / "src/andromede/libs/" + lib_sc_file = libs_path / "standard_sc.yml" + + with lib_sc_file.open() as f: + input_lib_sc = parse_yaml_library(f) + + lib_sc = resolve_library(input_lib_sc) + return lib_sc diff --git a/tests/unittests/test_electrolyzer_n_inputs.py b/tests/unittests/model/test_electrolyzer_n_inputs.py similarity index 100% rename from tests/unittests/test_electrolyzer_n_inputs.py rename to tests/unittests/model/test_electrolyzer_n_inputs.py diff --git a/tests/unittests/model/test_electrolyzer_n_inputs_yaml.py b/tests/unittests/model/test_electrolyzer_n_inputs_yaml.py index ed111c80..45f7e342 100644 --- a/tests/unittests/model/test_electrolyzer_n_inputs_yaml.py +++ b/tests/unittests/model/test_electrolyzer_n_inputs_yaml.py @@ -11,19 +11,8 @@ # This file is part of the Antares project. import math -import os from pathlib import Path -from andromede.libs.standard import DEMAND_MODEL, GENERATOR_MODEL, NODE_BALANCE_MODEL -from andromede.libs.standard_sc import ( - CONVERTOR_MODEL, - CONVERTOR_RECEIVE_IN, - DECOMPOSE_1_FLOW_INTO_2_FLOW, - NODE_BALANCE_MODEL_MOD, - TWO_INPUTS_CONVERTOR_MODEL, -) -from andromede.model.parsing import parse_yaml_library -from andromede.model.resolve_library import resolve_library from andromede.simulation import OutputValues, TimeBlock, build_problem from andromede.study import ( ConstantData, @@ -54,7 +43,7 @@ """ -def test_electrolyzer_n_inputs_1(data_dir: Path): +def test_electrolyzer_n_inputs_1(data_dir: Path, lib, lib_sc): """ Test with an electrolyzer for each input @@ -67,17 +56,6 @@ def test_electrolyzer_n_inputs_1(data_dir: Path): total gaz production = flow_ep1 * alpha_ez1 + flow_ep2 * alpha_ez2 + flow_gp """ - libs_path = Path(__file__).parents[3] / "src/andromede/libs/" - lib_file = data_dir / "lib.yml" - lib_sc_file = libs_path / "standard_sc.yml" - - with lib_file.open() as f: - input_lib = parse_yaml_library(f) - with lib_sc_file.open() as f: - input_lib_sc = parse_yaml_library(f) - - lib = resolve_library(input_lib) - lib_sc = resolve_library(input_lib_sc) gen_model = lib.models["generator"] node_model = lib.models["node"] @@ -168,7 +146,7 @@ def test_electrolyzer_n_inputs_1(data_dir: Path): assert math.isclose(problem.solver.Objective().Value(), 1990) -def test_electrolyzer_n_inputs_2(data_dir: Path): +def test_electrolyzer_n_inputs_2(data_dir: Path, lib, lib_sc): """ Test with one electrolyzer that has two inputs @@ -180,18 +158,6 @@ def test_electrolyzer_n_inputs_2(data_dir: Path): total gaz production = flow_ep1 * alpha1_ez + flow_ep2 * alpha2_ez + flow_gp """ - libs_path = Path(__file__).parents[3] / "src/andromede/libs/" - lib_file = data_dir / "lib.yml" - lib_sc_file = libs_path / "standard_sc.yml" - - with lib_file.open() as f: - input_lib = parse_yaml_library(f) - with lib_sc_file.open() as f: - input_lib_sc = parse_yaml_library(f) - - lib = resolve_library(input_lib) - lib_sc = resolve_library(input_lib_sc) - gen_model = lib.models["generator"] node_model = lib.models["node"] convertor_model = lib_sc.models["two_input_convertor"] @@ -278,7 +244,7 @@ def test_electrolyzer_n_inputs_2(data_dir: Path): assert math.isclose(problem.solver.Objective().Value(), 1990) -def test_electrolyzer_n_inputs_3(data_dir: Path): +def test_electrolyzer_n_inputs_3(data_dir: Path, lib, lib_sc): """ Test with a consumption_electrolyzer with two inputs @@ -291,17 +257,6 @@ def test_electrolyzer_n_inputs_3(data_dir: Path): The result is different since we only have one alpha at 0.7 """ - libs_path = Path(__file__).parents[3] / "src/andromede/libs/" - lib_file = data_dir / "lib.yml" - lib_sc_file = libs_path / "standard_sc.yml" - - with lib_file.open() as f: - input_lib = parse_yaml_library(f) - with lib_sc_file.open() as f: - input_lib_sc = parse_yaml_library(f) - - lib = resolve_library(input_lib) - lib_sc = resolve_library(input_lib_sc) gen_model = lib.models["generator"] node_model = lib.models["node"] @@ -394,7 +349,7 @@ def test_electrolyzer_n_inputs_3(data_dir: Path): assert math.isclose(problem.solver.Objective().Value(), 1750) -def test_electrolyzer_n_inputs_4(data_dir: Path): +def test_electrolyzer_n_inputs_4(data_dir: Path, lib, lib_sc): """ Test with one electrolyzer with one input that takes every inputs @@ -407,17 +362,6 @@ def test_electrolyzer_n_inputs_4(data_dir: Path): same as test 3, the result is different than the first two since we only have one alpha at 0.7 """ - libs_path = Path(__file__).parents[3] / "src/andromede/libs/" - lib_file = data_dir / "lib.yml" - lib_sc_file = libs_path / "standard_sc.yml" - - with lib_file.open() as f: - input_lib = parse_yaml_library(f) - with lib_sc_file.open() as f: - input_lib_sc = parse_yaml_library(f) - - lib = resolve_library(input_lib) - lib_sc = resolve_library(input_lib_sc) gen_model = lib.models["generator"] node_model = lib.models["node"] diff --git a/tests/unittests/test_quota_co2.py b/tests/unittests/model/test_quota_co2.py similarity index 100% rename from tests/unittests/test_quota_co2.py rename to tests/unittests/model/test_quota_co2.py diff --git a/tests/unittests/model/test_quota_co2_yaml.py b/tests/unittests/model/test_quota_co2_yaml.py index f6ffbe93..ffbb15ff 100644 --- a/tests/unittests/model/test_quota_co2_yaml.py +++ b/tests/unittests/model/test_quota_co2_yaml.py @@ -13,10 +13,6 @@ import math from pathlib import Path -from andromede.libs.standard import DEMAND_MODEL, LINK_MODEL, NODE_BALANCE_MODEL -from andromede.libs.standard_sc import C02_POWER_MODEL, QUOTA_CO2_MODEL -from andromede.model.parsing import parse_yaml_library -from andromede.model.resolve_library import resolve_library from andromede.simulation import OutputValues, TimeBlock, build_problem from andromede.study import ( ConstantData, @@ -42,18 +38,7 @@ """ Test of a generation of energy and co2 with a quota to limit the emission""" -def test_quota_co2(data_dir: Path): - libs_path = Path(__file__).parents[3] / "src/andromede/libs/" - lib_file = data_dir / "lib.yml" - lib_sc_file = libs_path / "standard_sc.yml" - - with lib_file.open() as f: - input_lib = parse_yaml_library(f) - with lib_sc_file.open() as f: - input_lib_sc = parse_yaml_library(f) - - lib = resolve_library(input_lib) - lib_sc = resolve_library(input_lib_sc) +def test_quota_co2(data_dir: Path, lib, lib_sc): gen_model = lib_sc.models["generator_with_co2"] node_model = lib.models["node"] From cddd635d9fab3ca850002e9b95790d5564b0049d Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Mon, 8 Apr 2024 09:48:48 +0200 Subject: [PATCH 17/29] reformat test --- tests/unittests/model/test_quota_co2_yaml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unittests/model/test_quota_co2_yaml.py b/tests/unittests/model/test_quota_co2_yaml.py index ffbb15ff..e414dc2b 100644 --- a/tests/unittests/model/test_quota_co2_yaml.py +++ b/tests/unittests/model/test_quota_co2_yaml.py @@ -26,7 +26,7 @@ """ build the quota CO² test system. - N1 -----N2----Demand ^ + N1 -----N2----Demand | | Oil1 Coal1 | | From 25d4b79b027a574047b33bde08b6eccca71a62fd Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Mon, 8 Apr 2024 11:33:29 +0200 Subject: [PATCH 18/29] move tests of model to tests/model --- tests/models/conftest.py | 45 +++++++++++++++++++ .../test_electrolyzer_n_inputs.py | 0 .../test_electrolyzer_n_inputs_yaml.py | 0 .../model => models}/test_quota_co2.py | 0 .../model => models}/test_quota_co2_yaml.py | 0 tests/unittests/conftest.py | 26 ----------- 6 files changed, 45 insertions(+), 26 deletions(-) create mode 100644 tests/models/conftest.py rename tests/{unittests/model => models}/test_electrolyzer_n_inputs.py (100%) rename tests/{unittests/model => models}/test_electrolyzer_n_inputs_yaml.py (100%) rename tests/{unittests/model => models}/test_quota_co2.py (100%) rename tests/{unittests/model => models}/test_quota_co2_yaml.py (100%) diff --git a/tests/models/conftest.py b/tests/models/conftest.py new file mode 100644 index 00000000..2154a0c4 --- /dev/null +++ b/tests/models/conftest.py @@ -0,0 +1,45 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. +from pathlib import Path + +import pytest + +from andromede.model.parsing import parse_yaml_library +from andromede.model.resolve_library import resolve_library + + +@pytest.fixture(scope="session") +def data_dir() -> Path: + return Path(__file__).parents[1] / "unittests/data" + + +@pytest.fixture(scope="session") +def lib(data_dir: Path): + lib_file = data_dir / "lib.yml" + + with lib_file.open() as f: + input_lib = parse_yaml_library(f) + + lib = resolve_library(input_lib) + return lib + + +@pytest.fixture(scope="session") +def lib_sc(data_dir: Path): + libs_path = Path(__file__).parents[2] / "src/andromede/libs/" + lib_sc_file = libs_path / "standard_sc.yml" + + with lib_sc_file.open() as f: + input_lib_sc = parse_yaml_library(f) + + lib_sc = resolve_library(input_lib_sc) + return lib_sc diff --git a/tests/unittests/model/test_electrolyzer_n_inputs.py b/tests/models/test_electrolyzer_n_inputs.py similarity index 100% rename from tests/unittests/model/test_electrolyzer_n_inputs.py rename to tests/models/test_electrolyzer_n_inputs.py diff --git a/tests/unittests/model/test_electrolyzer_n_inputs_yaml.py b/tests/models/test_electrolyzer_n_inputs_yaml.py similarity index 100% rename from tests/unittests/model/test_electrolyzer_n_inputs_yaml.py rename to tests/models/test_electrolyzer_n_inputs_yaml.py diff --git a/tests/unittests/model/test_quota_co2.py b/tests/models/test_quota_co2.py similarity index 100% rename from tests/unittests/model/test_quota_co2.py rename to tests/models/test_quota_co2.py diff --git a/tests/unittests/model/test_quota_co2_yaml.py b/tests/models/test_quota_co2_yaml.py similarity index 100% rename from tests/unittests/model/test_quota_co2_yaml.py rename to tests/models/test_quota_co2_yaml.py diff --git a/tests/unittests/conftest.py b/tests/unittests/conftest.py index afa153ea..fe667438 100644 --- a/tests/unittests/conftest.py +++ b/tests/unittests/conftest.py @@ -13,33 +13,7 @@ import pytest -from andromede.model.parsing import parse_yaml_library -from andromede.model.resolve_library import resolve_library - @pytest.fixture(scope="session") def data_dir() -> Path: return Path(__file__).parent / "data" - - -@pytest.fixture(scope="session") -def lib(data_dir: Path): - lib_file = data_dir / "lib.yml" - - with lib_file.open() as f: - input_lib = parse_yaml_library(f) - - lib = resolve_library(input_lib) - return lib - - -@pytest.fixture(scope="session") -def lib_sc(data_dir: Path): - libs_path = Path(__file__).parents[2] / "src/andromede/libs/" - lib_sc_file = libs_path / "standard_sc.yml" - - with lib_sc_file.open() as f: - input_lib_sc = parse_yaml_library(f) - - lib_sc = resolve_library(input_lib_sc) - return lib_sc From ed4f8a07bea0399e77490a6af2f2e511f9f97a83 Mon Sep 17 00:00:00 2001 From: Thomas Bittar Date: Tue, 9 Apr 2024 13:51:31 +0200 Subject: [PATCH 19/29] Fix formatting --- tests/models/test_quota_co2_yaml.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/models/test_quota_co2_yaml.py b/tests/models/test_quota_co2_yaml.py index e414dc2b..b25ed38e 100644 --- a/tests/models/test_quota_co2_yaml.py +++ b/tests/models/test_quota_co2_yaml.py @@ -39,7 +39,6 @@ def test_quota_co2(data_dir: Path, lib, lib_sc): - gen_model = lib_sc.models["generator_with_co2"] node_model = lib.models["node"] quota_co2_model = lib_sc.models["quota_co2"] From 4b949d67f1f6216945d3cd08836f1884265e9787 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Tue, 9 Apr 2024 14:02:54 +0200 Subject: [PATCH 20/29] re-added libs_dir to model/conftest.py --- tests/models/conftest.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/models/conftest.py b/tests/models/conftest.py index 2154a0c4..bdc3899e 100644 --- a/tests/models/conftest.py +++ b/tests/models/conftest.py @@ -17,6 +17,11 @@ from andromede.model.resolve_library import resolve_library +@pytest.fixture(scope="session") +def libs_dir() -> Path: + return Path(__file__).parent / "libs" + + @pytest.fixture(scope="session") def data_dir() -> Path: return Path(__file__).parents[1] / "unittests/data" From b3ed8e7893aa48cbbed76057b3aac1211fd57d47 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Mon, 15 Apr 2024 14:17:12 +0200 Subject: [PATCH 21/29] added type declaration and removed unused argument --- tests/models/conftest.py | 2 +- tests/models/test_electrolyzer_n_inputs_yaml.py | 8 ++++---- tests/models/test_quota_co2_yaml.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/models/conftest.py b/tests/models/conftest.py index bdc3899e..ee330ed3 100644 --- a/tests/models/conftest.py +++ b/tests/models/conftest.py @@ -39,7 +39,7 @@ def lib(data_dir: Path): @pytest.fixture(scope="session") -def lib_sc(data_dir: Path): +def lib_sc(): libs_path = Path(__file__).parents[2] / "src/andromede/libs/" lib_sc_file = libs_path / "standard_sc.yml" diff --git a/tests/models/test_electrolyzer_n_inputs_yaml.py b/tests/models/test_electrolyzer_n_inputs_yaml.py index 45f7e342..ac6e8a25 100644 --- a/tests/models/test_electrolyzer_n_inputs_yaml.py +++ b/tests/models/test_electrolyzer_n_inputs_yaml.py @@ -43,7 +43,7 @@ """ -def test_electrolyzer_n_inputs_1(data_dir: Path, lib, lib_sc): +def test_electrolyzer_n_inputs_1(data_dir: Path, lib: Path, lib_sc: Path): """ Test with an electrolyzer for each input @@ -146,7 +146,7 @@ def test_electrolyzer_n_inputs_1(data_dir: Path, lib, lib_sc): assert math.isclose(problem.solver.Objective().Value(), 1990) -def test_electrolyzer_n_inputs_2(data_dir: Path, lib, lib_sc): +def test_electrolyzer_n_inputs_2(data_dir: Path, lib: Path, lib_sc: Path): """ Test with one electrolyzer that has two inputs @@ -244,7 +244,7 @@ def test_electrolyzer_n_inputs_2(data_dir: Path, lib, lib_sc): assert math.isclose(problem.solver.Objective().Value(), 1990) -def test_electrolyzer_n_inputs_3(data_dir: Path, lib, lib_sc): +def test_electrolyzer_n_inputs_3(data_dir: Path, lib: Path, lib_sc: Path): """ Test with a consumption_electrolyzer with two inputs @@ -349,7 +349,7 @@ def test_electrolyzer_n_inputs_3(data_dir: Path, lib, lib_sc): assert math.isclose(problem.solver.Objective().Value(), 1750) -def test_electrolyzer_n_inputs_4(data_dir: Path, lib, lib_sc): +def test_electrolyzer_n_inputs_4(data_dir: Path, lib: Path, lib_sc: Path): """ Test with one electrolyzer with one input that takes every inputs diff --git a/tests/models/test_quota_co2_yaml.py b/tests/models/test_quota_co2_yaml.py index b25ed38e..f11a2369 100644 --- a/tests/models/test_quota_co2_yaml.py +++ b/tests/models/test_quota_co2_yaml.py @@ -38,7 +38,7 @@ """ Test of a generation of energy and co2 with a quota to limit the emission""" -def test_quota_co2(data_dir: Path, lib, lib_sc): +def test_quota_co2(data_dir: Path, lib: Path, lib_sc: Path): gen_model = lib_sc.models["generator_with_co2"] node_model = lib.models["node"] quota_co2_model = lib_sc.models["quota_co2"] From eb30fbcebc910ce737bfa2d16fb5459de107b867 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Fri, 19 Apr 2024 11:21:10 +0200 Subject: [PATCH 22/29] changed ports on link with storage model --- src/andromede/libs/standard_sc.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/andromede/libs/standard_sc.yml b/src/andromede/libs/standard_sc.yml index 6e20ad1a..f26083f7 100644 --- a/src/andromede/libs/standard_sc.yml +++ b/src/andromede/libs/standard_sc.yml @@ -73,15 +73,14 @@ library: - port: flow_to field: flow definition: f_to - - port: flow_from_pos - field: flow - definition: f_from_m - - port: flow_to_pos - field: flow - definition: f_to_p constraints: - name: Level expression: f_from = f_from_p - f_from_m + binding-constraints: + - name: FlowFromPos + expression: sum_connections(flow_from_pos.flow) = f_from_m + - name: FlowToPos + expression: sum_connections(flow_to_pos.flow) = f_to_p - id: convertor description: A basic convertor model From 564d10f9cc61790ebc3ed6eb85079edce1125da6 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Fri, 26 Apr 2024 11:54:37 +0200 Subject: [PATCH 23/29] test stock pipeline fix and added test gas_link_elec_compressor and stock final model --- src/andromede/libs/standard_sc.yml | 52 +++++- tests/models/test_gas_link_elec_compressor.py | 163 ++++++++++++++++++ tests/models/test_stock_pipeline.py | 120 +++++++++++++ 3 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 tests/models/test_gas_link_elec_compressor.py create mode 100644 tests/models/test_stock_pipeline.py diff --git a/src/andromede/libs/standard_sc.yml b/src/andromede/libs/standard_sc.yml index f26083f7..342a5f27 100644 --- a/src/andromede/libs/standard_sc.yml +++ b/src/andromede/libs/standard_sc.yml @@ -74,14 +74,62 @@ library: field: flow definition: f_to constraints: - - name: Level + - name: max0_from expression: f_from = f_from_p - f_from_m + - name: max0_to + expression: f_to = f_to_p - f_to_m + - name: r_h + expression: r[t+1] = r[t] + f_from - f_to binding-constraints: - name: FlowFromPos - expression: sum_connections(flow_from_pos.flow) = f_from_m + expression: sum_connections(flow_from_pos.flow) = f_from_p - name: FlowToPos expression: sum_connections(flow_to_pos.flow) = f_to_p + - id: stock_final_level + description: A stock model + parameters: + - name: p_max_in + time-dependent: false + scenario-dependent: false + - name: p_max_out + time-dependent: false + scenario-dependent: false + - name: capacity + time-dependent: false + scenario-dependent: false + - name: initial_level + time-dependent: false + scenario-dependent: false + - name: final_level + time-dependent: false + scenario-dependent: false + variables: + - name: r + lower-bound: 0 + upper-bound: capacity + - name: u_in + lower-bound: 0 + upper-bound: p_max_in + - name: u_out + lower-bound: 0 + upper-bound: p_max_out + ports: + - name: flow_s + type: flow + - name: flow_c + type: flow + port-field-definitions: + - port: flow_s + field: flow + definition: u_out - u_in + constraints: + - name: r_h + expression: r[t+1] = r[t] + u_in[t] - u_out[t] + binding-constraints: + - name: flow_in + expression: sum_connections(flow_c.flow) = u_in + - id: convertor description: A basic convertor model parameters: diff --git a/tests/models/test_gas_link_elec_compressor.py b/tests/models/test_gas_link_elec_compressor.py new file mode 100644 index 00000000..aec19848 --- /dev/null +++ b/tests/models/test_gas_link_elec_compressor.py @@ -0,0 +1,163 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +import math +from pathlib import Path + +from andromede.simulation import OutputValues, TimeBlock, build_problem +from andromede.study import ( + ConstantData, + DataBase, + Network, + Node, + PortRef, + create_component, +) + + +def test_gas_link_elec_compressor(data_dir: Path, lib: Path, lib_sc: Path): + """ + Test of a pipeline with stock capacity + + for the following test we have two gaz production and two gaz demands, + One of each on each sides of a pipeline that has a storage capacity + gaz_prod_1 and gaz_demand_1 are on the input side of the pipeline + gaz_prod_2 and gaz_demand_2 are on the output side of the pipeline + we have the following values: + electricity production: + - p_max = 200 + - cost = 10 + gaz production: + - p_max = 300 + - cost = 10 + gaz demand: + - demand = 100 + electricity demand: + - demand = 10 + pipeline: + - f_from_max = 100 + - f_to_max = 50 + - capacity = 100 + - initial_level = 20 + electrolyzer: + - alpha = 0.5 + compressor from: + - alpha = 0.7 + compressor to: + - alpha = 0.7 + """ + gen_model = lib.models["generator"] + node_model = lib.models["node"] + demand_model = lib.models["demand"] + link_with_storage_model = lib_sc.models["link_with_storage"] + convertor_model = lib_sc.models["convertor"] + + gaz_node_1 = Node(model=node_model, id="g1") + gaz_node_2 = Node(model=node_model, id="g2") + elec_node = Node(model=node_model, id="e") + gaz_prod = create_component(model=gen_model, id="pg") + elec_prod = create_component(model=gen_model, id="pe") + gaz_demand = create_component(model=demand_model, id="dg") + elec_demand = create_component(model=demand_model, id="de") + pipeline = create_component(model=link_with_storage_model, id="pp") + electrolyzer = create_component(model=convertor_model, id="ez") + compressor_from = create_component(model=convertor_model, id="cpf") + compressor_to = create_component(model=convertor_model, id="cpt") + + database = DataBase() + database.add_data("dg", "demand", ConstantData(100)) + database.add_data("de", "demand", ConstantData(10)) + + database.add_data("pg", "p_max", ConstantData(300)) + database.add_data("pg", "cost", ConstantData(10)) + database.add_data("pe", "p_max", ConstantData(200)) + database.add_data("pe", "cost", ConstantData(10)) + + database.add_data("pp", "f_from_max", ConstantData(100)) + database.add_data("pp", "f_to_max", ConstantData(50)) + database.add_data("pp", "capacity", ConstantData(100)) + database.add_data("pp", "initial_level", ConstantData(20)) + + database.add_data("ez", "alpha", ConstantData(0.5)) + + database.add_data("cpf", "alpha", ConstantData(0.7)) + database.add_data("cpt", "alpha", ConstantData(0.7)) + + network = Network("test") + network.add_node(gaz_node_1) + network.add_node(gaz_node_2) + network.add_node(elec_node) + network.add_component(gaz_prod) + network.add_component(elec_prod) + network.add_component(gaz_demand) + network.add_component(elec_demand) + network.add_component(pipeline) + network.add_component(compressor_from) + network.add_component(compressor_to) + network.add_component(electrolyzer) + + network.connect( + PortRef(gaz_node_2, "injection_port"), PortRef(gaz_prod, "injection_port") + ) + network.connect(PortRef(gaz_node_2, "injection_port"), PortRef(pipeline, "flow_to")) + network.connect(PortRef(gaz_node_2, "injection_port"), PortRef(pipeline, "flow_to")) + + network.connect( + PortRef(gaz_node_1, "injection_port"), PortRef(gaz_demand, "injection_port") + ) + network.connect( + PortRef(gaz_node_1, "injection_port"), PortRef(pipeline, "flow_from") + ) + network.connect( + PortRef(gaz_node_1, "injection_port"), PortRef(electrolyzer, "output_port") + ) + + network.connect( + PortRef(pipeline, "flow_from_pos"), PortRef(compressor_from, "output_port") + ) + network.connect( + PortRef(pipeline, "flow_to_pos"), PortRef(compressor_to, "output_port") + ) + + network.connect( + PortRef(elec_node, "injection_port"), PortRef(elec_demand, "injection_port") + ) + network.connect( + PortRef(elec_prod, "injection_port"), PortRef(elec_node, "injection_port") + ) + network.connect( + PortRef(elec_node, "injection_port"), PortRef(electrolyzer, "input_port") + ) + network.connect( + PortRef(elec_node, "injection_port"), PortRef(compressor_from, "input_port") + ) + network.connect( + PortRef(elec_node, "injection_port"), PortRef(compressor_to, "input_port") + ) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + output = OutputValues(problem) + r_value = output.component("pp").var("r").value + generation1 = output.component("pg").var("generation").value + generation2 = output.component("pe").var("generation").value + print("generation") + print(generation1) + print(generation2) + r = output.component("pp").var("r") + print(r) + assert status == problem.solver.OPTIMAL + assert math.isclose(problem.solver.Objective().Value(), 2100) + + assert math.isclose(r_value, 100) diff --git a/tests/models/test_stock_pipeline.py b/tests/models/test_stock_pipeline.py new file mode 100644 index 00000000..83930641 --- /dev/null +++ b/tests/models/test_stock_pipeline.py @@ -0,0 +1,120 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +import math +from pathlib import Path + +from andromede.simulation import OutputValues, TimeBlock, build_problem +from andromede.study import ( + ConstantData, + DataBase, + Network, + Node, + PortRef, + create_component, +) + + +def test_stock_pipeline(data_dir: Path, lib: Path, lib_sc: Path): + """ + Test of a pipeline with stock capacity + + for the following test we have two gaz production and two gaz demands, + One of each on each sides of a pipeline that has a storage capacity + gaz_prod_1 and gaz_demand_1 are on the input side of the pipeline + gaz_prod_2 and gaz_demand_2 are on the output side of the pipeline + we have the following values: + gaz production 1: + - p_max = 200 + - cost = 30 + gaz production 2: + - p_max = 50 + - cost = 10 + gaz demand 1: + - demand = 50 + gaz demand 2: + - demand = 50 + pipeline: + - f_from_max = 100 + - f_to_max = 100 + - capacity = 100 + - initial_level = 50 + """ + + gen_model = lib.models["generator"] + node_model = lib.models["node"] + demand_model = lib.models["demand"] + link_with_storage_model = lib_sc.models["link_with_storage"] + + gaz_node_1 = Node(model=node_model, id="g1") + gaz_node_2 = Node(model=node_model, id="g2") + gaz_prod_1 = create_component(model=gen_model, id="prodg1") + gaz_prod_2 = create_component(model=gen_model, id="prodg2") + gaz_demand_1 = create_component(model=demand_model, id="demandg1") + gaz_demand_2 = create_component(model=demand_model, id="demandg2") + pipeline = create_component(model=link_with_storage_model, id="pipeline") + + database = DataBase() + + database.add_data("prodg1", "p_max", ConstantData(200)) + database.add_data("prodg1", "cost", ConstantData(30)) + database.add_data("prodg2", "p_max", ConstantData(50)) + database.add_data("prodg2", "cost", ConstantData(10)) + database.add_data("demandg1", "demand", ConstantData(50)) + database.add_data("demandg2", "demand", ConstantData(50)) + database.add_data("pipeline", "f_from_max", ConstantData(100)) + database.add_data("pipeline", "f_to_max", ConstantData(100)) + database.add_data("pipeline", "capacity", ConstantData(100)) + database.add_data("pipeline", "initial_level", ConstantData(50)) + + network = Network("test") + network.add_node(gaz_node_1) + network.add_node(gaz_node_2) + network.add_component(gaz_prod_1) + network.add_component(gaz_prod_2) + network.add_component(gaz_demand_1) + network.add_component(gaz_demand_2) + network.add_component(pipeline) + + network.connect( + PortRef(gaz_prod_1, "injection_port"), PortRef(gaz_node_1, "injection_port") + ) + network.connect( + PortRef(gaz_node_1, "injection_port"), PortRef(gaz_demand_1, "injection_port") + ) + network.connect( + PortRef(gaz_node_1, "injection_port"), PortRef(pipeline, "flow_from") + ) + network.connect( + PortRef(gaz_prod_2, "injection_port"), PortRef(gaz_node_2, "injection_port") + ) + network.connect( + PortRef(gaz_node_2, "injection_port"), PortRef(gaz_demand_2, "injection_port") + ) + network.connect(PortRef(gaz_node_2, "injection_port"), PortRef(pipeline, "flow_to")) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + assert status == problem.solver.OPTIMAL + assert math.isclose(problem.solver.Objective().Value(), 2000) + + output = OutputValues(problem) + r_value = output.component("pipeline").var("r").value + generation1 = output.component("prodg1").var("generation").value + generation2 = output.component("prodg2").var("generation").value + print("generation") + print(generation1) + print(generation2) + + assert math.isclose(r_value, 100) From 14bc1e5ba2e4ae040e444b865a4e7f4ee6eb417f Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Fri, 17 May 2024 16:49:22 +0200 Subject: [PATCH 24/29] added two test for stock pipeline and test and model for gas stock --- src/andromede/libs/standard_sc.yml | 81 ++++++++- .../models/test_gas_stock_elec_compressor.py | 138 ++++++++++++++++ tests/models/test_stock_pipeline.py | 154 +++++++++++++++++- 3 files changed, 367 insertions(+), 6 deletions(-) create mode 100644 tests/models/test_gas_stock_elec_compressor.py diff --git a/src/andromede/libs/standard_sc.yml b/src/andromede/libs/standard_sc.yml index 342a5f27..302776ee 100644 --- a/src/andromede/libs/standard_sc.yml +++ b/src/andromede/libs/standard_sc.yml @@ -78,14 +78,71 @@ library: expression: f_from = f_from_p - f_from_m - name: max0_to expression: f_to = f_to_p - f_to_m - - name: r_h + - name: r_t1 expression: r[t+1] = r[t] + f_from - f_to + - name: r0 + expression: r[0] = initial_level + - name: rt + expression: r[t] = initial_level binding-constraints: - name: FlowFromPos expression: sum_connections(flow_from_pos.flow) = f_from_p - name: FlowToPos expression: sum_connections(flow_to_pos.flow) = f_to_p + - id: link_with_storage_2 + description: A link with energy storage without flow_from/to_pos + parameters: + - name: f_from_max + time-dependent: false + scenario-dependent: false + - name: f_to_max + time-dependent: false + scenario-dependent: false + - name: capacity + time-dependent: false + scenario-dependent: false + - name: initial_level + time-dependent: false + scenario-dependent: false + variables: + - name: r + lower-bound: 0 + upper-bound: capacity + - name: f_from + lower-bound: -f_from_max + upper-bound: f_from_max + - name: f_to + lower-bound: -f_to_max + upper-bound: f_to_max + - name: f_from_p + lower-bound: 0 + - name: f_from_m + lower-bound: 0 + - name: f_to_p + lower-bound: 0 + - name: f_to_m + lower-bound: 0 + ports: + - name: flow_from + type: flow + - name: flow_to + type: flow + port-field-definitions: + - port: flow_from + field: flow + definition: -f_from + - port: flow_to + field: flow + definition: f_to + constraints: + - name: r_t1 + expression: r[t+1] = r[t] + f_from - f_to + - name: r0 + expression: r[0] = initial_level + - name: rt + expression: r[t] = initial_level + - id: stock_final_level description: A stock model parameters: @@ -130,6 +187,28 @@ library: - name: flow_in expression: sum_connections(flow_c.flow) = u_in + - id: repartition_key + description: A repartition key + parameters: + - name: alpha + time-dependent: false + scenario-dependent: false + variables: + - name: input + lower-bound: 0 + ports: + - name: input_port + type: flow + - name: output_port + type: flow + port-field-definitions: + - port: input_port + field: flow + definition: -input + - port: output_port + field: flow + definition: input * alpha + - id: convertor description: A basic convertor model parameters: diff --git a/tests/models/test_gas_stock_elec_compressor.py b/tests/models/test_gas_stock_elec_compressor.py new file mode 100644 index 00000000..9fcb2417 --- /dev/null +++ b/tests/models/test_gas_stock_elec_compressor.py @@ -0,0 +1,138 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +import math +from pathlib import Path + +from andromede.simulation import OutputValues, TimeBlock, build_problem +from andromede.study import ( + ConstantData, + DataBase, + Network, + Node, + PortRef, + create_component, +) + + +def test_gas_stock_elec_compressor(data_dir: Path, lib: Path, lib_sc: Path): + """ + Test of a pipeline with stock capacity + + for the following test we have two gaz production and two gaz demands, + One of each on each sides of a pipeline that has a storage capacity + gaz_prod_1 and gaz_demand_1 are on the input side of the pipeline + gaz_prod_2 and gaz_demand_2 are on the output side of the pipeline + we have the following values: + gaz production 1: + - p_max = 200 + - cost = 30 + gaz production 2: + - p_max = 50 + - cost = 10 + gaz demand 1: + - demand = 100 + gaz demand 2: + - demand = 100 + pipeline: + - f_from_max = 50 + - f_to_max = 50 + - capacity = 100 + - initial_level = 20 + """ + gen_model = lib.models["generator"] + node_model = lib.models["node"] + demand_model = lib.models["demand"] + stock_final_model = lib_sc.models["stock_final_level"] + convertor_model = lib_sc.models["convertor"] + + gaz_node = Node(model=node_model, id="g") + elec_node = Node(model=node_model, id="e") + gaz_prod = create_component(model=gen_model, id="pg") + elec_prod = create_component(model=gen_model, id="pe") + gaz_demand = create_component(model=demand_model, id="dg") + elec_demand = create_component(model=demand_model, id="de") + gas_stock = create_component(model=stock_final_model, id="sg") + electrolyzer = create_component(model=convertor_model, id="ez") + compressor = create_component(model=convertor_model, id="cp") + + database = DataBase() + database.add_data("dg", "demand", ConstantData(100)) + database.add_data("de", "demand", ConstantData(10)) + + database.add_data("pg", "p_max", ConstantData(300)) + database.add_data("pg", "cost", ConstantData(10)) + database.add_data("pe", "p_max", ConstantData(200)) + database.add_data("pe", "cost", ConstantData(10)) + + database.add_data("sg", "p_max_in", ConstantData(100)) + database.add_data("sg", "p_max_out", ConstantData(50)) + database.add_data("sg", "capacity", ConstantData(100)) + database.add_data("sg", "initial_level", ConstantData(10)) + database.add_data("sg", "final_level", ConstantData(90)) + + database.add_data("ez", "alpha", ConstantData(0.5)) + + database.add_data("cp", "alpha", ConstantData(0.7)) + + network = Network("test") + network.add_node(gaz_node) + network.add_node(elec_node) + network.add_component(gaz_prod) + network.add_component(elec_prod) + network.add_component(gaz_demand) + network.add_component(elec_demand) + network.add_component(gas_stock) + network.add_component(compressor) + network.add_component(electrolyzer) + + network.connect( + PortRef(gaz_node, "injection_port"), PortRef(gaz_demand, "injection_port") + ) + network.connect(PortRef(gaz_node, "injection_port"), PortRef(gas_stock, "flow_s")) + network.connect( + PortRef(gaz_node, "injection_port"), PortRef(electrolyzer, "output_port") + ) + network.connect( + PortRef(gaz_node, "injection_port"), PortRef(gaz_prod, "injection_port") + ) + + network.connect(PortRef(gas_stock, "flow_c"), PortRef(compressor, "output_port")) + + network.connect( + PortRef(elec_node, "injection_port"), PortRef(elec_demand, "injection_port") + ) + network.connect( + PortRef(elec_prod, "injection_port"), PortRef(elec_node, "injection_port") + ) + network.connect( + PortRef(elec_node, "injection_port"), PortRef(electrolyzer, "input_port") + ) + network.connect( + PortRef(elec_node, "injection_port"), PortRef(compressor, "input_port") + ) + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + output = OutputValues(problem) + r_value = output.component("sg").var("r").value + generation1 = output.component("pg").var("generation").value + generation2 = output.component("pe").var("generation").value + print("generation") + print(generation1) + print(generation2) + print(r_value) + assert status == problem.solver.OPTIMAL + assert math.isclose(problem.solver.Objective().Value(), 1100) + + assert math.isclose(r_value, 100) diff --git a/tests/models/test_stock_pipeline.py b/tests/models/test_stock_pipeline.py index 83930641..f988d302 100644 --- a/tests/models/test_stock_pipeline.py +++ b/tests/models/test_stock_pipeline.py @@ -20,10 +20,81 @@ Network, Node, PortRef, + TimeIndex, + TimeSeriesData, create_component, ) +def test_stock_pipeline_as_link(data_dir: Path, lib: Path, lib_sc: Path): + """ + Test of the stock pipeline without using the stock to see if it work as a simple link + + The pipeline is between two nodes with a gaz production on a node and a demand on the other + we have the following values: + gaz production: + - p_max = 100 + - cost = 10 + gaz demand: + - demand = 50 + pipeline: + - f_from_max = 100 + - f_to_max = 100 + - capacity = 0 + - initial_level = 0 + """ + gen_model = lib.models["generator"] + node_model = lib.models["node"] + demand_model = lib.models["demand"] + link_with_storage_model = lib_sc.models["link_with_storage_2"] + + gaz_node_1 = Node(model=node_model, id="g1") + gaz_node_2 = Node(model=node_model, id="g2") + gaz_prod = create_component(model=gen_model, id="pg") + gaz_demand = create_component(model=demand_model, id="dg") + pipeline = create_component(model=link_with_storage_model, id="pipeline") + + database = DataBase() + + database.add_data("pg", "p_max", ConstantData(100)) + database.add_data("pg", "cost", ConstantData(10)) + database.add_data("dg", "demand", ConstantData(50)) + database.add_data("pipeline", "f_from_max", ConstantData(100)) + database.add_data("pipeline", "f_to_max", ConstantData(100)) + database.add_data("pipeline", "capacity", ConstantData(0)) + database.add_data("pipeline", "initial_level", ConstantData(0)) + + network = Network("test") + network.add_node(gaz_node_1) + network.add_node(gaz_node_2) + network.add_component(gaz_prod) + network.add_component(gaz_demand) + network.add_component(pipeline) + + network.connect( + PortRef(gaz_prod, "injection_port"), PortRef(gaz_node_1, "injection_port") + ) + network.connect(PortRef(gaz_node_1, "injection_port"), PortRef(pipeline, "flow_to")) + network.connect( + PortRef(gaz_node_2, "injection_port"), PortRef(gaz_demand, "injection_port") + ) + network.connect( + PortRef(gaz_node_2, "injection_port"), PortRef(pipeline, "flow_from") + ) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + output = OutputValues(problem) + generation1 = output.component("pg").var("generation").value + print("generation") + print(generation1) + + assert status == problem.solver.OPTIMAL + assert math.isclose(problem.solver.Objective().Value(), 500) + + def test_stock_pipeline(data_dir: Path, lib: Path, lib_sc: Path): """ Test of a pipeline with stock capacity @@ -53,7 +124,7 @@ def test_stock_pipeline(data_dir: Path, lib: Path, lib_sc: Path): gen_model = lib.models["generator"] node_model = lib.models["node"] demand_model = lib.models["demand"] - link_with_storage_model = lib_sc.models["link_with_storage"] + link_with_storage_model = lib_sc.models["link_with_storage_2"] gaz_node_1 = Node(model=node_model, id="g1") gaz_node_2 = Node(model=node_model, id="g2") @@ -69,8 +140,8 @@ def test_stock_pipeline(data_dir: Path, lib: Path, lib_sc: Path): database.add_data("prodg1", "cost", ConstantData(30)) database.add_data("prodg2", "p_max", ConstantData(50)) database.add_data("prodg2", "cost", ConstantData(10)) - database.add_data("demandg1", "demand", ConstantData(50)) - database.add_data("demandg2", "demand", ConstantData(50)) + database.add_data("demandg1", "demand", ConstantData(100)) + database.add_data("demandg2", "demand", ConstantData(100)) database.add_data("pipeline", "f_from_max", ConstantData(100)) database.add_data("pipeline", "f_to_max", ConstantData(100)) database.add_data("pipeline", "capacity", ConstantData(100)) @@ -107,7 +178,7 @@ def test_stock_pipeline(data_dir: Path, lib: Path, lib_sc: Path): status = problem.solver.Solve() assert status == problem.solver.OPTIMAL - assert math.isclose(problem.solver.Objective().Value(), 2000) + assert math.isclose(problem.solver.Objective().Value(), 5000) output = OutputValues(problem) r_value = output.component("pipeline").var("r").value @@ -117,4 +188,77 @@ def test_stock_pipeline(data_dir: Path, lib: Path, lib_sc: Path): print(generation1) print(generation2) - assert math.isclose(r_value, 100) + assert math.isclose(r_value, 50) + + +def test_stock_pipeline_time(data_dir: Path, lib: Path, lib_sc: Path): + """ + Test of a pipeline for 2 time unit + + The pipeline is between two nodes with a gaz production on a node and a demand on the other + we have the following values: + gaz production: + - p_max = 50 + - cost = 10 + gaz demand: + - demand = 0 then 100 + pipeline: + - f_from_max = 100 + - f_to_max = 100 + - capacity = 100 + - initial_level = 50 + """ + gen_model = lib.models["generator"] + node_model = lib.models["node"] + demand_model = lib.models["demand"] + link_with_storage_model = lib_sc.models["link_with_storage_2"] + + gaz_node_1 = Node(model=node_model, id="g1") + gaz_node_2 = Node(model=node_model, id="g2") + gaz_prod_1 = create_component(model=gen_model, id="prodg1") + gaz_demand_2 = create_component(model=demand_model, id="demandg2") + pipeline = create_component(model=link_with_storage_model, id="pipeline") + + database = DataBase() + + demand_data = TimeSeriesData({TimeIndex(0): 0, TimeIndex(1): 50}) + + database.add_data("prodg1", "p_max", ConstantData(50)) + database.add_data("prodg1", "cost", ConstantData(10)) + database.add_data("demandg2", "demand", demand_data) + database.add_data("pipeline", "f_from_max", ConstantData(100)) + database.add_data("pipeline", "f_to_max", ConstantData(100)) + database.add_data("pipeline", "capacity", ConstantData(100)) + database.add_data("pipeline", "initial_level", ConstantData(50)) + + network = Network("test") + network.add_node(gaz_node_1) + network.add_node(gaz_node_2) + network.add_component(gaz_prod_1) + network.add_component(gaz_demand_2) + network.add_component(pipeline) + + network.connect( + PortRef(gaz_prod_1, "injection_port"), PortRef(gaz_node_1, "injection_port") + ) + network.connect( + PortRef(gaz_node_1, "injection_port"), PortRef(pipeline, "flow_from") + ) + network.connect( + PortRef(gaz_node_2, "injection_port"), PortRef(gaz_demand_2, "injection_port") + ) + network.connect(PortRef(gaz_node_2, "injection_port"), PortRef(pipeline, "flow_to")) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0, 1]), scenarios) + status = problem.solver.Solve() + + output = OutputValues(problem) + r_value = output.component("pipeline").var("r").value + generation1 = output.component("prodg1").var("generation").value + print("generation") + print(generation1) + print(r_value) + + assert status == problem.solver.OPTIMAL + assert math.isclose(problem.solver.Objective().Value(), 500) From 24d0c06513b9110099ecdcdc0a1b88376146a333 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Tue, 21 May 2024 09:12:39 +0200 Subject: [PATCH 25/29] fix test gas link elec compressor --- tests/models/test_gas_link_elec_compressor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/models/test_gas_link_elec_compressor.py b/tests/models/test_gas_link_elec_compressor.py index aec19848..3c9f5a2c 100644 --- a/tests/models/test_gas_link_elec_compressor.py +++ b/tests/models/test_gas_link_elec_compressor.py @@ -160,4 +160,4 @@ def test_gas_link_elec_compressor(data_dir: Path, lib: Path, lib_sc: Path): assert status == problem.solver.OPTIMAL assert math.isclose(problem.solver.Objective().Value(), 2100) - assert math.isclose(r_value, 100) + assert math.isclose(r_value, 20) From 6e162304c7c9e4ea95826cd16a88c4966731c5e9 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Mon, 27 May 2024 17:12:08 +0200 Subject: [PATCH 26/29] Added test for gas stock 2 elec compressor with yaml components --- src/andromede/libs/standard_sc.yml | 61 ++++-- ...s_for_test_gas_stock_2_elec_compressor.yml | 176 ++++++++++++++++++ ...s_for_test_gas_stock_2_elec_compressor.yml | 140 ++++++++++++++ .../test_gas_stock_2_elec_compressor.py | 109 +++++++++++ .../models/test_gas_stock_elec_compressor.py | 9 +- 5 files changed, 477 insertions(+), 18 deletions(-) create mode 100644 tests/models/components_for_test_gas_stock_2_elec_compressor.yml create mode 100644 tests/models/models_for_test_gas_stock_2_elec_compressor.yml create mode 100644 tests/models/test_gas_stock_2_elec_compressor.py diff --git a/src/andromede/libs/standard_sc.yml b/src/andromede/libs/standard_sc.yml index 302776ee..3420f3c2 100644 --- a/src/andromede/libs/standard_sc.yml +++ b/src/andromede/libs/standard_sc.yml @@ -143,8 +143,8 @@ library: - name: rt expression: r[t] = initial_level - - id: stock_final_level - description: A stock model + - id: stock_final_level_input + description: A stock model with multiple input parameters: - name: p_max_in time-dependent: false @@ -187,27 +187,62 @@ library: - name: flow_in expression: sum_connections(flow_c.flow) = u_in - - id: repartition_key - description: A repartition key + - id: stock_final_level + description: A stock model parameters: - - name: alpha + - name: p_max_in + time-dependent: false + scenario-dependent: false + - name: p_max_out + time-dependent: false + scenario-dependent: false + - name: capacity + time-dependent: false + scenario-dependent: false + - name: initial_level + time-dependent: false + scenario-dependent: false + - name: final_level time-dependent: false scenario-dependent: false variables: - - name: input + - name: r + lower-bound: 0 + upper-bound: capacity + - name: u_in + lower-bound: 0 + upper-bound: p_max_in + - name: u_out lower-bound: 0 + upper-bound: p_max_out ports: - - name: input_port + - name: flow_s type: flow - - name: output_port + - name: flow_c type: flow port-field-definitions: - - port: input_port - field: flow - definition: -input - - port: output_port + - port: flow_s field: flow - definition: input * alpha + definition: u_out - u_in + constraints: + - name: r_h + expression: r[t+1] = r[t] + u_in[t] - u_out[t] + binding-constraints: + - name: flow_in + expression: sum_connections(flow_c.flow) = u_in + + - id: repartition_key + description: A repartition key + parameters: + - name: alpha + time-dependent: false + scenario-dependent: false + ports: + - name: flow_k + type: flow + binding-constraints: + - name: sum + expression: sum_connections(flow_k.flow) = 0 - id: convertor description: A basic convertor model diff --git a/tests/models/components_for_test_gas_stock_2_elec_compressor.yml b/tests/models/components_for_test_gas_stock_2_elec_compressor.yml new file mode 100644 index 00000000..1fca9754 --- /dev/null +++ b/tests/models/components_for_test_gas_stock_2_elec_compressor.yml @@ -0,0 +1,176 @@ +study: + nodes: + - id: NG + model: node + - id: NE1 + model: node + - id: NE2 + model: node + + components: + - id: DG + model: demand + parameters: + - name: demand + type: constant + value: 10 + - id: DE1 + model: demand + parameters: + - name: demand + type: constant + value: 50 + - id: DE2 + model: demand + parameters: + - name: demand + type: constant + value: 100 + - id: PG + model: generator + parameters: + - name: cost + type: constant + value: 10 + - name: p_max + type: constant + value: 300 + - id: PE1 + model: generator + parameters: + - name: cost + type: constant + value: 10 + - name: p_max + type: constant + value: 200 + - id: PE2 + model: generator + parameters: + - name: cost + type: constant + value: 10 + - name: p_max + type: constant + value: 200 + - id: GS + model: stock_final_level + parameters: + - name: p_max_in + type: constant + value: 100 + - name: p_max_out + type: constant + value: 50 + - name: capacity + type: constant + value: 100 + - name: initial_level + type: constant + value: 10 + - name: final_level + type: constant + value: 90 + - id: EZ1 + model: convertor + parameters: + - name: alpha + type: constant + value: 0.5 + - id: EZ2 + model: convertor + parameters: + - name: alpha + type: constant + value: 0.5 + - id: CP1 + model: convertor + parameters: + - name: alpha + type: constant + value: 0.5 + - id: CP2 + model: convertor + parameters: + - name: alpha + type: constant + value: 0.5 + - id: RC + model: repartition_key + parameters: + - name: alpha + type: constant + value: 0.5 + + + connections: + - component1: NG + port_1: injection_port + component2: DG + port_2: injection_port + - component1: NG + port_1: injection_port + component2: PG + port_2: injection_port + - component1: NG + port_1: injection_port + component2: GS + port_2: flow_s + + - component1: NE1 + port_1: injection_port + component2: PE1 + port_2: injection_port + - component1: NE1 + port_1: injection_port + component2: DE1 + port_2: injection_port + - component1: NE1 + port_1: injection_port + component2: CP1 + port_2: output_port + - component1: NE1 + port_1: injection_port + component2: EZ1 + port_2: input_port + - component1: EZ1 + port_1: input_port + component2: NG + port_2: injection_port + - component1: CP1 + port_1: input_port + component2: GS + port_2: flow_c + - component1: CP1 + port_1: input_port + component2: RC + port_2: flow_k + + - component1: NE2 + port_1: injection_port + component2: PE2 + port_2: injection_port + - component1: NE2 + port_1: injection_port + component2: DE2 + port_2: injection_port + - component1: NE2 + port_1: injection_port + component2: CP2 + port_2: output_port + - component1: NE2 + port_1: injection_port + component2: EZ2 + port_2: input_port + - component1: EZ2 + port_1: input_port + component2: NG + port_2: injection_port + - component1: CP2 + port_1: input_port + component2: GS + port_2: flow_c + - component1: CP2 + port_1: input_port + component2: RC + port_2: flow_k \ No newline at end of file diff --git a/tests/models/models_for_test_gas_stock_2_elec_compressor.yml b/tests/models/models_for_test_gas_stock_2_elec_compressor.yml new file mode 100644 index 00000000..6ecdc061 --- /dev/null +++ b/tests/models/models_for_test_gas_stock_2_elec_compressor.yml @@ -0,0 +1,140 @@ +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +library: + id: basic + description: Basic library + + port-types: + - id: flow + description: A port which transfers power flow + fields: + - name: flow + models: + - id: repartition_key + description: A repartition key + parameters: + - name: alpha + time-dependent: false + scenario-dependent: false + ports: + - name: flow_k + type: flow + binding-constraints: + - name: sum + expression: sum_connections(flow_k.flow) = 0 + + - id: generator + description: A basic generator model + parameters: + - name: cost + time-dependent: false + scenario-dependent: false + - name: p_max + time-dependent: false + scenario-dependent: false + variables: + - name: generation + lower-bound: 0 + upper-bound: p_max + ports: + - name: injection_port + type: flow + port-field-definitions: + - port: injection_port + field: flow + definition: generation + objective: expec(sum(cost * generation)) + + - id: node + description: A basic balancing node model + ports: + - name: injection_port + type: flow + binding-constraints: + - name: balance + expression: sum_connections(injection_port.flow) = 0 + + - id: demand + description: A basic fixed demand model + parameters: + - name: demand + time-dependent: true + scenario-dependent: true + ports: + - name: injection_port + type: flow + port-field-definitions: + - port: injection_port + field: flow + definition: -demand + + - id: convertor + description: A basic convertor model + parameters: + - name: alpha + time-dependent: false + scenario-dependent: false + variables: + - name: input + lower-bound: 0 + ports: + - name: input_port + type: flow + - name: output_port + type: flow + port-field-definitions: + - port: input_port + field: flow + definition: -input + - port: output_port + field: flow + definition: input * alpha + + - id: stock_final_level + description: A stock model + parameters: + - name: p_max_in + time-dependent: false + scenario-dependent: false + - name: p_max_out + time-dependent: false + scenario-dependent: false + - name: capacity + time-dependent: false + scenario-dependent: false + - name: initial_level + time-dependent: false + scenario-dependent: false + - name: final_level + time-dependent: false + scenario-dependent: false + variables: + - name: r + lower-bound: 0 + upper-bound: capacity + - name: u_in + lower-bound: 0 + upper-bound: p_max_in + - name: u_out + lower-bound: 0 + upper-bound: p_max_out + ports: + - name: flow_s + type: flow + - name: flow_c + type: flow + port-field-definitions: + - port: flow_s + field: flow + definition: u_out - u_in + constraints: + - name: r_h + expression: r[t+1] = r[t] + u_in[t] - u_out[t] + binding-constraints: + - name: flow_in + expression: sum_connections(flow_c.flow) = u_in \ No newline at end of file diff --git a/tests/models/test_gas_stock_2_elec_compressor.py b/tests/models/test_gas_stock_2_elec_compressor.py new file mode 100644 index 00000000..580743da --- /dev/null +++ b/tests/models/test_gas_stock_2_elec_compressor.py @@ -0,0 +1,109 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +import math +from pathlib import Path + +import pytest + +from andromede.model.parsing import parse_yaml_library +from andromede.model.resolve_library import resolve_library +from andromede.simulation import OutputValues, TimeBlock, build_problem +from andromede.study.parsing import InputComponents, parse_yaml_components +from andromede.study.resolve_components import ( + build_data_base, + build_network, + consistency_check, + resolve_components_and_cnx, +) + + +@pytest.fixture(scope="session") +def lib_models(): + libs_path = Path(__file__).parent + lib_sc_file = libs_path / "models_for_test_gas_stock_2_elec_compressor.yml" + + with lib_sc_file.open() as f: + input_lib_sc = parse_yaml_library(f) + + lib_sc = resolve_library(input_lib_sc) + return lib_sc + + +def test_gas_stock_2_elec_compressor(data_dir: Path, lib_models: Path): + """ + Test of a gas stock and a repartition compressor in a system with multiple set of electricity production and demand + + for the following test we have two electrical node, each connected to a demand, a production, an electrolyzer and a compressor + each compressor are connected to a unique gas stock witch is connected to a gas node with a demand and a production + each electrolyzer are also connected to the gas node + each compressor are also connected to a unique repartition compressor + + we have the following values: + gaz production: + - p_max = 10 + - cost = 10 + elec production 1: + - p_max = 100 + - cost = 10 + elec production 2: + - p_max = 100 + - cost = 10 + gaz demand: + - demand = 10 + elec demand 1: + - demand = 50 + elec demand 2: + - demand = 100 + gas stock: + - p_max_in = 100 + - p_max_out = 50 + - capacity = 100 + - initial_level = 10 + - final_level = 90 + electrolyzer 1: + - alpha 0.5 + electrolyzer 2: + - alpha 0.5 + compressor 1: + - alpha 0.5 + compressor 2: + - alpha 0.5 + """ + components_path = Path(__file__) + compo_file = ( + components_path.parent / "components_for_test_gas_stock_2_elec_compressor.yml" + ) + input_component = parse_yaml_components(compo_file.open()) + + components_input = resolve_components_and_cnx(input_component, lib_models) + consistency_check(components_input.components, lib_models.models) + + database = build_data_base(input_component, None) + network = build_network(components_input) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + output = OutputValues(problem) + r_value = output.component("GS").var("r").value + generation1 = output.component("PE1").var("generation").value + generation2 = output.component("PE2").var("generation").value + generation3 = output.component("PG").var("generation").value + print("generation") + print(generation1) + print(generation2) + print(generation3) + print(r_value) + assert status == problem.solver.OPTIMAL + assert math.isclose(problem.solver.Objective().Value(), 1600) diff --git a/tests/models/test_gas_stock_elec_compressor.py b/tests/models/test_gas_stock_elec_compressor.py index 9fcb2417..b341d8f8 100644 --- a/tests/models/test_gas_stock_elec_compressor.py +++ b/tests/models/test_gas_stock_elec_compressor.py @@ -26,12 +26,11 @@ def test_gas_stock_elec_compressor(data_dir: Path, lib: Path, lib_sc: Path): """ - Test of a pipeline with stock capacity + Test of a gas stock and compressor to pump the gas through - for the following test we have two gaz production and two gaz demands, - One of each on each sides of a pipeline that has a storage capacity - gaz_prod_1 and gaz_demand_1 are on the input side of the pipeline - gaz_prod_2 and gaz_demand_2 are on the output side of the pipeline + for the following test we have a gas production and demand, a electricity production and demand + a gas demand on a side of the pipeline and a gas production on the other side + two electrolyzer converts electricity to gas and are connected to the pipeline and the electricity node we have the following values: gaz production 1: - p_max = 200 From 8fba186cbf92268a6aa5b822fa3988abf0633911 Mon Sep 17 00:00:00 2001 From: "EUROGICIEL\\yt.kermorgant" Date: Fri, 31 May 2024 16:44:18 +0200 Subject: [PATCH 27/29] added stock hydro test --- ...omponents_for_test_stock_hydro_2_input.yml | 84 +++++++++++++ .../models_for_test_stock_hydro_2_input.yml | 110 ++++++++++++++++++ tests/models/test_stock_hydro_2_input.py | 69 +++++++++++ 3 files changed, 263 insertions(+) create mode 100644 tests/models/components_for_test_stock_hydro_2_input.yml create mode 100644 tests/models/models_for_test_stock_hydro_2_input.yml create mode 100644 tests/models/test_stock_hydro_2_input.py diff --git a/tests/models/components_for_test_stock_hydro_2_input.yml b/tests/models/components_for_test_stock_hydro_2_input.yml new file mode 100644 index 00000000..acd47a77 --- /dev/null +++ b/tests/models/components_for_test_stock_hydro_2_input.yml @@ -0,0 +1,84 @@ +study: + nodes: + - id: NE1 + model: node + - id: NE2 + model: node + + components: + - id: DE1 + model: demand + parameters: + - name: demand + type: constant + value: 50 + - id: DE2 + model: demand + parameters: + - name: demand + type: constant + value: 100 + - id: PE1 + model: generator + parameters: + - name: cost + type: constant + value: 10 + - name: p_max + type: constant + value: 200 + - id: PE2 + model: generator + parameters: + - name: cost + type: constant + value: 10 + - name: p_max + type: constant + value: 200 + - id: HS + model: stock_final_level_input + parameters: + - name: p_max_in + type: constant + value: 50 + - name: p_max_out + type: constant + value: 50 + - name: capacity + type: constant + value: 100 + - name: initial_level + type: constant + value: 10 + - name: final_level + type: constant + value: 20 + + + connections: + - component1: NE1 + port_1: injection_port + component2: PE1 + port_2: injection_port + - component1: NE1 + port_1: injection_port + component2: DE1 + port_2: injection_port + - component1: NE1 + port_1: injection_port + component2: HS + port_2: flow_s1 + + - component1: NE2 + port_1: injection_port + component2: PE2 + port_2: injection_port + - component1: NE2 + port_1: injection_port + component2: DE2 + port_2: injection_port + - component1: NE2 + port_1: injection_port + component2: HS + port_2: flow_s2 \ No newline at end of file diff --git a/tests/models/models_for_test_stock_hydro_2_input.yml b/tests/models/models_for_test_stock_hydro_2_input.yml new file mode 100644 index 00000000..99421e67 --- /dev/null +++ b/tests/models/models_for_test_stock_hydro_2_input.yml @@ -0,0 +1,110 @@ +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +library: + id: basic + description: Basic library + + port-types: + - id: flow + description: A port which transfers power flow + fields: + - name: flow + models: + + - id: generator + description: A basic generator model + parameters: + - name: cost + time-dependent: false + scenario-dependent: false + - name: p_max + time-dependent: false + scenario-dependent: false + variables: + - name: generation + lower-bound: 0 + upper-bound: p_max + ports: + - name: injection_port + type: flow + port-field-definitions: + - port: injection_port + field: flow + definition: generation + objective: expec(sum(cost * generation)) + + - id: node + description: A basic balancing node model + ports: + - name: injection_port + type: flow + binding-constraints: + - name: balance + expression: sum_connections(injection_port.flow) = 0 + + - id: demand + description: A basic fixed demand model + parameters: + - name: demand + time-dependent: true + scenario-dependent: true + ports: + - name: injection_port + type: flow + port-field-definitions: + - port: injection_port + field: flow + definition: -demand + + - id: stock_final_level_input + description: A stock model + parameters: + - name: p_max_in + time-dependent: false + scenario-dependent: false + - name: p_max_out + time-dependent: false + scenario-dependent: false + - name: capacity + time-dependent: false + scenario-dependent: false + - name: initial_level + time-dependent: false + scenario-dependent: false + - name: final_level + time-dependent: false + scenario-dependent: false + variables: + - name: r + lower-bound: 0 + upper-bound: capacity + - name: u1 + lower-bound: -p_max_in + upper-bound: p_max_out + - name: u2 + lower-bound: -p_max_in + upper-bound: p_max_out + ports: + - name: flow_s1 + type: flow + - name: flow_s2 + type: flow + port-field-definitions: + - port: flow_s1 + field: flow + definition: u1 + - port: flow_s2 + field: flow + definition: u2 + constraints: + - name: r_h + expression: r[t+1] = r[t] - u1[t] - u2[t] + - name: r0 + expression: r[0] = initial_level + - name: rt + expression: r[t] = initial_level \ No newline at end of file diff --git a/tests/models/test_stock_hydro_2_input.py b/tests/models/test_stock_hydro_2_input.py new file mode 100644 index 00000000..772804bd --- /dev/null +++ b/tests/models/test_stock_hydro_2_input.py @@ -0,0 +1,69 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +import math +from pathlib import Path + +import pytest + +from andromede.model.parsing import parse_yaml_library +from andromede.model.resolve_library import resolve_library +from andromede.simulation import OutputValues, TimeBlock, build_problem +from andromede.study.parsing import InputComponents, parse_yaml_components +from andromede.study.resolve_components import ( + build_data_base, + build_network, + consistency_check, + resolve_components_and_cnx, +) + + +@pytest.fixture(scope="session") +def lib_models(): + libs_path = Path(__file__).parent + lib_sc_file = libs_path / "models_for_test_stock_hydro_2_input.yml" + + with lib_sc_file.open() as f: + input_lib_sc = parse_yaml_library(f) + + lib_sc = resolve_library(input_lib_sc) + return lib_sc + + +def test_stock_hydro_2_input(data_dir: Path, lib_models: Path): + """ + Test of a hydro stock connected to 2 nodes with a electrical production and demand each + """ + components_path = Path(__file__) + compo_file = components_path.parent / "components_for_test_stock_hydro_2_input.yml" + input_component = parse_yaml_components(compo_file.open()) + + components_input = resolve_components_and_cnx(input_component, lib_models) + consistency_check(components_input.components, lib_models.models) + + database = build_data_base(input_component, None) + network = build_network(components_input) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + output = OutputValues(problem) + r_value = output.component("HS").var("r").value + generation1 = output.component("PE1").var("generation").value + generation2 = output.component("PE2").var("generation").value + print("generation") + print(generation1) + print(generation2) + print(r_value) + assert status == problem.solver.OPTIMAL + assert math.isclose(problem.solver.Objective().Value(), 1500) From 5bfdfccf6197f63ba56218f1443ebe67e7656b95 Mon Sep 17 00:00:00 2001 From: PIROT Killian Date: Mon, 1 Jul 2024 17:22:13 +0200 Subject: [PATCH 28/29] remove model stock_final_level_input --- src/andromede/libs/standard_sc.yml | 101 ----------------------------- tests/models/conftest.py | 2 +- 2 files changed, 1 insertion(+), 102 deletions(-) diff --git a/src/andromede/libs/standard_sc.yml b/src/andromede/libs/standard_sc.yml index 3420f3c2..ea767819 100644 --- a/src/andromede/libs/standard_sc.yml +++ b/src/andromede/libs/standard_sc.yml @@ -143,107 +143,6 @@ library: - name: rt expression: r[t] = initial_level - - id: stock_final_level_input - description: A stock model with multiple input - parameters: - - name: p_max_in - time-dependent: false - scenario-dependent: false - - name: p_max_out - time-dependent: false - scenario-dependent: false - - name: capacity - time-dependent: false - scenario-dependent: false - - name: initial_level - time-dependent: false - scenario-dependent: false - - name: final_level - time-dependent: false - scenario-dependent: false - variables: - - name: r - lower-bound: 0 - upper-bound: capacity - - name: u_in - lower-bound: 0 - upper-bound: p_max_in - - name: u_out - lower-bound: 0 - upper-bound: p_max_out - ports: - - name: flow_s - type: flow - - name: flow_c - type: flow - port-field-definitions: - - port: flow_s - field: flow - definition: u_out - u_in - constraints: - - name: r_h - expression: r[t+1] = r[t] + u_in[t] - u_out[t] - binding-constraints: - - name: flow_in - expression: sum_connections(flow_c.flow) = u_in - - - id: stock_final_level - description: A stock model - parameters: - - name: p_max_in - time-dependent: false - scenario-dependent: false - - name: p_max_out - time-dependent: false - scenario-dependent: false - - name: capacity - time-dependent: false - scenario-dependent: false - - name: initial_level - time-dependent: false - scenario-dependent: false - - name: final_level - time-dependent: false - scenario-dependent: false - variables: - - name: r - lower-bound: 0 - upper-bound: capacity - - name: u_in - lower-bound: 0 - upper-bound: p_max_in - - name: u_out - lower-bound: 0 - upper-bound: p_max_out - ports: - - name: flow_s - type: flow - - name: flow_c - type: flow - port-field-definitions: - - port: flow_s - field: flow - definition: u_out - u_in - constraints: - - name: r_h - expression: r[t+1] = r[t] + u_in[t] - u_out[t] - binding-constraints: - - name: flow_in - expression: sum_connections(flow_c.flow) = u_in - - - id: repartition_key - description: A repartition key - parameters: - - name: alpha - time-dependent: false - scenario-dependent: false - ports: - - name: flow_k - type: flow - binding-constraints: - - name: sum - expression: sum_connections(flow_k.flow) = 0 - - id: convertor description: A basic convertor model parameters: diff --git a/tests/models/conftest.py b/tests/models/conftest.py index ee330ed3..e507b58c 100644 --- a/tests/models/conftest.py +++ b/tests/models/conftest.py @@ -8,7 +8,7 @@ # # SPDX-License-Identifier: MPL-2.0 # -# This file is part of the Antares project. +# This file is part of the Antares project. from pathlib import Path import pytest From 3765536b83a2d8de51be2009a1e150abd3f86f06 Mon Sep 17 00:00:00 2001 From: PIROT Killian Date: Mon, 1 Jul 2024 17:30:34 +0200 Subject: [PATCH 29/29] move TODO things from stock_pipeline to scalian-stock-pipeline-dev --- ...s_for_test_gas_stock_2_elec_compressor.yml | 176 ------------------ ...omponents_for_test_stock_hydro_2_input.yml | 84 --------- ...s_for_test_gas_stock_2_elec_compressor.yml | 140 -------------- .../models_for_test_stock_hydro_2_input.yml | 110 ----------- tests/models/test_gas_link_elec_compressor.py | 163 ---------------- .../test_gas_stock_2_elec_compressor.py | 109 ----------- .../models/test_gas_stock_elec_compressor.py | 137 -------------- tests/models/test_stock_hydro_2_input.py | 69 ------- 8 files changed, 988 deletions(-) delete mode 100644 tests/models/components_for_test_gas_stock_2_elec_compressor.yml delete mode 100644 tests/models/components_for_test_stock_hydro_2_input.yml delete mode 100644 tests/models/models_for_test_gas_stock_2_elec_compressor.yml delete mode 100644 tests/models/models_for_test_stock_hydro_2_input.yml delete mode 100644 tests/models/test_gas_link_elec_compressor.py delete mode 100644 tests/models/test_gas_stock_2_elec_compressor.py delete mode 100644 tests/models/test_gas_stock_elec_compressor.py delete mode 100644 tests/models/test_stock_hydro_2_input.py diff --git a/tests/models/components_for_test_gas_stock_2_elec_compressor.yml b/tests/models/components_for_test_gas_stock_2_elec_compressor.yml deleted file mode 100644 index 1fca9754..00000000 --- a/tests/models/components_for_test_gas_stock_2_elec_compressor.yml +++ /dev/null @@ -1,176 +0,0 @@ -study: - nodes: - - id: NG - model: node - - id: NE1 - model: node - - id: NE2 - model: node - - components: - - id: DG - model: demand - parameters: - - name: demand - type: constant - value: 10 - - id: DE1 - model: demand - parameters: - - name: demand - type: constant - value: 50 - - id: DE2 - model: demand - parameters: - - name: demand - type: constant - value: 100 - - id: PG - model: generator - parameters: - - name: cost - type: constant - value: 10 - - name: p_max - type: constant - value: 300 - - id: PE1 - model: generator - parameters: - - name: cost - type: constant - value: 10 - - name: p_max - type: constant - value: 200 - - id: PE2 - model: generator - parameters: - - name: cost - type: constant - value: 10 - - name: p_max - type: constant - value: 200 - - id: GS - model: stock_final_level - parameters: - - name: p_max_in - type: constant - value: 100 - - name: p_max_out - type: constant - value: 50 - - name: capacity - type: constant - value: 100 - - name: initial_level - type: constant - value: 10 - - name: final_level - type: constant - value: 90 - - id: EZ1 - model: convertor - parameters: - - name: alpha - type: constant - value: 0.5 - - id: EZ2 - model: convertor - parameters: - - name: alpha - type: constant - value: 0.5 - - id: CP1 - model: convertor - parameters: - - name: alpha - type: constant - value: 0.5 - - id: CP2 - model: convertor - parameters: - - name: alpha - type: constant - value: 0.5 - - id: RC - model: repartition_key - parameters: - - name: alpha - type: constant - value: 0.5 - - - connections: - - component1: NG - port_1: injection_port - component2: DG - port_2: injection_port - - component1: NG - port_1: injection_port - component2: PG - port_2: injection_port - - component1: NG - port_1: injection_port - component2: GS - port_2: flow_s - - - component1: NE1 - port_1: injection_port - component2: PE1 - port_2: injection_port - - component1: NE1 - port_1: injection_port - component2: DE1 - port_2: injection_port - - component1: NE1 - port_1: injection_port - component2: CP1 - port_2: output_port - - component1: NE1 - port_1: injection_port - component2: EZ1 - port_2: input_port - - component1: EZ1 - port_1: input_port - component2: NG - port_2: injection_port - - component1: CP1 - port_1: input_port - component2: GS - port_2: flow_c - - component1: CP1 - port_1: input_port - component2: RC - port_2: flow_k - - - component1: NE2 - port_1: injection_port - component2: PE2 - port_2: injection_port - - component1: NE2 - port_1: injection_port - component2: DE2 - port_2: injection_port - - component1: NE2 - port_1: injection_port - component2: CP2 - port_2: output_port - - component1: NE2 - port_1: injection_port - component2: EZ2 - port_2: input_port - - component1: EZ2 - port_1: input_port - component2: NG - port_2: injection_port - - component1: CP2 - port_1: input_port - component2: GS - port_2: flow_c - - component1: CP2 - port_1: input_port - component2: RC - port_2: flow_k \ No newline at end of file diff --git a/tests/models/components_for_test_stock_hydro_2_input.yml b/tests/models/components_for_test_stock_hydro_2_input.yml deleted file mode 100644 index acd47a77..00000000 --- a/tests/models/components_for_test_stock_hydro_2_input.yml +++ /dev/null @@ -1,84 +0,0 @@ -study: - nodes: - - id: NE1 - model: node - - id: NE2 - model: node - - components: - - id: DE1 - model: demand - parameters: - - name: demand - type: constant - value: 50 - - id: DE2 - model: demand - parameters: - - name: demand - type: constant - value: 100 - - id: PE1 - model: generator - parameters: - - name: cost - type: constant - value: 10 - - name: p_max - type: constant - value: 200 - - id: PE2 - model: generator - parameters: - - name: cost - type: constant - value: 10 - - name: p_max - type: constant - value: 200 - - id: HS - model: stock_final_level_input - parameters: - - name: p_max_in - type: constant - value: 50 - - name: p_max_out - type: constant - value: 50 - - name: capacity - type: constant - value: 100 - - name: initial_level - type: constant - value: 10 - - name: final_level - type: constant - value: 20 - - - connections: - - component1: NE1 - port_1: injection_port - component2: PE1 - port_2: injection_port - - component1: NE1 - port_1: injection_port - component2: DE1 - port_2: injection_port - - component1: NE1 - port_1: injection_port - component2: HS - port_2: flow_s1 - - - component1: NE2 - port_1: injection_port - component2: PE2 - port_2: injection_port - - component1: NE2 - port_1: injection_port - component2: DE2 - port_2: injection_port - - component1: NE2 - port_1: injection_port - component2: HS - port_2: flow_s2 \ No newline at end of file diff --git a/tests/models/models_for_test_gas_stock_2_elec_compressor.yml b/tests/models/models_for_test_gas_stock_2_elec_compressor.yml deleted file mode 100644 index 6ecdc061..00000000 --- a/tests/models/models_for_test_gas_stock_2_elec_compressor.yml +++ /dev/null @@ -1,140 +0,0 @@ -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# SPDX-License-Identifier: MPL-2.0 -# -# This file is part of the Antares project. - -library: - id: basic - description: Basic library - - port-types: - - id: flow - description: A port which transfers power flow - fields: - - name: flow - models: - - id: repartition_key - description: A repartition key - parameters: - - name: alpha - time-dependent: false - scenario-dependent: false - ports: - - name: flow_k - type: flow - binding-constraints: - - name: sum - expression: sum_connections(flow_k.flow) = 0 - - - id: generator - description: A basic generator model - parameters: - - name: cost - time-dependent: false - scenario-dependent: false - - name: p_max - time-dependent: false - scenario-dependent: false - variables: - - name: generation - lower-bound: 0 - upper-bound: p_max - ports: - - name: injection_port - type: flow - port-field-definitions: - - port: injection_port - field: flow - definition: generation - objective: expec(sum(cost * generation)) - - - id: node - description: A basic balancing node model - ports: - - name: injection_port - type: flow - binding-constraints: - - name: balance - expression: sum_connections(injection_port.flow) = 0 - - - id: demand - description: A basic fixed demand model - parameters: - - name: demand - time-dependent: true - scenario-dependent: true - ports: - - name: injection_port - type: flow - port-field-definitions: - - port: injection_port - field: flow - definition: -demand - - - id: convertor - description: A basic convertor model - parameters: - - name: alpha - time-dependent: false - scenario-dependent: false - variables: - - name: input - lower-bound: 0 - ports: - - name: input_port - type: flow - - name: output_port - type: flow - port-field-definitions: - - port: input_port - field: flow - definition: -input - - port: output_port - field: flow - definition: input * alpha - - - id: stock_final_level - description: A stock model - parameters: - - name: p_max_in - time-dependent: false - scenario-dependent: false - - name: p_max_out - time-dependent: false - scenario-dependent: false - - name: capacity - time-dependent: false - scenario-dependent: false - - name: initial_level - time-dependent: false - scenario-dependent: false - - name: final_level - time-dependent: false - scenario-dependent: false - variables: - - name: r - lower-bound: 0 - upper-bound: capacity - - name: u_in - lower-bound: 0 - upper-bound: p_max_in - - name: u_out - lower-bound: 0 - upper-bound: p_max_out - ports: - - name: flow_s - type: flow - - name: flow_c - type: flow - port-field-definitions: - - port: flow_s - field: flow - definition: u_out - u_in - constraints: - - name: r_h - expression: r[t+1] = r[t] + u_in[t] - u_out[t] - binding-constraints: - - name: flow_in - expression: sum_connections(flow_c.flow) = u_in \ No newline at end of file diff --git a/tests/models/models_for_test_stock_hydro_2_input.yml b/tests/models/models_for_test_stock_hydro_2_input.yml deleted file mode 100644 index 99421e67..00000000 --- a/tests/models/models_for_test_stock_hydro_2_input.yml +++ /dev/null @@ -1,110 +0,0 @@ -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# SPDX-License-Identifier: MPL-2.0 -# -# This file is part of the Antares project. - -library: - id: basic - description: Basic library - - port-types: - - id: flow - description: A port which transfers power flow - fields: - - name: flow - models: - - - id: generator - description: A basic generator model - parameters: - - name: cost - time-dependent: false - scenario-dependent: false - - name: p_max - time-dependent: false - scenario-dependent: false - variables: - - name: generation - lower-bound: 0 - upper-bound: p_max - ports: - - name: injection_port - type: flow - port-field-definitions: - - port: injection_port - field: flow - definition: generation - objective: expec(sum(cost * generation)) - - - id: node - description: A basic balancing node model - ports: - - name: injection_port - type: flow - binding-constraints: - - name: balance - expression: sum_connections(injection_port.flow) = 0 - - - id: demand - description: A basic fixed demand model - parameters: - - name: demand - time-dependent: true - scenario-dependent: true - ports: - - name: injection_port - type: flow - port-field-definitions: - - port: injection_port - field: flow - definition: -demand - - - id: stock_final_level_input - description: A stock model - parameters: - - name: p_max_in - time-dependent: false - scenario-dependent: false - - name: p_max_out - time-dependent: false - scenario-dependent: false - - name: capacity - time-dependent: false - scenario-dependent: false - - name: initial_level - time-dependent: false - scenario-dependent: false - - name: final_level - time-dependent: false - scenario-dependent: false - variables: - - name: r - lower-bound: 0 - upper-bound: capacity - - name: u1 - lower-bound: -p_max_in - upper-bound: p_max_out - - name: u2 - lower-bound: -p_max_in - upper-bound: p_max_out - ports: - - name: flow_s1 - type: flow - - name: flow_s2 - type: flow - port-field-definitions: - - port: flow_s1 - field: flow - definition: u1 - - port: flow_s2 - field: flow - definition: u2 - constraints: - - name: r_h - expression: r[t+1] = r[t] - u1[t] - u2[t] - - name: r0 - expression: r[0] = initial_level - - name: rt - expression: r[t] = initial_level \ No newline at end of file diff --git a/tests/models/test_gas_link_elec_compressor.py b/tests/models/test_gas_link_elec_compressor.py deleted file mode 100644 index 3c9f5a2c..00000000 --- a/tests/models/test_gas_link_elec_compressor.py +++ /dev/null @@ -1,163 +0,0 @@ -# Copyright (c) 2024, RTE (https://www.rte-france.com) -# -# See AUTHORS.txt -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# SPDX-License-Identifier: MPL-2.0 -# -# This file is part of the Antares project. - -import math -from pathlib import Path - -from andromede.simulation import OutputValues, TimeBlock, build_problem -from andromede.study import ( - ConstantData, - DataBase, - Network, - Node, - PortRef, - create_component, -) - - -def test_gas_link_elec_compressor(data_dir: Path, lib: Path, lib_sc: Path): - """ - Test of a pipeline with stock capacity - - for the following test we have two gaz production and two gaz demands, - One of each on each sides of a pipeline that has a storage capacity - gaz_prod_1 and gaz_demand_1 are on the input side of the pipeline - gaz_prod_2 and gaz_demand_2 are on the output side of the pipeline - we have the following values: - electricity production: - - p_max = 200 - - cost = 10 - gaz production: - - p_max = 300 - - cost = 10 - gaz demand: - - demand = 100 - electricity demand: - - demand = 10 - pipeline: - - f_from_max = 100 - - f_to_max = 50 - - capacity = 100 - - initial_level = 20 - electrolyzer: - - alpha = 0.5 - compressor from: - - alpha = 0.7 - compressor to: - - alpha = 0.7 - """ - gen_model = lib.models["generator"] - node_model = lib.models["node"] - demand_model = lib.models["demand"] - link_with_storage_model = lib_sc.models["link_with_storage"] - convertor_model = lib_sc.models["convertor"] - - gaz_node_1 = Node(model=node_model, id="g1") - gaz_node_2 = Node(model=node_model, id="g2") - elec_node = Node(model=node_model, id="e") - gaz_prod = create_component(model=gen_model, id="pg") - elec_prod = create_component(model=gen_model, id="pe") - gaz_demand = create_component(model=demand_model, id="dg") - elec_demand = create_component(model=demand_model, id="de") - pipeline = create_component(model=link_with_storage_model, id="pp") - electrolyzer = create_component(model=convertor_model, id="ez") - compressor_from = create_component(model=convertor_model, id="cpf") - compressor_to = create_component(model=convertor_model, id="cpt") - - database = DataBase() - database.add_data("dg", "demand", ConstantData(100)) - database.add_data("de", "demand", ConstantData(10)) - - database.add_data("pg", "p_max", ConstantData(300)) - database.add_data("pg", "cost", ConstantData(10)) - database.add_data("pe", "p_max", ConstantData(200)) - database.add_data("pe", "cost", ConstantData(10)) - - database.add_data("pp", "f_from_max", ConstantData(100)) - database.add_data("pp", "f_to_max", ConstantData(50)) - database.add_data("pp", "capacity", ConstantData(100)) - database.add_data("pp", "initial_level", ConstantData(20)) - - database.add_data("ez", "alpha", ConstantData(0.5)) - - database.add_data("cpf", "alpha", ConstantData(0.7)) - database.add_data("cpt", "alpha", ConstantData(0.7)) - - network = Network("test") - network.add_node(gaz_node_1) - network.add_node(gaz_node_2) - network.add_node(elec_node) - network.add_component(gaz_prod) - network.add_component(elec_prod) - network.add_component(gaz_demand) - network.add_component(elec_demand) - network.add_component(pipeline) - network.add_component(compressor_from) - network.add_component(compressor_to) - network.add_component(electrolyzer) - - network.connect( - PortRef(gaz_node_2, "injection_port"), PortRef(gaz_prod, "injection_port") - ) - network.connect(PortRef(gaz_node_2, "injection_port"), PortRef(pipeline, "flow_to")) - network.connect(PortRef(gaz_node_2, "injection_port"), PortRef(pipeline, "flow_to")) - - network.connect( - PortRef(gaz_node_1, "injection_port"), PortRef(gaz_demand, "injection_port") - ) - network.connect( - PortRef(gaz_node_1, "injection_port"), PortRef(pipeline, "flow_from") - ) - network.connect( - PortRef(gaz_node_1, "injection_port"), PortRef(electrolyzer, "output_port") - ) - - network.connect( - PortRef(pipeline, "flow_from_pos"), PortRef(compressor_from, "output_port") - ) - network.connect( - PortRef(pipeline, "flow_to_pos"), PortRef(compressor_to, "output_port") - ) - - network.connect( - PortRef(elec_node, "injection_port"), PortRef(elec_demand, "injection_port") - ) - network.connect( - PortRef(elec_prod, "injection_port"), PortRef(elec_node, "injection_port") - ) - network.connect( - PortRef(elec_node, "injection_port"), PortRef(electrolyzer, "input_port") - ) - network.connect( - PortRef(elec_node, "injection_port"), PortRef(compressor_from, "input_port") - ) - network.connect( - PortRef(elec_node, "injection_port"), PortRef(compressor_to, "input_port") - ) - - scenarios = 1 - problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) - status = problem.solver.Solve() - - output = OutputValues(problem) - r_value = output.component("pp").var("r").value - generation1 = output.component("pg").var("generation").value - generation2 = output.component("pe").var("generation").value - print("generation") - print(generation1) - print(generation2) - r = output.component("pp").var("r") - print(r) - assert status == problem.solver.OPTIMAL - assert math.isclose(problem.solver.Objective().Value(), 2100) - - assert math.isclose(r_value, 20) diff --git a/tests/models/test_gas_stock_2_elec_compressor.py b/tests/models/test_gas_stock_2_elec_compressor.py deleted file mode 100644 index 580743da..00000000 --- a/tests/models/test_gas_stock_2_elec_compressor.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright (c) 2024, RTE (https://www.rte-france.com) -# -# See AUTHORS.txt -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# SPDX-License-Identifier: MPL-2.0 -# -# This file is part of the Antares project. - -import math -from pathlib import Path - -import pytest - -from andromede.model.parsing import parse_yaml_library -from andromede.model.resolve_library import resolve_library -from andromede.simulation import OutputValues, TimeBlock, build_problem -from andromede.study.parsing import InputComponents, parse_yaml_components -from andromede.study.resolve_components import ( - build_data_base, - build_network, - consistency_check, - resolve_components_and_cnx, -) - - -@pytest.fixture(scope="session") -def lib_models(): - libs_path = Path(__file__).parent - lib_sc_file = libs_path / "models_for_test_gas_stock_2_elec_compressor.yml" - - with lib_sc_file.open() as f: - input_lib_sc = parse_yaml_library(f) - - lib_sc = resolve_library(input_lib_sc) - return lib_sc - - -def test_gas_stock_2_elec_compressor(data_dir: Path, lib_models: Path): - """ - Test of a gas stock and a repartition compressor in a system with multiple set of electricity production and demand - - for the following test we have two electrical node, each connected to a demand, a production, an electrolyzer and a compressor - each compressor are connected to a unique gas stock witch is connected to a gas node with a demand and a production - each electrolyzer are also connected to the gas node - each compressor are also connected to a unique repartition compressor - - we have the following values: - gaz production: - - p_max = 10 - - cost = 10 - elec production 1: - - p_max = 100 - - cost = 10 - elec production 2: - - p_max = 100 - - cost = 10 - gaz demand: - - demand = 10 - elec demand 1: - - demand = 50 - elec demand 2: - - demand = 100 - gas stock: - - p_max_in = 100 - - p_max_out = 50 - - capacity = 100 - - initial_level = 10 - - final_level = 90 - electrolyzer 1: - - alpha 0.5 - electrolyzer 2: - - alpha 0.5 - compressor 1: - - alpha 0.5 - compressor 2: - - alpha 0.5 - """ - components_path = Path(__file__) - compo_file = ( - components_path.parent / "components_for_test_gas_stock_2_elec_compressor.yml" - ) - input_component = parse_yaml_components(compo_file.open()) - - components_input = resolve_components_and_cnx(input_component, lib_models) - consistency_check(components_input.components, lib_models.models) - - database = build_data_base(input_component, None) - network = build_network(components_input) - - scenarios = 1 - problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) - status = problem.solver.Solve() - - output = OutputValues(problem) - r_value = output.component("GS").var("r").value - generation1 = output.component("PE1").var("generation").value - generation2 = output.component("PE2").var("generation").value - generation3 = output.component("PG").var("generation").value - print("generation") - print(generation1) - print(generation2) - print(generation3) - print(r_value) - assert status == problem.solver.OPTIMAL - assert math.isclose(problem.solver.Objective().Value(), 1600) diff --git a/tests/models/test_gas_stock_elec_compressor.py b/tests/models/test_gas_stock_elec_compressor.py deleted file mode 100644 index b341d8f8..00000000 --- a/tests/models/test_gas_stock_elec_compressor.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright (c) 2024, RTE (https://www.rte-france.com) -# -# See AUTHORS.txt -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# SPDX-License-Identifier: MPL-2.0 -# -# This file is part of the Antares project. - -import math -from pathlib import Path - -from andromede.simulation import OutputValues, TimeBlock, build_problem -from andromede.study import ( - ConstantData, - DataBase, - Network, - Node, - PortRef, - create_component, -) - - -def test_gas_stock_elec_compressor(data_dir: Path, lib: Path, lib_sc: Path): - """ - Test of a gas stock and compressor to pump the gas through - - for the following test we have a gas production and demand, a electricity production and demand - a gas demand on a side of the pipeline and a gas production on the other side - two electrolyzer converts electricity to gas and are connected to the pipeline and the electricity node - we have the following values: - gaz production 1: - - p_max = 200 - - cost = 30 - gaz production 2: - - p_max = 50 - - cost = 10 - gaz demand 1: - - demand = 100 - gaz demand 2: - - demand = 100 - pipeline: - - f_from_max = 50 - - f_to_max = 50 - - capacity = 100 - - initial_level = 20 - """ - gen_model = lib.models["generator"] - node_model = lib.models["node"] - demand_model = lib.models["demand"] - stock_final_model = lib_sc.models["stock_final_level"] - convertor_model = lib_sc.models["convertor"] - - gaz_node = Node(model=node_model, id="g") - elec_node = Node(model=node_model, id="e") - gaz_prod = create_component(model=gen_model, id="pg") - elec_prod = create_component(model=gen_model, id="pe") - gaz_demand = create_component(model=demand_model, id="dg") - elec_demand = create_component(model=demand_model, id="de") - gas_stock = create_component(model=stock_final_model, id="sg") - electrolyzer = create_component(model=convertor_model, id="ez") - compressor = create_component(model=convertor_model, id="cp") - - database = DataBase() - database.add_data("dg", "demand", ConstantData(100)) - database.add_data("de", "demand", ConstantData(10)) - - database.add_data("pg", "p_max", ConstantData(300)) - database.add_data("pg", "cost", ConstantData(10)) - database.add_data("pe", "p_max", ConstantData(200)) - database.add_data("pe", "cost", ConstantData(10)) - - database.add_data("sg", "p_max_in", ConstantData(100)) - database.add_data("sg", "p_max_out", ConstantData(50)) - database.add_data("sg", "capacity", ConstantData(100)) - database.add_data("sg", "initial_level", ConstantData(10)) - database.add_data("sg", "final_level", ConstantData(90)) - - database.add_data("ez", "alpha", ConstantData(0.5)) - - database.add_data("cp", "alpha", ConstantData(0.7)) - - network = Network("test") - network.add_node(gaz_node) - network.add_node(elec_node) - network.add_component(gaz_prod) - network.add_component(elec_prod) - network.add_component(gaz_demand) - network.add_component(elec_demand) - network.add_component(gas_stock) - network.add_component(compressor) - network.add_component(electrolyzer) - - network.connect( - PortRef(gaz_node, "injection_port"), PortRef(gaz_demand, "injection_port") - ) - network.connect(PortRef(gaz_node, "injection_port"), PortRef(gas_stock, "flow_s")) - network.connect( - PortRef(gaz_node, "injection_port"), PortRef(electrolyzer, "output_port") - ) - network.connect( - PortRef(gaz_node, "injection_port"), PortRef(gaz_prod, "injection_port") - ) - - network.connect(PortRef(gas_stock, "flow_c"), PortRef(compressor, "output_port")) - - network.connect( - PortRef(elec_node, "injection_port"), PortRef(elec_demand, "injection_port") - ) - network.connect( - PortRef(elec_prod, "injection_port"), PortRef(elec_node, "injection_port") - ) - network.connect( - PortRef(elec_node, "injection_port"), PortRef(electrolyzer, "input_port") - ) - network.connect( - PortRef(elec_node, "injection_port"), PortRef(compressor, "input_port") - ) - scenarios = 1 - problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) - status = problem.solver.Solve() - - output = OutputValues(problem) - r_value = output.component("sg").var("r").value - generation1 = output.component("pg").var("generation").value - generation2 = output.component("pe").var("generation").value - print("generation") - print(generation1) - print(generation2) - print(r_value) - assert status == problem.solver.OPTIMAL - assert math.isclose(problem.solver.Objective().Value(), 1100) - - assert math.isclose(r_value, 100) diff --git a/tests/models/test_stock_hydro_2_input.py b/tests/models/test_stock_hydro_2_input.py deleted file mode 100644 index 772804bd..00000000 --- a/tests/models/test_stock_hydro_2_input.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright (c) 2024, RTE (https://www.rte-france.com) -# -# See AUTHORS.txt -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# SPDX-License-Identifier: MPL-2.0 -# -# This file is part of the Antares project. - -import math -from pathlib import Path - -import pytest - -from andromede.model.parsing import parse_yaml_library -from andromede.model.resolve_library import resolve_library -from andromede.simulation import OutputValues, TimeBlock, build_problem -from andromede.study.parsing import InputComponents, parse_yaml_components -from andromede.study.resolve_components import ( - build_data_base, - build_network, - consistency_check, - resolve_components_and_cnx, -) - - -@pytest.fixture(scope="session") -def lib_models(): - libs_path = Path(__file__).parent - lib_sc_file = libs_path / "models_for_test_stock_hydro_2_input.yml" - - with lib_sc_file.open() as f: - input_lib_sc = parse_yaml_library(f) - - lib_sc = resolve_library(input_lib_sc) - return lib_sc - - -def test_stock_hydro_2_input(data_dir: Path, lib_models: Path): - """ - Test of a hydro stock connected to 2 nodes with a electrical production and demand each - """ - components_path = Path(__file__) - compo_file = components_path.parent / "components_for_test_stock_hydro_2_input.yml" - input_component = parse_yaml_components(compo_file.open()) - - components_input = resolve_components_and_cnx(input_component, lib_models) - consistency_check(components_input.components, lib_models.models) - - database = build_data_base(input_component, None) - network = build_network(components_input) - - scenarios = 1 - problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) - status = problem.solver.Solve() - - output = OutputValues(problem) - r_value = output.component("HS").var("r").value - generation1 = output.component("PE1").var("generation").value - generation2 = output.component("PE2").var("generation").value - print("generation") - print(generation1) - print(generation2) - print(r_value) - assert status == problem.solver.OPTIMAL - assert math.isclose(problem.solver.Objective().Value(), 1500)