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.

Files changed (63) hide show
  1. flixopt/__init__.py +35 -1
  2. flixopt/aggregation.py +60 -81
  3. flixopt/calculation.py +381 -196
  4. flixopt/components.py +1022 -359
  5. flixopt/config.py +553 -191
  6. flixopt/core.py +475 -1315
  7. flixopt/effects.py +477 -214
  8. flixopt/elements.py +591 -344
  9. flixopt/features.py +403 -957
  10. flixopt/flow_system.py +781 -293
  11. flixopt/interface.py +1159 -189
  12. flixopt/io.py +50 -55
  13. flixopt/linear_converters.py +384 -92
  14. flixopt/modeling.py +759 -0
  15. flixopt/network_app.py +789 -0
  16. flixopt/plotting.py +273 -135
  17. flixopt/results.py +639 -383
  18. flixopt/solvers.py +25 -21
  19. flixopt/structure.py +928 -442
  20. flixopt/utils.py +34 -5
  21. flixopt-3.0.0.dist-info/METADATA +209 -0
  22. flixopt-3.0.0.dist-info/RECORD +26 -0
  23. {flixopt-2.2.0b0.dist-info → flixopt-3.0.0.dist-info}/WHEEL +1 -1
  24. flixopt-3.0.0.dist-info/top_level.txt +1 -0
  25. docs/examples/00-Minimal Example.md +0 -5
  26. docs/examples/01-Basic Example.md +0 -5
  27. docs/examples/02-Complex Example.md +0 -10
  28. docs/examples/03-Calculation Modes.md +0 -5
  29. docs/examples/index.md +0 -5
  30. docs/faq/contribute.md +0 -49
  31. docs/faq/index.md +0 -3
  32. docs/images/architecture_flixOpt-pre2.0.0.png +0 -0
  33. docs/images/architecture_flixOpt.png +0 -0
  34. docs/images/flixopt-icon.svg +0 -1
  35. docs/javascripts/mathjax.js +0 -18
  36. docs/release-notes/_template.txt +0 -32
  37. docs/release-notes/index.md +0 -7
  38. docs/release-notes/v2.0.0.md +0 -93
  39. docs/release-notes/v2.0.1.md +0 -12
  40. docs/release-notes/v2.1.0.md +0 -31
  41. docs/release-notes/v2.2.0.md +0 -55
  42. docs/user-guide/Mathematical Notation/Bus.md +0 -33
  43. docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +0 -132
  44. docs/user-guide/Mathematical Notation/Flow.md +0 -26
  45. docs/user-guide/Mathematical Notation/Investment.md +0 -115
  46. docs/user-guide/Mathematical Notation/LinearConverter.md +0 -21
  47. docs/user-guide/Mathematical Notation/Piecewise.md +0 -49
  48. docs/user-guide/Mathematical Notation/Storage.md +0 -44
  49. docs/user-guide/Mathematical Notation/index.md +0 -22
  50. docs/user-guide/Mathematical Notation/others.md +0 -3
  51. docs/user-guide/index.md +0 -124
  52. flixopt/config.yaml +0 -10
  53. flixopt-2.2.0b0.dist-info/METADATA +0 -146
  54. flixopt-2.2.0b0.dist-info/RECORD +0 -59
  55. flixopt-2.2.0b0.dist-info/top_level.txt +0 -5
  56. pics/architecture_flixOpt-pre2.0.0.png +0 -0
  57. pics/architecture_flixOpt.png +0 -0
  58. pics/flixOpt_plotting.jpg +0 -0
  59. pics/flixopt-icon.svg +0 -1
  60. pics/pics.pptx +0 -0
  61. scripts/gen_ref_pages.py +0 -54
  62. tests/ressources/Zeitreihen2020.csv +0 -35137
  63. {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 Dict, Literal, Optional, Tuple, Union
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
- from .core import TimeSeries
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(CustomDumper, self).increase_indent(flow, False)
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) -> Dict[str, str]:
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: Union[str, pathlib.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 the attrs as a json string in the 'attrs' attribute.
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('netCDF4') is not None:
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 "netcdf4".'
258
- 'Install netcdf4 via `pip install netcdf4`.'
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': 5} for data_var in ds.data_vars},
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: Union[str, pathlib.Path]) -> xr.Dataset:
251
+ def load_dataset_from_netcdf(path: str | pathlib.Path) -> xr.Dataset:
271
252
  """
272
- Load a dataset from a netcdf file. Load the attrs from the 'attrs' attribute.
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
- ds.attrs = json.loads(ds.attrs['attrs'])
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) -> Dict[str, pathlib.Path]:
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: Optional[str] = None, new_folder: Optional[pathlib.Path] = None) -> None:
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