From b856ac5131c4c4940252a82a4369655b5bab354c Mon Sep 17 00:00:00 2001 From: Mike Turner Date: Tue, 25 Aug 2026 09:14:26 +0800 Subject: [PATCH 1/3] Allow dangling ports on EVs --- src/echo/models/base.py | 9 ++++++--- src/echo/models/electrical.py | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/echo/models/base.py b/src/echo/models/base.py index 0f15b0cb..2f1e77d1 100644 --- a/src/echo/models/base.py +++ b/src/echo/models/base.py @@ -68,6 +68,8 @@ class Port(BaseModel): slack: bool = False objective: en.numeric_expr.NumericExpression | float = 0 # this will eventually be a pyomo expression + allow_dangling_port: bool = False + # Validators for import/export constraint values import_con_sign = validator("import_constraint_value", allow_reuse=True)(import_cons_check) export_con_sign = validator("export_constraint_value", allow_reuse=True)(export_cons_check) @@ -1435,11 +1437,12 @@ def print_port_names(self) -> None: for pn, p in n.ports.items(): print(pn, ", ", p.port_name) - def get_port_names_from_nodes(self) -> set[str]: + def get_port_names_from_nodes(self, skip_dangling_ports: bool = False) -> set[str]: output = set() for n in self.node_obj.values(): for p in n.ports.values(): - output.add(p.port_name) + if skip_dangling_ports and not p.allow_dangling_port: + output.add(p.port_name) return output def get_port_names_from_edges(self) -> set[str]: @@ -1454,7 +1457,7 @@ def verify_graph(self) -> None: are unconnected ports""" validate(nx.is_connected(self.convert_to_nx()) is True, "Graph is not connected.") # Check graph for ports that are not connected - ports_on_edges = self.get_port_names_from_nodes() + ports_on_edges = self.get_port_names_from_nodes(skip_dangling_ports=True) ports_on_nodes = self.get_port_names_from_edges() diff = ports_on_edges - ports_on_nodes # check overlap if len(diff) != 0: diff --git a/src/echo/models/electrical.py b/src/echo/models/electrical.py index a9cbc584..bc318a3c 100644 --- a/src/echo/models/electrical.py +++ b/src/echo/models/electrical.py @@ -272,7 +272,8 @@ def _create_usage_port(self) -> None: """ # Create the usage port and assign it to the EV object - self.ports["usage"] = ElectricalDemand() + # Usage ports are not connected to other ports via edges so we set `allow_dangling_port` to True + self.ports["usage"] = ElectricalDemand(allow_dangling_port=True) # TODO: Set demand here if set_stateful_attrs_at_init is True @@ -303,6 +304,7 @@ def _create_vehicle_port(self) -> None: enable_trip_slack=self.enable_trip_slack, soc_conserv=self.soc_conserv, soc_conserv_cost=self.soc_conserv_cost, + allow_dangling_port=True, # vehicle port is not connect to another port ) def _create_connection_point_port(self) -> None: From a325cadfb7d600194af3291a04736cd07446cd5c Mon Sep 17 00:00:00 2001 From: Mike Turner Date: Tue, 25 Aug 2026 10:35:08 +0800 Subject: [PATCH 2/3] Update changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab795a91..9f920cdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Unreleased + +#### Adds + +- Adds support for unconnected (dangling) ports by setting `all_dangling_port` to True. + +#### Changes + +- Prevents warnings for unconnected (dangling) ports on EVs. + + ## Releases ### v2.2.6 (2026-08-10) From cdcb8faff346c2b2ff6d53f4b2863cb275a804b1 Mon Sep 17 00:00:00 2001 From: Mike Turner Date: Tue, 25 Aug 2026 10:37:19 +0800 Subject: [PATCH 3/3] Remove blankline --- src/echo/models/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/echo/models/base.py b/src/echo/models/base.py index 2f1e77d1..305e920a 100644 --- a/src/echo/models/base.py +++ b/src/echo/models/base.py @@ -67,7 +67,6 @@ class Port(BaseModel): active_periods: dict[tuple[int, int], Any] | None = None slack: bool = False objective: en.numeric_expr.NumericExpression | float = 0 # this will eventually be a pyomo expression - allow_dangling_port: bool = False # Validators for import/export constraint values