flixopt 2.2.0b0__py3-none-any.whl → 3.0.0__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.
Potentially problematic release.
This version of flixopt might be problematic. Click here for more details.
- flixopt/__init__.py +35 -1
- flixopt/aggregation.py +60 -81
- flixopt/calculation.py +381 -196
- flixopt/components.py +1022 -359
- flixopt/config.py +553 -191
- flixopt/core.py +475 -1315
- flixopt/effects.py +477 -214
- flixopt/elements.py +591 -344
- flixopt/features.py +403 -957
- flixopt/flow_system.py +781 -293
- flixopt/interface.py +1159 -189
- flixopt/io.py +50 -55
- flixopt/linear_converters.py +384 -92
- flixopt/modeling.py +759 -0
- flixopt/network_app.py +789 -0
- flixopt/plotting.py +273 -135
- flixopt/results.py +639 -383
- flixopt/solvers.py +25 -21
- flixopt/structure.py +928 -442
- flixopt/utils.py +34 -5
- flixopt-3.0.0.dist-info/METADATA +209 -0
- flixopt-3.0.0.dist-info/RECORD +26 -0
- {flixopt-2.2.0b0.dist-info → flixopt-3.0.0.dist-info}/WHEEL +1 -1
- flixopt-3.0.0.dist-info/top_level.txt +1 -0
- docs/examples/00-Minimal Example.md +0 -5
- docs/examples/01-Basic Example.md +0 -5
- docs/examples/02-Complex Example.md +0 -10
- docs/examples/03-Calculation Modes.md +0 -5
- docs/examples/index.md +0 -5
- docs/faq/contribute.md +0 -49
- docs/faq/index.md +0 -3
- docs/images/architecture_flixOpt-pre2.0.0.png +0 -0
- docs/images/architecture_flixOpt.png +0 -0
- docs/images/flixopt-icon.svg +0 -1
- docs/javascripts/mathjax.js +0 -18
- docs/release-notes/_template.txt +0 -32
- docs/release-notes/index.md +0 -7
- docs/release-notes/v2.0.0.md +0 -93
- docs/release-notes/v2.0.1.md +0 -12
- docs/release-notes/v2.1.0.md +0 -31
- docs/release-notes/v2.2.0.md +0 -55
- docs/user-guide/Mathematical Notation/Bus.md +0 -33
- docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +0 -132
- docs/user-guide/Mathematical Notation/Flow.md +0 -26
- docs/user-guide/Mathematical Notation/Investment.md +0 -115
- docs/user-guide/Mathematical Notation/LinearConverter.md +0 -21
- docs/user-guide/Mathematical Notation/Piecewise.md +0 -49
- docs/user-guide/Mathematical Notation/Storage.md +0 -44
- docs/user-guide/Mathematical Notation/index.md +0 -22
- docs/user-guide/Mathematical Notation/others.md +0 -3
- docs/user-guide/index.md +0 -124
- flixopt/config.yaml +0 -10
- flixopt-2.2.0b0.dist-info/METADATA +0 -146
- flixopt-2.2.0b0.dist-info/RECORD +0 -59
- flixopt-2.2.0b0.dist-info/top_level.txt +0 -5
- pics/architecture_flixOpt-pre2.0.0.png +0 -0
- pics/architecture_flixOpt.png +0 -0
- pics/flixOpt_plotting.jpg +0 -0
- pics/flixopt-icon.svg +0 -1
- pics/pics.pptx +0 -0
- scripts/gen_ref_pages.py +0 -54
- tests/ressources/Zeitreihen2020.csv +0 -35137
- {flixopt-2.2.0b0.dist-info → flixopt-3.0.0.dist-info}/licenses/LICENSE +0 -0
flixopt/io.py
CHANGED
|
@@ -1,56 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import importlib.util
|
|
2
4
|
import json
|
|
3
5
|
import logging
|
|
4
6
|
import pathlib
|
|
5
7
|
import re
|
|
6
8
|
from dataclasses import dataclass
|
|
7
|
-
from typing import
|
|
9
|
+
from typing import TYPE_CHECKING, Literal
|
|
8
10
|
|
|
9
|
-
import linopy
|
|
10
11
|
import xarray as xr
|
|
11
12
|
import yaml
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
import linopy
|
|
14
16
|
|
|
15
17
|
logger = logging.getLogger('flixopt')
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
def replace_timeseries(obj, mode: Literal['name', 'stats', 'data'] = 'name'):
|
|
19
|
-
"""Recursively replaces TimeSeries objects with their names prefixed by '::::'."""
|
|
20
|
-
if isinstance(obj, dict):
|
|
21
|
-
return {k: replace_timeseries(v, mode) for k, v in obj.items()}
|
|
22
|
-
elif isinstance(obj, list):
|
|
23
|
-
return [replace_timeseries(v, mode) for v in obj]
|
|
24
|
-
elif isinstance(obj, TimeSeries): # Adjust this based on the actual class
|
|
25
|
-
if obj.all_equal:
|
|
26
|
-
return obj.selected_data.values.max().item()
|
|
27
|
-
elif mode == 'name':
|
|
28
|
-
return f'::::{obj.name}'
|
|
29
|
-
elif mode == 'stats':
|
|
30
|
-
return obj.stats
|
|
31
|
-
elif mode == 'data':
|
|
32
|
-
return obj
|
|
33
|
-
else:
|
|
34
|
-
raise ValueError(f'Invalid mode {mode}')
|
|
35
|
-
else:
|
|
36
|
-
return obj
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def insert_dataarray(obj, ds: xr.Dataset):
|
|
40
|
-
"""Recursively inserts TimeSeries objects into a dataset."""
|
|
41
|
-
if isinstance(obj, dict):
|
|
42
|
-
return {k: insert_dataarray(v, ds) for k, v in obj.items()}
|
|
43
|
-
elif isinstance(obj, list):
|
|
44
|
-
return [insert_dataarray(v, ds) for v in obj]
|
|
45
|
-
elif isinstance(obj, str) and obj.startswith('::::'):
|
|
46
|
-
da = ds[obj[4:]]
|
|
47
|
-
if 'time' in da.dims and da.isel(time=-1).isnull().any().item():
|
|
48
|
-
return da.isel(time=slice(0, -1))
|
|
49
|
-
return da
|
|
50
|
-
else:
|
|
51
|
-
return obj
|
|
52
|
-
|
|
53
|
-
|
|
54
20
|
def remove_none_and_empty(obj):
|
|
55
21
|
"""Recursively removes None and empty dicts and lists values from a dictionary or list."""
|
|
56
22
|
|
|
@@ -102,7 +68,7 @@ def _save_to_yaml(data, output_file='formatted_output.yaml'):
|
|
|
102
68
|
# Configure dumper options for better formatting
|
|
103
69
|
class CustomDumper(yaml.SafeDumper):
|
|
104
70
|
def increase_indent(self, flow=False, indentless=False):
|
|
105
|
-
return super(
|
|
71
|
+
return super().increase_indent(flow, False)
|
|
106
72
|
|
|
107
73
|
# Write to file with settings that ensure proper formatting
|
|
108
74
|
with open(output_file, 'w', encoding='utf-8') as file:
|
|
@@ -175,7 +141,7 @@ def _normalize_string_content(text):
|
|
|
175
141
|
return text.strip()
|
|
176
142
|
|
|
177
143
|
|
|
178
|
-
def document_linopy_model(model: linopy.Model, path: pathlib.Path = None) ->
|
|
144
|
+
def document_linopy_model(model: linopy.Model, path: pathlib.Path | None = None) -> dict[str, str]:
|
|
179
145
|
"""
|
|
180
146
|
Convert all model variables and constraints to a structured string representation.
|
|
181
147
|
This can take multiple seconds for large models.
|
|
@@ -224,18 +190,19 @@ def document_linopy_model(model: linopy.Model, path: pathlib.Path = None) -> Dic
|
|
|
224
190
|
if path is not None:
|
|
225
191
|
if path.suffix not in ['.yaml', '.yml']:
|
|
226
192
|
raise ValueError(f'Invalid file extension for path {path}. Only .yaml and .yml are supported')
|
|
227
|
-
_save_to_yaml(documentation, path)
|
|
193
|
+
_save_to_yaml(documentation, str(path))
|
|
228
194
|
|
|
229
195
|
return documentation
|
|
230
196
|
|
|
231
197
|
|
|
232
198
|
def save_dataset_to_netcdf(
|
|
233
199
|
ds: xr.Dataset,
|
|
234
|
-
path:
|
|
200
|
+
path: str | pathlib.Path,
|
|
235
201
|
compression: int = 0,
|
|
202
|
+
engine: Literal['netcdf4', 'scipy', 'h5netcdf'] = 'h5netcdf',
|
|
236
203
|
) -> None:
|
|
237
204
|
"""
|
|
238
|
-
Save a dataset to a netcdf file. Store
|
|
205
|
+
Save a dataset to a netcdf file. Store all attrs as JSON strings in 'attrs' attributes.
|
|
239
206
|
|
|
240
207
|
Args:
|
|
241
208
|
ds: Dataset to save.
|
|
@@ -245,40 +212,68 @@ def save_dataset_to_netcdf(
|
|
|
245
212
|
Raises:
|
|
246
213
|
ValueError: If the path has an invalid file extension.
|
|
247
214
|
"""
|
|
215
|
+
path = pathlib.Path(path)
|
|
248
216
|
if path.suffix not in ['.nc', '.nc4']:
|
|
249
217
|
raise ValueError(f'Invalid file extension for path {path}. Only .nc and .nc4 are supported')
|
|
250
218
|
|
|
251
219
|
apply_encoding = False
|
|
252
220
|
if compression != 0:
|
|
253
|
-
if importlib.util.find_spec(
|
|
221
|
+
if importlib.util.find_spec(engine) is not None:
|
|
254
222
|
apply_encoding = True
|
|
255
223
|
else:
|
|
256
224
|
logger.warning(
|
|
257
|
-
'Dataset was exported without compression due to missing dependency "
|
|
258
|
-
'Install
|
|
225
|
+
f'Dataset was exported without compression due to missing dependency "{engine}".'
|
|
226
|
+
f'Install {engine} via `pip install {engine}`.'
|
|
259
227
|
)
|
|
228
|
+
|
|
260
229
|
ds = ds.copy(deep=True)
|
|
261
230
|
ds.attrs = {'attrs': json.dumps(ds.attrs)}
|
|
231
|
+
|
|
232
|
+
# Convert all DataArray attrs to JSON strings
|
|
233
|
+
for var_name, data_var in ds.data_vars.items():
|
|
234
|
+
if data_var.attrs: # Only if there are attrs
|
|
235
|
+
ds[var_name].attrs = {'attrs': json.dumps(data_var.attrs)}
|
|
236
|
+
|
|
237
|
+
# Also handle coordinate attrs if they exist
|
|
238
|
+
for coord_name, coord_var in ds.coords.items():
|
|
239
|
+
if hasattr(coord_var, 'attrs') and coord_var.attrs:
|
|
240
|
+
ds[coord_name].attrs = {'attrs': json.dumps(coord_var.attrs)}
|
|
241
|
+
|
|
262
242
|
ds.to_netcdf(
|
|
263
243
|
path,
|
|
264
244
|
encoding=None
|
|
265
245
|
if not apply_encoding
|
|
266
|
-
else {data_var: {'zlib': True, 'complevel':
|
|
246
|
+
else {data_var: {'zlib': True, 'complevel': compression} for data_var in ds.data_vars},
|
|
247
|
+
engine=engine,
|
|
267
248
|
)
|
|
268
249
|
|
|
269
250
|
|
|
270
|
-
def load_dataset_from_netcdf(path:
|
|
251
|
+
def load_dataset_from_netcdf(path: str | pathlib.Path) -> xr.Dataset:
|
|
271
252
|
"""
|
|
272
|
-
Load a dataset from a netcdf file. Load
|
|
253
|
+
Load a dataset from a netcdf file. Load all attrs from 'attrs' attributes.
|
|
273
254
|
|
|
274
255
|
Args:
|
|
275
256
|
path: Path to load the dataset from.
|
|
276
257
|
|
|
277
258
|
Returns:
|
|
278
|
-
Dataset: Loaded dataset.
|
|
259
|
+
Dataset: Loaded dataset with restored attrs.
|
|
279
260
|
"""
|
|
280
|
-
ds = xr.load_dataset(path)
|
|
281
|
-
|
|
261
|
+
ds = xr.load_dataset(str(path), engine='h5netcdf')
|
|
262
|
+
|
|
263
|
+
# Restore Dataset attrs
|
|
264
|
+
if 'attrs' in ds.attrs:
|
|
265
|
+
ds.attrs = json.loads(ds.attrs['attrs'])
|
|
266
|
+
|
|
267
|
+
# Restore DataArray attrs
|
|
268
|
+
for var_name, data_var in ds.data_vars.items():
|
|
269
|
+
if 'attrs' in data_var.attrs:
|
|
270
|
+
ds[var_name].attrs = json.loads(data_var.attrs['attrs'])
|
|
271
|
+
|
|
272
|
+
# Restore coordinate attrs
|
|
273
|
+
for coord_name, coord_var in ds.coords.items():
|
|
274
|
+
if hasattr(coord_var, 'attrs') and 'attrs' in coord_var.attrs:
|
|
275
|
+
ds[coord_name].attrs = json.loads(coord_var.attrs['attrs'])
|
|
276
|
+
|
|
282
277
|
return ds
|
|
283
278
|
|
|
284
279
|
|
|
@@ -302,7 +297,7 @@ class CalculationResultsPaths:
|
|
|
302
297
|
self.flow_system = self.folder / f'{self.name}--flow_system.nc4'
|
|
303
298
|
self.model_documentation = self.folder / f'{self.name}--model_documentation.yaml'
|
|
304
299
|
|
|
305
|
-
def all_paths(self) ->
|
|
300
|
+
def all_paths(self) -> dict[str, pathlib.Path]:
|
|
306
301
|
"""Return a dictionary of all paths."""
|
|
307
302
|
return {
|
|
308
303
|
'linopy_model': self.linopy_model,
|
|
@@ -326,7 +321,7 @@ class CalculationResultsPaths:
|
|
|
326
321
|
f'Folder {self.folder} and its parent do not exist. Please create them first.'
|
|
327
322
|
) from e
|
|
328
323
|
|
|
329
|
-
def update(self, new_name:
|
|
324
|
+
def update(self, new_name: str | None = None, new_folder: pathlib.Path | None = None) -> None:
|
|
330
325
|
"""Update name and/or folder and refresh all paths."""
|
|
331
326
|
if new_name is not None:
|
|
332
327
|
self.name = new_name
|