flixopt 3.2.0__py3-none-any.whl → 3.3.1__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/structure.py CHANGED
@@ -7,12 +7,16 @@ from __future__ import annotations
7
7
 
8
8
  import inspect
9
9
  import logging
10
+ import re
10
11
  from dataclasses import dataclass
12
+ from difflib import get_close_matches
11
13
  from io import StringIO
12
14
  from typing import (
13
15
  TYPE_CHECKING,
14
16
  Any,
17
+ Generic,
15
18
  Literal,
19
+ TypeVar,
16
20
  )
17
21
 
18
22
  import linopy
@@ -168,7 +172,7 @@ class FlowSystemModel(linopy.Model, SubmodelsMixin):
168
172
  },
169
173
  'Effects': {
170
174
  effect.label_full: effect.submodel.results_structure()
171
- for effect in sorted(self.flow_system.effects, key=lambda effect: effect.label_full.upper())
175
+ for effect in sorted(self.flow_system.effects.values(), key=lambda effect: effect.label_full.upper())
172
176
  },
173
177
  'Flows': {
174
178
  flow.label_full: flow.submodel.results_structure()
@@ -242,9 +246,7 @@ class FlowSystemModel(linopy.Model, SubmodelsMixin):
242
246
  }
243
247
 
244
248
  # Format sections with headers and underlines
245
- formatted_sections = []
246
- for section_header, section_content in sections.items():
247
- formatted_sections.append(f'{section_header}\n{"-" * len(section_header)}\n{section_content}')
249
+ formatted_sections = fx_io.format_sections_with_headers(sections)
248
250
 
249
251
  title = f'FlowSystemModel ({self.type})'
250
252
  all_sections = '\n'.join(formatted_sections)
@@ -506,6 +508,33 @@ class Interface:
506
508
  unexpected_params = ', '.join(f"'{param}'" for param in extra_kwargs.keys())
507
509
  raise TypeError(f'{class_name}.__init__() got unexpected keyword argument(s): {unexpected_params}')
508
510
 
511
+ @staticmethod
512
+ def _has_value(param: Any) -> bool:
513
+ """Check if a parameter has a meaningful value.
514
+
515
+ Args:
516
+ param: The parameter to check.
517
+
518
+ Returns:
519
+ False for:
520
+ - None
521
+ - Empty collections (dict, list, tuple, set, frozenset)
522
+
523
+ True for all other values, including:
524
+ - Non-empty collections
525
+ - xarray DataArrays (even if they contain NaN/empty data)
526
+ - Scalar values (0, False, empty strings, etc.)
527
+ - NumPy arrays (even if empty - use .size to check those explicitly)
528
+ """
529
+ if param is None:
530
+ return False
531
+
532
+ # Check for empty collections (but not strings, arrays, or DataArrays)
533
+ if isinstance(param, (dict, list, tuple, set, frozenset)) and len(param) == 0:
534
+ return False
535
+
536
+ return True
537
+
509
538
  @classmethod
510
539
  def _resolve_dataarray_reference(
511
540
  cls, reference: str, arrays_dict: dict[str, xr.DataArray]
@@ -793,40 +822,7 @@ class Interface:
793
822
 
794
823
  def __repr__(self):
795
824
  """Return a detailed string representation for debugging."""
796
- try:
797
- # Get the constructor arguments and their current values
798
- init_signature = inspect.signature(self.__init__)
799
- init_args = init_signature.parameters
800
-
801
- # Create a dictionary with argument names and their values, with better formatting
802
- args_parts = []
803
- for name in init_args:
804
- if name == 'self':
805
- continue
806
- value = getattr(self, name, None)
807
- # Truncate long representations
808
- value_repr = repr(value)
809
- if len(value_repr) > 50:
810
- value_repr = value_repr[:47] + '...'
811
- args_parts.append(f'{name}={value_repr}')
812
-
813
- args_str = ', '.join(args_parts)
814
- return f'{self.__class__.__name__}({args_str})'
815
- except Exception:
816
- # Fallback if introspection fails
817
- return f'{self.__class__.__name__}(<repr_failed>)'
818
-
819
- def __str__(self):
820
- """Return a user-friendly string representation."""
821
- try:
822
- data = self.get_structure(clean=True, stats=True)
823
- with StringIO() as output_buffer:
824
- console = Console(file=output_buffer, width=1000) # Adjust width as needed
825
- console.print(Pretty(data, expand_all=True, indent_guides=True))
826
- return output_buffer.getvalue()
827
- except Exception:
828
- # Fallback if structure generation fails
829
- return f'{self.__class__.__name__} instance'
825
+ return fx_io.build_repr_from_init(self, excluded_params={'self', 'label', 'kwargs'})
830
826
 
831
827
  def copy(self) -> Interface:
832
828
  """
@@ -876,6 +872,10 @@ class Element(Interface):
876
872
  def label_full(self) -> str:
877
873
  return self.label
878
874
 
875
+ def __repr__(self) -> str:
876
+ """Return string representation."""
877
+ return fx_io.build_repr_from_init(self, excluded_params={'self', 'label', 'kwargs'}, skip_default_size=True)
878
+
879
879
  @staticmethod
880
880
  def _valid_label(label: str) -> str:
881
881
  """Checks if the label is valid. If not, it is replaced by the default label.
@@ -895,6 +895,329 @@ class Element(Interface):
895
895
  return label
896
896
 
897
897
 
898
+ # Precompiled regex pattern for natural sorting
899
+ _NATURAL_SPLIT = re.compile(r'(\d+)')
900
+
901
+
902
+ def _natural_sort_key(text):
903
+ """Sort key for natural ordering (e.g., bus1, bus2, bus10 instead of bus1, bus10, bus2)."""
904
+ return [int(c) if c.isdigit() else c.lower() for c in _NATURAL_SPLIT.split(text)]
905
+
906
+
907
+ # Type variable for containers
908
+ T = TypeVar('T')
909
+
910
+
911
+ class ContainerMixin(dict[str, T]):
912
+ """
913
+ Mixin providing shared container functionality with nice repr and error messages.
914
+
915
+ Subclasses must implement _get_label() to extract the label from elements.
916
+ """
917
+
918
+ def __init__(
919
+ self,
920
+ elements: list[T] | dict[str, T] | None = None,
921
+ element_type_name: str = 'elements',
922
+ ):
923
+ """
924
+ Args:
925
+ elements: Initial elements to add (list or dict)
926
+ element_type_name: Name for display (e.g., 'components', 'buses')
927
+ """
928
+ super().__init__()
929
+ self._element_type_name = element_type_name
930
+
931
+ if elements is not None:
932
+ if isinstance(elements, dict):
933
+ for element in elements.values():
934
+ self.add(element)
935
+ else:
936
+ for element in elements:
937
+ self.add(element)
938
+
939
+ def _get_label(self, element: T) -> str:
940
+ """
941
+ Extract label from element. Must be implemented by subclasses.
942
+
943
+ Args:
944
+ element: Element to get label from
945
+
946
+ Returns:
947
+ Label string
948
+ """
949
+ raise NotImplementedError('Subclasses must implement _get_label()')
950
+
951
+ def add(self, element: T) -> None:
952
+ """Add an element to the container."""
953
+ label = self._get_label(element)
954
+ if label in self:
955
+ raise ValueError(
956
+ f'Element with label "{label}" already exists in {self._element_type_name}. '
957
+ f'Each element must have a unique label.'
958
+ )
959
+ self[label] = element
960
+
961
+ def __setitem__(self, label: str, element: T) -> None:
962
+ """Set element with validation."""
963
+ element_label = self._get_label(element)
964
+ if label != element_label:
965
+ raise ValueError(
966
+ f'Key "{label}" does not match element label "{element_label}". '
967
+ f'Use the correct label as key or use .add() method.'
968
+ )
969
+ super().__setitem__(label, element)
970
+
971
+ def __getitem__(self, label: str) -> T:
972
+ """
973
+ Get element by label with helpful error messages.
974
+
975
+ Args:
976
+ label: Label of the element to retrieve
977
+
978
+ Returns:
979
+ The element with the given label
980
+
981
+ Raises:
982
+ KeyError: If element is not found, with suggestions for similar labels
983
+ """
984
+ try:
985
+ return super().__getitem__(label)
986
+ except KeyError:
987
+ # Provide helpful error with close matches suggestions
988
+ suggestions = get_close_matches(label, self.keys(), n=3, cutoff=0.6)
989
+ error_msg = f'Element "{label}" not found in {self._element_type_name}.'
990
+ if suggestions:
991
+ error_msg += f' Did you mean: {", ".join(suggestions)}?'
992
+ else:
993
+ available = list(self.keys())
994
+ if len(available) <= 5:
995
+ error_msg += f' Available: {", ".join(available)}'
996
+ else:
997
+ error_msg += f' Available: {", ".join(available[:5])} ... (+{len(available) - 5} more)'
998
+ raise KeyError(error_msg) from None
999
+
1000
+ def __repr__(self) -> str:
1001
+ """Return a string representation similar to linopy.model.Variables."""
1002
+ count = len(self)
1003
+ title = f'{self._element_type_name.capitalize()} ({count} item{"s" if count != 1 else ""})'
1004
+
1005
+ if not self:
1006
+ r = fx_io.format_title_with_underline(title)
1007
+ r += '<empty>\n'
1008
+ else:
1009
+ r = fx_io.format_title_with_underline(title)
1010
+ for name in sorted(self.keys(), key=_natural_sort_key):
1011
+ r += f' * {name}\n'
1012
+
1013
+ return r
1014
+
1015
+
1016
+ class ElementContainer(ContainerMixin[T]):
1017
+ """
1018
+ Container for Element objects (Component, Bus, Flow, Effect).
1019
+
1020
+ Uses element.label_full for keying.
1021
+ """
1022
+
1023
+ def _get_label(self, element: T) -> str:
1024
+ """Extract label_full from Element."""
1025
+ return element.label_full
1026
+
1027
+
1028
+ class ResultsContainer(ContainerMixin[T]):
1029
+ """
1030
+ Container for Results objects (ComponentResults, BusResults, etc).
1031
+
1032
+ Uses element.label for keying.
1033
+ """
1034
+
1035
+ def _get_label(self, element: T) -> str:
1036
+ """Extract label from Results object."""
1037
+ return element.label
1038
+
1039
+
1040
+ T_element = TypeVar('T_element')
1041
+
1042
+
1043
+ class CompositeContainerMixin(Generic[T_element]):
1044
+ """
1045
+ Mixin providing unified dict-like access across multiple typed containers.
1046
+
1047
+ This mixin enables classes that manage multiple containers (e.g., components,
1048
+ buses, effects, flows) to provide a unified interface for accessing elements
1049
+ across all containers, as if they were a single collection.
1050
+
1051
+ Type Parameter:
1052
+ T_element: The type of elements stored in the containers. Can be a union type
1053
+ for containers holding multiple types (e.g., 'ComponentResults | BusResults').
1054
+
1055
+ Key Features:
1056
+ - Dict-like access: `obj['element_name']` searches all containers
1057
+ - Iteration: `for label in obj:` iterates over all elements
1058
+ - Membership: `'element' in obj` checks across all containers
1059
+ - Standard dict methods: keys(), values(), items()
1060
+ - Grouped display: Formatted repr showing elements by type
1061
+ - Type hints: Full IDE and type checker support
1062
+
1063
+ Subclasses must implement:
1064
+ _get_container_groups() -> dict[str, dict]:
1065
+ Returns a dictionary mapping group names (e.g., 'Components', 'Buses')
1066
+ to container dictionaries. Containers are displayed in the order returned.
1067
+
1068
+ Example:
1069
+ ```python
1070
+ class MySystem(CompositeContainerMixin[Component | Bus]):
1071
+ def __init__(self):
1072
+ self.components = {'Boiler': Component(...), 'CHP': Component(...)}
1073
+ self.buses = {'Heat': Bus(...), 'Power': Bus(...)}
1074
+
1075
+ def _get_container_groups(self):
1076
+ return {
1077
+ 'Components': self.components,
1078
+ 'Buses': self.buses,
1079
+ }
1080
+
1081
+
1082
+ system = MySystem()
1083
+ comp = system['Boiler'] # Type: Component | Bus (with proper IDE support)
1084
+ 'Heat' in system # True
1085
+ labels = system.keys() # Type: list[str]
1086
+ elements = system.values() # Type: list[Component | Bus]
1087
+ ```
1088
+
1089
+ Integration with ContainerMixin:
1090
+ This mixin is designed to work alongside ContainerMixin-based containers
1091
+ (ElementContainer, ResultsContainer) by aggregating them into a unified
1092
+ interface while preserving their individual functionality.
1093
+ """
1094
+
1095
+ def _get_container_groups(self) -> dict[str, ContainerMixin[Any]]:
1096
+ """
1097
+ Return ordered dict of container groups to aggregate.
1098
+
1099
+ Returns:
1100
+ Dictionary mapping group names to container objects (e.g., ElementContainer, ResultsContainer).
1101
+ Group names should be capitalized (e.g., 'Components', 'Buses').
1102
+ Order determines display order in __repr__.
1103
+
1104
+ Example:
1105
+ ```python
1106
+ return {
1107
+ 'Components': self.components,
1108
+ 'Buses': self.buses,
1109
+ 'Effects': self.effects,
1110
+ }
1111
+ ```
1112
+ """
1113
+ raise NotImplementedError('Subclasses must implement _get_container_groups()')
1114
+
1115
+ def __getitem__(self, key: str) -> T_element:
1116
+ """
1117
+ Get element by label, searching all containers.
1118
+
1119
+ Args:
1120
+ key: Element label to find
1121
+
1122
+ Returns:
1123
+ The element with the given label
1124
+
1125
+ Raises:
1126
+ KeyError: If element not found, with helpful suggestions
1127
+ """
1128
+ # Search all containers in order
1129
+ for container in self._get_container_groups().values():
1130
+ if key in container:
1131
+ return container[key]
1132
+
1133
+ # Element not found - provide helpful error
1134
+ all_elements = {}
1135
+ for container in self._get_container_groups().values():
1136
+ all_elements.update(container)
1137
+
1138
+ suggestions = get_close_matches(key, all_elements.keys(), n=3, cutoff=0.6)
1139
+ error_msg = f'Element "{key}" not found.'
1140
+
1141
+ if suggestions:
1142
+ error_msg += f' Did you mean: {", ".join(suggestions)}?'
1143
+ else:
1144
+ available = list(all_elements.keys())
1145
+ if len(available) <= 5:
1146
+ error_msg += f' Available: {", ".join(available)}'
1147
+ else:
1148
+ error_msg += f' Available: {", ".join(available[:5])} ... (+{len(available) - 5} more)'
1149
+
1150
+ raise KeyError(error_msg)
1151
+
1152
+ def __iter__(self):
1153
+ """Iterate over all element labels across all containers."""
1154
+ for container in self._get_container_groups().values():
1155
+ yield from container.keys()
1156
+
1157
+ def __len__(self) -> int:
1158
+ """Return total count of elements across all containers."""
1159
+ return sum(len(container) for container in self._get_container_groups().values())
1160
+
1161
+ def __contains__(self, key: str) -> bool:
1162
+ """Check if element exists in any container."""
1163
+ return any(key in container for container in self._get_container_groups().values())
1164
+
1165
+ def keys(self) -> list[str]:
1166
+ """Return all element labels across all containers."""
1167
+ return list(self)
1168
+
1169
+ def values(self) -> list[T_element]:
1170
+ """Return all element objects across all containers."""
1171
+ vals = []
1172
+ for container in self._get_container_groups().values():
1173
+ vals.extend(container.values())
1174
+ return vals
1175
+
1176
+ def items(self) -> list[tuple[str, T_element]]:
1177
+ """Return (label, element) pairs for all elements."""
1178
+ items = []
1179
+ for container in self._get_container_groups().values():
1180
+ items.extend(container.items())
1181
+ return items
1182
+
1183
+ def _format_grouped_containers(self, title: str | None = None) -> str:
1184
+ """
1185
+ Format containers as grouped string representation using each container's repr.
1186
+
1187
+ Args:
1188
+ title: Optional title for the representation. If None, no title is shown.
1189
+
1190
+ Returns:
1191
+ Formatted string with groups and their elements.
1192
+ Empty groups are automatically hidden.
1193
+
1194
+ Example output:
1195
+ ```
1196
+ Components (1 item)
1197
+ -------------------
1198
+ * Boiler
1199
+
1200
+ Buses (2 items)
1201
+ ---------------
1202
+ * Heat
1203
+ * Power
1204
+ ```
1205
+ """
1206
+ parts = []
1207
+
1208
+ if title:
1209
+ parts.append(fx_io.format_title_with_underline(title))
1210
+
1211
+ container_groups = self._get_container_groups()
1212
+ for container in container_groups.values():
1213
+ if container: # Only show non-empty groups
1214
+ if parts: # Add spacing between sections
1215
+ parts.append('')
1216
+ parts.append(repr(container).rstrip('\n'))
1217
+
1218
+ return '\n'.join(parts)
1219
+
1220
+
898
1221
  class Submodel(SubmodelsMixin):
899
1222
  """Stores Variables and Constraints. Its a subset of a FlowSystemModel.
900
1223
  Variables and constraints are stored in the main FlowSystemModel, and are referenced here.
@@ -1056,9 +1379,7 @@ class Submodel(SubmodelsMixin):
1056
1379
  }
1057
1380
 
1058
1381
  # Format sections with headers and underlines
1059
- formatted_sections = []
1060
- for section_header, section_content in sections.items():
1061
- formatted_sections.append(f'{section_header}\n{"-" * len(section_header)}\n{section_content}')
1382
+ formatted_sections = fx_io.format_sections_with_headers(sections)
1062
1383
 
1063
1384
  model_string = f'Submodel "{self.label_of_model}":'
1064
1385
  all_sections = '\n'.join(formatted_sections)
@@ -1102,7 +1423,7 @@ class Submodels:
1102
1423
  def __repr__(self) -> str:
1103
1424
  """Simple representation of the submodels collection."""
1104
1425
  if not self.data:
1105
- return 'flixopt.structure.Submodels:\n----------------------------\n <empty>\n'
1426
+ return fx_io.format_title_with_underline('flixopt.structure.Submodels') + ' <empty>\n'
1106
1427
 
1107
1428
  total_vars = sum(len(submodel.variables) for submodel in self.data.values())
1108
1429
  total_cons = sum(len(submodel.constraints) for submodel in self.data.values())
@@ -1110,18 +1431,15 @@ class Submodels:
1110
1431
  title = (
1111
1432
  f'flixopt.structure.Submodels ({total_vars} vars, {total_cons} constraints, {len(self.data)} submodels):'
1112
1433
  )
1113
- underline = '-' * len(title)
1114
1434
 
1115
- if not self.data:
1116
- return f'{title}\n{underline}\n <empty>\n'
1117
- sub_models_string = ''
1435
+ result = fx_io.format_title_with_underline(title)
1118
1436
  for name, submodel in self.data.items():
1119
1437
  type_name = submodel.__class__.__name__
1120
1438
  var_count = len(submodel.variables)
1121
1439
  con_count = len(submodel.constraints)
1122
- sub_models_string += f'\n * {name} [{type_name}] ({var_count}v/{con_count}c)'
1440
+ result += f' * {name} [{type_name}] ({var_count}v/{con_count}c)\n'
1123
1441
 
1124
- return f'{title}\n{underline}{sub_models_string}\n'
1442
+ return result
1125
1443
 
1126
1444
  def items(self) -> ItemsView[str, Submodel]:
1127
1445
  return self.data.items()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flixopt
3
- Version: 3.2.0
3
+ Version: 3.3.1
4
4
  Summary: Vector based energy and material flow optimization framework in Python.
5
5
  Author-email: "Chair of Building Energy Systems and Heat Supply, TU Dresden" <peter.stange@tu-dresden.de>, Felix Bumann <felixbumann387@gmail.com>, Felix Panitz <baumbude@googlemail.com>, Peter Stange <peter.stange@tu-dresden.de>
6
6
  Maintainer-email: Felix Bumann <felixbumann387@gmail.com>, Peter Stange <peter.stange@tu-dresden.de>
@@ -67,7 +67,7 @@ Requires-Dist: networkx==3.0.0; extra == "dev"
67
67
  Requires-Dist: werkzeug==3.0.0; extra == "dev"
68
68
  Provides-Extra: docs
69
69
  Requires-Dist: mkdocs==1.6.1; extra == "docs"
70
- Requires-Dist: mkdocs-material==9.6.21; extra == "docs"
70
+ Requires-Dist: mkdocs-material==9.6.22; extra == "docs"
71
71
  Requires-Dist: mkdocstrings-python==1.18.2; extra == "docs"
72
72
  Requires-Dist: mkdocs-table-reader-plugin==3.1.0; extra == "docs"
73
73
  Requires-Dist: mkdocs-gen-files==0.5.0; extra == "docs"
@@ -1,26 +1,26 @@
1
1
  flixopt/__init__.py,sha256=_5d7Buc1ugaip5QbDGc9ebMO8LK0WWAjYHQMX2Th8P0,2217
2
2
  flixopt/aggregation.py,sha256=ZE0LcUAZ8xNet13YjxvvMw8BAL7Qo4TcJBwBCE2sHqE,16562
3
- flixopt/calculation.py,sha256=YkVzpWJ4lYxCReP7H-lBYrkNGrza_SggjqGMU3xVf7o,29605
3
+ flixopt/calculation.py,sha256=uWgWeU-eg7bbLris85oDtm6fyj7cHFI8CQ-U13vy2u0,29658
4
4
  flixopt/color_processing.py,sha256=bSq6iAnreiEBFz4Xf0AIUMyENJsWbJ-5xpiqM7_teUc,9027
5
5
  flixopt/commons.py,sha256=ZNlUN1z-h9OGHPo-s-n5OLlJaoPZKVGcAdRyGKpMk4M,1256
6
- flixopt/components.py,sha256=0EKnzoehrx4Wk-5b6NokHvmT1i7t1msi0JAMM5BA0WU,57711
6
+ flixopt/components.py,sha256=XYNjQF5IWN1buFfvY4HyWrRD1S3q9cq_ApyaOdT6BE4,58118
7
7
  flixopt/config.py,sha256=ScqPyYn_URJmOo_aQDViQ-TktF3TZPrcCSoBCQXVpXc,24591
8
8
  flixopt/core.py,sha256=OG789eUaS5Lu0CjJiMIdtaixqnV5ZtMiKfERjCPRTv8,26366
9
- flixopt/effects.py,sha256=1UZaqmjHEjMahbKBcmgXP3JojIRDhUZKjPgLgOAJvO0,34056
10
- flixopt/elements.py,sha256=92SQ3ax57I9VLjhe0-4L6u3jaYDylrKcmuSFGuzneqI,36371
9
+ flixopt/effects.py,sha256=YXx0Ou1Pu3xMugB8DvcqvqC2x8Vx7fDWdHuvn_Eh214,34307
10
+ flixopt/elements.py,sha256=N2RG3OMbGRO8qEROYd-_FsH0Vkx4C-ePii3YiHN0mqA,38712
11
11
  flixopt/features.py,sha256=kd-fMvADv8GXoKkrXObYjRJLN8toBG-5bOHTuh-59kk,25073
12
- flixopt/flow_system.py,sha256=HI4nOGRFjleumBcusjCU1Momw0DN7Q2oCgy0gU0dCug,40339
13
- flixopt/interface.py,sha256=ACWFIwdbjVN0x52QukKetpJgM0YGsWpvlBAtFnHgHa4,57925
14
- flixopt/io.py,sha256=1vjwFTyAr2ohkiwqE4qVX9juAG0l1wgxltcWcEPaFcQ,18895
12
+ flixopt/flow_system.py,sha256=7iSWYCItIQhwUqVZ3VUG_cIUUm0O-OeJbC0tQeyp0OU,43757
13
+ flixopt/interface.py,sha256=TEm1tF24cWwCbP_0yBhhH0aVy_j5Fbgl3LI49H5yOIE,58692
14
+ flixopt/io.py,sha256=Oh1pRA6H_HOeRzvFZtQ8zXlAjWv-J-lYOWCoKIB-n2M,33409
15
15
  flixopt/linear_converters.py,sha256=tcz5c1SI36hRFbCX-4NXced12ss9VETg5BE7zOdyeo4,22699
16
16
  flixopt/modeling.py,sha256=s0zipbblq-LJrSe7angKT3Imxgr3kIbprG98HUvmkzI,31322
17
17
  flixopt/network_app.py,sha256=LnVAlAgzL1BgMYLsJ20a62j6nQUmNccF1zo4ACUXzL4,29433
18
18
  flixopt/plotting.py,sha256=C_VyBVQIUP1HYt8roXk__Gz9m17cSSPikXZL4jidIpg,65024
19
- flixopt/results.py,sha256=gZAj-BQpb7CHp0CcYy99ZF4_IEZvJnQfTeOEZe2TdCU,118942
19
+ flixopt/results.py,sha256=b35Y8bkduFEbHFn_nHFc2PW7vjQzPWrM4mCC5rz0Njw,120436
20
20
  flixopt/solvers.py,sha256=m38Smc22MJfHYMiqfNf1MA3OmvbTRm5OWS9nECkDdQk,2355
21
- flixopt/structure.py,sha256=CrMqp1rzo45S-XJTVmJW4kQWh-a_ukz38uBp18LnBLU,47585
22
- flixopt-3.2.0.dist-info/licenses/LICENSE,sha256=HKsZnbrM_3Rvnr_u9cWSG90cBsj5_slaqI_z_qcxnGI,1118
23
- flixopt-3.2.0.dist-info/METADATA,sha256=uBnKopA20TTqYiHXdbxW1X3szcF4s4HS2JqBUXQ8cao,12855
24
- flixopt-3.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
- flixopt-3.2.0.dist-info/top_level.txt,sha256=fanTzb9NylIXfv6Ic7spU97fVmRgGDPKvI_91tw4S3E,8
26
- flixopt-3.2.0.dist-info/RECORD,,
21
+ flixopt/structure.py,sha256=ZB36753ei-VhbaONHLLms9ee_SOs_sEnJLkexRdwoa4,58141
22
+ flixopt-3.3.1.dist-info/licenses/LICENSE,sha256=HKsZnbrM_3Rvnr_u9cWSG90cBsj5_slaqI_z_qcxnGI,1118
23
+ flixopt-3.3.1.dist-info/METADATA,sha256=4HxgUkfhMLstct176MJPqpIvtx7Q1yIv_LlmvRlUF9o,12855
24
+ flixopt-3.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
+ flixopt-3.3.1.dist-info/top_level.txt,sha256=fanTzb9NylIXfv6Ic7spU97fVmRgGDPKvI_91tw4S3E,8
26
+ flixopt-3.3.1.dist-info/RECORD,,