flixopt 1.0.12__py3-none-any.whl → 2.0.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.

Files changed (73) hide show
  1. docs/examples/00-Minimal Example.md +5 -0
  2. docs/examples/01-Basic Example.md +5 -0
  3. docs/examples/02-Complex Example.md +10 -0
  4. docs/examples/03-Calculation Modes.md +5 -0
  5. docs/examples/index.md +5 -0
  6. docs/faq/contribute.md +49 -0
  7. docs/faq/index.md +3 -0
  8. docs/images/architecture_flixOpt-pre2.0.0.png +0 -0
  9. docs/images/architecture_flixOpt.png +0 -0
  10. docs/images/flixopt-icon.svg +1 -0
  11. docs/javascripts/mathjax.js +18 -0
  12. docs/release-notes/_template.txt +32 -0
  13. docs/release-notes/index.md +7 -0
  14. docs/release-notes/v2.0.0.md +93 -0
  15. docs/release-notes/v2.0.1.md +12 -0
  16. docs/user-guide/Mathematical Notation/Bus.md +33 -0
  17. docs/user-guide/Mathematical Notation/Effects, Penalty & Objective.md +132 -0
  18. docs/user-guide/Mathematical Notation/Flow.md +26 -0
  19. docs/user-guide/Mathematical Notation/LinearConverter.md +21 -0
  20. docs/user-guide/Mathematical Notation/Piecewise.md +49 -0
  21. docs/user-guide/Mathematical Notation/Storage.md +44 -0
  22. docs/user-guide/Mathematical Notation/index.md +22 -0
  23. docs/user-guide/Mathematical Notation/others.md +3 -0
  24. docs/user-guide/index.md +124 -0
  25. {flixOpt → flixopt}/__init__.py +5 -2
  26. {flixOpt → flixopt}/aggregation.py +113 -140
  27. flixopt/calculation.py +455 -0
  28. {flixOpt → flixopt}/commons.py +7 -4
  29. flixopt/components.py +630 -0
  30. {flixOpt → flixopt}/config.py +9 -8
  31. {flixOpt → flixopt}/config.yaml +3 -3
  32. flixopt/core.py +970 -0
  33. flixopt/effects.py +386 -0
  34. flixopt/elements.py +534 -0
  35. flixopt/features.py +1042 -0
  36. flixopt/flow_system.py +409 -0
  37. flixopt/interface.py +265 -0
  38. flixopt/io.py +308 -0
  39. flixopt/linear_converters.py +331 -0
  40. flixopt/plotting.py +1340 -0
  41. flixopt/results.py +898 -0
  42. flixopt/solvers.py +77 -0
  43. flixopt/structure.py +630 -0
  44. flixopt/utils.py +62 -0
  45. flixopt-2.0.1.dist-info/METADATA +145 -0
  46. flixopt-2.0.1.dist-info/RECORD +57 -0
  47. {flixopt-1.0.12.dist-info → flixopt-2.0.1.dist-info}/WHEEL +1 -1
  48. flixopt-2.0.1.dist-info/top_level.txt +6 -0
  49. pics/architecture_flixOpt-pre2.0.0.png +0 -0
  50. pics/architecture_flixOpt.png +0 -0
  51. pics/flixopt-icon.svg +1 -0
  52. pics/pics.pptx +0 -0
  53. scripts/gen_ref_pages.py +54 -0
  54. site/release-notes/_template.txt +32 -0
  55. flixOpt/calculation.py +0 -629
  56. flixOpt/components.py +0 -614
  57. flixOpt/core.py +0 -182
  58. flixOpt/effects.py +0 -410
  59. flixOpt/elements.py +0 -489
  60. flixOpt/features.py +0 -942
  61. flixOpt/flow_system.py +0 -351
  62. flixOpt/interface.py +0 -203
  63. flixOpt/linear_converters.py +0 -325
  64. flixOpt/math_modeling.py +0 -1145
  65. flixOpt/plotting.py +0 -712
  66. flixOpt/results.py +0 -563
  67. flixOpt/solvers.py +0 -21
  68. flixOpt/structure.py +0 -733
  69. flixOpt/utils.py +0 -134
  70. flixopt-1.0.12.dist-info/METADATA +0 -174
  71. flixopt-1.0.12.dist-info/RECORD +0 -29
  72. flixopt-1.0.12.dist-info/top_level.txt +0 -3
  73. {flixopt-1.0.12.dist-info → flixopt-2.0.1.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
+ }
@@ -1,5 +1,5 @@
1
1
  """
2
- This module makes the commonly used classes and functions available in the flixOpt framework.
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, create_datetime_array
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',