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
|
@@ -1,42 +1,41 @@
|
|
|
1
1
|
"""
|
|
2
|
-
This module contains the Aggregation functionality for the
|
|
2
|
+
This module contains the Aggregation functionality for the flixopt framework.
|
|
3
3
|
Through this, aggregating TimeSeriesData is possible.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import copy
|
|
7
7
|
import logging
|
|
8
|
+
import pathlib
|
|
8
9
|
import timeit
|
|
9
10
|
import warnings
|
|
10
|
-
from
|
|
11
|
-
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
|
11
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union
|
|
12
12
|
|
|
13
|
+
import linopy
|
|
13
14
|
import numpy as np
|
|
14
15
|
import pandas as pd
|
|
15
16
|
|
|
16
17
|
try:
|
|
17
18
|
import tsam.timeseriesaggregation as tsam
|
|
19
|
+
|
|
18
20
|
TSAM_AVAILABLE = True
|
|
19
21
|
except ImportError:
|
|
20
22
|
TSAM_AVAILABLE = False
|
|
21
23
|
|
|
22
24
|
from .components import Storage
|
|
23
|
-
from .core import
|
|
25
|
+
from .core import Scalar, TimeSeriesData
|
|
24
26
|
from .elements import Component
|
|
25
27
|
from .flow_system import FlowSystem
|
|
26
|
-
from .math_modeling import Equation, Variable, VariableTS
|
|
27
28
|
from .structure import (
|
|
28
29
|
Element,
|
|
29
|
-
|
|
30
|
+
Model,
|
|
30
31
|
SystemModel,
|
|
31
|
-
create_equation,
|
|
32
|
-
create_variable,
|
|
33
32
|
)
|
|
34
33
|
|
|
35
34
|
if TYPE_CHECKING:
|
|
36
35
|
import plotly.graph_objects as go
|
|
37
36
|
|
|
38
37
|
warnings.filterwarnings('ignore', category=DeprecationWarning)
|
|
39
|
-
logger = logging.getLogger('
|
|
38
|
+
logger = logging.getLogger('flixopt')
|
|
40
39
|
|
|
41
40
|
|
|
42
41
|
class Aggregation:
|
|
@@ -47,24 +46,27 @@ class Aggregation:
|
|
|
47
46
|
def __init__(
|
|
48
47
|
self,
|
|
49
48
|
original_data: pd.DataFrame,
|
|
50
|
-
hours_per_time_step:
|
|
51
|
-
hours_per_period:
|
|
49
|
+
hours_per_time_step: Scalar,
|
|
50
|
+
hours_per_period: Scalar,
|
|
52
51
|
nr_of_periods: int = 8,
|
|
53
52
|
weights: Dict[str, float] = None,
|
|
54
53
|
time_series_for_high_peaks: List[str] = None,
|
|
55
54
|
time_series_for_low_peaks: List[str] = None,
|
|
56
55
|
):
|
|
57
56
|
"""
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
Args:
|
|
58
|
+
original_data: The original data to aggregate
|
|
59
|
+
hours_per_time_step: The duration of each timestep in hours.
|
|
60
|
+
hours_per_period: The duration of each period in hours.
|
|
61
|
+
nr_of_periods: The number of typical periods to use in the aggregation.
|
|
62
|
+
weights: The weights for aggregation. If None, all time series are equally weighted.
|
|
63
|
+
time_series_for_high_peaks: List of time series to use for explicitly selecting periods with high values.
|
|
64
|
+
time_series_for_low_peaks: List of time series to use for explicitly selecting periods with low values.
|
|
64
65
|
"""
|
|
65
66
|
if not TSAM_AVAILABLE:
|
|
66
|
-
raise ImportError(
|
|
67
|
-
|
|
67
|
+
raise ImportError(
|
|
68
|
+
"The 'tsam' package is required for clustering functionality. Install it with 'pip install tsam'."
|
|
69
|
+
)
|
|
68
70
|
self.original_data = copy.deepcopy(original_data)
|
|
69
71
|
self.hours_per_time_step = hours_per_time_step
|
|
70
72
|
self.hours_per_period = hours_per_period
|
|
@@ -93,7 +95,7 @@ class Aggregation:
|
|
|
93
95
|
extremePeriodMethod='new_cluster_center'
|
|
94
96
|
if self.use_extreme_periods
|
|
95
97
|
else 'None', # Wenn Extremperioden eingebunden werden sollen, nutze die Methode 'new_cluster_center' aus tsam
|
|
96
|
-
weightDict=self.weights,
|
|
98
|
+
weightDict={name: weight for name, weight in self.weights.items() if name in self.original_data.columns},
|
|
97
99
|
addPeakMax=self.time_series_for_high_peaks,
|
|
98
100
|
addPeakMin=self.time_series_for_low_peaks,
|
|
99
101
|
)
|
|
@@ -139,7 +141,7 @@ class Aggregation:
|
|
|
139
141
|
def use_extreme_periods(self):
|
|
140
142
|
return self.time_series_for_high_peaks or self.time_series_for_low_peaks
|
|
141
143
|
|
|
142
|
-
def plot(self, colormap: str = 'viridis', show: bool = True) -> 'go.Figure':
|
|
144
|
+
def plot(self, colormap: str = 'viridis', show: bool = True, save: Optional[pathlib.Path] = None) -> 'go.Figure':
|
|
143
145
|
from . import plotting
|
|
144
146
|
|
|
145
147
|
df_org = self.original_data.copy().rename(
|
|
@@ -151,11 +153,21 @@ class Aggregation:
|
|
|
151
153
|
fig = plotting.with_plotly(df_org, 'line', colors=colormap)
|
|
152
154
|
for trace in fig.data:
|
|
153
155
|
trace.update(dict(line=dict(dash='dash')))
|
|
154
|
-
fig = plotting.with_plotly(df_agg, 'line', colors=colormap,
|
|
156
|
+
fig = plotting.with_plotly(df_agg, 'line', colors=colormap, fig=fig)
|
|
155
157
|
|
|
156
158
|
fig.update_layout(
|
|
157
159
|
title='Original vs Aggregated Data (original = ---)', xaxis_title='Index', yaxis_title='Value'
|
|
158
160
|
)
|
|
161
|
+
|
|
162
|
+
plotting.export_figure(
|
|
163
|
+
figure_like=fig,
|
|
164
|
+
default_path=pathlib.Path('aggregated data.html'),
|
|
165
|
+
default_filetype='.html',
|
|
166
|
+
user_path=None if isinstance(save, bool) else pathlib.Path(save),
|
|
167
|
+
show=show,
|
|
168
|
+
save=True if save else False,
|
|
169
|
+
)
|
|
170
|
+
|
|
159
171
|
return fig
|
|
160
172
|
|
|
161
173
|
def get_cluster_indices(self) -> Dict[str, List[np.ndarray]]:
|
|
@@ -217,60 +229,6 @@ class Aggregation:
|
|
|
217
229
|
return np.array(idx_var1), np.array(idx_var2)
|
|
218
230
|
|
|
219
231
|
|
|
220
|
-
class TimeSeriesCollection:
|
|
221
|
-
def __init__(self, time_series_list: List[TimeSeries]):
|
|
222
|
-
self.time_series_list = time_series_list
|
|
223
|
-
self.group_weights: Dict[str, float] = {}
|
|
224
|
-
self._unique_labels()
|
|
225
|
-
self._calculate_aggregation_weigths()
|
|
226
|
-
self.weights: Dict[str, float] = {
|
|
227
|
-
time_series.label: time_series.aggregation_weight for time_series in self.time_series_list
|
|
228
|
-
}
|
|
229
|
-
self.data: Dict[str, np.ndarray] = {
|
|
230
|
-
time_series.label: time_series.active_data for time_series in self.time_series_list
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if np.all(np.isclose(list(self.weights.values()), 1, atol=1e-6)):
|
|
234
|
-
logger.info('All Aggregation weights were set to 1')
|
|
235
|
-
|
|
236
|
-
def _calculate_aggregation_weigths(self):
|
|
237
|
-
"""Calculates the aggergation weights of all TimeSeries. Necessary to use groups"""
|
|
238
|
-
groups = [
|
|
239
|
-
time_series.aggregation_group
|
|
240
|
-
for time_series in self.time_series_list
|
|
241
|
-
if time_series.aggregation_group is not None
|
|
242
|
-
]
|
|
243
|
-
group_size = dict(Counter(groups))
|
|
244
|
-
self.group_weights = {group: 1 / size for group, size in group_size.items()}
|
|
245
|
-
for time_series in self.time_series_list:
|
|
246
|
-
time_series.aggregation_weight = self.group_weights.get(
|
|
247
|
-
time_series.aggregation_group, time_series.aggregation_weight or 1
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
def _unique_labels(self):
|
|
251
|
-
"""Makes sure every label of the TimeSeries in time_series_list is unique"""
|
|
252
|
-
label_counts = Counter([time_series.label for time_series in self.time_series_list])
|
|
253
|
-
duplicates = [label for label, count in label_counts.items() if count > 1]
|
|
254
|
-
assert duplicates == [], 'Duplicate TimeSeries labels found: {}.'.format(', '.join(duplicates))
|
|
255
|
-
|
|
256
|
-
def insert_data(self, data: Dict[str, np.ndarray]):
|
|
257
|
-
for time_series in self.time_series_list:
|
|
258
|
-
if time_series.label in data:
|
|
259
|
-
time_series.aggregated_data = data[time_series.label]
|
|
260
|
-
logger.debug(f'Inserted data for {time_series.label}')
|
|
261
|
-
|
|
262
|
-
def description(self) -> str:
|
|
263
|
-
# TODO:
|
|
264
|
-
result = f'{len(self.time_series_list)} TimeSeries used for aggregation:\n'
|
|
265
|
-
for time_series in self.time_series_list:
|
|
266
|
-
result += f' -> {time_series.label} (weight: {time_series.aggregation_weight:.4f}; group: "{time_series.aggregation_group}")\n'
|
|
267
|
-
if self.group_weights:
|
|
268
|
-
result += f'Aggregation_Groups: {list(self.group_weights.keys())}\n'
|
|
269
|
-
else:
|
|
270
|
-
result += 'Warning!: no agg_types defined, i.e. all TS have weight 1 (or explicitly given weight)!\n'
|
|
271
|
-
return result
|
|
272
|
-
|
|
273
|
-
|
|
274
232
|
class AggregationParameters:
|
|
275
233
|
def __init__(
|
|
276
234
|
self,
|
|
@@ -286,29 +244,20 @@ class AggregationParameters:
|
|
|
286
244
|
"""
|
|
287
245
|
Initializes aggregation parameters for time series data
|
|
288
246
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
Specifies the maximum percentage (0–100) of binary values within each period
|
|
304
|
-
that can deviate as "free variables", chosen by the solver (default is 0).
|
|
305
|
-
This allows binary variables to be 'partly equated' between aggregated periods.
|
|
306
|
-
penalty_of_period_freedom : float, optional
|
|
307
|
-
The penalty associated with each "free variable"; defaults to 0. Added to Penalty
|
|
308
|
-
time_series_for_high_peaks : list of TimeSeriesData
|
|
309
|
-
List of time series to use for explicitly selecting periods with high values.
|
|
310
|
-
time_series_for_low_peaks : list of TimeSeriesData
|
|
311
|
-
List of time series to use for explicitly selecting periods with low values.
|
|
247
|
+
Args:
|
|
248
|
+
hours_per_period: Duration of each period in hours.
|
|
249
|
+
nr_of_periods: Number of typical periods to use in the aggregation.
|
|
250
|
+
fix_storage_flows: Whether to aggregate storage flows (load/unload); if other flows
|
|
251
|
+
are fixed, fixing storage flows is usually not required.
|
|
252
|
+
aggregate_data_and_fix_non_binary_vars: Whether to aggregate all time series data, which allows to fix all time series variables (like flow_rate),
|
|
253
|
+
or only fix binary variables. If False non time_series data is changed!! If True, the mathematical Problem
|
|
254
|
+
is simplified even further.
|
|
255
|
+
percentage_of_period_freedom: Specifies the maximum percentage (0–100) of binary values within each period
|
|
256
|
+
that can deviate as "free variables", chosen by the solver (default is 0).
|
|
257
|
+
This allows binary variables to be 'partly equated' between aggregated periods.
|
|
258
|
+
penalty_of_period_freedom: The penalty associated with each "free variable"; defaults to 0. Added to Penalty
|
|
259
|
+
time_series_for_high_peaks: List of TimeSeriesData to use for explicitly selecting periods with high values.
|
|
260
|
+
time_series_for_low_peaks: List of TimeSeriesData to use for explicitly selecting periods with low values.
|
|
312
261
|
"""
|
|
313
262
|
self.hours_per_period = hours_per_period
|
|
314
263
|
self.nr_of_periods = nr_of_periods
|
|
@@ -336,13 +285,14 @@ class AggregationParameters:
|
|
|
336
285
|
return self.time_series_for_low_peaks is not None
|
|
337
286
|
|
|
338
287
|
|
|
339
|
-
class AggregationModel(
|
|
288
|
+
class AggregationModel(Model):
|
|
340
289
|
"""The AggregationModel holds equations and variables related to the Aggregation of a FLowSystem.
|
|
341
290
|
It creates Equations that equates indices of variables, and introduces penalties related to binary variables, that
|
|
342
291
|
escape the equation to their related binaries in other periods"""
|
|
343
292
|
|
|
344
293
|
def __init__(
|
|
345
294
|
self,
|
|
295
|
+
model: SystemModel,
|
|
346
296
|
aggregation_parameters: AggregationParameters,
|
|
347
297
|
flow_system: FlowSystem,
|
|
348
298
|
aggregation_data: Aggregation,
|
|
@@ -351,13 +301,13 @@ class AggregationModel(ElementModel):
|
|
|
351
301
|
"""
|
|
352
302
|
Modeling-Element for "index-equating"-equations
|
|
353
303
|
"""
|
|
354
|
-
super().__init__(
|
|
304
|
+
super().__init__(model, label_of_element='Aggregation', label_full='Aggregation')
|
|
355
305
|
self.flow_system = flow_system
|
|
356
306
|
self.aggregation_parameters = aggregation_parameters
|
|
357
307
|
self.aggregation_data = aggregation_data
|
|
358
308
|
self.components_to_clusterize = components_to_clusterize
|
|
359
309
|
|
|
360
|
-
def do_modeling(self
|
|
310
|
+
def do_modeling(self):
|
|
361
311
|
if not self.components_to_clusterize:
|
|
362
312
|
components = self.flow_system.components.values()
|
|
363
313
|
else:
|
|
@@ -365,66 +315,89 @@ class AggregationModel(ElementModel):
|
|
|
365
315
|
|
|
366
316
|
indices = self.aggregation_data.get_equation_indices(skip_first_index_of_period=True)
|
|
367
317
|
|
|
318
|
+
time_variables: Set[str] = {k for k, v in self._model.variables.data.items() if 'time' in v.indexes}
|
|
319
|
+
binary_variables: Set[str] = {k for k, v in self._model.variables.data.items() if k in self._model.binaries}
|
|
320
|
+
binary_time_variables: Set[str] = time_variables & binary_variables
|
|
321
|
+
|
|
368
322
|
for component in components:
|
|
369
323
|
if isinstance(component, Storage) and not self.aggregation_parameters.fix_storage_flows:
|
|
370
324
|
continue # Fix Nothing in The Storage
|
|
371
325
|
|
|
372
|
-
all_variables_of_component = component.model.
|
|
326
|
+
all_variables_of_component = set(component.model.variables)
|
|
327
|
+
|
|
373
328
|
if self.aggregation_parameters.aggregate_data_and_fix_non_binary_vars:
|
|
374
|
-
|
|
329
|
+
relevant_variables = component.model.variables[all_variables_of_component & time_variables]
|
|
375
330
|
else:
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
]
|
|
379
|
-
for variable in all_relevant_variables:
|
|
380
|
-
self.equate_indices(variable, indices, system_model)
|
|
331
|
+
relevant_variables = component.model.variables[all_variables_of_component & binary_time_variables]
|
|
332
|
+
for variable in relevant_variables:
|
|
333
|
+
self._equate_indices(component.model.variables[variable], indices)
|
|
381
334
|
|
|
382
335
|
penalty = self.aggregation_parameters.penalty_of_period_freedom
|
|
383
336
|
if (self.aggregation_parameters.percentage_of_period_freedom > 0) and penalty != 0:
|
|
384
|
-
for
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
def equate_indices(
|
|
390
|
-
self, variable: Variable, indices: Tuple[np.ndarray, np.ndarray], system_model: SystemModel
|
|
391
|
-
) -> Equation:
|
|
392
|
-
# Gleichung:
|
|
393
|
-
# eq1: x(p1,t) - x(p3,t) = 0 # wobei p1 und p3 im gleichen Cluster sind und t = 0..N_p
|
|
394
|
-
length = len(indices[0])
|
|
337
|
+
for variable in self.variables_direct.values():
|
|
338
|
+
self._model.effects.add_share_to_penalty('Aggregation', variable * penalty)
|
|
339
|
+
|
|
340
|
+
def _equate_indices(self, variable: linopy.Variable, indices: Tuple[np.ndarray, np.ndarray]) -> None:
|
|
395
341
|
assert len(indices[0]) == len(indices[1]), 'The length of the indices must match!!'
|
|
342
|
+
length = len(indices[0])
|
|
396
343
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
344
|
+
# Gleichung:
|
|
345
|
+
# eq1: x(p1,t) - x(p3,t) = 0 # wobei p1 und p3 im gleichen Cluster sind und t = 0..N_p
|
|
346
|
+
con = self.add(
|
|
347
|
+
self._model.add_constraints(
|
|
348
|
+
variable.isel(time=indices[0]) - variable.isel(time=indices[1]) == 0,
|
|
349
|
+
name=f'{self.label_full}|equate_indices|{variable.name}',
|
|
350
|
+
),
|
|
351
|
+
f'equate_indices|{variable.name}',
|
|
352
|
+
)
|
|
400
353
|
|
|
401
354
|
# Korrektur: (bisher nur für Binärvariablen:)
|
|
402
|
-
if
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
355
|
+
if (
|
|
356
|
+
variable.name in self._model.variables.binaries
|
|
357
|
+
and self.aggregation_parameters.percentage_of_period_freedom > 0
|
|
358
|
+
):
|
|
359
|
+
var_k1 = self.add(
|
|
360
|
+
self._model.add_variables(
|
|
361
|
+
binary=True,
|
|
362
|
+
coords={'time': variable.isel(time=indices[0]).indexes['time']},
|
|
363
|
+
name=f'{self.label_full}|correction1|{variable.name}',
|
|
364
|
+
),
|
|
365
|
+
f'correction1|{variable.name}',
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
var_k0 = self.add(
|
|
369
|
+
self._model.add_variables(
|
|
370
|
+
binary=True,
|
|
371
|
+
coords={'time': variable.isel(time=indices[0]).indexes['time']},
|
|
372
|
+
name=f'{self.label_full}|correction0|{variable.name}',
|
|
373
|
+
),
|
|
374
|
+
f'correction0|{variable.name}',
|
|
375
|
+
)
|
|
376
|
+
|
|
406
377
|
# equation extends ...
|
|
407
378
|
# --> On(p3) can be 0/1 independent of On(p1,t)!
|
|
408
379
|
# eq1: On(p1,t) - On(p3,t) + K1(p3,t) - K0(p3,t) = 0
|
|
409
380
|
# --> correction On(p3) can be:
|
|
410
381
|
# On(p1,t) = 1 -> On(p3) can be 0 -> K0=1 (,K1=0)
|
|
411
382
|
# On(p1,t) = 0 -> On(p3) can be 1 -> K1=1 (,K0=1)
|
|
412
|
-
|
|
413
|
-
eq.add_summand(var_k0, -1)
|
|
383
|
+
con.lhs += 1 * var_k1 - 1 * var_k0
|
|
414
384
|
|
|
415
385
|
# interlock var_k1 and var_K2:
|
|
416
386
|
# eq: var_k0(t)+var_k1(t) <= 1.1
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
387
|
+
self.add(
|
|
388
|
+
self._model.add_constraints(
|
|
389
|
+
var_k0 + var_k1 <= 1.1, name=f'{self.label_full}|lock_k0_and_k1|{variable.name}'
|
|
390
|
+
),
|
|
391
|
+
f'lock_k0_and_k1|{variable.name}',
|
|
392
|
+
)
|
|
421
393
|
|
|
422
394
|
# Begrenzung der Korrektur-Anzahl:
|
|
423
395
|
# eq: sum(K) <= n_Corr_max
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
396
|
+
self.add(
|
|
397
|
+
self._model.add_constraints(
|
|
398
|
+
sum(var_k0) + sum(var_k1)
|
|
399
|
+
<= round(self.aggregation_parameters.percentage_of_period_freedom / 100 * length),
|
|
400
|
+
name=f'{self.label_full}|limit_corrections|{variable.name}',
|
|
401
|
+
),
|
|
402
|
+
f'limit_corrections|{variable.name}',
|
|
403
|
+
)
|