diff --git a/src/rtctools_heat_network/electricity_physics_mixin.py b/src/rtctools_heat_network/electricity_physics_mixin.py index a002170c..a3b3a954 100644 --- a/src/rtctools_heat_network/electricity_physics_mixin.py +++ b/src/rtctools_heat_network/electricity_physics_mixin.py @@ -481,6 +481,11 @@ def __electrolyzer_path_constaint(self, ensemble_member): power_consumed_vect = ca.repmat(power_consumed, len(linear_coef_a)) gas_mass_flow_out_vect = ca.repmat(gas_mass_flow_out, len(linear_coef_a)) gass_mass_out_linearized_vect = linear_coef_a * power_consumed_vect + linear_coef_b + var_name = self.__asset_is_switched_on_map[asset] + asset_is_switched_on = self.state(var_name) + + gass_mass_out_max = linear_coef_a[-1] * self.bounds()[f"{asset}.Power_consumed"][1] + linear_coef_b[-1] + big_m = gass_mass_out_max * 2 nominal = ( self.variable_nominal(f"{asset}.Gas_mass_flow_out") * min(linear_coef_a) @@ -489,7 +494,7 @@ def __electrolyzer_path_constaint(self, ensemble_member): constraints.extend( [ ( - (gas_mass_flow_out_vect - gass_mass_out_linearized_vect) / nominal, + (gas_mass_flow_out_vect - gass_mass_out_linearized_vect - (1 - asset_is_switched_on) * big_m) / nominal, -np.inf, 0.0, ), @@ -498,8 +503,7 @@ def __electrolyzer_path_constaint(self, ensemble_member): # Add constraints to ensure the electrolyzer is switched off when it reaches a power # input below the minimum operating value - var_name = self.__asset_is_switched_on_map[asset] - asset_is_switched_on = self.state(var_name) + big_m = self.bounds()[f"{asset}.ElectricityIn.Power"][1] * 1.5 * 10.0 constraints.append( diff --git a/src/rtctools_heat_network/esdl/esdl_heat_model.py b/src/rtctools_heat_network/esdl/esdl_heat_model.py index 1ad52e11..875d80be 100644 --- a/src/rtctools_heat_network/esdl/esdl_heat_model.py +++ b/src/rtctools_heat_network/esdl/esdl_heat_model.py @@ -101,7 +101,7 @@ def get_density(self, asset_name, carrier): "T", 273.15 + temperature, "P", - carrier.pressure, + carrier.pressure*1e5, NetworkSettings.NETWORK_COMPOSITION_GAS, ) elif NetworkSettings.NETWORK_TYPE_HYDROGEN in carrier.name: @@ -110,7 +110,7 @@ def get_density(self, asset_name, carrier): "T", 273.15 + temperature, "P", - carrier.pressure, + carrier.pressure*1e5, str(NetworkSettings.NETWORK_TYPE_HYDROGEN).upper(), ) else: @@ -1216,7 +1216,7 @@ def convert_gas_demand(self, asset: Asset) -> Tuple[Type[GasDemand], MODIFIERS]: modifiers = dict( Q_nominal=self._get_connected_q_nominal(asset), id_mapping_carrier=id_mapping, - Gas_demand_mass_flow=dict(min=0., max=asset.attributes["power"]*hydrogen_specfic_energy), + # Gas_demand_mass_flow=dict(min=0., max=asset.attributes["power"]*hydrogen_specfic_energy), density=self.get_density(asset.name, asset.in_ports[0].carrier), GasIn=dict( Q=dict( @@ -1344,7 +1344,7 @@ def convert_gas_tank_storage(self, asset: Asset) -> Tuple[Type[GasTankStorage], Q_nominal=self._get_connected_q_nominal(asset), density=self.get_density(asset.name, asset.in_ports[0].carrier), volume=asset.attributes["workingVolume"], - Gas_tank_flow=dict(min=-hydrogen_specific_energy*asset.attributes["maxDischargeRate"], max=hydrogen_specific_energy*asset.attributes["maxChargeRate"]), + # Gas_tank_flow=dict(min=-hydrogen_specific_energy*asset.attributes["maxDischargeRate"], max=hydrogen_specific_energy*asset.attributes["maxChargeRate"]), # TODO: Fix -> Gas network is currenlty non-limiting, mass flow is decoupled from the # volumetric flow # Gas_tank_flow=dict( diff --git a/tests/models/emerge/model/emerge.esdl b/tests/models/emerge/model/emerge.esdl index cb71caf7..c3875acf 100644 --- a/tests/models/emerge/model/emerge.esdl +++ b/tests/models/emerge/model/emerge.esdl @@ -2,8 +2,8 @@ - - + + @@ -70,7 +70,7 @@ - + @@ -78,7 +78,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/tests/models/emerge/src/example.py b/tests/models/emerge/src/example.py index 10742422..24af8036 100644 --- a/tests/models/emerge/src/example.py +++ b/tests/models/emerge/src/example.py @@ -5,6 +5,7 @@ from rtctools.optimization.collocated_integrated_optimization_problem import ( CollocatedIntegratedOptimizationProblem, ) +from rtctools.optimization.goal_programming_mixin import GoalProgrammingMixin from rtctools.optimization.goal_programming_mixin_base import Goal from rtctools.optimization.linearized_order_goal_programming_mixin import ( LinearizedOrderGoalProgrammingMixin, @@ -73,9 +74,9 @@ def __init__(self, asset_name: str): ---------- source : string of the source name that is going to be minimized """ - self.target_max = 0.0 - self.function_range = (0.0, 1.0e8) - self.function_nominal = 1.0e7 + # self.target_max = 0 + # self.function_range = (-1.0e9, 1.0e9) + # self.function_nominal = 1.0e7 self.asset_name = asset_name @@ -94,6 +95,7 @@ def function( ------- The negative hydrogen production state of the optimization problem. """ + #TODO: not yet scaled return -optimization_problem.extra_variable(f"{self.asset_name}__revenue", ensemble_member) class MinCost(Goal): @@ -104,7 +106,7 @@ class MinCost(Goal): def __init__(self, asset_name: str): self.target_max = 0.0 - self.function_range = (0.0, 1.0e8) + self.function_range = (0.0, 1.0e9) self.function_nominal = 1.0e7 self.asset_name = asset_name @@ -116,7 +118,7 @@ def function(self, optimization_problem: CollocatedIntegratedOptimizationProblem class EmergeTest( ESDLAdditionalVarsMixin, - PhysicsMixin, + TechnoEconomicMixin, LinearizedOrderGoalProgrammingMixin, SinglePassGoalProgrammingMixin, ESDLMixin, @@ -135,43 +137,56 @@ def __init__(self, *args, **kwargs): self.gas_network_settings["head_loss_option"] = HeadLossOption.NO_HEADLOSS - def path_goals(self): - """ - This function adds the minimization goal for minimizing the heat production. + # def path_goals(self): + # """ + # This function adds the minimization goal for minimizing the heat production. + # + # Returns + # ------- + # The appended list of goals + # """ + # goals = super().path_goals().copy() + # + # for s in self.energy_system_components["electrolyzer"]: # ["name_electrolyzer_1", "name_electrolyzer_2", ...] + # goals.append(MaxHydrogenProduction(s)) + # + # return goals + + def goals(self): + + goals = super().goals().copy() + + for asset_name in self.energy_system_components["electricity_demand"]: + goals.append(MaxRevenue(asset_name)) + # goals.append(MinCost(asset_name)) + + for asset_name in self.energy_system_components["gas_demand"]: + goals.append(MaxRevenue(asset_name)) + # goals.append(MinCost(asset_name)) + + # for asset_name in [*self.energy_system_components.get("electricity_source", []), + # *self.energy_system_components.get("gas_tank_storage", []), + # #TODO: battery + # *self.energy_system_components.get("electrolyzer", []), + # *self.energy_system_components.get("heat_pump_elec", [])]: + # goals.append(MinCost(asset_name)) - Returns - ------- - The appended list of goals - """ - goals = super().path_goals().copy() - for s in self.energy_system_components["electrolyzer"]: # ["name_electrolyzer_1", "name_electrolyzer_2", ...] - goals.append(MaxHydrogenProduction(s)) return goals - # def goals(self): - # - # goals = super().goals().copy() - # - # for asset_name in self.energy_system_components["electricity_demand"]: - # goals.append(MaxRevenue(asset_name)) - # # goals.append(MinCost(asset_name)) - # - # for asset_name in self.energy_system_components["gas_demand"]: - # goals.append(MaxRevenue(asset_name)) - # # goals.append(MinCost(asset_name)) - # - # # for asset_name in [*self.energy_system_components.get("electricity_source", []), - # # *self.energy_system_components.get("gas_tank_storage", []), - # # #TODO: battery - # # *self.energy_system_components.get("electrolyzer", []), - # # *self.energy_system_components.get("heat_pump_elec", [])]: - # # goals.append(MinCost(asset_name)) - # # - # - # - # return goals + def constraints(self, ensemble_member): + constraints = super().constraints(ensemble_member) + + for gs in self.energy_system_components.get("gas_tank_storage", []): + canonical, sign = self.alias_relation.canonical_signed(f"{gs}.Stored_gas_mass") + storage_t0 = sign * self.state_vector(canonical, ensemble_member)[0] + constraints.append((storage_t0, 0.0, 0.0)) + canonical, sign = self.alias_relation.canonical_signed(f"{gs}.Gas_tank_flow") + gas_flow_t0 = sign * self.state_vector(canonical, ensemble_member)[0] + constraints.append((gas_flow_t0, 0.0, 0.0)) + + return constraints def solver_options(self): """ @@ -185,8 +200,8 @@ def solver_options(self): options["solver"] = "gurobi" return options - # def times(self, variable=None): - # return super().times(variable)[:25] + def times(self, variable=None): + return super().times(variable)[:25] def energy_system_options(self): """ @@ -208,10 +223,10 @@ def energy_system_options(self): if __name__ == "__main__": elect = run_optimization_problem( EmergeTest, - esdl_file_name="h2.esdl", + esdl_file_name="emerge.esdl", esdl_parser=ESDLFileParser, profile_reader=ProfileReaderFromFile, - input_timeseries_file="timeseries_h2.csv", + input_timeseries_file="timeseries.csv", ) results = elect.extract_results() a = 1