flixopt 2.1.6__py3-none-any.whl → 2.1.7__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.

@@ -2,4 +2,4 @@
2
2
 
3
3
  ```python
4
4
  {! ../examples/00_Minmal/minimal_example.py !}
5
- ```
5
+ ```
@@ -2,4 +2,4 @@
2
2
 
3
3
  ```python
4
4
  {! ../examples/01_Simple/simple_example.py !}
5
- ```
5
+ ```
@@ -7,4 +7,4 @@ This saves the results of a calculation to file and reloads them to analyze the
7
7
  ## Load the Results from file
8
8
  ```python
9
9
  {! ../examples/02_Complex/complex_example_results.py !}
10
- ```
10
+ ```
docs/examples/index.md CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  Here you can find a collection of examples that demonstrate how to use FlixOpt.
4
4
 
5
- We work on improving this gallery. If you have something to share, please contact us!
5
+ We work on improving this gallery. If you have something to share, please contact us!
docs/faq/contribute.md CHANGED
@@ -4,17 +4,29 @@ We warmly welcome contributions from the community! This guide will help you get
4
4
 
5
5
  ## Development Setup
6
6
  1. Clone the repository `git clone https://github.com/flixOpt/flixopt.git`
7
- 2. Install the development dependencies `pip install -editable .[dev, docs]`
8
- 3. Run `pytest` and `ruff check .` to ensure your code passes all tests
9
-
10
- ## Documentation
11
- FlixOpt uses [mkdocs](https://www.mkdocs.org/) to generate documentation. To preview the documentation locally, run `mkdocs serve` in the root directory.
12
-
13
- ## Helpful Commands
14
- - `mkdocs serve` to preview the documentation locally. Navigate to `http://127.0.0.1:8000/` to view the documentation.
15
- - `pytest` to run the test suite (You can also run the provided python script `run_all_test.py`)
16
- - `ruff check .` to run the linter
17
- - `ruff check . --fix` to automatically fix linting issues
7
+ 2. Install the development dependencies `pip install -e ".[dev]"`
8
+ 3. Install pre-commit hooks `pre-commit install` (one-time setup)
9
+ 4. Run `pytest` to ensure your code passes all tests
10
+
11
+ ## Code Quality
12
+ We use [Ruff](https://github.com/astral-sh/ruff) for linting and formatting. After the one-time setup above, **code quality checks run automatically on every commit**.
13
+
14
+ To run manually:
15
+ - `ruff check --fix .` to check and fix linting issues
16
+ - `ruff format .` to format code
17
+
18
+ ## Documentation (Optional)
19
+ FlixOpt uses [mkdocs](https://www.mkdocs.org/) to generate documentation.
20
+ To work on documentation:
21
+ ```bash
22
+ pip install -e ".[docs]"
23
+ mkdocs serve
24
+ ```
25
+ Then navigate to http://127.0.0.1:8000/
26
+
27
+ ## Testing
28
+ - `pytest` to run the test suite
29
+ - You can also run the provided python script `run_all_test.py`
18
30
 
19
31
  ---
20
32
  # Best practices
@@ -30,8 +42,8 @@ FlixOpt uses [mkdocs](https://www.mkdocs.org/) to generate documentation. To pre
30
42
  ## Branches
31
43
  As we start to think FlixOpt in **Releases**, we decided to introduce multiple **dev**-branches instead of only one:
32
44
  Following the **Semantic Versioning** guidelines, we introduced:
33
- - `next/patch`: This is where all pull requests for the next patch release (1.0.x) go.
34
- - `next/minor`: This is where all pull requests for the next minor release (1.x.0) go.
45
+ - `next/patch`: This is where all pull requests for the next patch release (1.0.x) go.
46
+ - `next/minor`: This is where all pull requests for the next minor release (1.x.0) go.
35
47
  - `next/major`: This is where all pull requests for the next major release (x.0.0) go.
36
48
 
37
49
  Everything else remains in `feature/...`-branches.
@@ -44,6 +56,6 @@ At some point, `next/minor` or `next/major` will get merged into `main` using a
44
56
  ## Releases
45
57
  As stated, we follow **Semantic Versioning**.
46
58
  Right after one of the 3 [release branches](#branches) is merged into main, a **Tag** should be added to the merge commit and pushed to the main branch. The tag has the form `v1.2.3`.
47
- With this tag, a release with **Release Notes** must be created.
59
+ With this tag, a release with **Release Notes** must be created.
48
60
 
49
61
  *This is our current best practice*
docs/faq/index.md CHANGED
@@ -1,3 +1,3 @@
1
1
  # Frequently Asked Questions
2
2
 
3
- ## Work in progress
3
+ ## Work in progress
@@ -15,4 +15,4 @@ document$.subscribe(() => {
15
15
  MathJax.typesetClear()
16
16
  MathJax.texReset()
17
17
  MathJax.typesetPromise()
18
- })
18
+ })
@@ -30,4 +30,4 @@ With:
30
30
  - $\phi_\text{in}(\text{t}_i)$ and $\phi_\text{out}(\text{t}_i)$ being the missing or excess flow-rate at time $\text{t}_i$, respectively
31
31
  - $\text{t}_i$ being the time step
32
32
  - $s_{b \rightarrow \Phi}(\text{t}_i)$ being the penalty term
33
- - $\text a_{b \rightarrow \Phi}(\text{t}_i)$ being the penalty coefficient (`excess_penalty_per_flow_hour`)
33
+ - $\text a_{b \rightarrow \Phi}(\text{t}_i)$ being the penalty coefficient (`excess_penalty_per_flow_hour`)
@@ -8,17 +8,17 @@ These arise from so called **Shares**, which originate from **Elements** like [F
8
8
  Assiziated effects could be:
9
9
  - costs - given in [€/kWh]...
10
10
  - ...or emissions - given in [kg/kWh].
11
- -
11
+ -
12
12
  Effects are allocated seperatly for investments and operation.
13
13
 
14
14
  ### Shares to Effects
15
15
 
16
16
  $$ \label{eq:Share_invest}
17
- s_{l \rightarrow e, \text{inv}} = \sum_{v \in \mathcal{V}_{l, \text{inv}}} v \cdot \text a_{v \rightarrow e}
17
+ s_{l \rightarrow e, \text{inv}} = \sum_{v \in \mathcal{V}_{l, \text{inv}}} v \cdot \text a_{v \rightarrow e}
18
18
  $$
19
19
 
20
20
  $$ \label{eq:Share_operation}
21
- s_{l \rightarrow e, \text{op}}(\text{t}_i) = \sum_{v \in \mathcal{V}_{l,\text{op}}} v(\text{t}_i) \cdot \text a_{v \rightarrow e}(\text{t}_i)
21
+ s_{l \rightarrow e, \text{op}}(\text{t}_i) = \sum_{v \in \mathcal{V}_{l,\text{op}}} v(\text{t}_i) \cdot \text a_{v \rightarrow e}(\text{t}_i)
22
22
  $$
23
23
 
24
24
  With:
@@ -36,26 +36,26 @@ With:
36
36
 
37
37
  ### Shares between different Effects
38
38
 
39
- Furthermore, the Effect $x$ can contribute a share to another Effect ${e} \in \mathcal{E}\backslash x$.
40
- This share is defined by the factor $\text r_{x \rightarrow e}$.
39
+ Furthermore, the Effect $x$ can contribute a share to another Effect ${e} \in \mathcal{E}\backslash x$.
40
+ This share is defined by the factor $\text r_{x \rightarrow e}$.
41
41
 
42
- For example, the Effect "CO$_2$ emissions" (unit: kg)
43
- can cause an additional share to Effect "monetary costs" (unit: €).
42
+ For example, the Effect "CO$_2$ emissions" (unit: kg)
43
+ can cause an additional share to Effect "monetary costs" (unit: €).
44
44
  In this case, the factor $\text a_{x \rightarrow e}$ is the specific CO$_2$ price in €/kg. However, circular references have to be avoided.
45
45
 
46
46
  The overall sum of investment shares of an Effect $e$ is given by $\eqref{Effect_invest}$
47
47
 
48
48
  $$ \label{eq:Effect_invest}
49
- E_{e, \text{inv}} =
50
- \sum_{l \in \mathcal{L}} s_{l \rightarrow e,\text{inv}} +
49
+ E_{e, \text{inv}} =
50
+ \sum_{l \in \mathcal{L}} s_{l \rightarrow e,\text{inv}} +
51
51
  \sum_{x \in \mathcal{E}\backslash e} E_{x, \text{inv}} \cdot \text{r}_{x \rightarrow e,\text{inv}}
52
52
  $$
53
53
 
54
54
  The overall sum of operation shares is given by $\eqref{eq:Effect_Operation}$
55
55
 
56
56
  $$ \label{eq:Effect_Operation}
57
- E_{e, \text{op}}(\text{t}_{i}) =
58
- \sum_{l \in \mathcal{L}} s_{l \rightarrow e, \text{op}}(\text{t}_i) +
57
+ E_{e, \text{op}}(\text{t}_{i}) =
58
+ \sum_{l \in \mathcal{L}} s_{l \rightarrow e, \text{op}}(\text{t}_i) +
59
59
  \sum_{x \in \mathcal{E}\backslash e} E_{x, \text{op}}(\text{t}_i) \cdot \text{r}_{x \rightarrow {e},\text{op}}(\text{t}_i)
60
60
  $$
61
61
 
@@ -100,7 +100,7 @@ $$
100
100
 
101
101
  Additionally to the user defined [Effects](#effects), a Penalty $\Phi$ is part of every FlixOpt Model.
102
102
  Its used to prevent unsolvable problems and simplify troubleshooting.
103
- Shares to the penalty can originate from every Element and are constructed similarly to
103
+ Shares to the penalty can originate from every Element and are constructed similarly to
104
104
  $\eqref{Share_invest}$ and $\eqref{Share_operation}$.
105
105
 
106
106
  $$ \label{eq:Penalty}
@@ -129,4 +129,4 @@ With:
129
129
 
130
130
  This approach allows for a multi-criteria optimization using both...
131
131
  - ... the **Weigted Sum**Method, as the chosen **Objective Effect** can incorporate other Effects.
132
- - ... the ($\epsilon$-constraint method) by constraining effects.
132
+ - ... the ($\epsilon$-constraint method) by constraining effects.
@@ -23,4 +23,4 @@ $$
23
23
 
24
24
  This mathematical Formulation can be extended or changed when using [OnOffParameters](#onoffparameters)
25
25
  to define the On/Off state of the Flow, or [InvestParameters](#investments),
26
- which changes the size of the Flow from a constant to an optimization variable.
26
+ which changes the size of the Flow from a constant to an optimization variable.
@@ -10,7 +10,7 @@ With:
10
10
  - $p_{f_\text{in}}(\text{t}_i)$ and $p_{f_\text{out}}(\text{t}_i)$ being the flow-rate at time $\text{t}_i$ for flow $f_\text{in}$ and $f_\text{out}$, respectively
11
11
  - $\text a_{f_\text{in}}(\text{t}_i)$ and $\text b_{f_\text{out}}(\text{t}_i)$ being the ratio of the flow-rate at time $\text{t}_i$ for flow $f_\text{in}$ and $f_\text{out}$, respectively
12
12
 
13
- With one incoming **Flow** and one outgoing **Flow**, this can be simplified to:
13
+ With one incoming **Flow** and one outgoing **Flow**, this can be simplified to:
14
14
 
15
15
  $$ \label{eq:Linear-Transformer-Ratio-simple}
16
16
  \text a(\text{t}_i) \cdot p_{f_\text{in}}(\text{t}_i) = p_{f_\text{out}}(\text{t}_i)
@@ -18,4 +18,4 @@ $$
18
18
 
19
19
  where $\text a$ can be interpreted as the conversion efficiency of the **LinearTransformer**.
20
20
  #### Piecewise Concersion factors
21
- The conversion efficiency can be defined as a piecewise linear approximation. See [Piecewise](Piecewise.md) for more details.
21
+ The conversion efficiency can be defined as a piecewise linear approximation. See [Piecewise](Piecewise.md) for more details.
@@ -40,7 +40,7 @@ Which can also be described as $v \in \{0\} \cup [\text{v}_{\text{start_k}}, \te
40
40
 
41
41
  ## Combining multiple Piecewises
42
42
 
43
- Piecewise allows representing non-linear relationships.
43
+ Piecewise allows representing non-linear relationships.
44
44
  This is a powerful technique in linear optimization to model non-linear behaviors while maintaining the problem's linearity.
45
45
 
46
46
  Therefore, each Piecewise must have the same number of Pieces $k$.
@@ -41,4 +41,4 @@ Where:
41
41
  - $p_{f_\text{in}}(\text{t}_i)$ is the input flow rate at time $\text{t}_i$
42
42
  - $\eta_\text{in}(\text{t}_i)$ is the charging efficiency at time $\text{t}_i$
43
43
  - $p_{f_\text{out}}(\text{t}_i)$ is the output flow rate at time $\text{t}_i$
44
- - $\eta_\text{out}(\text{t}_i)$ is the discharging efficiency at time $\text{t}_i$
44
+ - $\eta_\text{out}(\text{t}_i)$ is the discharging efficiency at time $\text{t}_i$
@@ -14,7 +14,7 @@ FlixOpt uses the following naming conventions:
14
14
 
15
15
  ## Timesteps
16
16
  Time steps are defined as a sequence of discrete time steps $\text{t}_i \in \mathcal{T} \quad \text{for} \quad i \in \{1, 2, \dots, \text{n}\}$ (left-aligned in its timespan).
17
- From this sequence, the corresponding time intervals $\Delta \text{t}_i \in \Delta \mathcal{T}$ are derived as
17
+ From this sequence, the corresponding time intervals $\Delta \text{t}_i \in \Delta \mathcal{T}$ are derived as
18
18
 
19
19
  $$\Delta \text{t}_i = \text{t}_{i+1} - \text{t}_i \quad \text{for} \quad i \in \{1, 2, \dots, \text{n}-1\}$$
20
20
 
@@ -1,3 +1,3 @@
1
1
  # Work in Progress
2
2
 
3
- This is a work in progress.
3
+ This is a work in progress.
docs/user-guide/index.md CHANGED
@@ -6,7 +6,7 @@ FlixOpt is built around a set of core concepts that work together to represent a
6
6
 
7
7
  ### FlowSystem
8
8
 
9
- The [`FlowSystem`][flixopt.flow_system.FlowSystem] is the central organizing unit in FlixOpt.
9
+ The [`FlowSystem`][flixopt.flow_system.FlowSystem] is the central organizing unit in FlixOpt.
10
10
  Every FlixOpt model starts with creating a FlowSystem. It:
11
11
 
12
12
  - Defines the timesteps for the optimization
@@ -40,7 +40,7 @@ Examples:
40
40
  [`Bus`][flixopt.elements.Bus] objects represent nodes or connection points in a FlowSystem. They:
41
41
 
42
42
  - Balance incoming and outgoing flows
43
- - Can represent physical networks like heat, electricity, or gas
43
+ - Can represent physical networks like heat, electricity, or gas
44
44
  - Handle infeasible balances gently by allowing the balance to be closed in return for a big Penalty (optional)
45
45
 
46
46
  ### Components
flixopt/__init__.py CHANGED
@@ -2,6 +2,10 @@
2
2
  This module bundles all common functionality of flixopt and sets up the logging
3
3
  """
4
4
 
5
+ from importlib.metadata import version
6
+
7
+ __version__ = version('flixopt')
8
+
5
9
  from .commons import (
6
10
  CONFIG,
7
11
  AggregatedCalculation,
flixopt/calculation.py CHANGED
@@ -62,13 +62,9 @@ class Calculation:
62
62
  self.folder = pathlib.Path.cwd() / 'results' if folder is None else pathlib.Path(folder)
63
63
  self.results: Optional[CalculationResults] = None
64
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
65
+ if self.folder.exists() and not self.folder.is_dir():
66
+ raise NotADirectoryError(f'Path {self.folder} exists and is not a directory.')
67
+ self.folder.mkdir(parents=False, exist_ok=True)
72
68
 
73
69
  @property
74
70
  def main_results(self) -> Dict[str, Union[Scalar, Dict]]:
flixopt/components.py CHANGED
@@ -674,7 +674,7 @@ class Source(Component):
674
674
  outputs: List[Flow] = None,
675
675
  meta_data: Optional[Dict] = None,
676
676
  prevent_simultaneous_flow_rates: bool = False,
677
- **kwargs
677
+ **kwargs,
678
678
  ):
679
679
  """
680
680
  Args:
@@ -694,7 +694,12 @@ class Source(Component):
694
694
  outputs = [source]
695
695
 
696
696
  self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
697
- super().__init__(label, outputs=outputs, meta_data=meta_data, prevent_simultaneous_flows=outputs if prevent_simultaneous_flow_rates else None)
697
+ super().__init__(
698
+ label,
699
+ outputs=outputs,
700
+ meta_data=meta_data,
701
+ prevent_simultaneous_flows=outputs if prevent_simultaneous_flow_rates else None,
702
+ )
698
703
 
699
704
  @property
700
705
  def source(self) -> Flow:
@@ -714,7 +719,7 @@ class Sink(Component):
714
719
  inputs: List[Flow] = None,
715
720
  meta_data: Optional[Dict] = None,
716
721
  prevent_simultaneous_flow_rates: bool = False,
717
- **kwargs
722
+ **kwargs,
718
723
  ):
719
724
  """
720
725
  Args:
@@ -734,7 +739,12 @@ class Sink(Component):
734
739
  inputs = [sink]
735
740
 
736
741
  self.prevent_simultaneous_flow_rates = prevent_simultaneous_flow_rates
737
- super().__init__(label, inputs=inputs, meta_data=meta_data, prevent_simultaneous_flows=inputs if prevent_simultaneous_flow_rates else None)
742
+ super().__init__(
743
+ label,
744
+ inputs=inputs,
745
+ meta_data=meta_data,
746
+ prevent_simultaneous_flows=inputs if prevent_simultaneous_flow_rates else None,
747
+ )
738
748
 
739
749
  @property
740
750
  def sink(self) -> Flow:
flixopt/core.py CHANGED
@@ -62,17 +62,21 @@ class DataConverter:
62
62
  return xr.DataArray(data, coords=coords, dims=dims)
63
63
  elif isinstance(data, pd.DataFrame):
64
64
  if not data.index.equals(timesteps):
65
- raise ConversionError(f"DataFrame index doesn't match timesteps index. "
66
- f"Its missing the following time steps: {timesteps.difference(data.index)}. "
67
- f"Some parameters might need an extra timestep at the end.")
65
+ raise ConversionError(
66
+ f"DataFrame index doesn't match timesteps index. "
67
+ f'Its missing the following time steps: {timesteps.difference(data.index)}. '
68
+ f'Some parameters might need an extra timestep at the end.'
69
+ )
68
70
  if not len(data.columns) == 1:
69
71
  raise ConversionError('DataFrame must have exactly one column')
70
72
  return xr.DataArray(data.values.flatten(), coords=coords, dims=dims)
71
73
  elif isinstance(data, pd.Series):
72
74
  if not data.index.equals(timesteps):
73
- raise ConversionError(f"Series index doesn't match timesteps index. "
74
- f"Its missing the following time steps: {timesteps.difference(data.index)}. "
75
- f"Some parameters might need an extra timestep at the end.")
75
+ raise ConversionError(
76
+ f"Series index doesn't match timesteps index. "
77
+ f'Its missing the following time steps: {timesteps.difference(data.index)}. '
78
+ f'Some parameters might need an extra timestep at the end.'
79
+ )
76
80
  return xr.DataArray(data.values, coords=coords, dims=dims)
77
81
  elif isinstance(data, np.ndarray):
78
82
  if data.ndim != 1:
flixopt/effects.py CHANGED
@@ -94,7 +94,8 @@ class Effect(Element):
94
94
  f'{self.label_full}|minimum_operation_per_hour', self.minimum_operation_per_hour
95
95
  )
96
96
  self.maximum_operation_per_hour = flow_system.create_time_series(
97
- f'{self.label_full}|maximum_operation_per_hour', self.maximum_operation_per_hour,
97
+ f'{self.label_full}|maximum_operation_per_hour',
98
+ self.maximum_operation_per_hour,
98
99
  )
99
100
 
100
101
  self.specific_share_to_other_effects_operation = flow_system.create_effect_time_series(
flixopt/elements.py CHANGED
@@ -352,8 +352,10 @@ class FlowModel(ElementModel):
352
352
  label_of_element=self.label_of_element,
353
353
  parameters=self.element.size,
354
354
  defining_variable=self.flow_rate,
355
- relative_bounds_of_defining_variable=(self.flow_rate_lower_bound_relative,
356
- self.flow_rate_upper_bound_relative),
355
+ relative_bounds_of_defining_variable=(
356
+ self.flow_rate_lower_bound_relative,
357
+ self.flow_rate_upper_bound_relative,
358
+ ),
357
359
  on_variable=self.on_off.on if self.on_off is not None else None,
358
360
  ),
359
361
  'investment',
@@ -448,7 +450,7 @@ class FlowModel(ElementModel):
448
450
 
449
451
  @property
450
452
  def flow_rate_upper_bound_relative(self) -> NumericData:
451
- """ Returns the upper bound of the flow_rate relative to its size"""
453
+ """Returns the upper bound of the flow_rate relative to its size"""
452
454
  fixed_profile = self.element.fixed_relative_profile
453
455
  if fixed_profile is None:
454
456
  return self.element.relative_maximum.active_data
flixopt/features.py CHANGED
@@ -90,7 +90,10 @@ class InvestmentModel(Model):
90
90
  # share: divest_effects - isInvested * divest_effects
91
91
  self._model.effects.add_share_to_effects(
92
92
  name=self.label_of_element,
93
- expressions={effect: -self.is_invested * factor + factor for effect, factor in self.parameters.divest_effects.items()},
93
+ expressions={
94
+ effect: -self.is_invested * factor + factor
95
+ for effect, factor in self.parameters.divest_effects.items()
96
+ },
94
97
  target='invest',
95
98
  )
96
99
 
@@ -304,13 +307,11 @@ class StateModel(Model):
304
307
  )
305
308
 
306
309
  # Constraint: on * upper_bound >= def_var
307
- self.add(
308
- self._model.add_constraints(self.on * ub >= def_var, name=f'{self.label_full}|on_con2'), 'on_con2'
309
- )
310
+ self.add(self._model.add_constraints(self.on * ub >= def_var, name=f'{self.label_full}|on_con2'), 'on_con2')
310
311
  else:
311
312
  # Case for multiple defining variables
312
313
  ub = sum(bound[1] for bound in self._defining_bounds) / nr_of_def_vars
313
- lb = CONFIG.modeling.EPSILON #TODO: Can this be a bigger value? (maybe the smallest bound?)
314
+ lb = CONFIG.modeling.EPSILON # TODO: Can this be a bigger value? (maybe the smallest bound?)
314
315
 
315
316
  # Constraint: on * epsilon <= sum(all_defining_variables)
316
317
  self.add(
@@ -426,7 +427,9 @@ class SwitchStateModel(Model):
426
427
 
427
428
  # Mutual exclusivity constraint
428
429
  self.add(
429
- self._model.add_constraints(self.switch_on + self.switch_off <= 1.1, name=f'{self.label_full}|switch_on_or_off'),
430
+ self._model.add_constraints(
431
+ self.switch_on + self.switch_off <= 1.1, name=f'{self.label_full}|switch_on_or_off'
432
+ ),
430
433
  'switch_on_or_off',
431
434
  )
432
435
 
@@ -502,9 +505,7 @@ class ConsecutiveStateModel(Model):
502
505
 
503
506
  # Upper bound constraint
504
507
  self.add(
505
- self._model.add_constraints(
506
- self.duration <= self._state_variable * mega, name=f'{self.label_full}|con1'
507
- ),
508
+ self._model.add_constraints(self.duration <= self._state_variable * mega, name=f'{self.label_full}|con1'),
508
509
  'con1',
509
510
  )
510
511
 
@@ -556,8 +557,8 @@ class ConsecutiveStateModel(Model):
556
557
  # Set initial value
557
558
  self.add(
558
559
  self._model.add_constraints(
559
- self.duration.isel(time=0) ==
560
- (hours_per_step.isel(time=0) + self.previous_duration) * self._state_variable.isel(time=0),
560
+ self.duration.isel(time=0)
561
+ == (hours_per_step.isel(time=0) + self.previous_duration) * self._state_variable.isel(time=0),
561
562
  name=f'{self.label_full}|initial',
562
563
  ),
563
564
  'initial',
@@ -568,7 +569,7 @@ class ConsecutiveStateModel(Model):
568
569
  @property
569
570
  def previous_duration(self) -> Scalar:
570
571
  """Computes the previous duration of the state variable"""
571
- #TODO: Allow for other/dynamic timestep resolutions
572
+ # TODO: Allow for other/dynamic timestep resolutions
572
573
  return ConsecutiveStateModel.compute_consecutive_hours_in_state(
573
574
  self._previous_states, self._model.hours_per_step.isel(time=0).item()
574
575
  )
@@ -619,7 +620,10 @@ class ConsecutiveStateModel(Model):
619
620
  f'as {binary_values=}'
620
621
  )
621
622
 
622
- return np.sum(binary_values[-nr_of_indexes_with_consecutive_ones:] * hours_per_timestep[-nr_of_indexes_with_consecutive_ones:])
623
+ return np.sum(
624
+ binary_values[-nr_of_indexes_with_consecutive_ones:]
625
+ * hours_per_timestep[-nr_of_indexes_with_consecutive_ones:]
626
+ )
623
627
 
624
628
 
625
629
  class OnOffModel(Model):
flixopt/flow_system.py CHANGED
@@ -257,9 +257,9 @@ class FlowSystem:
257
257
 
258
258
  if not DASH_CYTOSCAPE_AVAILABLE:
259
259
  raise ImportError(
260
- f"Network visualization requires optional dependencies. "
261
- f"Install with: pip install flixopt[viz], flixopt[full] or pip install dash dash_cytoscape networkx werkzeug. "
262
- f"Original error: {VISUALIZATION_ERROR}"
260
+ f'Network visualization requires optional dependencies. '
261
+ f'Install with: pip install flixopt[viz], flixopt[full] or pip install dash dash_cytoscape networkx werkzeug. '
262
+ f'Original error: {VISUALIZATION_ERROR}'
263
263
  )
264
264
 
265
265
  if not self._connected:
@@ -274,6 +274,7 @@ class FlowSystem:
274
274
  def stop_network_app(self):
275
275
  """Stop the network visualization server."""
276
276
  from .network_app import DASH_CYTOSCAPE_AVAILABLE, VISUALIZATION_ERROR
277
+
277
278
  if not DASH_CYTOSCAPE_AVAILABLE:
278
279
  raise ImportError(
279
280
  f'Network visualization requires optional dependencies. '