flixopt 3.2.1__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/calculation.py +1 -1
- flixopt/components.py +10 -0
- flixopt/effects.py +23 -27
- flixopt/elements.py +54 -1
- flixopt/flow_system.py +139 -84
- flixopt/interface.py +23 -2
- flixopt/io.py +396 -12
- flixopt/results.py +48 -22
- flixopt/structure.py +366 -48
- {flixopt-3.2.1.dist-info → flixopt-3.3.1.dist-info}/METADATA +1 -1
- {flixopt-3.2.1.dist-info → flixopt-3.3.1.dist-info}/RECORD +14 -14
- {flixopt-3.2.1.dist-info → flixopt-3.3.1.dist-info}/WHEEL +0 -0
- {flixopt-3.2.1.dist-info → flixopt-3.3.1.dist-info}/licenses/LICENSE +0 -0
- {flixopt-3.2.1.dist-info → flixopt-3.3.1.dist-info}/top_level.txt +0 -0
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1440
|
+
result += f' * {name} [{type_name}] ({var_count}v/{con_count}c)\n'
|
|
1123
1441
|
|
|
1124
|
-
return
|
|
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.
|
|
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>
|
|
@@ -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=
|
|
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=
|
|
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=
|
|
10
|
-
flixopt/elements.py,sha256=
|
|
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=
|
|
13
|
-
flixopt/interface.py,sha256=
|
|
14
|
-
flixopt/io.py,sha256=
|
|
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=
|
|
19
|
+
flixopt/results.py,sha256=b35Y8bkduFEbHFn_nHFc2PW7vjQzPWrM4mCC5rz0Njw,120436
|
|
20
20
|
flixopt/solvers.py,sha256=m38Smc22MJfHYMiqfNf1MA3OmvbTRm5OWS9nECkDdQk,2355
|
|
21
|
-
flixopt/structure.py,sha256=
|
|
22
|
-
flixopt-3.
|
|
23
|
-
flixopt-3.
|
|
24
|
-
flixopt-3.
|
|
25
|
-
flixopt-3.
|
|
26
|
-
flixopt-3.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|