Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
b01e5c9
added discrete path variables pycml object variable creation
FJanssen-TNO Feb 17, 2025
dfa65f4
first discrete variables working
FJanssen-TNO Feb 17, 2025
f1a4d79
added electrolyzer is switched on as discrete variable to the electro…
FJanssen-TNO Feb 28, 2025
ad20956
added creation of discrete variable linear_line_active of electrolyze…
FJanssen-TNO Feb 28, 2025
ff637e7
flow_dir variables as discrete variables in pycml, however requires a…
FJanssen-TNO Feb 28, 2025
2988655
heat pipe flow dir var in pycml, requiring heatphysics updates
FJanssen-TNO Feb 28, 2025
21877d9
updated the discrete variables for flow direction
FJanssen-TNO Feb 28, 2025
96ef0d2
fixing tests
FJanssen-TNO Mar 4, 2025
7bbbe05
style
FJanssen-TNO Mar 4, 2025
6ce95f1
added discrete variable is_disconnected
FJanssen-TNO Mar 5, 2025
5de903b
moved control_var flow direction variable to control_valve.py
FJanssen-TNO Mar 5, 2025
6d0897e
updates to create effective_power_discharging variable in electricity…
FJanssen-TNO Mar 5, 2025
3abf28f
cleanup
FJanssen-TNO Mar 5, 2025
437359e
moved variables of gas_physics_mixin to the pycml objects
FJanssen-TNO Mar 5, 2025
f64840e
bugfix is_disconnected heat_pipe
FJanssen-TNO Mar 5, 2025
6949b88
bugfixes in tests
FJanssen-TNO Mar 5, 2025
f7026b7
cleanup
FJanssen-TNO Mar 5, 2025
7a0aabc
cleanup
FJanssen-TNO Mar 5, 2025
70dbd92
cleanup
FJanssen-TNO Mar 5, 2025
fc85b64
Merge branch 'main' into discrete_variables
FJanssen-TNO Mar 5, 2025
5af9e6a
allowing formatted strings for variable documentation
FJanssen-TNO Mar 6, 2025
ee3167e
allowing formatted strings for variable documentation of discrete var…
FJanssen-TNO Mar 6, 2025
3e78545
update on style
FJanssen-TNO Mar 21, 2025
bd3362e
temperary code to find issue
KobusVanRooyen Apr 1, 2025
b15ac07
wip
KobusVanRooyen Apr 1, 2025
19124e9
wip
KobusVanRooyen Apr 1, 2025
411d8b1
wip
KobusVanRooyen Apr 1, 2025
3ee5f37
wip
KobusVanRooyen Apr 1, 2025
5eb7e79
wip
KobusVanRooyen Apr 1, 2025
86c6858
wip
KobusVanRooyen Apr 1, 2025
3047b3c
wip
KobusVanRooyen Apr 1, 2025
e255d0c
wip
KobusVanRooyen Apr 1, 2025
e4559e1
wip
KobusVanRooyen Apr 1, 2025
d370395
wip
KobusVanRooyen Apr 1, 2025
5598787
wip
KobusVanRooyen Apr 1, 2025
0e33567
wip
KobusVanRooyen Apr 1, 2025
ef04cf6
wip
KobusVanRooyen Apr 1, 2025
49cecdf
wip
KobusVanRooyen Apr 1, 2025
00a26f7
wip
KobusVanRooyen Apr 1, 2025
a43cac7
wip
KobusVanRooyen Apr 1, 2025
8e8491a
wip
KobusVanRooyen Apr 1, 2025
ef91664
wip
KobusVanRooyen Apr 1, 2025
8eceed3
try again
KobusVanRooyen Apr 1, 2025
717a89b
arrrr
KobusVanRooyen Apr 1, 2025
1453b72
wip
KobusVanRooyen Apr 1, 2025
6a88b19
wip
KobusVanRooyen Apr 1, 2025
2548829
wip
KobusVanRooyen Apr 1, 2025
792b75e
wip
KobusVanRooyen Apr 1, 2025
0987a92
wip
KobusVanRooyen Apr 1, 2025
38cd7ee
wip
KobusVanRooyen Apr 3, 2025
0abad73
wip
KobusVanRooyen Apr 3, 2025
14b516d
wip
KobusVanRooyen Apr 3, 2025
b8c826d
wip
KobusVanRooyen Apr 3, 2025
dd05cfc
wip
KobusVanRooyen Apr 3, 2025
af492f1
wip
KobusVanRooyen Apr 3, 2025
2ebcd09
wip
KobusVanRooyen Apr 3, 2025
b863bf9
wip
KobusVanRooyen Apr 3, 2025
1fecc04
wip
KobusVanRooyen Apr 3, 2025
391ac68
wip
KobusVanRooyen Apr 3, 2025
1647104
no dh optim
KobusVanRooyen Apr 3, 2025
f41cd61
head loss back
KobusVanRooyen Apr 4, 2025
9eb3b4b
wip
KobusVanRooyen Apr 4, 2025
562b112
check specific pipe
KobusVanRooyen Apr 7, 2025
82efd32
specific pipe
KobusVanRooyen Apr 7, 2025
9759bc7
specific pipe
KobusVanRooyen Apr 7, 2025
7e15d07
wip
KobusVanRooyen Apr 7, 2025
5eefdf7
wip
KobusVanRooyen Apr 7, 2025
5c1dd0c
wip
KobusVanRooyen Apr 7, 2025
e96ab52
last try
KobusVanRooyen Apr 7, 2025
dbf285f
ffff
KobusVanRooyen Apr 7, 2025
dc3320b
wip
KobusVanRooyen Apr 7, 2025
b77e5bf
wip
KobusVanRooyen Apr 7, 2025
55d0db6
cbc
KobusVanRooyen Apr 8, 2025
11a35a2
revert repo check code
KobusVanRooyen Apr 8, 2025
8729207
high and big_m
KobusVanRooyen Apr 8, 2025
9690042
highs code comment issue
KobusVanRooyen Apr 8, 2025
3c3f6d9
big_m
KobusVanRooyen Apr 8, 2025
6b0b605
cbc
KobusVanRooyen Apr 8, 2025
0b4a910
high big_m*=10
KobusVanRooyen Apr 8, 2025
f2c979d
revert changes and add comment
KobusVanRooyen Apr 8, 2025
edd4abd
revert changes
KobusVanRooyen Apr 8, 2025
f171c4c
remove big_m tweak
KobusVanRooyen Apr 8, 2025
31753ae
potential fix for flowdir en -0.0 Q to heatflow of 0.0
FJanssen-TNO Apr 15, 2025
72cbc76
fix style and cleanup after review
FJanssen-TNO May 13, 2025
baf9a07
fix test, eventhough locally it does run with any failures
FJanssen-TNO May 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion examples/municipality/src/run_municipality.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from mesido.esdl.esdl_parser import ESDLFileParser
from mesido.workflows import EndScenarioSizingStaged, run_end_scenario_sizing


if __name__ == "__main__":
import time

Expand Down
1 change: 1 addition & 0 deletions examples/pipe_diameter_sizing/src/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ def solver_options(self):
self._qpsol = CachingQPSol()
options["casadi_solver"] = self._qpsol
options["solver"] = "highs"

return options


Expand Down
3 changes: 3 additions & 0 deletions src/mesido/component_type_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ def pre(self):
other_pipe = pipes_map[other_pipe_port]
if f"{other_pipe}.Q" not in alias_relation.canonical_variables:
alias_relation.add(f"{p}.Q", f"{sign_prefix}{other_pipe}.Q")
if self.has_related_pipe(p):
cold_pipe = self.hot_to_cold_pipe(p)
alias_relation.add(f"{p}.__flow_direct_var", f"{cold_pipe}.__flow_direct_var")

node_to_node_logical_link_map = {}

Expand Down
54 changes: 10 additions & 44 deletions src/mesido/electricity_physics_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,12 @@ def __init__(self, *args, **kwargs):

# Variable for when in time an asset switched on due to meeting a requirement
self.__asset_is_switched_on_map = {}
self.__asset_is_switched_on_var = {}
self.__asset_is_switched_on_bounds = {}

self.__electricity_producer_upper_bounds = {}

self._electricity_cable_topo_cable_class_map = {}

# Boolean path-variable for the charging of storage assets
self.__storage_charging_var = {}
self.__storage_charging_bounds = {}
self.__storage_charging_map = {}

self.__set_point_var = {}
Expand All @@ -74,11 +70,8 @@ def __init__(self, *args, **kwargs):

# Boolean path-variable for the equality constraint of the electrolyzer
self.__electrolyzer_is_active_linear_segment_map = {}
self.__electrolyzer_is_active_linear_segment_var = {}
self.__electrolyzer_is_active_linear_segment_bounds = {}
self.__electricity_storage_discharge_var = {}

self.__electricity_storage_discharge_bounds = {}
self.__electricity_storage_discharge_nominals = {}
self.__electricity_storage_discharge_map = {}

# Map for setting node nominals in case of logical links.
Expand Down Expand Up @@ -123,10 +116,7 @@ def pre(self):
for asset in [
*self.energy_system_components.get("electrolyzer", []),
]:
var_name = f"{asset}__asset_is_switched_on"
self.__asset_is_switched_on_map[asset] = var_name
self.__asset_is_switched_on_var[var_name] = ca.MX.sym(var_name)
self.__asset_is_switched_on_bounds[var_name] = (0.0, 1.0)
self.__asset_is_switched_on_map[asset] = f"{asset}.__asset_is_switched_on"

if options["electrolyzer_efficiency"] == ElectrolyzerOption.LINEARIZED_THREE_LINES_EQUALITY:
for asset in [
Expand All @@ -135,32 +125,23 @@ def pre(self):
self.__electrolyzer_is_active_linear_segment_map[asset] = {}
n_lines = 3
for n_line in range(n_lines):
var_name = f"{asset}__line_{n_line}_active"

var_name = f"{asset}.__line_{n_line}_active"
self.__electrolyzer_is_active_linear_segment_map[asset][
f"line_{n_line}"
] = var_name
self.__electrolyzer_is_active_linear_segment_var[var_name] = ca.MX.sym(var_name)
self.__electrolyzer_is_active_linear_segment_bounds[var_name] = (0.0, 1.0)

for asset in [*self.energy_system_components.get("electricity_storage", [])]:
var_name = f"{asset}__is_charging"
var_name = f"{asset}.__is_charging"
self.__storage_charging_map[asset] = var_name
self.__storage_charging_var[var_name] = ca.MX.sym(var_name)
self.__storage_charging_bounds[var_name] = (0.0, 1.0)

if options["electricity_storage_discharge_variables"]:
bound_storage = -self.bounds()[f"{asset}.Effective_power_charging"][0]
if isinstance(bound_storage, Timeseries):
bound_storage = copy.deepcopy(bound_storage)
bound_storage.values[bound_storage.values < 0] = 0.0
var_name = f"{asset}__effective_power_discharging"
var_name = f"{asset}.__effective_power_discharging"
self.__electricity_storage_discharge_map[asset] = var_name
self.__electricity_storage_discharge_var[var_name] = ca.MX.sym(var_name)
self.__electricity_storage_discharge_bounds[var_name] = (0, bound_storage)
self.__electricity_storage_discharge_nominals[var_name] = self.variable_nominal(
f"{asset}.Effective_power_charging"
)

for asset in [*self.energy_system_components.get("electricity_source", [])]:
if isinstance(self.bounds()[f"{asset}.Electricity_source"][1], Timeseries):
Expand Down Expand Up @@ -223,11 +204,7 @@ def path_variables(self):
"""
variables = super().path_variables.copy()

variables.extend(self.__asset_is_switched_on_var.values())
variables.extend(self.__storage_charging_var.values())
variables.extend(self.__set_point_var.values())
variables.extend(self.__electrolyzer_is_active_linear_segment_var.values())
variables.extend(self.__electricity_storage_discharge_var.values())

return variables

Expand All @@ -236,22 +213,14 @@ def variable_is_discrete(self, variable):
All variables that only can take integer values should be added to this function.
"""

if variable in self.__electrolyzer_is_active_linear_segment_var:
return True
if variable in self.__asset_is_switched_on_var:
return True
if variable in self.__storage_charging_var:
return True
else:
return super().variable_is_discrete(variable)
return super().variable_is_discrete(variable)

def variable_nominal(self, variable):
"""
In this function we add all the nominals for the variables defined/added in the HeatMixin.
"""
if variable in self.__electricity_storage_discharge_nominals:
return self.__electricity_storage_discharge_nominals[variable]
elif variable in self.__bus_variable_nominal:

if variable in self.__bus_variable_nominal:
return self.__bus_variable_nominal[variable]
else:
return super().variable_nominal(variable)
Expand All @@ -263,9 +232,6 @@ def bounds(self):
"""
bounds = super().bounds()

bounds.update(self.__electrolyzer_is_active_linear_segment_bounds)
bounds.update(self.__asset_is_switched_on_bounds)
bounds.update(self.__storage_charging_bounds)
bounds.update(self.__electricity_producer_upper_bounds)
bounds.update(self.__set_point_bounds)
bounds.update(self.__electricity_storage_discharge_bounds)
Expand Down Expand Up @@ -568,7 +534,7 @@ def __electricity_storage_path_constraints(self, ensemble_member):

# is_charging is 1 if charging and powerin>0
big_m = 2 * max(np.abs(self.bounds()[f"{asset}.ElectricityIn.Power"]))
is_charging = self.state(f"{asset}__is_charging")
is_charging = self.state(f"{asset}.__is_charging")
constraints.append(((power_in + (1 - is_charging) * big_m) / power_nom, 0.0, np.inf))
constraints.append(((power_in - is_charging * big_m) / power_nom, -np.inf, 0.0))

Expand Down Expand Up @@ -647,7 +613,7 @@ def __electricity_storage_discharge_var_path_constraints(
for storage in self.energy_system_components.get("electricity_storage", []):
storage_eff_power_charge_var = self.state(f"{storage}.Effective_power_charging")
discharge_var_name = self.__electricity_storage_discharge_map[storage]
storage_discharge_var = self.__electricity_storage_discharge_var[discharge_var_name]
storage_discharge_var = self.state(discharge_var_name)
nominal = self.variable_nominal(discharge_var_name)

# P_effective_charge represents both charging and discharing based on the sign.
Expand Down
8 changes: 8 additions & 0 deletions src/mesido/esdl/esdl_heat_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def __init__(
cp=4200.0,
min_fraction_tank_volume=0.05,
v_max_gas=15.0,
energy_system_options=None,
**kwargs,
):
super().__init__(*args, **kwargs)
Expand All @@ -86,6 +87,7 @@ def __init__(
self.cp = cp
self.v_max_gas = v_max_gas
self.min_fraction_tank_volume = min_fraction_tank_volume
self.energy_system_options = dict() if not energy_system_options else energy_system_options
if "primary_port_name_convention" in kwargs.keys():
self.primary_port_name_convention = kwargs["primary_port_name_convention"]
if "secondary_port_name_convention" in kwargs.keys():
Expand Down Expand Up @@ -1721,6 +1723,9 @@ def convert_electricity_storage(
min_voltage=v_min,
max_capacity=max_capacity,
Stored_electricity=dict(min=0.0, max=max_capacity),
discharge_var=self.energy_system_options.get(
"electricity_storage_discharge_variables", False
),
ElectricityIn=dict(
V=dict(min=v_min, nominal=v_min),
I=dict(min=-i_max, max=i_max, nominal=i_nom),
Expand Down Expand Up @@ -2155,6 +2160,8 @@ def equations(x):
Q_nominal=q_nominal,
density=density,
efficiency=eff_max,
include_asset_is_switched_on=self.energy_system_options["include_asset_is_switched_on"],
electrolyzer_efficiency_option=self.energy_system_options["electrolyzer_efficiency"],
GasOut=dict(
Q=dict(
min=0.0,
Expand Down Expand Up @@ -2222,6 +2229,7 @@ def convert_gas_tank_storage(self, asset: Asset) -> Tuple[Type[GasTankStorage],
Q_nominal=q_nominal,
density=density,
volume=asset.attributes["workingVolume"],
discharge_var=self.energy_system_options.get("gas_storage_discharge_variables", False),
# 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
Expand Down
10 changes: 8 additions & 2 deletions src/mesido/esdl/esdl_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,15 @@ def __init__(self, *args, **kwargs) -> None:
# Although we work with the names, the FEWS import data uses the component IDs
self.__timeseries_id_map = {a.id: a.name for a in assets.values()}
name_to_id_map = {a.name: a.id for a in assets.values()}

if isinstance(self, PhysicsMixin):
self.__model = ESDLHeatModel(assets, name_to_id_map, **self.esdl_heat_model_options())
self.__model = ESDLHeatModel(
assets,
name_to_id_map,
**dict(
energy_system_options=self.energy_system_options(),
**self.esdl_heat_model_options(),
),
)
else:
assert isinstance(self, QTHMixin)

Expand Down
31 changes: 6 additions & 25 deletions src/mesido/gas_physics_mixin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import copy
import logging

import casadi as ca

from mesido.base_component_type_mixin import BaseComponentTypeMixin
from mesido.head_loss_class import HeadLossClass, HeadLossOption
from mesido.network_common import NetworkSettings
Expand Down Expand Up @@ -113,7 +111,6 @@ def __init__(self, *args, **kwargs):
self._gn_pipe_to_head_loss_map = {}

# Boolean path-variable for the direction of the flow, inport to outport is positive flow.
self.__gas_flow_direct_var = {}
self.__gas_flow_direct_bounds = {}
self._gas_pipe_to_flow_direct_map = {}

Expand All @@ -124,7 +121,6 @@ def __init__(self, *args, **kwargs):
# self._gas_pipe_disconnect_map = {}

# Boolean variables for the linear line segment options per pipe.
# TDOD: change name to _gas_pipe_...
self.__gas_pipe_linear_line_segment_var = {} # value 0/1: line segment - not active/active
self.__gas_pipe_linear_line_segment_var_bounds = {}
self._gas_pipe_linear_line_segment_map = {}
Expand All @@ -133,13 +129,9 @@ def __init__(self, *args, **kwargs):

self._gas_pipe_topo_pipe_class_map = {}

# self.__gas_pipe_disconnect_var = {}
# self.__gas_pipe_disconnect_var_bounds = {}
self._gas_pipe_disconnect_map = {}

self.__gas_storage_discharge_var = {}
self.__gas_storage_discharge_bounds = {}
self.__gas_storage_discharge_nominals = {}
self.__gas_storage_discharge_map = {}

# Map for setting port variable nominals in the case they were not set during the model
Expand Down Expand Up @@ -228,10 +220,10 @@ def _get_min_bound(bound):
] = initialized_vars[10][pipe_linear_line_segment_var_name]

# Integer variables
flow_dir_var = f"{pipe_name}__gas_flow_direct_var"
flow_dir_var = f"{pipe_name}.__gas_flow_direct_var"

self._gas_pipe_to_flow_direct_map[pipe_name] = flow_dir_var
self.__gas_flow_direct_var[flow_dir_var] = ca.MX.sym(flow_dir_var)
# self.__gas_flow_direct_var[flow_dir_var] = ca.MX.sym(flow_dir_var)

# Fix the directions that are already implied by the bounds on milp
# Nonnegative milp implies that flow direction Boolean is equal to one.
Expand Down Expand Up @@ -260,17 +252,14 @@ def _get_min_bound(bound):

if options["gas_storage_discharge_variables"]:
for storage in self.energy_system_components.get("gas_tank_storage", []):
# updating bounds
bound_storage_q = -self.bounds()[f"{storage}.GasIn.Q"][0]
if isinstance(bound_storage_q, Timeseries):
bound_storage_q = copy.deepcopy(bound_storage_q)
bound_storage_q.values[bound_storage_q.values < 0] = 0.0
var_name = f"{storage}__Q_discharge"
var_name = f"{storage}.__Q_discharge"
self.__gas_storage_discharge_map[storage] = var_name
self.__gas_storage_discharge_var[var_name] = ca.MX.sym(var_name)
self.__gas_storage_discharge_bounds[var_name] = (0, bound_storage_q)
self.__gas_storage_discharge_nominals[var_name] = self.variable_nominal(
f"{storage}.GasIn.Q"
)

# Setting the node nominals using the connected assets.
for node, connected_assets in self.energy_system_topology.gas_nodes.items():
Expand Down Expand Up @@ -340,21 +329,15 @@ def path_variables(self):
"""
variables = super().path_variables.copy()
variables.extend(self.__gas_pipe_head_loss_var.values())
variables.extend(self.__gas_flow_direct_var.values())
# variables.extend(self.__gas_pipe_disconnect_var.values()) # still to be implemented
variables.extend(self.__gas_pipe_linear_line_segment_var.values())
variables.extend(self.__gas_storage_discharge_var.values())

return variables

def variable_is_discrete(self, variable):
"""
All variables that only can take integer values should be added to this function.
"""
if (
variable in self.__gas_flow_direct_var
or variable in self.__gas_pipe_linear_line_segment_var
):
if variable in self.__gas_pipe_linear_line_segment_var:
return True
else:
return super().variable_is_discrete(variable)
Expand All @@ -366,8 +349,6 @@ def variable_nominal(self, variable):

if variable in self.__gas_pipe_head_loss_nominals:
return self.__gas_pipe_head_loss_nominals[variable]
elif variable in self.__gas_storage_discharge_nominals:
return self.__gas_storage_discharge_nominals[variable]
elif variable in self.__gas_node_variable_nominal:
return self.__gas_node_variable_nominal[variable]
else:
Expand Down Expand Up @@ -606,7 +587,7 @@ def __gas_storage_discharge_path_constraints(self, ensemble_member):
if options["gas_storage_discharge_variables"]:
for storage in self.energy_system_components.get("gas_tank_storage", []):
storage_charge_var = self.state(f"{storage}.GasIn.Q")
storage_discharge_var_name = f"{storage}__Q_discharge"
storage_discharge_var_name = f"{storage}.__Q_discharge"
storage_discharge_var = self.state(storage_discharge_var_name)
nominal = self.variable_nominal(storage_discharge_var_name)

Expand Down
Loading
Loading