fram-core 0.0.0__py3-none-any.whl → 0.1.0a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fram_core-0.1.0a2.dist-info/METADATA +42 -0
- fram_core-0.1.0a2.dist-info/RECORD +100 -0
- {fram_core-0.0.0.dist-info → fram_core-0.1.0a2.dist-info}/WHEEL +1 -2
- fram_core-0.1.0a2.dist-info/licenses/LICENSE.md +8 -0
- framcore/Base.py +142 -0
- framcore/Model.py +73 -0
- framcore/__init__.py +9 -0
- framcore/aggregators/Aggregator.py +153 -0
- framcore/aggregators/HydroAggregator.py +837 -0
- framcore/aggregators/NodeAggregator.py +495 -0
- framcore/aggregators/WindSolarAggregator.py +323 -0
- framcore/aggregators/__init__.py +13 -0
- framcore/aggregators/_utils.py +184 -0
- framcore/attributes/Arrow.py +305 -0
- framcore/attributes/ElasticDemand.py +90 -0
- framcore/attributes/ReservoirCurve.py +37 -0
- framcore/attributes/SoftBound.py +19 -0
- framcore/attributes/StartUpCost.py +54 -0
- framcore/attributes/Storage.py +146 -0
- framcore/attributes/TargetBound.py +18 -0
- framcore/attributes/__init__.py +65 -0
- framcore/attributes/hydro/HydroBypass.py +42 -0
- framcore/attributes/hydro/HydroGenerator.py +83 -0
- framcore/attributes/hydro/HydroPump.py +156 -0
- framcore/attributes/hydro/HydroReservoir.py +27 -0
- framcore/attributes/hydro/__init__.py +13 -0
- framcore/attributes/level_profile_attributes.py +714 -0
- framcore/components/Component.py +112 -0
- framcore/components/Demand.py +130 -0
- framcore/components/Flow.py +167 -0
- framcore/components/HydroModule.py +330 -0
- framcore/components/Node.py +76 -0
- framcore/components/Thermal.py +204 -0
- framcore/components/Transmission.py +183 -0
- framcore/components/_PowerPlant.py +81 -0
- framcore/components/__init__.py +22 -0
- framcore/components/wind_solar.py +67 -0
- framcore/curves/Curve.py +44 -0
- framcore/curves/LoadedCurve.py +155 -0
- framcore/curves/__init__.py +9 -0
- framcore/events/__init__.py +21 -0
- framcore/events/events.py +51 -0
- framcore/expressions/Expr.py +490 -0
- framcore/expressions/__init__.py +28 -0
- framcore/expressions/_get_constant_from_expr.py +483 -0
- framcore/expressions/_time_vector_operations.py +615 -0
- framcore/expressions/_utils.py +73 -0
- framcore/expressions/queries.py +423 -0
- framcore/expressions/units.py +207 -0
- framcore/fingerprints/__init__.py +11 -0
- framcore/fingerprints/fingerprint.py +293 -0
- framcore/juliamodels/JuliaModel.py +161 -0
- framcore/juliamodels/__init__.py +7 -0
- framcore/loaders/__init__.py +10 -0
- framcore/loaders/loaders.py +407 -0
- framcore/metadata/Div.py +73 -0
- framcore/metadata/ExprMeta.py +50 -0
- framcore/metadata/LevelExprMeta.py +17 -0
- framcore/metadata/Member.py +55 -0
- framcore/metadata/Meta.py +44 -0
- framcore/metadata/__init__.py +15 -0
- framcore/populators/Populator.py +108 -0
- framcore/populators/__init__.py +7 -0
- framcore/querydbs/CacheDB.py +50 -0
- framcore/querydbs/ModelDB.py +34 -0
- framcore/querydbs/QueryDB.py +45 -0
- framcore/querydbs/__init__.py +11 -0
- framcore/solvers/Solver.py +48 -0
- framcore/solvers/SolverConfig.py +272 -0
- framcore/solvers/__init__.py +9 -0
- framcore/timeindexes/AverageYearRange.py +20 -0
- framcore/timeindexes/ConstantTimeIndex.py +17 -0
- framcore/timeindexes/DailyIndex.py +21 -0
- framcore/timeindexes/FixedFrequencyTimeIndex.py +762 -0
- framcore/timeindexes/HourlyIndex.py +21 -0
- framcore/timeindexes/IsoCalendarDay.py +31 -0
- framcore/timeindexes/ListTimeIndex.py +197 -0
- framcore/timeindexes/ModelYear.py +17 -0
- framcore/timeindexes/ModelYears.py +18 -0
- framcore/timeindexes/OneYearProfileTimeIndex.py +21 -0
- framcore/timeindexes/ProfileTimeIndex.py +32 -0
- framcore/timeindexes/SinglePeriodTimeIndex.py +37 -0
- framcore/timeindexes/TimeIndex.py +90 -0
- framcore/timeindexes/WeeklyIndex.py +21 -0
- framcore/timeindexes/__init__.py +36 -0
- framcore/timevectors/ConstantTimeVector.py +135 -0
- framcore/timevectors/LinearTransformTimeVector.py +114 -0
- framcore/timevectors/ListTimeVector.py +123 -0
- framcore/timevectors/LoadedTimeVector.py +104 -0
- framcore/timevectors/ReferencePeriod.py +41 -0
- framcore/timevectors/TimeVector.py +94 -0
- framcore/timevectors/__init__.py +17 -0
- framcore/utils/__init__.py +36 -0
- framcore/utils/get_regional_volumes.py +369 -0
- framcore/utils/get_supported_components.py +60 -0
- framcore/utils/global_energy_equivalent.py +46 -0
- framcore/utils/isolate_subnodes.py +163 -0
- framcore/utils/loaders.py +97 -0
- framcore/utils/node_flow_utils.py +236 -0
- framcore/utils/storage_subsystems.py +107 -0
- fram_core-0.0.0.dist-info/METADATA +0 -5
- fram_core-0.0.0.dist-info/RECORD +0 -4
- fram_core-0.0.0.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from framcore.events import send_warning_event
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from framcore import Model
|
|
10
|
+
from framcore.loaders import Loader
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def add_loaders_if(loaders: set, value: object | None) -> None:
|
|
14
|
+
"""Call value.add_loaders(loaders) if value is not None."""
|
|
15
|
+
_check_type(loaders, "loaders", set)
|
|
16
|
+
if value is None:
|
|
17
|
+
return
|
|
18
|
+
value.add_loaders(loaders)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def add_loaders(loaders: set[Loader], model: Model) -> None:
|
|
22
|
+
"""Add all loaders stored in Model to loaders set."""
|
|
23
|
+
from framcore import Model # noqa: PLC0415
|
|
24
|
+
from framcore.components import Component, Flow, Node # noqa: PLC0415
|
|
25
|
+
from framcore.curves import Curve # noqa: PLC0415
|
|
26
|
+
from framcore.expressions import Expr # noqa: PLC0415
|
|
27
|
+
from framcore.timevectors import TimeVector # noqa: PLC0415
|
|
28
|
+
from framcore.utils import get_supported_components # noqa: PLC0415
|
|
29
|
+
|
|
30
|
+
_check_type(loaders, "loaders", set)
|
|
31
|
+
_check_type(model, "model", Model)
|
|
32
|
+
|
|
33
|
+
data = model.get_data()
|
|
34
|
+
components = dict()
|
|
35
|
+
|
|
36
|
+
for key, value in data.items():
|
|
37
|
+
if isinstance(value, Expr):
|
|
38
|
+
value.add_loaders(loaders)
|
|
39
|
+
|
|
40
|
+
elif isinstance(value, TimeVector | Curve):
|
|
41
|
+
loader = value.get_loader()
|
|
42
|
+
if loader is not None:
|
|
43
|
+
loaders.add(loader)
|
|
44
|
+
|
|
45
|
+
elif isinstance(value, Component):
|
|
46
|
+
components[key] = value
|
|
47
|
+
|
|
48
|
+
graph: dict[str, Flow | Node] = get_supported_components(components, (Flow, Node), tuple())
|
|
49
|
+
|
|
50
|
+
for c in graph.values():
|
|
51
|
+
c.add_loaders(loaders)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def replace_loader_path(loaders: set[Loader], old: Path, new: Path) -> None:
|
|
55
|
+
"""Replace old path with new for all loaders using old path."""
|
|
56
|
+
from framcore.loaders import FileLoader # noqa: PLC0415
|
|
57
|
+
|
|
58
|
+
_check_type(loaders, "loaders", set)
|
|
59
|
+
|
|
60
|
+
new = _check_path(new, "new", make_absolute=True)
|
|
61
|
+
old = _check_path(old, "old", error_if_not_absolute=True)
|
|
62
|
+
|
|
63
|
+
for loader in loaders:
|
|
64
|
+
try:
|
|
65
|
+
source = loader.get_source()
|
|
66
|
+
except Exception:
|
|
67
|
+
send_warning_event(f"loader.get_source() failed for {loader}. Skipping this one.")
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
if isinstance(source, Path) and old in source.parents:
|
|
71
|
+
loader.set_source(new_source=new / source.relative_to(old))
|
|
72
|
+
|
|
73
|
+
if isinstance(loader, FileLoader) and not isinstance(source, Path):
|
|
74
|
+
send_warning_event(f"FileLoader.get_source() does not return Path as it should for loader {loader}. Instead of Path, got {source}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _check_type(value, name, expected) -> None: # noqa: ANN001
|
|
78
|
+
if not isinstance(value, expected):
|
|
79
|
+
message = f"Expected {name} to be {type(expected).__name__}. Got Got {type(value).__name__}"
|
|
80
|
+
raise TypeError(message)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _check_path(
|
|
84
|
+
path: Path,
|
|
85
|
+
new_old: str,
|
|
86
|
+
make_absolute: bool = False,
|
|
87
|
+
error_if_not_absolute: bool = False,
|
|
88
|
+
) -> Path:
|
|
89
|
+
if not isinstance(path, Path):
|
|
90
|
+
message = f"{new_old} must be Path. Got {path}"
|
|
91
|
+
raise ValueError(message)
|
|
92
|
+
if make_absolute and not path.is_absolute():
|
|
93
|
+
path = path.resolve()
|
|
94
|
+
if error_if_not_absolute and not path.is_absolute():
|
|
95
|
+
message = f"{new_old} must be an absolute Path. Got {path}"
|
|
96
|
+
raise ValueError(message)
|
|
97
|
+
return path
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
from __future__ import annotations # NB! added for type hint to work
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from framcore import Base
|
|
7
|
+
from framcore.components import Component, Flow, Node
|
|
8
|
+
from framcore.utils import get_supported_components
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from framcore import Model
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FlowInfo(Base):
|
|
15
|
+
"""Holds info about one or two related Arrows of a Flow."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
category: str,
|
|
20
|
+
node_out: str | None = None,
|
|
21
|
+
commodity_out: str | None = None,
|
|
22
|
+
node_in: str | None = None,
|
|
23
|
+
commodity_in: str | None = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Based on its arrows, we derive properties about a Flow.
|
|
27
|
+
|
|
28
|
+
We use this class to store such info.
|
|
29
|
+
"""
|
|
30
|
+
self.category = category
|
|
31
|
+
self.node_out = node_out
|
|
32
|
+
self.commodity_out = commodity_out
|
|
33
|
+
self.node_in = node_in
|
|
34
|
+
self.commodity_in = commodity_in
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _check_type(value: object, expected) -> None: # noqa: ANN001
|
|
38
|
+
assert isinstance(value, expected), f"Expected {expected}. Got {type(value.__name__)}."
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_node_to_commodity(data: dict[str, object]) -> dict[str, str]:
|
|
42
|
+
"""Return dict with commodity (str) for each node id (str) in data."""
|
|
43
|
+
_check_type(data, dict)
|
|
44
|
+
|
|
45
|
+
components = {k: v for k, v in data.items() if isinstance(v, Component)}
|
|
46
|
+
for k in components:
|
|
47
|
+
assert isinstance(k, str), f"Got invalid key {k}"
|
|
48
|
+
|
|
49
|
+
g = get_supported_components(components, (Node, Flow), tuple())
|
|
50
|
+
|
|
51
|
+
out = dict()
|
|
52
|
+
for k, v in g.items():
|
|
53
|
+
if isinstance(v, Node):
|
|
54
|
+
_check_type(k, str)
|
|
55
|
+
out[k] = v.get_commodity()
|
|
56
|
+
return out
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_flow_infos(flow: Flow, node_to_commodity: dict[str, str]) -> list[FlowInfo]:
|
|
60
|
+
"""Get flow infos from analysis of all its arrows."""
|
|
61
|
+
_check_type(flow, Flow)
|
|
62
|
+
_check_type(node_to_commodity, dict)
|
|
63
|
+
|
|
64
|
+
arrows = flow.get_arrows()
|
|
65
|
+
|
|
66
|
+
if len(arrows) == 1:
|
|
67
|
+
arrow = next(iter(arrows))
|
|
68
|
+
node_id = arrow.get_node()
|
|
69
|
+
|
|
70
|
+
if node_id not in node_to_commodity:
|
|
71
|
+
message = f"node_id {node_id} missing from node_to_commodity for flow\n{flow}"
|
|
72
|
+
raise RuntimeError(message)
|
|
73
|
+
|
|
74
|
+
commodity = node_to_commodity[node_id]
|
|
75
|
+
if arrow.is_ingoing():
|
|
76
|
+
info = FlowInfo(
|
|
77
|
+
"direct_in",
|
|
78
|
+
node_in=node_id,
|
|
79
|
+
commodity_in=commodity,
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
info = FlowInfo(
|
|
83
|
+
"direct_out",
|
|
84
|
+
node_out=node_id,
|
|
85
|
+
commodity_out=commodity,
|
|
86
|
+
)
|
|
87
|
+
return [info]
|
|
88
|
+
|
|
89
|
+
seen: set[tuple[str, str]] = set()
|
|
90
|
+
infos: list[FlowInfo] = []
|
|
91
|
+
for x in arrows:
|
|
92
|
+
for y in arrows:
|
|
93
|
+
if x is y:
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
if x.is_ingoing() != y.is_ingoing():
|
|
97
|
+
arrow_in = x if x.is_ingoing() else y
|
|
98
|
+
arrow_out = x if y.is_ingoing() else y
|
|
99
|
+
|
|
100
|
+
node_in = arrow_in.get_node()
|
|
101
|
+
node_out = arrow_out.get_node()
|
|
102
|
+
|
|
103
|
+
if node_in not in node_to_commodity:
|
|
104
|
+
message = f"node_in {node_in} missing from node_to_commodity for flow\n{flow}"
|
|
105
|
+
raise RuntimeError(message)
|
|
106
|
+
|
|
107
|
+
if node_out not in node_to_commodity:
|
|
108
|
+
message = f"node_out {node_out} missing from node_to_commodity for flow\n{flow}"
|
|
109
|
+
raise RuntimeError(message)
|
|
110
|
+
|
|
111
|
+
commodity_in = node_to_commodity[node_in]
|
|
112
|
+
commodity_out = node_to_commodity[node_out]
|
|
113
|
+
|
|
114
|
+
info = FlowInfo(
|
|
115
|
+
category="transport" if commodity_in == commodity_out else "conversion",
|
|
116
|
+
node_in=node_in,
|
|
117
|
+
commodity_in=commodity_in,
|
|
118
|
+
node_out=node_out,
|
|
119
|
+
commodity_out=commodity_out,
|
|
120
|
+
)
|
|
121
|
+
key = (node_in, node_out)
|
|
122
|
+
if key in seen:
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
infos.append(info)
|
|
126
|
+
seen.add(key)
|
|
127
|
+
|
|
128
|
+
for arrow in arrows:
|
|
129
|
+
node = arrow.get_node()
|
|
130
|
+
if any(node in [info.node_in, info.node_out] for info in infos):
|
|
131
|
+
continue
|
|
132
|
+
node_id = arrow.get_node()
|
|
133
|
+
commodity = node_to_commodity[node_id]
|
|
134
|
+
if arrow.is_ingoing():
|
|
135
|
+
info = FlowInfo(
|
|
136
|
+
"direct_in",
|
|
137
|
+
node_in=node_id,
|
|
138
|
+
commodity_in=commodity,
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
info = FlowInfo(
|
|
142
|
+
"direct_out",
|
|
143
|
+
node_out=node_id,
|
|
144
|
+
commodity_out=commodity,
|
|
145
|
+
)
|
|
146
|
+
infos.append(info)
|
|
147
|
+
|
|
148
|
+
return infos
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_component_to_nodes(data: Model | dict[str, object]) -> dict[str, set[str]]:
|
|
152
|
+
"""For each str key in data where value is a Comonent find all Node id str in data directly connected to the Component."""
|
|
153
|
+
from framcore import Model # noqa: PLC0415
|
|
154
|
+
|
|
155
|
+
_check_type(data, Model | dict)
|
|
156
|
+
|
|
157
|
+
if isinstance(data, Model):
|
|
158
|
+
data = data.get_data()
|
|
159
|
+
|
|
160
|
+
components = {k: v for k, v in data.items() if isinstance(v, Component)}
|
|
161
|
+
for k in components:
|
|
162
|
+
assert isinstance(k, str), f"Got invalid key {k}"
|
|
163
|
+
|
|
164
|
+
g = get_supported_components(components, (Node, Flow), tuple())
|
|
165
|
+
|
|
166
|
+
nodes = {k: v for k, v in g.items() if isinstance(v, Node)}
|
|
167
|
+
flows = {k: v for k, v in g.items() if isinstance(v, Flow)}
|
|
168
|
+
|
|
169
|
+
domain_nodes = {k: v for k, v in nodes.items() if (k in components) and isinstance(v, Node)}
|
|
170
|
+
assert all(isinstance(v, Node) for v in domain_nodes.values())
|
|
171
|
+
|
|
172
|
+
parent_keys = {v: k for k, v in components.items()}
|
|
173
|
+
|
|
174
|
+
out = defaultdict(set)
|
|
175
|
+
for flow in flows.values():
|
|
176
|
+
parent_key = parent_keys[flow.get_top_parent()]
|
|
177
|
+
for a in flow.get_arrows():
|
|
178
|
+
node_id = a.get_node()
|
|
179
|
+
if node_id in domain_nodes:
|
|
180
|
+
out[parent_key].add(node_id)
|
|
181
|
+
|
|
182
|
+
return out
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def get_transports_by_commodity(data: Model | dict[str, object], commodity: str) -> dict[str, tuple[str, str]]:
|
|
186
|
+
"""Return dict with key component_id and value (from_node_id, to_node_id) where both nodes belong to given commodity."""
|
|
187
|
+
from framcore import Model # noqa: PLC0415
|
|
188
|
+
|
|
189
|
+
_check_type(data, Model | dict)
|
|
190
|
+
_check_type(commodity, str)
|
|
191
|
+
|
|
192
|
+
if isinstance(data, Model):
|
|
193
|
+
data = data.get_data()
|
|
194
|
+
|
|
195
|
+
components = {k: v for k, v in data.items() if isinstance(v, Component)}
|
|
196
|
+
for k in components:
|
|
197
|
+
assert isinstance(k, str), f"Got invalid key {k}"
|
|
198
|
+
|
|
199
|
+
node_to_commodity = get_node_to_commodity(components)
|
|
200
|
+
|
|
201
|
+
g = get_supported_components(components, (Node, Flow), tuple())
|
|
202
|
+
|
|
203
|
+
flows = {k: v for k, v in g.items() if isinstance(v, Flow)}
|
|
204
|
+
|
|
205
|
+
parent_keys = {v: k for k, v in components.items()}
|
|
206
|
+
|
|
207
|
+
out = dict()
|
|
208
|
+
for flow in flows.values():
|
|
209
|
+
parent_key = parent_keys[flow.get_top_parent()]
|
|
210
|
+
infos = get_flow_infos(flow, node_to_commodity)
|
|
211
|
+
if len(infos) != 1:
|
|
212
|
+
continue
|
|
213
|
+
info = infos[0]
|
|
214
|
+
if info.category != "transport":
|
|
215
|
+
continue
|
|
216
|
+
if info.commodity_in != commodity:
|
|
217
|
+
continue
|
|
218
|
+
out[parent_key] = (info.node_out, info.node_in)
|
|
219
|
+
|
|
220
|
+
return out
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def is_transport_by_commodity(flow: Flow, node_to_commodity: dict[str, str], commodity: str) -> bool:
|
|
224
|
+
"""Return True if flow is a transport of the given commodity."""
|
|
225
|
+
_check_type(flow, Flow)
|
|
226
|
+
_check_type(node_to_commodity, dict)
|
|
227
|
+
arrows = flow.get_arrows()
|
|
228
|
+
try:
|
|
229
|
+
x, y = tuple(arrows)
|
|
230
|
+
opposite_directions = x.is_ingoing() != y.is_ingoing()
|
|
231
|
+
x_commodity = node_to_commodity[x.get_node()]
|
|
232
|
+
y_commodity = node_to_commodity[y.get_node()]
|
|
233
|
+
correct_commodity = x_commodity == y_commodity == commodity
|
|
234
|
+
return opposite_directions and correct_commodity
|
|
235
|
+
except Exception:
|
|
236
|
+
return False
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations # NB! added for type hint to work
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
|
|
5
|
+
from framcore import Model
|
|
6
|
+
from framcore.components import Component, Flow, Node
|
|
7
|
+
from framcore.utils import get_supported_components
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# TODO: Finish implementation, test and demo
|
|
11
|
+
def get_storage_subsystems(domain_components: dict[str, Component] | Model) -> dict[str, set[str]]:
|
|
12
|
+
if isinstance(domain_components, Model):
|
|
13
|
+
domain_components = {k: v for k, v in domain_components.get_data() if isinstance(v, Component)}
|
|
14
|
+
|
|
15
|
+
# translate domain_components to graph consisting of just Flow and Node components
|
|
16
|
+
graph: dict[str, Flow | Node] = get_supported_components(
|
|
17
|
+
components=domain_components,
|
|
18
|
+
supported_types=(Node, Flow),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
abstract_subsystems, __ = get_one_commodity_storage_subsystems(graph, include_boundaries=True)
|
|
22
|
+
|
|
23
|
+
# TODO: Use Component.get_top_level to lift abstract_subsystems back to domain_components
|
|
24
|
+
domain_subsystems = abstract_subsystems
|
|
25
|
+
|
|
26
|
+
return domain_subsystems
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_one_commodity_storage_subsystems(
|
|
30
|
+
graph: dict[str, Node | Flow],
|
|
31
|
+
include_boundaries: bool,
|
|
32
|
+
) -> dict[str, tuple[str, set[str], set[str]]]:
|
|
33
|
+
"""
|
|
34
|
+
Group all storage subsystems belonging to same commodity.
|
|
35
|
+
|
|
36
|
+
Returns dict[subsystem_id, (domain_commodity, member_component_ids, boundary_domain_commodities)]
|
|
37
|
+
|
|
38
|
+
The boundary_domain_commodities of the output is a set of boundary commodities.
|
|
39
|
+
Some algorithms can only handle one boundary commodity, so this output is useful
|
|
40
|
+
to verify that those conditions apply, and to derive conversion factor unit,
|
|
41
|
+
which need both storage_commodity unit and boundray_commodity unit.
|
|
42
|
+
|
|
43
|
+
If include_boundaries is False only nodes with same commodity as storage_node will
|
|
44
|
+
be included in the subsystem.
|
|
45
|
+
"""
|
|
46
|
+
if not all(isinstance(c, Flow | Node) for c in graph.values()):
|
|
47
|
+
invalid = {k: v for k, v in graph.items() if not isinstance(v, Flow | Node)}
|
|
48
|
+
message = f"All values in graph must be Flow or Node objects. Found invalid objects: {invalid}"
|
|
49
|
+
raise ValueError(message)
|
|
50
|
+
|
|
51
|
+
flows: dict[str, Flow] = {k: v for k, v in graph.items() if isinstance(v, Flow)}
|
|
52
|
+
nodes: dict[str, Node] = {k: v for k, v in graph.items() if isinstance(v, Node)}
|
|
53
|
+
|
|
54
|
+
storage_nodes: dict[str, Node] = {k: v for k, v in nodes.items() if v.get_storage()}
|
|
55
|
+
|
|
56
|
+
node_to_flows: dict[str, set[str]] = defaultdict(set)
|
|
57
|
+
flow_to_nodes: dict[str, set[str]] = defaultdict(set)
|
|
58
|
+
for flow_id, flow in flows.items():
|
|
59
|
+
for arrow in flow.get_arrows():
|
|
60
|
+
node_id = arrow.get_node()
|
|
61
|
+
node_to_flows[node_id].add(flow_id)
|
|
62
|
+
flow_to_nodes[flow_id].add(node_id)
|
|
63
|
+
|
|
64
|
+
out = dict()
|
|
65
|
+
allocated: set[str] = set()
|
|
66
|
+
for storage_node_id, storage_node in storage_nodes.items():
|
|
67
|
+
if storage_node_id in allocated:
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
subsystem_id = storage_node_id
|
|
71
|
+
storage_commodity = storage_node.get_commodity()
|
|
72
|
+
|
|
73
|
+
member_component_ids: set[str] = set()
|
|
74
|
+
boundary_commodities: set[str] = set()
|
|
75
|
+
|
|
76
|
+
visited: set[str] = set()
|
|
77
|
+
remaining: set[str] = set()
|
|
78
|
+
|
|
79
|
+
remaining.add(storage_node_id)
|
|
80
|
+
|
|
81
|
+
while remaining:
|
|
82
|
+
component_id = remaining.pop()
|
|
83
|
+
if component_id in visited:
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
visited.add(component_id)
|
|
87
|
+
|
|
88
|
+
if component_id in nodes:
|
|
89
|
+
node: Node = nodes[component_id]
|
|
90
|
+
node_commodity = node.get_commodity()
|
|
91
|
+
|
|
92
|
+
if node_commodity == storage_commodity:
|
|
93
|
+
allocated.add(component_id)
|
|
94
|
+
remaining.update(node_to_flows.get(component_id, set()))
|
|
95
|
+
else:
|
|
96
|
+
boundary_commodities.add(node_commodity)
|
|
97
|
+
|
|
98
|
+
if include_boundaries or node_commodity == storage_commodity:
|
|
99
|
+
member_component_ids.add(component_id)
|
|
100
|
+
|
|
101
|
+
else:
|
|
102
|
+
remaining.update(flow_to_nodes.get(component_id, set()))
|
|
103
|
+
member_component_ids.add(component_id)
|
|
104
|
+
|
|
105
|
+
out[subsystem_id] = (storage_commodity, member_component_ids, boundary_commodities)
|
|
106
|
+
|
|
107
|
+
return out
|
fram_core-0.0.0.dist-info/RECORD
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
fram_core-0.0.0.dist-info/METADATA,sha256=MXr1hQAqr_E4d7YDIhkuSeF-G0jYcTZJKTqr5fwOxPo,114
|
|
2
|
-
fram_core-0.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
3
|
-
fram_core-0.0.0.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
4
|
-
fram_core-0.0.0.dist-info/RECORD,,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|