flixopt 1.0.12__py3-none-any.whl → 2.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.
- docs/examples/00-Minimal Example.md +5 -0
- docs/examples/01-Basic Example.md +5 -0
- docs/examples/02-Complex Example.md +10 -0
- docs/examples/03-Calculation Modes.md +5 -0
- docs/examples/index.md +5 -0
- docs/faq/contribute.md +49 -0
- docs/faq/index.md +3 -0
- docs/images/architecture_flixOpt-pre2.0.0.png +0 -0
- docs/images/architecture_flixOpt.png +0 -0
- docs/images/flixopt-icon.svg +1 -0
- docs/javascripts/mathjax.js +18 -0
- docs/release-notes/_template.txt +32 -0
- docs/release-notes/index.md +7 -0
- docs/release-notes/v2.0.0.md +93 -0
- docs/user-guide/Mathematical Notation/Bus.md +33 -0
- docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +132 -0
- docs/user-guide/Mathematical Notation/Flow.md +26 -0
- docs/user-guide/Mathematical Notation/LinearConverter.md +21 -0
- docs/user-guide/Mathematical Notation/Piecewise.md +49 -0
- docs/user-guide/Mathematical Notation/Storage.md +44 -0
- docs/user-guide/Mathematical Notation/index.md +22 -0
- docs/user-guide/Mathematical Notation/others.md +3 -0
- docs/user-guide/index.md +124 -0
- {flixOpt → flixopt}/__init__.py +5 -2
- {flixOpt → flixopt}/aggregation.py +113 -140
- flixopt/calculation.py +455 -0
- {flixOpt → flixopt}/commons.py +7 -4
- flixopt/components.py +630 -0
- {flixOpt → flixopt}/config.py +9 -8
- {flixOpt → flixopt}/config.yaml +3 -3
- flixopt/core.py +914 -0
- flixopt/effects.py +386 -0
- flixopt/elements.py +529 -0
- flixopt/features.py +1042 -0
- flixopt/flow_system.py +409 -0
- flixopt/interface.py +265 -0
- flixopt/io.py +308 -0
- flixopt/linear_converters.py +331 -0
- flixopt/plotting.py +1337 -0
- flixopt/results.py +898 -0
- flixopt/solvers.py +77 -0
- flixopt/structure.py +630 -0
- flixopt/utils.py +62 -0
- flixopt-2.0.0.dist-info/METADATA +145 -0
- flixopt-2.0.0.dist-info/RECORD +56 -0
- {flixopt-1.0.12.dist-info → flixopt-2.0.0.dist-info}/WHEEL +1 -1
- flixopt-2.0.0.dist-info/top_level.txt +6 -0
- pics/architecture_flixOpt-pre2.0.0.png +0 -0
- pics/architecture_flixOpt.png +0 -0
- pics/flixopt-icon.svg +1 -0
- pics/pics.pptx +0 -0
- scripts/gen_ref_pages.py +54 -0
- site/release-notes/_template.txt +32 -0
- flixOpt/calculation.py +0 -629
- flixOpt/components.py +0 -614
- flixOpt/core.py +0 -182
- flixOpt/effects.py +0 -410
- flixOpt/elements.py +0 -489
- flixOpt/features.py +0 -942
- flixOpt/flow_system.py +0 -351
- flixOpt/interface.py +0 -203
- flixOpt/linear_converters.py +0 -325
- flixOpt/math_modeling.py +0 -1145
- flixOpt/plotting.py +0 -712
- flixOpt/results.py +0 -563
- flixOpt/solvers.py +0 -21
- flixOpt/structure.py +0 -733
- flixOpt/utils.py +0 -134
- flixopt-1.0.12.dist-info/METADATA +0 -174
- flixopt-1.0.12.dist-info/RECORD +0 -29
- flixopt-1.0.12.dist-info/top_level.txt +0 -3
- {flixopt-1.0.12.dist-info → flixopt-2.0.0.dist-info/licenses}/LICENSE +0 -0
flixopt/calculation.py
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the Calculation functionality for the flixopt framework.
|
|
3
|
+
It is used to calculate a SystemModel for a given FlowSystem through a solver.
|
|
4
|
+
There are three different Calculation types:
|
|
5
|
+
1. FullCalculation: Calculates the SystemModel for the full FlowSystem
|
|
6
|
+
2. AggregatedCalculation: Calculates the SystemModel for the full FlowSystem, but aggregates the TimeSeriesData.
|
|
7
|
+
This simplifies the mathematical model and usually speeds up the solving process.
|
|
8
|
+
3. SegmentedCalculation: Solves a SystemModel for each individual Segment of the FlowSystem.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import math
|
|
13
|
+
import pathlib
|
|
14
|
+
import timeit
|
|
15
|
+
from typing import Any, Dict, List, Optional, Union
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
import pandas as pd
|
|
19
|
+
import yaml
|
|
20
|
+
|
|
21
|
+
from . import io as fx_io
|
|
22
|
+
from . import utils as utils
|
|
23
|
+
from .aggregation import AggregationModel, AggregationParameters
|
|
24
|
+
from .components import Storage
|
|
25
|
+
from .config import CONFIG
|
|
26
|
+
from .core import Scalar
|
|
27
|
+
from .elements import Component
|
|
28
|
+
from .features import InvestmentModel
|
|
29
|
+
from .flow_system import FlowSystem
|
|
30
|
+
from .results import CalculationResults, SegmentedCalculationResults
|
|
31
|
+
from .solvers import _Solver
|
|
32
|
+
from .structure import SystemModel, copy_and_convert_datatypes, get_compact_representation
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger('flixopt')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Calculation:
|
|
38
|
+
"""
|
|
39
|
+
class for defined way of solving a flow_system optimization
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
name: str,
|
|
45
|
+
flow_system: FlowSystem,
|
|
46
|
+
active_timesteps: Optional[pd.DatetimeIndex] = None,
|
|
47
|
+
folder: Optional[pathlib.Path] = None,
|
|
48
|
+
):
|
|
49
|
+
"""
|
|
50
|
+
Args:
|
|
51
|
+
name: name of calculation
|
|
52
|
+
flow_system: flow_system which should be calculated
|
|
53
|
+
active_timesteps: list with indices, which should be used for calculation. If None, then all timesteps are used.
|
|
54
|
+
folder: folder where results should be saved. If None, then the current working directory is used.
|
|
55
|
+
"""
|
|
56
|
+
self.name = name
|
|
57
|
+
self.flow_system = flow_system
|
|
58
|
+
self.model: Optional[SystemModel] = None
|
|
59
|
+
self.active_timesteps = active_timesteps
|
|
60
|
+
|
|
61
|
+
self.durations = {'modeling': 0.0, 'solving': 0.0, 'saving': 0.0}
|
|
62
|
+
self.folder = pathlib.Path.cwd() / 'results' if folder is None else pathlib.Path(folder)
|
|
63
|
+
self.results: Optional[CalculationResults] = None
|
|
64
|
+
|
|
65
|
+
if not self.folder.exists():
|
|
66
|
+
try:
|
|
67
|
+
self.folder.mkdir(parents=False)
|
|
68
|
+
except FileNotFoundError as e:
|
|
69
|
+
raise FileNotFoundError(
|
|
70
|
+
f'Folder {self.folder} and its parent do not exist. Please create them first.'
|
|
71
|
+
) from e
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def main_results(self) -> Dict[str, Union[Scalar, Dict]]:
|
|
75
|
+
from flixopt.features import InvestmentModel
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
'Objective': self.model.objective.value,
|
|
79
|
+
'Penalty': float(self.model.effects.penalty.total.solution.values),
|
|
80
|
+
'Effects': {
|
|
81
|
+
f'{effect.label} [{effect.unit}]': {
|
|
82
|
+
'operation': float(effect.model.operation.total.solution.values),
|
|
83
|
+
'invest': float(effect.model.invest.total.solution.values),
|
|
84
|
+
'total': float(effect.model.total.solution.values),
|
|
85
|
+
}
|
|
86
|
+
for effect in self.flow_system.effects
|
|
87
|
+
},
|
|
88
|
+
'Invest-Decisions': {
|
|
89
|
+
'Invested': {
|
|
90
|
+
model.label_of_element: float(model.size.solution)
|
|
91
|
+
for component in self.flow_system.components.values()
|
|
92
|
+
for model in component.model.all_sub_models
|
|
93
|
+
if isinstance(model, InvestmentModel) and float(model.size.solution) >= CONFIG.modeling.EPSILON
|
|
94
|
+
},
|
|
95
|
+
'Not invested': {
|
|
96
|
+
model.label_of_element: float(model.size.solution)
|
|
97
|
+
for component in self.flow_system.components.values()
|
|
98
|
+
for model in component.model.all_sub_models
|
|
99
|
+
if isinstance(model, InvestmentModel) and float(model.size.solution) < CONFIG.modeling.EPSILON
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
'Buses with excess': [
|
|
103
|
+
{
|
|
104
|
+
bus.label_full: {
|
|
105
|
+
'input': float(np.sum(bus.model.excess_input.solution.values)),
|
|
106
|
+
'output': float(np.sum(bus.model.excess_output.solution.values)),
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for bus in self.flow_system.buses.values()
|
|
110
|
+
if bus.with_excess
|
|
111
|
+
and (
|
|
112
|
+
float(np.sum(bus.model.excess_input.solution.values)) > 1e-3
|
|
113
|
+
or float(np.sum(bus.model.excess_output.solution.values)) > 1e-3
|
|
114
|
+
)
|
|
115
|
+
],
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def summary(self):
|
|
120
|
+
return {
|
|
121
|
+
'Name': self.name,
|
|
122
|
+
'Number of timesteps': len(self.flow_system.time_series_collection.timesteps),
|
|
123
|
+
'Calculation Type': self.__class__.__name__,
|
|
124
|
+
'Constraints': self.model.constraints.ncons,
|
|
125
|
+
'Variables': self.model.variables.nvars,
|
|
126
|
+
'Main Results': self.main_results,
|
|
127
|
+
'Durations': self.durations,
|
|
128
|
+
'Config': CONFIG.to_dict(),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class FullCalculation(Calculation):
|
|
133
|
+
"""
|
|
134
|
+
class for defined way of solving a flow_system optimization
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def do_modeling(self) -> SystemModel:
|
|
138
|
+
t_start = timeit.default_timer()
|
|
139
|
+
self._activate_time_series()
|
|
140
|
+
|
|
141
|
+
self.model = self.flow_system.create_model()
|
|
142
|
+
self.model.do_modeling()
|
|
143
|
+
|
|
144
|
+
self.durations['modeling'] = round(timeit.default_timer() - t_start, 2)
|
|
145
|
+
return self.model
|
|
146
|
+
|
|
147
|
+
def solve(self, solver: _Solver, log_file: Optional[pathlib.Path] = None, log_main_results: bool = True):
|
|
148
|
+
t_start = timeit.default_timer()
|
|
149
|
+
|
|
150
|
+
self.model.solve(
|
|
151
|
+
log_fn=pathlib.Path(log_file) if log_file is not None else self.folder / f'{self.name}.log',
|
|
152
|
+
solver_name=solver.name,
|
|
153
|
+
**solver.options,
|
|
154
|
+
)
|
|
155
|
+
self.durations['solving'] = round(timeit.default_timer() - t_start, 2)
|
|
156
|
+
|
|
157
|
+
if self.model.status == 'warning':
|
|
158
|
+
# Save the model and the flow_system to file in case of infeasibility
|
|
159
|
+
paths = fx_io.CalculationResultsPaths(self.folder, self.name)
|
|
160
|
+
from .io import document_linopy_model
|
|
161
|
+
|
|
162
|
+
document_linopy_model(self.model, paths.model_documentation)
|
|
163
|
+
self.flow_system.to_netcdf(paths.flow_system)
|
|
164
|
+
raise RuntimeError(
|
|
165
|
+
f'Model was infeasible. Please check {paths.model_documentation=} and {paths.flow_system=} for more information.'
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Log the formatted output
|
|
169
|
+
if log_main_results:
|
|
170
|
+
logger.info(f'{" Main Results ":#^80}')
|
|
171
|
+
logger.info(
|
|
172
|
+
'\n'
|
|
173
|
+
+ yaml.dump(
|
|
174
|
+
utils.round_floats(self.main_results),
|
|
175
|
+
default_flow_style=False,
|
|
176
|
+
sort_keys=False,
|
|
177
|
+
allow_unicode=True,
|
|
178
|
+
indent=4,
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
self.results = CalculationResults.from_calculation(self)
|
|
183
|
+
|
|
184
|
+
def _activate_time_series(self):
|
|
185
|
+
self.flow_system.transform_data()
|
|
186
|
+
self.flow_system.time_series_collection.activate_timesteps(
|
|
187
|
+
active_timesteps=self.active_timesteps,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class AggregatedCalculation(FullCalculation):
|
|
192
|
+
"""
|
|
193
|
+
class for defined way of solving a flow_system optimization
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
def __init__(
|
|
197
|
+
self,
|
|
198
|
+
name: str,
|
|
199
|
+
flow_system: FlowSystem,
|
|
200
|
+
aggregation_parameters: AggregationParameters,
|
|
201
|
+
components_to_clusterize: Optional[List[Component]] = None,
|
|
202
|
+
active_timesteps: Optional[pd.DatetimeIndex] = None,
|
|
203
|
+
folder: Optional[pathlib.Path] = None,
|
|
204
|
+
):
|
|
205
|
+
"""
|
|
206
|
+
Class for Optimizing the `FlowSystem` including:
|
|
207
|
+
1. Aggregating TimeSeriesData via typical periods using tsam.
|
|
208
|
+
2. Equalizing variables of typical periods.
|
|
209
|
+
Args:
|
|
210
|
+
name: name of calculation
|
|
211
|
+
flow_system: flow_system which should be calculated
|
|
212
|
+
aggregation_parameters: Parameters for aggregation. See documentation of AggregationParameters class.
|
|
213
|
+
components_to_clusterize: List of Components to perform aggregation on. If None, then all components are aggregated.
|
|
214
|
+
This means, teh variables in the components are equalized to each other, according to the typical periods
|
|
215
|
+
computed in the DataAggregation
|
|
216
|
+
active_timesteps: pd.DatetimeIndex or None
|
|
217
|
+
list with indices, which should be used for calculation. If None, then all timesteps are used.
|
|
218
|
+
folder: folder where results should be saved. If None, then the current working directory is used.
|
|
219
|
+
"""
|
|
220
|
+
super().__init__(name, flow_system, active_timesteps, folder=folder)
|
|
221
|
+
self.aggregation_parameters = aggregation_parameters
|
|
222
|
+
self.components_to_clusterize = components_to_clusterize
|
|
223
|
+
self.aggregation = None
|
|
224
|
+
|
|
225
|
+
def do_modeling(self) -> SystemModel:
|
|
226
|
+
t_start = timeit.default_timer()
|
|
227
|
+
self._activate_time_series()
|
|
228
|
+
self._perform_aggregation()
|
|
229
|
+
|
|
230
|
+
# Model the System
|
|
231
|
+
self.model = self.flow_system.create_model()
|
|
232
|
+
self.model.do_modeling()
|
|
233
|
+
# Add Aggregation Model after modeling the rest
|
|
234
|
+
self.aggregation = AggregationModel(
|
|
235
|
+
self.model, self.aggregation_parameters, self.flow_system, self.aggregation, self.components_to_clusterize
|
|
236
|
+
)
|
|
237
|
+
self.aggregation.do_modeling()
|
|
238
|
+
self.durations['modeling'] = round(timeit.default_timer() - t_start, 2)
|
|
239
|
+
return self.model
|
|
240
|
+
|
|
241
|
+
def _perform_aggregation(self):
|
|
242
|
+
from .aggregation import Aggregation
|
|
243
|
+
|
|
244
|
+
t_start_agg = timeit.default_timer()
|
|
245
|
+
|
|
246
|
+
# Validation
|
|
247
|
+
dt_min, dt_max = (
|
|
248
|
+
np.min(self.flow_system.time_series_collection.hours_per_timestep),
|
|
249
|
+
np.max(self.flow_system.time_series_collection.hours_per_timestep),
|
|
250
|
+
)
|
|
251
|
+
if not dt_min == dt_max:
|
|
252
|
+
raise ValueError(
|
|
253
|
+
f'Aggregation failed due to inconsistent time step sizes:'
|
|
254
|
+
f'delta_t varies from {dt_min} to {dt_max} hours.'
|
|
255
|
+
)
|
|
256
|
+
steps_per_period = (
|
|
257
|
+
self.aggregation_parameters.hours_per_period
|
|
258
|
+
/ self.flow_system.time_series_collection.hours_per_timestep.max()
|
|
259
|
+
)
|
|
260
|
+
is_integer = (
|
|
261
|
+
self.aggregation_parameters.hours_per_period
|
|
262
|
+
% self.flow_system.time_series_collection.hours_per_timestep.max()
|
|
263
|
+
).item() == 0
|
|
264
|
+
if not (steps_per_period.size == 1 and is_integer):
|
|
265
|
+
raise ValueError(
|
|
266
|
+
f'The selected {self.aggregation_parameters.hours_per_period=} does not match the time '
|
|
267
|
+
f'step size of {dt_min} hours). It must be a multiple of {dt_min} hours.'
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
logger.info(f'{"":#^80}')
|
|
271
|
+
logger.info(f'{" Aggregating TimeSeries Data ":#^80}')
|
|
272
|
+
|
|
273
|
+
# Aggregation - creation of aggregated timeseries:
|
|
274
|
+
self.aggregation = Aggregation(
|
|
275
|
+
original_data=self.flow_system.time_series_collection.to_dataframe(
|
|
276
|
+
include_extra_timestep=False
|
|
277
|
+
), # Exclude last row (NaN)
|
|
278
|
+
hours_per_time_step=float(dt_min),
|
|
279
|
+
hours_per_period=self.aggregation_parameters.hours_per_period,
|
|
280
|
+
nr_of_periods=self.aggregation_parameters.nr_of_periods,
|
|
281
|
+
weights=self.flow_system.time_series_collection.calculate_aggregation_weights(),
|
|
282
|
+
time_series_for_high_peaks=self.aggregation_parameters.labels_for_high_peaks,
|
|
283
|
+
time_series_for_low_peaks=self.aggregation_parameters.labels_for_low_peaks,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
self.aggregation.cluster()
|
|
287
|
+
self.aggregation.plot(show=True, save=self.folder / 'aggregation.html')
|
|
288
|
+
if self.aggregation_parameters.aggregate_data_and_fix_non_binary_vars:
|
|
289
|
+
self.flow_system.time_series_collection.insert_new_data(
|
|
290
|
+
self.aggregation.aggregated_data, include_extra_timestep=False
|
|
291
|
+
)
|
|
292
|
+
self.durations['aggregation'] = round(timeit.default_timer() - t_start_agg, 2)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class SegmentedCalculation(Calculation):
|
|
296
|
+
def __init__(
|
|
297
|
+
self,
|
|
298
|
+
name: str,
|
|
299
|
+
flow_system: FlowSystem,
|
|
300
|
+
timesteps_per_segment: int,
|
|
301
|
+
overlap_timesteps: int,
|
|
302
|
+
nr_of_previous_values: int = 1,
|
|
303
|
+
folder: Optional[pathlib.Path] = None,
|
|
304
|
+
):
|
|
305
|
+
"""
|
|
306
|
+
Dividing and Modeling the problem in (overlapping) segments.
|
|
307
|
+
The final values of each Segment are recognized by the following segment, effectively coupling
|
|
308
|
+
charge_states and flow_rates between segments.
|
|
309
|
+
Because of this intersection, both modeling and solving is done in one step
|
|
310
|
+
|
|
311
|
+
Take care:
|
|
312
|
+
Parameters like InvestParameters, sum_of_flow_hours and other restrictions over the total time_series
|
|
313
|
+
don't really work in this Calculation. Lower bounds to such SUMS can lead to weird results.
|
|
314
|
+
This is NOT yet explicitly checked for...
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
name: name of calculation
|
|
318
|
+
flow_system: flow_system which should be calculated
|
|
319
|
+
timesteps_per_segment: The number of time_steps per individual segment (without the overlap)
|
|
320
|
+
overlap_timesteps: The number of time_steps that are added to each individual model. Used for better
|
|
321
|
+
results of storages)
|
|
322
|
+
folder: folder where results should be saved. If None, then the current working directory is used.
|
|
323
|
+
"""
|
|
324
|
+
super().__init__(name, flow_system, folder=folder)
|
|
325
|
+
self.timesteps_per_segment = timesteps_per_segment
|
|
326
|
+
self.overlap_timesteps = overlap_timesteps
|
|
327
|
+
self.nr_of_previous_values = nr_of_previous_values
|
|
328
|
+
self.sub_calculations: List[FullCalculation] = []
|
|
329
|
+
|
|
330
|
+
self.all_timesteps = self.flow_system.time_series_collection.all_timesteps
|
|
331
|
+
self.all_timesteps_extra = self.flow_system.time_series_collection.all_timesteps_extra
|
|
332
|
+
|
|
333
|
+
self.segment_names = [
|
|
334
|
+
f'Segment_{i + 1}' for i in range(math.ceil(len(self.all_timesteps) / self.timesteps_per_segment))
|
|
335
|
+
]
|
|
336
|
+
self.active_timesteps_per_segment = self._calculate_timesteps_of_segment()
|
|
337
|
+
|
|
338
|
+
assert timesteps_per_segment > 2, 'The Segment length must be greater 2, due to unwanted internal side effects'
|
|
339
|
+
assert self.timesteps_per_segment_with_overlap <= len(self.all_timesteps), (
|
|
340
|
+
f'{self.timesteps_per_segment_with_overlap=} cant be greater than the total length {len(self.all_timesteps)}'
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
self.flow_system._connect_network() # Connect network to ensure that all FLows know their Component
|
|
344
|
+
# Storing all original start values
|
|
345
|
+
self._original_start_values = {
|
|
346
|
+
**{flow.label_full: flow.previous_flow_rate for flow in self.flow_system.flows.values()},
|
|
347
|
+
**{
|
|
348
|
+
comp.label_full: comp.initial_charge_state
|
|
349
|
+
for comp in self.flow_system.components.values()
|
|
350
|
+
if isinstance(comp, Storage)
|
|
351
|
+
},
|
|
352
|
+
}
|
|
353
|
+
self._transfered_start_values: List[Dict[str, Any]] = []
|
|
354
|
+
|
|
355
|
+
def do_modeling_and_solve(
|
|
356
|
+
self, solver: _Solver, log_file: Optional[pathlib.Path] = None, log_main_results: bool = False
|
|
357
|
+
):
|
|
358
|
+
logger.info(f'{"":#^80}')
|
|
359
|
+
logger.info(f'{" Segmented Solving ":#^80}')
|
|
360
|
+
|
|
361
|
+
for i, (segment_name, timesteps_of_segment) in enumerate(
|
|
362
|
+
zip(self.segment_names, self.active_timesteps_per_segment, strict=False)
|
|
363
|
+
):
|
|
364
|
+
if self.sub_calculations:
|
|
365
|
+
self._transfer_start_values(i)
|
|
366
|
+
|
|
367
|
+
logger.info(
|
|
368
|
+
f'{segment_name} [{i + 1:>2}/{len(self.segment_names):<2}] '
|
|
369
|
+
f'({timesteps_of_segment[0]} -> {timesteps_of_segment[-1]}):'
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
calculation = FullCalculation(
|
|
373
|
+
f'{self.name}-{segment_name}', self.flow_system, active_timesteps=timesteps_of_segment
|
|
374
|
+
)
|
|
375
|
+
self.sub_calculations.append(calculation)
|
|
376
|
+
calculation.do_modeling()
|
|
377
|
+
invest_elements = [
|
|
378
|
+
model.label_full
|
|
379
|
+
for component in self.flow_system.components.values()
|
|
380
|
+
for model in component.model.all_sub_models
|
|
381
|
+
if isinstance(model, InvestmentModel)
|
|
382
|
+
]
|
|
383
|
+
if invest_elements:
|
|
384
|
+
logger.critical(
|
|
385
|
+
f'Investments are not supported in Segmented Calculation! '
|
|
386
|
+
f'Following InvestmentModels were found: {invest_elements}'
|
|
387
|
+
)
|
|
388
|
+
calculation.solve(
|
|
389
|
+
solver,
|
|
390
|
+
log_file=pathlib.Path(log_file) if log_file is not None else self.folder / f'{self.name}.log',
|
|
391
|
+
log_main_results=log_main_results,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
self._reset_start_values()
|
|
395
|
+
|
|
396
|
+
for calc in self.sub_calculations:
|
|
397
|
+
for key, value in calc.durations.items():
|
|
398
|
+
self.durations[key] += value
|
|
399
|
+
|
|
400
|
+
self.results = SegmentedCalculationResults.from_calculation(self)
|
|
401
|
+
|
|
402
|
+
def _transfer_start_values(self, segment_index: int):
|
|
403
|
+
"""
|
|
404
|
+
This function gets the last values of the previous solved segment and
|
|
405
|
+
inserts them as start values for the next segment
|
|
406
|
+
"""
|
|
407
|
+
timesteps_of_prior_segment = self.active_timesteps_per_segment[segment_index - 1]
|
|
408
|
+
|
|
409
|
+
start = self.active_timesteps_per_segment[segment_index][0]
|
|
410
|
+
start_previous_values = timesteps_of_prior_segment[self.timesteps_per_segment - self.nr_of_previous_values]
|
|
411
|
+
end_previous_values = timesteps_of_prior_segment[self.timesteps_per_segment - 1]
|
|
412
|
+
|
|
413
|
+
logger.debug(
|
|
414
|
+
f'start of next segment: {start}. indices of previous values: {start_previous_values}:{end_previous_values}'
|
|
415
|
+
)
|
|
416
|
+
start_values_of_this_segment = {}
|
|
417
|
+
for flow in self.flow_system.flows.values():
|
|
418
|
+
flow.previous_flow_rate = flow.model.flow_rate.solution.sel(
|
|
419
|
+
time=slice(start_previous_values, end_previous_values)
|
|
420
|
+
).values
|
|
421
|
+
start_values_of_this_segment[flow.label_full] = flow.previous_flow_rate
|
|
422
|
+
for comp in self.flow_system.components.values():
|
|
423
|
+
if isinstance(comp, Storage):
|
|
424
|
+
comp.initial_charge_state = comp.model.charge_state.solution.sel(time=start).item()
|
|
425
|
+
start_values_of_this_segment[comp.label_full] = comp.initial_charge_state
|
|
426
|
+
|
|
427
|
+
self._transfered_start_values.append(start_values_of_this_segment)
|
|
428
|
+
|
|
429
|
+
def _reset_start_values(self):
|
|
430
|
+
"""This resets the start values of all Elements to its original state"""
|
|
431
|
+
for flow in self.flow_system.flows.values():
|
|
432
|
+
flow.previous_flow_rate = self._original_start_values[flow.label_full]
|
|
433
|
+
for comp in self.flow_system.components.values():
|
|
434
|
+
if isinstance(comp, Storage):
|
|
435
|
+
comp.initial_charge_state = self._original_start_values[comp.label_full]
|
|
436
|
+
|
|
437
|
+
def _calculate_timesteps_of_segment(self) -> List[pd.DatetimeIndex]:
|
|
438
|
+
active_timesteps_per_segment = []
|
|
439
|
+
for i, _ in enumerate(self.segment_names):
|
|
440
|
+
start = self.timesteps_per_segment * i
|
|
441
|
+
end = min(start + self.timesteps_per_segment_with_overlap, len(self.all_timesteps))
|
|
442
|
+
active_timesteps_per_segment.append(self.all_timesteps[start:end])
|
|
443
|
+
return active_timesteps_per_segment
|
|
444
|
+
|
|
445
|
+
@property
|
|
446
|
+
def timesteps_per_segment_with_overlap(self):
|
|
447
|
+
return self.timesteps_per_segment + self.overlap_timesteps
|
|
448
|
+
|
|
449
|
+
@property
|
|
450
|
+
def start_values_of_segments(self) -> Dict[int, Dict[str, Any]]:
|
|
451
|
+
"""Gives an overview of the start values of all Segments"""
|
|
452
|
+
return {
|
|
453
|
+
0: {element.label_full: value for element, value in self._original_start_values.items()},
|
|
454
|
+
**{i: start_values for i, start_values in enumerate(self._transfered_start_values, 1)},
|
|
455
|
+
}
|
{flixOpt → flixopt}/commons.py
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
This module makes the commonly used classes and functions available in the
|
|
2
|
+
This module makes the commonly used classes and functions available in the flixopt framework.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from . import linear_converters, plotting, results, solvers
|
|
@@ -17,8 +17,8 @@ from .config import CONFIG, change_logging_level
|
|
|
17
17
|
from .core import TimeSeriesData
|
|
18
18
|
from .effects import Effect
|
|
19
19
|
from .elements import Bus, Flow
|
|
20
|
-
from .flow_system import FlowSystem
|
|
21
|
-
from .interface import InvestParameters, OnOffParameters
|
|
20
|
+
from .flow_system import FlowSystem
|
|
21
|
+
from .interface import InvestParameters, OnOffParameters, Piece, Piecewise, PiecewiseConversion, PiecewiseEffects
|
|
22
22
|
|
|
23
23
|
__all__ = [
|
|
24
24
|
'TimeSeriesData',
|
|
@@ -34,12 +34,15 @@ __all__ = [
|
|
|
34
34
|
'LinearConverter',
|
|
35
35
|
'Transmission',
|
|
36
36
|
'FlowSystem',
|
|
37
|
-
'create_datetime_array',
|
|
38
37
|
'FullCalculation',
|
|
39
38
|
'SegmentedCalculation',
|
|
40
39
|
'AggregatedCalculation',
|
|
41
40
|
'InvestParameters',
|
|
42
41
|
'OnOffParameters',
|
|
42
|
+
'Piece',
|
|
43
|
+
'Piecewise',
|
|
44
|
+
'PiecewiseConversion',
|
|
45
|
+
'PiecewiseEffects',
|
|
43
46
|
'AggregationParameters',
|
|
44
47
|
'plotting',
|
|
45
48
|
'results',
|