From f564e7583d5844c9d9bc1f9bb5b30763bb3d61be Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 19 Nov 2025 21:57:53 +0100 Subject: [PATCH 01/79] Flow.md - Fully restructured with: - Tab-based organization (Core / Advanced / Patterns / Examples) - Collapsible definition blocks - Links to both Flow and FlowModel classes - Updated docstrings with absolute URLs 2. Bus.md - Restructured with tab organization and dual class linking 3. Storage.md - Restructured with comprehensive examples and dual class linking 4. LinearConverter.md - Restructured with detailed examples including specialized converters 5. InvestParameters.md - Restructured with clear separation of core vs. advanced features --- .../mathematical-notation/elements/Bus.md | 127 +++-- .../mathematical-notation/elements/Flow.md | 198 ++++++-- .../elements/LinearConverter.md | 212 ++++++-- .../mathematical-notation/elements/Storage.md | 234 ++++++--- .../features/InvestParameters.md | 473 +++++++++--------- flixopt/elements.py | 12 +- 6 files changed, 860 insertions(+), 396 deletions(-) diff --git a/docs/user-guide/mathematical-notation/elements/Bus.md b/docs/user-guide/mathematical-notation/elements/Bus.md index bfe57d234..3d5a115a3 100644 --- a/docs/user-guide/mathematical-notation/elements/Bus.md +++ b/docs/user-guide/mathematical-notation/elements/Bus.md @@ -1,49 +1,112 @@ -A Bus is a simple nodal balance between its incoming and outgoing flow rates. +# Bus -$$ \label{eq:bus_balance} - \sum_{f_\text{in} \in \mathcal{F}_\text{in}} p_{f_\text{in}}(\text{t}_i) = - \sum_{f_\text{out} \in \mathcal{F}_\text{out}} p_{f_\text{out}}(\text{t}_i) -$$ +A Bus represents a node in the energy/material flow network where flow balance constraints ensure conservation (inflows equal outflows). -Optionally, a Bus can have a `excess_penalty_per_flow_hour` parameter, which allows to penaltize the balance for missing or excess flow-rates. -This is usefull as it handles a possible ifeasiblity gently. +**Implementation:** -This changes the balance to +- **Element Class:** [`Bus`][flixopt.elements.Bus] +- **Model Class:** [`BusModel`][flixopt.elements.BusModel] -$$ \label{eq:bus_balance-excess} - \sum_{f_\text{in} \in \mathcal{F}_\text{in}} p_{f_ \text{in}}(\text{t}_i) + \phi_\text{in}(\text{t}_i) = - \sum_{f_\text{out} \in \mathcal{F}_\text{out}} p_{f_\text{out}}(\text{t}_i) + \phi_\text{out}(\text{t}_i) -$$ +**Related:** [`Flow`](Flow.md) · [`Storage`](Storage.md) · [`LinearConverter`](LinearConverter.md) -The penalty term is defined as +--- -$$ \label{eq:bus_penalty} - s_{b \rightarrow \Phi}(\text{t}_i) = - \text a_{b \rightarrow \Phi}(\text{t}_i) \cdot \Delta \text{t}_i - \cdot [ \phi_\text{in}(\text{t}_i) + \phi_\text{out}(\text{t}_i) ] -$$ +=== "Core Formulation" -With: + ## Nodal Balance Equation -- $\mathcal{F}_\text{in}$ and $\mathcal{F}_\text{out}$ being the set of all incoming and outgoing flows -- $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 -- $\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 -- $\text{t}_i$ being the time step -- $s_{b \rightarrow \Phi}(\text{t}_i)$ being the penalty term -- $\text a_{b \rightarrow \Phi}(\text{t}_i)$ being the penalty coefficient (`excess_penalty_per_flow_hour`) + The fundamental constraint of a Bus is that all incoming flow rates must equal all outgoing flow rates at every timestep: ---- + $$ \label{eq:bus_balance} + \sum_{f_\text{in} \in \mathcal{F}_\text{in}} p_{f_\text{in}}(\text{t}_i) = + \sum_{f_\text{out} \in \mathcal{F}_\text{out}} p_{f_\text{out}}(\text{t}_i) + $$ + + ??? info "Variables" + | Symbol | Description | Domain | + |--------|-------------|--------| + | $p_{f_\text{in}}(\text{t}_i)$ | Flow rate of incoming flow $f_\text{in}$ at time $\text{t}_i$ | $\mathbb{R}$ | + | $p_{f_\text{out}}(\text{t}_i)$ | Flow rate of outgoing flow $f_\text{out}$ at time $\text{t}_i$ | $\mathbb{R}$ | + + ??? info "Sets" + | Symbol | Description | + |--------|-------------| + | $\mathcal{F}_\text{in}$ | Set of all incoming flows to the bus | + | $\mathcal{F}_\text{out}$ | Set of all outgoing flows from the bus | + + This strict equality ensures energy/material conservation at each node. + +=== "Advanced & Edge Cases" + + ## Excess Penalty (Soft Constraints) + + When `excess_penalty_per_flow_hour` is specified, the Bus allows balance violations with a penalty cost. This creates a "soft" constraint useful for handling potential infeasibilities gracefully. + + ### Modified Balance Equation + + The balance equation becomes: + + $$ \label{eq:bus_balance_excess} + \sum_{f_\text{in} \in \mathcal{F}_\text{in}} p_{f_ \text{in}}(\text{t}_i) + \phi_\text{in}(\text{t}_i) = + \sum_{f_\text{out} \in \mathcal{F}_\text{out}} p_{f_\text{out}}(\text{t}_i) + \phi_\text{out}(\text{t}_i) + $$ + + ??? info "Additional Variables" + | Symbol | Description | Domain | + |--------|-------------|--------| + | $\phi_\text{in}(\text{t}_i)$ | Missing inflow (shortage) at time $\text{t}_i$ | $\mathbb{R}_+ $ | + | $\phi_\text{out}(\text{t}_i)$ | Excess outflow (surplus) at time $\text{t}_i$ | $\mathbb{R}_+$ | + + ### Penalty Cost + + The penalty term added to the objective function: + + $$ \label{eq:bus_penalty} + s_{b \rightarrow \Phi}(\text{t}_i) = + \text a_{b \rightarrow \Phi}(\text{t}_i) \cdot \Delta \text{t}_i + \cdot [ \phi_\text{in}(\text{t}_i) + \phi_\text{out}(\text{t}_i) ] + $$ + + ??? info "Parameters" + | Symbol | Description | Units | + |--------|-------------|-------| + | $\text a_{b \rightarrow \Phi}(\text{t}_i)$ | Penalty coefficient (`excess_penalty_per_flow_hour`) | Cost per unit flow-hour | + | $\Delta \text{t}_i$ | Timestep duration | hours | + | $s_{b \rightarrow \Phi}(\text{t}_i)$ | Total penalty cost at time $\text{t}_i$ | Cost units | + + **Use Case:** This soft constraint approach prevents model infeasibility when supply and demand cannot be perfectly balanced, making it easier to identify and diagnose problem areas in the energy system model. + +=== "Examples" + + ## Basic Bus with Strict Balance + + ```python + from flixopt import Bus + + electricity_grid = Bus( + label='electricity_grid', + ) + ``` + + This creates a strict nodal balance: all electricity inflows must exactly equal all outflows at every timestep. + + ## Bus with Excess Penalty -## Implementation + ```python + from flixopt import Bus -**Python Class:** [`Bus`][flixopt.elements.Bus] + heat_network = Bus( + label='heating_network', + excess_penalty_per_flow_hour=1000, # High penalty for unmet demand + ) + ``` -See the API documentation for implementation details and usage examples. + This allows the model to violate the heat balance if necessary, but applies a penalty of 1000 cost units per kWh of unbalanced flow. Useful for debugging infeasible models or modeling emergency scenarios. --- ## See Also -- [Flow](../elements/Flow.md) - Definition of flow rates in the balance -- [Effects, Penalty & Objective](../effects-penalty-objective.md) - How penalties are included in the objective function -- [Modeling Patterns](../modeling-patterns/index.md) - Mathematical building blocks +- **Elements:** [Flow](Flow.md) · [Storage](Storage.md) · [LinearConverter](LinearConverter.md) +- **System-Level:** [Effects, Penalty & Objective](../effects-penalty-objective.md) +- **Patterns:** [Modeling Patterns](../modeling-patterns/index.md) diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md index 5914ba911..24e6ed21c 100644 --- a/docs/user-guide/mathematical-notation/elements/Flow.md +++ b/docs/user-guide/mathematical-notation/elements/Flow.md @@ -1,64 +1,180 @@ # Flow -The flow_rate is the main optimization variable of the Flow. It's limited by the size of the Flow and relative bounds \eqref{eq:flow_rate}. +A Flow represents the transfer of energy or material between a Bus and a Component, with the flow rate as the primary optimization variable. -$$ \label{eq:flow_rate} - \text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) - \leq p(\text{t}_{i}) \leq - \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i}) -$$ +**Implementation:** -With: +- **Element Class:** [`Flow`][flixopt.elements.Flow] +- **Model Class:** [`FlowModel`][flixopt.elements.FlowModel] -- $\text P$ being the size of the Flow -- $p(\text{t}_{i})$ being the flow-rate at time $\text{t}_{i}$ -- $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i})$ being the relative lower bound (typically 0) -- $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ being the relative upper bound (typically 1) +**Related:** [`Bus`](Bus.md) · [`Storage`](Storage.md) · [`LinearConverter`](LinearConverter.md) -With $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) = 0$ and $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i}) = 1$, -equation \eqref{eq:flow_rate} simplifies to +--- -$$ - 0 \leq p(\text{t}_{i}) \leq \text P -$$ +=== "Core Formulation" + ## Flow Rate Bounds -This mathematical formulation can be extended by using [OnOffParameters](../features/OnOffParameters.md) -to define the on/off state of the Flow, or by using [InvestParameters](../features/InvestParameters.md) -to change the size of the Flow from a constant to an optimization variable. + The flow rate $p(\text{t}_{i})$ is constrained by the flow size and relative bounds: ---- + $$ \label{eq:flow_rate} + \text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) + \leq p(\text{t}_{i}) \leq + \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i}) + $$ -## Mathematical Patterns Used + ??? info "Variables" + | Symbol | Description | Domain | + |--------|-------------|--------| + | $p(\text{t}_{i})$ | Flow rate at time $\text{t}_{i}$ | $\mathbb{R}$ | + | $\text P$ | Flow size (capacity) | $\mathbb{R}_+$ or decision variable (with [InvestParameters](../features/InvestParameters.md)) | -Flow formulation uses the following modeling patterns: + ??? info "Parameters" + | Symbol | Description | Typical Value | + |--------|-------------|---------------| + | $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i})$ | Relative lower bound at time $\text{t}_{i}$ | 0 | + | $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ | Relative upper bound at time $\text{t}_{i}$ | 1 | -- **[Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)** - Basic flow rate bounds (equation $\eqref{eq:flow_rate}$) -- **[Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state)** - When combined with [OnOffParameters](../features/OnOffParameters.md) -- **[Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state)** - Investment decisions with [InvestParameters](../features/InvestParameters.md) + ## Simplified Form ---- + With standard bounds $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) = 0$ and $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i}) = 1$, equation \eqref{eq:flow_rate} simplifies to: + + $$ + 0 \leq p(\text{t}_{i}) \leq \text P + $$ + +=== "Advanced & Edge Cases" + + ## Extensions with Optional Features + + The basic flow formulation can be extended with additional constraints: + + ### On/Off Operation + + When combined with [OnOffParameters](../features/OnOffParameters.md), the flow gains binary on/off states with startup costs, minimum run times, and switching constraints. The formulation becomes: + + - **[Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state)** + + ### Investment Sizing + + When using [InvestParameters](../features/InvestParameters.md), the flow size $\text P$ becomes an optimization variable rather than a fixed parameter: + + - **[Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state)** + + ### Load Factor Constraints + + Minimum and maximum average utilization over time periods: + + $$ + \text{load\_factor\_min} \cdot \text P \cdot N_t \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \leq \text{load\_factor\_max} \cdot \text P \cdot N_t + $$ + + Where $N_t$ is the number of timesteps in the period. + + ### Flow Hours Limits + + Direct constraints on cumulative flow-hours (energy/material throughput): + + $$ + \text{flow\_hours\_min} \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{flow\_hours\_max} + $$ + + ### Fixed Relative Profile + + When a predetermined pattern is specified, the flow rate becomes: + + $$ + p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i}) + $$ + + This is commonly used for renewable generation (solar, wind) or fixed demand patterns. + + ### Initial Flow State + + For flows with on/off parameters, the previous flow rate can be specified to properly model startup/shutdown transitions at the beginning of the optimization horizon: + + - `previous_flow_rate`: Initial condition for on/off dynamics (default: None, interpreted as off/zero flow) + +=== "Mathematical Patterns" + + Flow formulation builds on the following modeling patterns: + + - **[Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)** - Basic flow rate constraint (equation $\eqref{eq:flow_rate}$) + - **[Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state)** - When combined with [OnOffParameters](../features/OnOffParameters.md) + - **[Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state)** - Investment decisions with [InvestParameters](../features/InvestParameters.md) + + See [Modeling Patterns](../modeling-patterns/index.md) for detailed explanations of these building blocks. + +=== "Examples" + + ## Basic Fixed Capacity Flow + + ```python + from flixopt import Flow + + generator_output = Flow( + label='electricity_out', + bus='electricity_grid', + size=100, # 100 MW capacity + relative_minimum=0.4, # Cannot operate below 40 MW + effects_per_flow_hour={'fuel_cost': 45, 'co2_emissions': 0.8}, + ) + ``` + + ## Investment Decision + + ```python + from flixopt import Flow, InvestParameters + + battery_flow = Flow( + label='electricity_storage', + bus='electricity_grid', + size=InvestParameters( + minimum_size=10, # Minimum 10 MWh + maximum_size=100, # Maximum 100 MWh + specific_effects={'cost': 150_000}, # €150k/MWh annualized + ), + ) + ``` + + ## On/Off Operation with Constraints + + ```python + from flixopt import Flow, OnOffParameters -## Implementation + heat_pump_flow = Flow( + label='heat_output', + bus='heating_network', + size=50, # 50 kW thermal + relative_minimum=0.3, # Minimum 15 kW when on + effects_per_flow_hour={'electricity_cost': 25}, + on_off_parameters=OnOffParameters( + effects_per_switch_on={'startup_cost': 100}, + consecutive_on_hours_min=2, # Min run time + consecutive_off_hours_min=1, # Min off time + ), + ) + ``` -**Python Class:** [`Flow`][flixopt.elements.Flow] + ## Fixed Profile (Renewable Generation) -**Key Parameters:** -- `size`: Flow size $\text{P}$ (can be fixed or variable with InvestParameters) -- `relative_minimum`, `relative_maximum`: Relative bounds $\text{p}^{\text{L}}_{\text{rel}}, \text{p}^{\text{U}}_{\text{rel}}$ -- `effects_per_flow_hour`: Operational effects (costs, emissions, etc.) -- `invest_parameters`: Optional investment modeling (see [InvestParameters](../features/InvestParameters.md)) -- `on_off_parameters`: Optional on/off operation (see [OnOffParameters](../features/OnOffParameters.md)) + ```python + import numpy as np + from flixopt import Flow -See the [`Flow`][flixopt.elements.Flow] API documentation for complete parameter list and usage examples. + solar_generation = Flow( + label='solar_power', + bus='electricity_grid', + size=25, # 25 MW installed + fixed_relative_profile=np.array([0, 0.1, 0.4, 0.8, 0.9, 0.7, 0.3, 0.1, 0]), + ) + ``` --- ## See Also -- [OnOffParameters](../features/OnOffParameters.md) - Binary on/off operation -- [InvestParameters](../features/InvestParameters.md) - Variable flow sizing -- [Bus](../elements/Bus.md) - Flow balance constraints -- [LinearConverter](../elements/LinearConverter.md) - Flow ratio constraints -- [Storage](../elements/Storage.md) - Flow integration over time -- [Modeling Patterns](../modeling-patterns/index.md) - Mathematical building blocks +- **Features:** [OnOffParameters](../features/OnOffParameters.md) · [InvestParameters](../features/InvestParameters.md) +- **Elements:** [Bus](Bus.md) · [Storage](Storage.md) · [LinearConverter](LinearConverter.md) +- **Patterns:** [Modeling Patterns](../modeling-patterns/index.md) +- **Effects:** [Effects, Penalty & Objective](../effects-penalty-objective.md) diff --git a/docs/user-guide/mathematical-notation/elements/LinearConverter.md b/docs/user-guide/mathematical-notation/elements/LinearConverter.md index b007aa7f5..076ae3174 100644 --- a/docs/user-guide/mathematical-notation/elements/LinearConverter.md +++ b/docs/user-guide/mathematical-notation/elements/LinearConverter.md @@ -1,50 +1,204 @@ -[`LinearConverters`][flixopt.components.LinearConverter] define a ratio between incoming and outgoing [Flows](../elements/Flow.md). +# LinearConverter -$$ \label{eq:Linear-Transformer-Ratio} - \sum_{f_{\text{in}} \in \mathcal F_{in}} \text a_{f_{\text{in}}}(\text{t}_i) \cdot p_{f_\text{in}}(\text{t}_i) = \sum_{f_{\text{out}} \in \mathcal F_{out}} \text b_{f_\text{out}}(\text{t}_i) \cdot p_{f_\text{out}}(\text{t}_i) -$$ +A LinearConverter defines linear relationships (ratios) between incoming and outgoing flows, representing energy/material conversion processes with fixed or variable efficiencies. -With: +**Implementation:** -- $\mathcal F_{in}$ and $\mathcal F_{out}$ being the set of all incoming and outgoing flows -- $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 -- $\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 +- **Component Class:** [`LinearConverter`][flixopt.components.LinearConverter] +- **Model Class:** [`LinearConverterModel`][flixopt.components.LinearConverterModel] -With one incoming **Flow** and one outgoing **Flow**, this can be simplified to: +**Related:** [`Flow`](Flow.md) · [`Bus`](Bus.md) · [`Storage`](Storage.md) -$$ \label{eq:Linear-Transformer-Ratio-simple} - \text a(\text{t}_i) \cdot p_{f_\text{in}}(\text{t}_i) = p_{f_\text{out}}(\text{t}_i) -$$ +--- -where $\text a$ can be interpreted as the conversion efficiency of the **LinearConverter**. +=== "Core Formulation" -#### Piecewise Conversion factors -The conversion efficiency can be defined as a piecewise linear approximation. See [Piecewise](../features/Piecewise.md) for more details. + ## General Linear Ratio Constraint ---- + The fundamental constraint relates all incoming and outgoing flows through linear conversion factors: + + $$ \label{eq:Linear-Transformer-Ratio} + \sum_{f_{\text{in}} \in \mathcal F_{in}} \text a_{f_{\text{in}}}(\text{t}_i) \cdot p_{f_\text{in}}(\text{t}_i) = \sum_{f_{\text{out}} \in \mathcal F_{out}} \text b_{f_\text{out}}(\text{t}_i) \cdot p_{f_\text{out}}(\text{t}_i) + $$ + + ??? info "Variables" + | Symbol | Description | Domain | + |--------|-------------|--------| + | $p_{f_\text{in}}(\text{t}_i)$ | Flow rate of incoming flow $f_\text{in}$ at time $\text{t}_i$ | $\mathbb{R}$ | + | $p_{f_\text{out}}(\text{t}_i)$ | Flow rate of outgoing flow $f_\text{out}$ at time $\text{t}_i$ | $\mathbb{R}$ | + + ??? info "Parameters" + | Symbol | Description | Interpretation | + |--------|-------------|----------------| + | $\text a_{f_\text{in}}(\text{t}_i)$ | Conversion factor for incoming flow $f_\text{in}$ | Weight/efficiency coefficient | + | $\text b_{f_\text{out}}(\text{t}_i)$ | Conversion factor for outgoing flow $f_\text{out}$ | Weight/efficiency coefficient | + + ??? info "Sets" + | Symbol | Description | + |--------|-------------| + | $\mathcal F_{in}$ | Set of all incoming flows | + | $\mathcal F_{out}$ | Set of all outgoing flows | + + ## Simplified Single-Input Single-Output Form + + For the common case of one incoming and one outgoing flow, equation $\eqref{eq:Linear-Transformer-Ratio}$ simplifies to: + + $$ \label{eq:Linear-Transformer-Ratio-simple} + \text a(\text{t}_i) \cdot p_{f_\text{in}}(\text{t}_i) = p_{f_\text{out}}(\text{t}_i) + $$ + + **Physical Interpretation:** + + - $\text a$ represents the **conversion efficiency** or **conversion ratio** + - For a heat pump: $\text a$ is the Coefficient of Performance (COP) + - For a boiler: $\text a$ is the thermal efficiency (typically 0.85-0.95) + - For a generator: $\text a$ is the electrical efficiency (typically 0.3-0.5) + +=== "Advanced & Edge Cases" + + ## Piecewise Linear Conversion Factors + + Conversion efficiencies often vary with operating conditions (e.g., partial load efficiency, temperature-dependent COP). This is modeled using [Piecewise](../features/Piecewise.md) linear approximations. + + **Example:** Heat pump COP as a function of ambient temperature or load fraction. + + See [Piecewise](../features/Piecewise.md) for the mathematical formulation of piecewise linear relationships. + + ## Multiple Inputs/Outputs + + LinearConverters can model complex multi-flow processes: + + - **CHP (Combined Heat and Power):** 1 fuel input → 2 outputs (electricity + heat) + - **Heat pump with auxiliary:** 2 inputs (electricity + ambient heat) → 1 output (useful heat) + - **Multi-fuel boiler:** Multiple fuel inputs → 1 heat output + + Each flow has its own conversion factor $\text a_{f}$ or $\text b_{f}$ defining the ratio. + + ## Time-Varying Conversion Factors + + Conversion factors can be time-dependent $\text a(\text{t}_i)$ to model: + + - Seasonal efficiency variations + - Temperature-dependent performance + - Degradation over time + - Scheduled maintenance periods -## Implementation + ## Investment Sizing -**Python Class:** [`LinearConverter`][flixopt.components.LinearConverter] + When using [InvestParameters](../features/InvestParameters.md), the converter size becomes an optimization variable. Flow sizes are then linked to the converter's optimized capacity. -**Specialized Linear Converters:** + ## On/Off Operation -FlixOpt provides specialized linear converter classes for common applications: + Combining with [OnOffParameters](../features/OnOffParameters.md) allows modeling: -- **[`HeatPump`][flixopt.linear_converters.HeatPump]** - Coefficient of Performance (COP) based conversion -- **[`Power2Heat`][flixopt.linear_converters.Power2Heat]** - Electric heating with efficiency ≤ 1 + - Minimum run times + - Startup costs + - Part-load restrictions (minimum load when operating) + +=== "Mathematical Patterns" + + LinearConverter formulation relies on: + + - **Linear equality constraints** - Enforcing the ratio relationship + - **[Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)** - For flow rate bounds + - **[Piecewise Linear Approximations](../features/Piecewise.md)** - For non-constant conversion factors + +=== "Examples" + + ## Simple Boiler (Single Input/Output) + + ```python + from flixopt import LinearConverter, Flow + + boiler = LinearConverter( + label='gas_boiler', + inputs=[Flow(label='gas_in', bus='natural_gas', size=100)], + outputs=[Flow(label='heat_out', bus='heating', size=90)], + conversion_factors={('gas_in', 'heat_out'): 0.9}, # 90% efficiency + ) + ``` + + ## CHP Plant (One Input, Two Outputs) + + ```python + from flixopt import LinearConverter, Flow + + chp = LinearConverter( + label='chp_unit', + inputs=[Flow(label='fuel_in', bus='natural_gas', size=100)], + outputs=[ + Flow(label='electricity_out', bus='electricity', size=35), + Flow(label='heat_out', bus='heating', size=55), + ], + conversion_factors={ + ('fuel_in', 'electricity_out'): 0.35, # 35% electrical efficiency + ('fuel_in', 'heat_out'): 0.55, # 55% thermal efficiency + }, + ) + ``` + + ## Heat Pump with Temperature-Dependent COP + + ```python + from flixopt import LinearConverter, Flow, Piecewise, Piece + import numpy as np + + # COP varies from 2.5 (cold) to 4.0 (warm) + cop_curve = Piecewise( + [ + Piece((-10, 2.5), (0, 3.0)), # -10°C to 0°C + Piece((0, 3.0), (10, 3.5)), # 0°C to 10°C + Piece((10, 3.5), (20, 4.0)), # 10°C to 20°C + ] + ) + + heat_pump = LinearConverter( + label='heat_pump', + inputs=[Flow(label='electricity_in', bus='electricity', size=25)], + outputs=[Flow(label='heat_out', bus='heating', size=100)], + conversion_factors={('electricity_in', 'heat_out'): cop_curve}, + ) + ``` + + ## Converter with Investment Decision + + ```python + from flixopt import LinearConverter, Flow, InvestParameters + + electrolyzer = LinearConverter( + label='hydrogen_electrolyzer', + inputs=[Flow( + label='electricity_in', + bus='electricity', + size=InvestParameters( + minimum_size=0, + maximum_size=1000, # Up to 1 MW + specific_effects={'cost': 800}, # €800/kW annualized + ), + )], + outputs=[Flow(label='h2_out', bus='hydrogen', size=1)], # Sized by ratio + conversion_factors={('electricity_in', 'h2_out'): 0.65}, # 65% efficiency + ) + ``` + +--- + +## Specialized LinearConverter Classes + +FlixOpt provides specialized classes that automatically configure the conversion factors based on physical relationships: + +- **[`HeatPump`][flixopt.linear_converters.HeatPump]** - COP-based electric heating +- **[`Power2Heat`][flixopt.linear_converters.Power2Heat]** - Direct electric heating (efficiency ≤ 1) - **[`CHP`][flixopt.linear_converters.CHP]** - Combined heat and power generation - **[`Boiler`][flixopt.linear_converters.Boiler]** - Fuel to heat conversion +- **[`HeatPumpWithSource`][flixopt.linear_converters.HeatPumpWithSource]** - Heat pump with explicit ambient source -These classes handle the mathematical formulation automatically based on physical relationships. - -See the API documentation for implementation details and usage examples. +These provide more intuitive interfaces for common applications. --- ## See Also -- [Flow](../elements/Flow.md) - Definition of flow rates -- [Piecewise](../features/Piecewise.md) - Non-linear conversion efficiency modeling -- [InvestParameters](../features/InvestParameters.md) - Variable converter sizing -- [Modeling Patterns](../modeling-patterns/index.md) - Mathematical building blocks +- **Elements:** [Flow](Flow.md) · [Bus](Bus.md) · [Storage](Storage.md) +- **Features:** [Piecewise](../features/Piecewise.md) · [InvestParameters](../features/InvestParameters.md) · [OnOffParameters](../features/OnOffParameters.md) +- **Patterns:** [Modeling Patterns](../modeling-patterns/index.md) diff --git a/docs/user-guide/mathematical-notation/elements/Storage.md b/docs/user-guide/mathematical-notation/elements/Storage.md index cd7046592..02443c3f4 100644 --- a/docs/user-guide/mathematical-notation/elements/Storage.md +++ b/docs/user-guide/mathematical-notation/elements/Storage.md @@ -1,79 +1,187 @@ -# Storages -**Storages** have one incoming and one outgoing **[Flow](../elements/Flow.md)** with a charging and discharging efficiency. -A storage has a state of charge $c(\text{t}_i)$ which is limited by its `size` $\text C$ and relative bounds $\eqref{eq:Storage_Bounds}$. - -$$ \label{eq:Storage_Bounds} - \text C \cdot \text c^{\text{L}}_{\text{rel}}(\text t_{i}) - \leq c(\text{t}_i) \leq - \text C \cdot \text c^{\text{U}}_{\text{rel}}(\text t_{i}) -$$ - -Where: - -- $\text C$ is the size of the storage -- $c(\text{t}_i)$ is the state of charge at time $\text{t}_i$ -- $\text c^{\text{L}}_{\text{rel}}(\text t_{i})$ is the relative lower bound (typically 0) -- $\text c^{\text{U}}_{\text{rel}}(\text t_{i})$ is the relative upper bound (typically 1) - -With $\text c^{\text{L}}_{\text{rel}}(\text t_{i}) = 0$ and $\text c^{\text{U}}_{\text{rel}}(\text t_{i}) = 1$, -Equation $\eqref{eq:Storage_Bounds}$ simplifies to - -$$ 0 \leq c(\text t_{i}) \leq \text C $$ - -The state of charge $c(\text{t}_i)$ decreases by a fraction of the prior state of charge. The belonging parameter -$ \dot{ \text c}_\text{rel, loss}(\text{t}_i)$ expresses the "loss fraction per hour". The storage balance from $\text{t}_i$ to $\text t_{i+1}$ is - -$$ -\begin{align*} - c(\text{t}_{i+1}) &= c(\text{t}_{i}) \cdot (1-\dot{\text{c}}_\text{rel,loss}(\text{t}_i))^{\Delta \text{t}_{i}} \\ - &\quad + p_{f_\text{in}}(\text{t}_i) \cdot \Delta \text{t}_i \cdot \eta_\text{in}(\text{t}_i) \\ - &\quad - p_{f_\text{out}}(\text{t}_i) \cdot \Delta \text{t}_i \cdot \eta_\text{out}(\text{t}_i) - \tag{3} -\end{align*} -$$ - -Where: - -- $c(\text{t}_{i+1})$ is the state of charge at time $\text{t}_{i+1}$ -- $c(\text{t}_{i})$ is the state of charge at time $\text{t}_{i}$ -- $\dot{\text{c}}_\text{rel,loss}(\text{t}_i)$ is the relative loss rate (self-discharge) per hour -- $\Delta \text{t}_{i}$ is the time step duration in hours -- $p_{f_\text{in}}(\text{t}_i)$ is the input flow rate at time $\text{t}_i$ -- $\eta_\text{in}(\text{t}_i)$ is the charging efficiency at time $\text{t}_i$ -- $p_{f_\text{out}}(\text{t}_i)$ is the output flow rate at time $\text{t}_i$ -- $\eta_\text{out}(\text{t}_i)$ is the discharging efficiency at time $\text{t}_i$ +# Storage + +A Storage component represents energy or material accumulation with charging/discharging flows, state of charge tracking, and efficiency losses. + +**Implementation:** + +- **Component Class:** [`Storage`][flixopt.components.Storage] +- **Model Class:** [`StorageModel`][flixopt.components.StorageModel] + +**Related:** [`Flow`](Flow.md) · [`Bus`](Bus.md) · [`LinearConverter`](LinearConverter.md) --- -## Mathematical Patterns Used +=== "Core Formulation" -Storage formulation uses the following modeling patterns: + ## State of Charge Bounds -- **[Basic Bounds](../modeling-patterns/bounds-and-states.md#basic-bounds)** - For charge state bounds (equation $\eqref{eq:Storage_Bounds}$) -- **[Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)** - For flow rate bounds relative to storage size + The state of charge $c(\text{t}_i)$ is bounded by the storage size and relative limits: -When combined with investment parameters, storage can use: -- **[Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state)** - Investment decisions (see [InvestParameters](../features/InvestParameters.md)) + $$ \label{eq:Storage_Bounds} + \text C \cdot \text c^{\text{L}}_{\text{rel}}(\text t_{i}) + \leq c(\text{t}_i) \leq + \text C \cdot \text c^{\text{U}}_{\text{rel}}(\text t_{i}) + $$ ---- + ??? info "Variables" + | Symbol | Description | Domain | + |--------|-------------|--------| + | $c(\text{t}_i)$ | State of charge at time $\text{t}_i$ | $\mathbb{R}_+$ | + | $\text C$ | Storage capacity | $\mathbb{R}_+$ or decision variable (with [InvestParameters](../features/InvestParameters.md)) | + + ??? info "Parameters" + | Symbol | Description | Typical Value | + |--------|-------------|---------------| + | $\text c^{\text{L}}_{\text{rel}}(\text t_{i})$ | Relative lower bound at time $\text{t}_i$ | 0 | + | $\text c^{\text{U}}_{\text{rel}}(\text t_{i})$ | Relative upper bound at time $\text{t}_i$ | 1 | + + ### Simplified Form + + With standard bounds $\text c^{\text{L}}_{\text{rel}} = 0$ and $\text c^{\text{U}}_{\text{rel}} = 1$, equation $\eqref{eq:Storage_Bounds}$ simplifies to: + + $$ 0 \leq c(\text t_{i}) \leq \text C $$ + + ## Storage Balance Equation + + The state of charge evolves according to charging/discharging flows and self-discharge losses: + + $$ + \begin{align} + c(\text{t}_{i+1}) &= c(\text{t}_{i}) \cdot (1-\dot{\text{c}}_\text{rel,loss}(\text{t}_i))^{\Delta \text{t}_{i}} \nonumber \\ + &\quad + p_{f_\text{in}}(\text{t}_i) \cdot \Delta \text{t}_i \cdot \eta_\text{in}(\text{t}_i) \nonumber \\ + &\quad - p_{f_\text{out}}(\text{t}_i) \cdot \Delta \text{t}_i / \eta_\text{out}(\text{t}_i) + \label{eq:storage_balance} + \end{align} + $$ + + ??? info "Flow Variables" + | Symbol | Description | Domain | + |--------|-------------|--------| + | $p_{f_\text{in}}(\text{t}_i)$ | Input flow rate (charging power) at time $\text{t}_i$ | $\mathbb{R}_+$ | + | $p_{f_\text{out}}(\text{t}_i)$ | Output flow rate (discharging power) at time $\text{t}_i$ | $\mathbb{R}_+$ | + + ??? info "Efficiency Parameters" + | Symbol | Description | Typical Range | Units | + |--------|-------------|---------------|-------| + | $\eta_\text{in}(\text{t}_i)$ | Charging efficiency | 0.85-0.98 | dimensionless | + | $\eta_\text{out}(\text{t}_i)$ | Discharging efficiency | 0.85-0.98 | dimensionless | + | $\dot{\text{c}}_\text{rel,loss}(\text{t}_i)$ | Relative self-discharge rate | 0-0.05 | per hour | + | $\Delta \text{t}_{i}$ | Timestep duration | - | hours | + + **Physical Interpretation:** + + - The first term represents self-discharge (exponential decay) + - The second term adds energy from charging (accounting for charging losses) + - The third term subtracts energy from discharging (accounting for discharging losses) + +=== "Advanced & Edge Cases" + + ## Initial State of Charge + + The storage must be initialized with a starting charge state: + + - **Parameter:** `initial_charge_state` sets $c(\text{t}_0)$ + - **Default:** Often set to some fraction of capacity (e.g., 0.5 × $\text C$) + + ## Final State of Charge Constraints + + Optional bounds on the state of charge at the end of the optimization horizon: + + $$ + \text c^{\text{L}}_{\text{final}} \leq c(\text{t}_\text{end}) \leq \text c^{\text{U}}_{\text{final}} + $$ + + **Use Case:** Ensure the storage ends with sufficient charge for the next optimization period, or enforce cyclic conditions. + + **Parameters:** + - `minimal_final_charge_state`: Lower bound $\text c^{\text{L}}_{\text{final}}$ + - `maximal_final_charge_state`: Upper bound $\text c^{\text{U}}_{\text{final}}$ + + ## Investment Sizing + + When using [InvestParameters](../features/InvestParameters.md), the storage capacity $\text C$ becomes an optimization variable: + + - Storage size is determined by the optimizer to minimize total system cost + - Flow capacities (charging/discharging rates) can be independently sized or linked to storage capacity + + ## Prevent Simultaneous Charging/Discharging + + Some storage types (e.g., batteries with shared converters) cannot charge and discharge simultaneously. This is modeled using [OnOffParameters](../features/OnOffParameters.md) on the input/output flows with appropriate constraints. + + ## Variable Timesteps + + The formulation naturally handles variable timestep durations $\Delta \text{t}_i$ through the balance equation $\eqref{eq:storage_balance}$. + +=== "Mathematical Patterns" + + Storage formulation builds on the following modeling patterns: + + - **[Basic Bounds](../modeling-patterns/bounds-and-states.md#basic-bounds)** - Charge state bounds (equation $\eqref{eq:Storage_Bounds}$) + - **[Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)** - Flow rate bounds relative to storage size + - **[Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state)** - When using [InvestParameters](../features/InvestParameters.md) for capacity investment + +=== "Examples" + + ## Basic Battery Storage + + ```python + from flixopt import Storage, Flow + + battery = Storage( + label='battery', + inputs=[Flow(label='charge', bus='electricity', size=50)], # 50 kW charging + outputs=[Flow(label='discharge', bus='electricity', size=50)], # 50 kW discharging + capacity_in_flow_hours=200, # 200 kWh capacity + initial_charge_state=100, # Start at 100 kWh (50% SOC) + eta_charge=0.95, # 95% charging efficiency + eta_discharge=0.95, # 95% discharging efficiency + relative_loss_per_hour=0.001, # 0.1% self-discharge per hour + ) + ``` + + ## Thermal Storage with Final State Constraint + + ```python + from flixopt import Storage, Flow -## Implementation + thermal_storage = Storage( + label='heat_tank', + inputs=[Flow(label='heat_in', bus='heating', size=100)], + outputs=[Flow(label='heat_out', bus='heating', size=100)], + capacity_in_flow_hours=500, # 500 kWh thermal capacity + initial_charge_state=250, # Start half-full + minimal_final_charge_state=250, # End at least half-full + eta_charge=0.98, # Minimal losses charging + eta_discharge=0.98, + relative_loss_per_hour=0.02, # 2% heat loss per hour + ) + ``` -**Python Class:** [`Storage`][flixopt.components.Storage] + ## Storage with Investment Decision -**Key Parameters:** -- `capacity_in_flow_hours`: Storage capacity $\text{C}$ -- `relative_loss_per_hour`: Self-discharge rate $\dot{\text{c}}_\text{rel,loss}$ -- `initial_charge_state`: Initial charge $c(\text{t}_0)$ -- `minimal_final_charge_state`, `maximal_final_charge_state`: Final charge bounds $c(\text{t}_\text{end})$ (optional) -- `eta_charge`, `eta_discharge`: Charging/discharging efficiencies $\eta_\text{in}, \eta_\text{out}$ + ```python + from flixopt import Storage, Flow, InvestParameters -See the [`Storage`][flixopt.components.Storage] API documentation for complete parameter list and usage examples. + optimized_battery = Storage( + label='battery_investment', + inputs=[Flow(label='charge', bus='electricity', size=100)], + outputs=[Flow(label='discharge', bus='electricity', size=100)], + capacity_in_flow_hours=InvestParameters( + minimum_size=0, # Can choose not to build + maximum_size=1000, # Up to 1 MWh + specific_effects={'cost': 200}, # €200 per kWh annualized + ), + initial_charge_state=0, + eta_charge=0.92, + eta_discharge=0.92, + ) + ``` --- ## See Also -- [Flow](../elements/Flow.md) - Input and output flow definitions -- [InvestParameters](../features/InvestParameters.md) - Variable storage sizing -- [Modeling Patterns](../modeling-patterns/index.md) - Mathematical building blocks +- **Elements:** [Flow](Flow.md) · [Bus](Bus.md) +- **Features:** [InvestParameters](../features/InvestParameters.md) · [OnOffParameters](../features/OnOffParameters.md) +- **Patterns:** [Modeling Patterns](../modeling-patterns/index.md) +- **Components:** [LinearConverter](LinearConverter.md) diff --git a/docs/user-guide/mathematical-notation/features/InvestParameters.md b/docs/user-guide/mathematical-notation/features/InvestParameters.md index 14fe02c79..9304c5918 100644 --- a/docs/user-guide/mathematical-notation/features/InvestParameters.md +++ b/docs/user-guide/mathematical-notation/features/InvestParameters.md @@ -1,302 +1,317 @@ # InvestParameters -[`InvestParameters`][flixopt.interface.InvestParameters] model investment decisions in optimization problems, enabling both binary (invest/don't invest) and continuous sizing choices with comprehensive cost modeling. +InvestParameters enable investment decision modeling in optimization, supporting both binary (invest/don't invest) and continuous sizing choices with comprehensive cost modeling. -## Investment Decision Types +**Implementation:** -FlixOpt supports two main types of investment decisions: +- **Feature Class:** [`InvestParameters`][flixopt.interface.InvestParameters] +- **Used by:** [`Flow`][flixopt.elements.Flow] · [`Storage`][flixopt.components.Storage] · [`LinearConverter`][flixopt.components.LinearConverter] -### Binary Investment - -Fixed-size investment creating a yes/no decision (e.g., install a 100 kW generator): - -$$\label{eq:invest_binary} -v_\text{invest} = s_\text{invest} \cdot \text{size}_\text{fixed} -$$ - -With: -- $v_\text{invest}$ being the resulting investment size -- $s_\text{invest} \in \{0, 1\}$ being the binary investment decision -- $\text{size}_\text{fixed}$ being the predefined component size - -**Behavior:** -- $s_\text{invest} = 0$: no investment ($v_\text{invest} = 0$) -- $s_\text{invest} = 1$: invest at fixed size ($v_\text{invest} = \text{size}_\text{fixed}$) +**Related:** [`OnOffParameters`](OnOffParameters.md) · [`Piecewise`](Piecewise.md) --- -### Continuous Sizing +=== "Core Formulation" -Variable-size investment with bounds (e.g., battery capacity from 10-1000 kWh): + ## Binary Investment Decision -$$\label{eq:invest_continuous} -s_\text{invest} \cdot \text{size}_\text{min} \leq v_\text{invest} \leq s_\text{invest} \cdot \text{size}_\text{max} -$$ + Fixed-size investment creating a yes/no decision (e.g., install a 100 kW generator): -With: -- $v_\text{invest}$ being the investment size variable (continuous) -- $s_\text{invest} \in \{0, 1\}$ being the binary investment decision -- $\text{size}_\text{min}$ being the minimum investment size (if investing) -- $\text{size}_\text{max}$ being the maximum investment size + $$\label{eq:invest_binary} + v_\text{invest} = s_\text{invest} \cdot \text{size}_\text{fixed} + $$ -**Behavior:** -- $s_\text{invest} = 0$: no investment ($v_\text{invest} = 0$) -- $s_\text{invest} = 1$: invest with size in $[\text{size}_\text{min}, \text{size}_\text{max}]$ + ??? info "Variables" + | Symbol | Description | Domain | + |--------|-------------|--------| + | $s_\text{invest}$ | Binary investment decision | $\{0, 1\}$ | + | $v_\text{invest}$ | Resulting investment size | $\mathbb{R}_+$ | -This uses the **bounds with state** pattern described in [Bounds and States](../modeling-patterns/bounds-and-states.md#bounds-with-state). + ??? info "Parameters" + | Symbol | Description | + |--------|-------------| + | $\text{size}_\text{fixed}$ | Predefined component size | ---- + **Behavior:** -### Optional vs. Mandatory Investment + - $s_\text{invest} = 0$: no investment ($v_\text{invest} = 0$) + - $s_\text{invest} = 1$: invest at fixed size ($v_\text{invest} = \text{size}_\text{fixed}$) -The `mandatory` parameter controls whether investment is required: + ## Continuous Sizing Decision -**Optional Investment** (`mandatory=False`, default): -$$\label{eq:invest_optional} -s_\text{invest} \in \{0, 1\} -$$ + Variable-size investment with bounds (e.g., battery capacity from 10-1000 kWh): -The optimization can freely choose to invest or not. + $$\label{eq:invest_continuous} + s_\text{invest} \cdot \text{size}_\text{min} \leq v_\text{invest} \leq s_\text{invest} \cdot \text{size}_\text{max} + $$ -**Mandatory Investment** (`mandatory=True`): -$$\label{eq:invest_mandatory} -s_\text{invest} = 1 -$$ + ??? info "Variables" + | Symbol | Description | Domain | + |--------|-------------|--------| + | $s_\text{invest}$ | Binary investment decision | $\{0, 1\}$ | + | $v_\text{invest}$ | Investment size (continuous) | $\mathbb{R}_+$ | -The investment must occur (useful for mandatory upgrades or replacements). + ??? info "Parameters" + | Symbol | Description | + |--------|-------------| + | $\text{size}_\text{min}$ | Minimum investment size (if investing) | + | $\text{size}_\text{max}$ | Maximum investment size | ---- + **Behavior:** -## Effect Modeling + - $s_\text{invest} = 0$: no investment ($v_\text{invest} = 0$) + - $s_\text{invest} = 1$: invest with size in $[\text{size}_\text{min}, \text{size}_\text{max}]$ -Investment effects (costs, emissions, etc.) are modeled using three components: + This uses the **[Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state)** pattern. -### Fixed Effects + ## Investment Effects (Costs) -One-time effects incurred if investment is made, independent of size: + ### Fixed Effects -$$\label{eq:invest_fixed_effects} -E_{e,\text{fix}} = s_\text{invest} \cdot \text{fix}_e -$$ + One-time effects incurred if investment is made, independent of size: -With: -- $E_{e,\text{fix}}$ being the fixed contribution to effect $e$ -- $\text{fix}_e$ being the fixed effect value (e.g., fixed installation cost) + $$\label{eq:invest_fixed_effects} + E_{e,\text{fix}} = s_\text{invest} \cdot \text{fix}_e + $$ -**Examples:** -- Fixed installation costs (permits, grid connection) -- One-time environmental impacts (land preparation) -- Fixed labor or administrative costs + **Examples:** Fixed installation costs (permits, grid connection), one-time environmental impacts. ---- + ### Specific Effects (Per-Unit Costs) -### Specific Effects + Effects proportional to investment size: -Effects proportional to investment size (per-unit costs): + $$\label{eq:invest_specific_effects} + E_{e,\text{spec}} = v_\text{invest} \cdot \text{spec}_e + $$ -$$\label{eq:invest_specific_effects} -E_{e,\text{spec}} = v_\text{invest} \cdot \text{spec}_e -$$ + **Examples:** Equipment costs (€/kW), material requirements (kg steel/kW), recurring maintenance (€/kW/year). -With: -- $E_{e,\text{spec}}$ being the size-dependent contribution to effect $e$ -- $\text{spec}_e$ being the specific effect value per unit size (e.g., €/kW) + ### Total Investment Effects -**Examples:** -- Equipment costs (€/kW) -- Material requirements (kg steel/kW) -- Recurring costs (€/kW/year maintenance) + $$\label{eq:invest_total_effects} + E_{e,\text{invest}} = E_{e,\text{fix}} + E_{e,\text{spec}} + E_{e,\text{pw}} + E_{e,\text{retirement}} + $$ ---- + Where $E_{e,\text{pw}}$ is the piecewise contribution (see Advanced tab) and $E_{e,\text{retirement}}$ are retirement effects (see Advanced tab). -### Piecewise Effects +=== "Advanced & Edge Cases" -Non-linear effect relationships using piecewise linear approximations: + ## Optional vs. Mandatory Investment -$$\label{eq:invest_piecewise_effects} -E_{e,\text{pw}} = \sum_{k=1}^{K} \lambda_k \cdot r_{e,k} -$$ + The `mandatory` parameter controls whether investment is required: -Subject to: -$$ -v_\text{invest} = \sum_{k=1}^{K} \lambda_k \cdot v_k -$$ + **Optional Investment** (`mandatory=False`, default): + $$\label{eq:invest_optional} + s_\text{invest} \in \{0, 1\} + $$ -With: -- $E_{e,\text{pw}}$ being the piecewise contribution to effect $e$ -- $\lambda_k$ being the piecewise lambda variables (see [Piecewise](../features/Piecewise.md)) -- $r_{e,k}$ being the effect rate at piece $k$ -- $v_k$ being the size points defining the pieces + The optimization can freely choose to invest or not. -**Use cases:** -- Economies of scale (bulk discounts) -- Technology learning curves -- Threshold effects (capacity tiers with different costs) + **Mandatory Investment** (`mandatory=True`): + $$\label{eq:invest_mandatory} + s_\text{invest} = 1 + $$ -See [Piecewise](../features/Piecewise.md) for detailed mathematical formulation. + The investment must occur (useful for mandatory upgrades or replacements). ---- + ## Retirement Effects -### Retirement Effects + Effects incurred if investment is NOT made (when retiring/not replacing existing equipment): -Effects incurred if investment is NOT made (when retiring/not replacing existing equipment): + $$\label{eq:invest_retirement_effects} + E_{e,\text{retirement}} = (1 - s_\text{invest}) \cdot \text{retirement}_e + $$ -$$\label{eq:invest_retirement_effects} -E_{e,\text{retirement}} = (1 - s_\text{invest}) \cdot \text{retirement}_e -$$ + **Behavior:** -With: -- $E_{e,\text{retirement}}$ being the retirement contribution to effect $e$ -- $\text{retirement}_e$ being the retirement effect value + - $s_\text{invest} = 0$: retirement effects are incurred + - $s_\text{invest} = 1$: no retirement effects -**Behavior:** -- $s_\text{invest} = 0$: retirement effects are incurred -- $s_\text{invest} = 1$: no retirement effects + **Examples:** Demolition/disposal costs, decommissioning expenses, contractual penalties, opportunity costs. -**Examples:** -- Demolition or disposal costs -- Decommissioning expenses -- Contractual penalties for not investing -- Opportunity costs or lost revenues + ## Piecewise Effects (Economies of Scale) ---- + Non-linear effect relationships using piecewise linear approximations: -### Total Investment Effects + $$\label{eq:invest_piecewise_effects} + E_{e,\text{pw}} = \sum_{k=1}^{K} \lambda_k \cdot r_{e,k} + $$ -The total contribution to effect $e$ from an investment is: + Subject to: + $$ + v_\text{invest} = \sum_{k=1}^{K} \lambda_k \cdot v_k + $$ -$$\label{eq:invest_total_effects} -E_{e,\text{invest}} = E_{e,\text{fix}} + E_{e,\text{spec}} + E_{e,\text{pw}} + E_{e,\text{retirement}} -$$ + ??? info "Piecewise Variables" + | Symbol | Description | + |--------|-------------| + | $\lambda_k$ | Piecewise lambda variables (see [Piecewise](Piecewise.md)) | + | $r_{e,k}$ | Effect rate at piece $k$ | + | $v_k$ | Size points defining the pieces | -Effects integrate into the overall system effects as described in [Effects, Penalty & Objective](../effects-penalty-objective.md). + **Use cases:** Economies of scale (bulk discounts), technology learning curves, threshold effects. ---- + See [Piecewise](Piecewise.md) for detailed mathematical formulation. -## Integration with Components + ## Integration with Component Sizing -Investment parameters modify component sizing: + Investment parameters modify component sizing: -### Without Investment -Component size is a fixed parameter: -$$ -\text{size} = \text{size}_\text{nominal} -$$ + **Without Investment:** + $$ + \text{size} = \text{size}_\text{nominal} + $$ -### With Investment -Component size becomes a variable: -$$ -\text{size} = v_\text{invest} -$$ + **With Investment:** + $$ + \text{size} = v_\text{invest} + $$ -This size variable then appears in component constraints. For example, flow rate bounds become: + This size variable then appears in component constraints. For example, flow rate bounds become: -$$ -v_\text{invest} \cdot \text{rel}_\text{lower} \leq p(t) \leq v_\text{invest} \cdot \text{rel}_\text{upper} -$$ + $$ + v_\text{invest} \cdot \text{rel}_\text{lower} \leq p(t) \leq v_\text{invest} \cdot \text{rel}_\text{upper} + $$ -Using the **scaled bounds** pattern from [Bounds and States](../modeling-patterns/bounds-and-states.md#scaled-bounds). + Using the **[Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)** pattern. ---- + ## Cost Annualization -## Cost Annualization + **Important:** All investment cost values must be properly weighted to match the optimization model's time horizon. -**Important:** All investment cost values must be properly weighted to match the optimization model's time horizon. + For long-term investments, costs should be annualized: -For long-term investments, costs should be annualized: + $$\label{eq:annualization} + \text{cost}_\text{annual} = \frac{\text{cost}_\text{capital} \cdot r}{1 - (1 + r)^{-n}} + $$ -$$\label{eq:annualization} -\text{cost}_\text{annual} = \frac{\text{cost}_\text{capital} \cdot r}{1 - (1 + r)^{-n}} -$$ + ??? info "Annualization Parameters" + | Symbol | Description | + |--------|-------------| + | $\text{cost}_\text{capital}$ | Upfront investment cost | + | $r$ | Discount rate | + | $n$ | Equipment lifetime (years) | -With: -- $\text{cost}_\text{capital}$ being the upfront investment cost -- $r$ being the discount rate -- $n$ being the equipment lifetime in years + **Example:** €1,000,000 equipment with 20-year life and 5% discount rate + $$ + \text{cost}_\text{annual} = \frac{1{,}000{,}000 \cdot 0.05}{1 - (1.05)^{-20}} \approx €80{,}243/\text{year} + $$ -**Example:** €1,000,000 equipment with 20-year life and 5% discount rate -$$ -\text{cost}_\text{annual} = \frac{1{,}000{,}000 \cdot 0.05}{1 - (1.05)^{-20}} \approx €80{,}243/\text{year} -$$ +=== "Mathematical Patterns" + + InvestParameters relies on: + + - **[Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state)** - For continuous sizing with binary investment decision + - **[Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)** - For linking investment size to component constraints + - **[Piecewise Linear Approximations](Piecewise.md)** - For non-linear cost structures + +=== "Examples" + + ## Binary Investment (Solar Panels) + + ```python + from flixopt import Flow, InvestParameters + + solar_flow = Flow( + label='solar_generation', + bus='electricity', + size=InvestParameters( + fixed_size=100, # 100 kW system (all or nothing) + mandatory=False, # Optional investment + effects_of_investment={'cost': 25000}, # Fixed installation + effects_of_investment_per_size={'cost': 1200}, # €1200/kW + ), + ) + ``` + + ## Continuous Sizing (Battery) + + ```python + from flixopt import Storage, Flow, InvestParameters + + battery = Storage( + label='battery_storage', + inputs=[Flow(label='charge', bus='electricity', size=100)], + outputs=[Flow(label='discharge', bus='electricity', size=100)], + capacity_in_flow_hours=InvestParameters( + minimum_size=10, # Minimum 10 kWh if investing + maximum_size=1000, # Maximum 1 MWh + effects_of_investment={'cost': 5000}, # Grid connection + effects_of_investment_per_size={'cost': 600}, # €600/kWh + ), + ) + ``` + + ## With Retirement Costs (Replacement Decision) + + ```python + from flixopt import LinearConverter, Flow, InvestParameters + + boiler = LinearConverter( + label='boiler_replacement', + inputs=[Flow( + label='gas_in', + bus='natural_gas', + size=InvestParameters( + minimum_size=50, # 50 kW minimum + maximum_size=200, # 200 kW maximum + effects_of_investment={'cost': 15000}, # New installation + effects_of_investment_per_size={'cost': 400}, # €400/kW + effects_of_retirement={'cost': 8000}, # Demolition if not replaced + ), + )], + outputs=[Flow(label='heat_out', bus='heating', size=1)], + conversion_factors={('gas_in', 'heat_out'): 0.9}, + ) + ``` + + ## Economies of Scale (Piecewise Costs) + + ```python + from flixopt import Storage, Flow, InvestParameters, Piecewise, Piece, PiecewiseEffects + + battery_investment = InvestParameters( + minimum_size=10, + maximum_size=1000, + piecewise_effects_of_investment=PiecewiseEffects( + piecewise_origin=Piecewise([ + Piece((0, 0), (100, 100)), # Small (0-100 kWh) + Piece((100, 100), (500, 500)), # Medium (100-500 kWh) + Piece((500, 500), (1000, 1000)), # Large (500-1000 kWh) + ]), + piecewise_shares={ + 'cost': Piecewise([ + Piece((0, 800), (100, 750)), # €800-750/kWh (small) + Piece((100, 750), (500, 600)), # €750-600/kWh (medium) + Piece((500, 600), (1000, 500)), # €600-500/kWh (large, bulk discount) + ]) + }, + ), + ) + ``` + + ## Mandatory Investment (Upgrade Required) + + ```python + from flixopt import Flow, InvestParameters + + grid_upgrade = Flow( + label='grid_connection', + bus='electricity', + size=InvestParameters( + minimum_size=100, + maximum_size=500, + mandatory=True, # Must upgrade + effects_of_investment_per_size={'cost': 1000}, # €1000/kW + ), + ) + ``` --- -## Implementation - -**Python Class:** [`InvestParameters`][flixopt.interface.InvestParameters] - -**Key Parameters:** -- `fixed_size`: For binary investments (mutually exclusive with continuous sizing) -- `minimum_size`, `maximum_size`: For continuous sizing -- `mandatory`: Whether investment is required (default: `False`) -- `effects_of_investment`: Fixed effects incurred when investing (replaces deprecated `fix_effects`) -- `effects_of_investment_per_size`: Per-unit effects proportional to size (replaces deprecated `specific_effects`) -- `piecewise_effects_of_investment`: Non-linear effect modeling (replaces deprecated `piecewise_effects`) -- `effects_of_retirement`: Effects for not investing (replaces deprecated `divest_effects`) - -See the [`InvestParameters`][flixopt.interface.InvestParameters] API documentation for complete parameter list and usage examples. - -**Used in:** -- [`Flow`][flixopt.elements.Flow] - Flexible capacity decisions -- [`Storage`][flixopt.components.Storage] - Storage sizing optimization -- [`LinearConverter`][flixopt.components.LinearConverter] - Converter capacity planning -- All components supporting investment decisions - ---- +## See Also -## Examples - -### Binary Investment (Solar Panels) -```python -solar_investment = InvestParameters( - fixed_size=100, # 100 kW system - mandatory=False, # Optional investment (default) - effects_of_investment={'cost': 25000}, # Installation costs - effects_of_investment_per_size={'cost': 1200}, # €1200/kW -) -``` - -### Continuous Sizing (Battery) -```python -battery_investment = InvestParameters( - minimum_size=10, # kWh - maximum_size=1000, - mandatory=False, # Optional investment (default) - effects_of_investment={'cost': 5000}, # Grid connection - effects_of_investment_per_size={'cost': 600}, # €600/kWh -) -``` - -### With Retirement Costs (Replacement) -```python -boiler_replacement = InvestParameters( - minimum_size=50, # kW - maximum_size=200, - mandatory=False, # Optional investment (default) - effects_of_investment={'cost': 15000}, - effects_of_investment_per_size={'cost': 400}, - effects_of_retirement={'cost': 8000}, # Demolition if not replaced -) -``` - -### Economies of Scale (Piecewise) -```python -battery_investment = InvestParameters( - minimum_size=10, - maximum_size=1000, - piecewise_effects_of_investment=PiecewiseEffects( - piecewise_origin=Piecewise([ - Piece(0, 100), # Small - Piece(100, 500), # Medium - Piece(500, 1000), # Large - ]), - piecewise_shares={ - 'cost': Piecewise([ - Piece(800, 750), # €800-750/kWh - Piece(750, 600), # €750-600/kWh - Piece(600, 500), # €600-500/kWh (bulk discount) - ]) - }, - ), -) -``` +- **Elements:** [Flow](../elements/Flow.md) · [Storage](../elements/Storage.md) · [LinearConverter](../elements/LinearConverter.md) +- **Features:** [OnOffParameters](OnOffParameters.md) · [Piecewise](Piecewise.md) +- **Patterns:** [Bounds and States](../modeling-patterns/bounds-and-states.md) +- **System-Level:** [Effects, Penalty & Objective](../effects-penalty-objective.md) diff --git a/flixopt/elements.py b/flixopt/elements.py index 2f63e8bdb..da4839b70 100644 --- a/flixopt/elements.py +++ b/flixopt/elements.py @@ -332,8 +332,7 @@ class Flow(Element): - **OnOffParameters**: Used for `on_off_parameters` when flow has discrete states Mathematical Formulation: - See the complete mathematical model in the documentation: - [Flow](../user-guide/mathematical-notation/elements/Flow.md) + See Args: label: Unique flow identifier within its component. @@ -668,6 +667,15 @@ def _format_invest_params(self, params: InvestParameters) -> str: class FlowModel(ElementModel): + """Mathematical model implementation for Flow elements. + + Creates optimization variables and constraints for flow rate bounds, + flow-hours tracking, and load factors. + + Mathematical Formulation: + See + """ + element: Flow # Type hint def __init__(self, model: FlowSystemModel, element: Flow): From 1bf2bb7d2f52fbe8c69d0b0bd1f3cd8249066fde Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:10:53 +0100 Subject: [PATCH 02/79] Improve organization --- .../mathematical-notation/elements/Flow.md | 177 ++++++++++++------ 1 file changed, 122 insertions(+), 55 deletions(-) diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md index 24e6ed21c..e01decb1c 100644 --- a/docs/user-guide/mathematical-notation/elements/Flow.md +++ b/docs/user-guide/mathematical-notation/elements/Flow.md @@ -11,11 +11,23 @@ A Flow represents the transfer of energy or material between a Bus and a Compone --- -=== "Core Formulation" +=== "Variables" + + | Variable Name | Symbol | Description | Domain | Created When | + |---------------|--------|-------------|--------|--------------| + | **flow_rate** | $p(\text{t}_{i})$ | Flow rate at each timestep | $\mathbb{R}$ | Always | + | **size** | $\text P$ | Flow capacity (decision variable) | $\mathbb{R}_+$ | `size` is `InvestParameters` | + | **invest_binary** | $s_\text{invest}$ | Binary investment decision | $\{0,1\}$ | `size` is `InvestParameters` | + | **total_flow_hours** | - | Cumulative flow-hours per period | $\mathbb{R}_+$ | `flow_hours_min/max` or `load_factor_min/max` specified | + | **on_off_state** | $s(\text{t}_i)$ | Binary on/off state | $\{0,1\}$ | `on_off_parameters` specified | + | **switch_on** | - | Startup indicator | $\{0,1\}$ | `on_off_parameters` specified | + | **switch_off** | - | Shutdown indicator | $\{0,1\}$ | `on_off_parameters` specified | + +=== "Constraints" ## Flow Rate Bounds - The flow rate $p(\text{t}_{i})$ is constrained by the flow size and relative bounds: + The primary constraint limits flow rate by size and relative bounds: $$ \label{eq:flow_rate} \text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) @@ -23,89 +35,91 @@ A Flow represents the transfer of energy or material between a Bus and a Compone \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i}) $$ - ??? info "Variables" - | Symbol | Description | Domain | - |--------|-------------|--------| - | $p(\text{t}_{i})$ | Flow rate at time $\text{t}_{i}$ | $\mathbb{R}$ | - | $\text P$ | Flow size (capacity) | $\mathbb{R}_+$ or decision variable (with [InvestParameters](../features/InvestParameters.md)) | - - ??? info "Parameters" - | Symbol | Description | Typical Value | - |--------|-------------|---------------| - | $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i})$ | Relative lower bound at time $\text{t}_{i}$ | 0 | - | $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ | Relative upper bound at time $\text{t}_{i}$ | 1 | + | Symbol | Python Parameter | Description | Default | + |--------|------------------|-------------|---------| + | $\text P$ | `size` | Flow capacity | Required | + | $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i})$ | `relative_minimum` | Relative lower bound (fraction of size) | 0 | + | $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ | `relative_maximum` | Relative upper bound (fraction of size) | 1 | - ## Simplified Form + **Simplified form** (with default bounds): $0 \leq p(\text{t}_{i}) \leq \text P$ - With standard bounds $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) = 0$ and $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i}) = 1$, equation \eqref{eq:flow_rate} simplifies to: - - $$ - 0 \leq p(\text{t}_{i}) \leq \text P - $$ + **Mathematical Pattern:** [Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds) -=== "Advanced & Edge Cases" + --- - ## Extensions with Optional Features + ## Load Factor Constraints - The basic flow formulation can be extended with additional constraints: + When specified, constrains the average utilization over a period: - ### On/Off Operation - - When combined with [OnOffParameters](../features/OnOffParameters.md), the flow gains binary on/off states with startup costs, minimum run times, and switching constraints. The formulation becomes: - - - **[Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state)** - - ### Investment Sizing + $$ + \text{LF}_\text{min} \cdot \text P \cdot N_t \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \leq \text{LF}_\text{max} \cdot \text P \cdot N_t + $$ - When using [InvestParameters](../features/InvestParameters.md), the flow size $\text P$ becomes an optimization variable rather than a fixed parameter: + | Symbol | Python Parameter | Description | Default | + |--------|------------------|-------------|---------| + | $\text{LF}_\text{min}$ | `load_factor_min` | Minimum average utilization (0-1) | 0 | + | $\text{LF}_\text{max}$ | `load_factor_max` | Maximum average utilization (0-1) | 1 | + | $N_t$ | - | Number of timesteps in period | - | - - **[Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state)** + --- - ### Load Factor Constraints + ## Flow Hours Limits - Minimum and maximum average utilization over time periods: + Direct constraints on cumulative flow-hours (energy/material throughput): $$ - \text{load\_factor\_min} \cdot \text P \cdot N_t \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \leq \text{load\_factor\_max} \cdot \text P \cdot N_t + \text{FH}_\text{min} \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{FH}_\text{max} $$ - Where $N_t$ is the number of timesteps in the period. + | Symbol | Python Parameter | Description | Default | + |--------|------------------|-------------|---------| + | $\text{FH}_\text{min}$ | `flow_hours_min` | Minimum cumulative flow-hours | None | + | $\text{FH}_\text{max}$ | `flow_hours_max` | Maximum cumulative flow-hours | None | + | - | `flow_hours_min_over_periods` | Minimum across all periods (weighted) | None | + | - | `flow_hours_max_over_periods` | Maximum across all periods (weighted) | None | + | $\Delta t_i$ | - | Timestep duration (hours) | - | - ### Flow Hours Limits + --- - Direct constraints on cumulative flow-hours (energy/material throughput): + ## Fixed Profile + + When a predetermined pattern is specified: $$ - \text{flow\_hours\_min} \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{flow\_hours\_max} + p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i}) $$ - ### Fixed Relative Profile + | Python Parameter | Description | + |------------------|-------------| + | `fixed_relative_profile` | Array of relative flow rates (fraction of size) for each timestep | - When a predetermined pattern is specified, the flow rate becomes: + **Use case:** Renewable generation (solar, wind) or fixed demand patterns. - $$ - p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i}) - $$ + --- + + ## On/Off Operation - This is commonly used for renewable generation (solar, wind) or fixed demand patterns. + When combined with [OnOffParameters](../features/OnOffParameters.md): - ### Initial Flow State + - Minimum/maximum consecutive on/off times + - Startup costs and switch limits + - Part-load restrictions - For flows with on/off parameters, the previous flow rate can be specified to properly model startup/shutdown transitions at the beginning of the optimization horizon: + **Mathematical Pattern:** [Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state) - - `previous_flow_rate`: Initial condition for on/off dynamics (default: None, interpreted as off/zero flow) + --- -=== "Mathematical Patterns" + ## Initial Conditions - Flow formulation builds on the following modeling patterns: + For flows with on/off parameters: - - **[Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)** - Basic flow rate constraint (equation $\eqref{eq:flow_rate}$) - - **[Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state)** - When combined with [OnOffParameters](../features/OnOffParameters.md) - - **[Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state)** - Investment decisions with [InvestParameters](../features/InvestParameters.md) + | Python Parameter | Description | Default | + |------------------|-------------|---------| + | `previous_flow_rate` | Flow rate before optimization horizon | None (interpreted as 0/off) | - See [Modeling Patterns](../modeling-patterns/index.md) for detailed explanations of these building blocks. + **Use case:** Properly model startup/shutdown transitions at the beginning of the optimization horizon. -=== "Examples" +=== "Use Cases" ## Basic Fixed Capacity Flow @@ -121,6 +135,12 @@ A Flow represents the transfer of energy or material between a Bus and a Compone ) ``` + **Variables created:** `flow_rate[t]` + + **Constraints:** $40 \leq p(t) \leq 100$ MW + + --- + ## Investment Decision ```python @@ -137,6 +157,15 @@ A Flow represents the transfer of energy or material between a Bus and a Compone ) ``` + **Variables created:** `flow_rate[t]`, `size`, `invest_binary` + + **Constraints:** + + - If investing: $10 \leq \text{size} \leq 100$ + - Flow bounds: $0 \leq p(t) \leq \text{size}$ + + --- + ## On/Off Operation with Constraints ```python @@ -156,6 +185,17 @@ A Flow represents the transfer of energy or material between a Bus and a Compone ) ``` + **Variables created:** `flow_rate[t]`, `on_off_state[t]`, `switch_on[t]`, `switch_off[t]` + + **Constraints:** + + - When on: $15 \leq p(t) \leq 50$ kW + - When off: $p(t) = 0$ + - Minimum 2 consecutive hours when on + - Minimum 1 hour off between runs + + --- + ## Fixed Profile (Renewable Generation) ```python @@ -170,11 +210,38 @@ A Flow represents the transfer of energy or material between a Bus and a Compone ) ``` + **Variables created:** `flow_rate[t]` (but values are fixed by profile) + + **Constraints:** $p(t) = 25 \cdot \text{profile}(t)$ MW + + --- + + ## Load Factor Constraint + + ```python + from flixopt import Flow + + baseload_plant = Flow( + label='baseload_output', + bus='electricity', + size=200, # 200 MW + load_factor_min=0.7, # Must run at least 70% average capacity + effects_per_flow_hour={'cost': 30}, + ) + ``` + + **Variables created:** `flow_rate[t]`, `total_flow_hours` + + **Constraints:** + + - Flow bounds: $0 \leq p(t) \leq 200$ MW + - Load factor: $\sum_t p(t) \geq 0.7 \times 200 \times N_t$ + --- ## See Also - **Features:** [OnOffParameters](../features/OnOffParameters.md) · [InvestParameters](../features/InvestParameters.md) - **Elements:** [Bus](Bus.md) · [Storage](Storage.md) · [LinearConverter](LinearConverter.md) -- **Patterns:** [Modeling Patterns](../modeling-patterns/index.md) +- **Patterns:** [Modeling Patterns](../modeling-patterns/index.md) · [Bounds and States](../modeling-patterns/bounds-and-states.md) - **Effects:** [Effects, Penalty & Objective](../effects-penalty-objective.md) From 3cd40b96c4510a5880158de43d0b0cfe992a4dcf Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:15:17 +0100 Subject: [PATCH 03/79] Improve organization --- .../mathematical-notation/elements/Flow.md | 129 +++++------------- 1 file changed, 34 insertions(+), 95 deletions(-) diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md index e01decb1c..b7fc77e64 100644 --- a/docs/user-guide/mathematical-notation/elements/Flow.md +++ b/docs/user-guide/mathematical-notation/elements/Flow.md @@ -2,15 +2,6 @@ A Flow represents the transfer of energy or material between a Bus and a Component, with the flow rate as the primary optimization variable. -**Implementation:** - -- **Element Class:** [`Flow`][flixopt.elements.Flow] -- **Model Class:** [`FlowModel`][flixopt.elements.FlowModel] - -**Related:** [`Bus`](Bus.md) · [`Storage`](Storage.md) · [`LinearConverter`](LinearConverter.md) - ---- - === "Variables" | Variable Name | Symbol | Description | Domain | Created When | @@ -25,99 +16,59 @@ A Flow represents the transfer of energy or material between a Bus and a Compone === "Constraints" - ## Flow Rate Bounds - - The primary constraint limits flow rate by size and relative bounds: + **Flow rate bounds** ([Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)): - $$ \label{eq:flow_rate} - \text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) - \leq p(\text{t}_{i}) \leq - \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i}) + $$ + \text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) \leq p(\text{t}_{i}) \leq \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i}) $$ - | Symbol | Python Parameter | Description | Default | - |--------|------------------|-------------|---------| - | $\text P$ | `size` | Flow capacity | Required | - | $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i})$ | `relative_minimum` | Relative lower bound (fraction of size) | 0 | - | $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ | `relative_maximum` | Relative upper bound (fraction of size) | 1 | - - **Simplified form** (with default bounds): $0 \leq p(\text{t}_{i}) \leq \text P$ - - **Mathematical Pattern:** [Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds) + Parameters: `size` ($\text P$), `relative_minimum` ($\text p^{\text{L}}_{\text{rel}}$, default: 0), `relative_maximum` ($\text p^{\text{U}}_{\text{rel}}$, default: 1) --- - ## Load Factor Constraints - - When specified, constrains the average utilization over a period: + **Load factor** (when `load_factor_min` or `load_factor_max` specified): $$ - \text{LF}_\text{min} \cdot \text P \cdot N_t \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \leq \text{LF}_\text{max} \cdot \text P \cdot N_t + \text{LF}_\text{min} \cdot \text P \cdot N_t \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \leq \text{LF}_\text{max} \cdot \text P \cdot N_t $$ - | Symbol | Python Parameter | Description | Default | - |--------|------------------|-------------|---------| - | $\text{LF}_\text{min}$ | `load_factor_min` | Minimum average utilization (0-1) | 0 | - | $\text{LF}_\text{max}$ | `load_factor_max` | Maximum average utilization (0-1) | 1 | - | $N_t$ | - | Number of timesteps in period | - | + Parameters: `load_factor_min` ($\text{LF}_\text{min}$, default: 0), `load_factor_max` ($\text{LF}_\text{max}$, default: 1) --- - ## Flow Hours Limits - - Direct constraints on cumulative flow-hours (energy/material throughput): + **Flow hours limits** (when `flow_hours_min` or `flow_hours_max` specified): $$ - \text{FH}_\text{min} \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{FH}_\text{max} + \text{FH}_\text{min} \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{FH}_\text{max} $$ - | Symbol | Python Parameter | Description | Default | - |--------|------------------|-------------|---------| - | $\text{FH}_\text{min}$ | `flow_hours_min` | Minimum cumulative flow-hours | None | - | $\text{FH}_\text{max}$ | `flow_hours_max` | Maximum cumulative flow-hours | None | - | - | `flow_hours_min_over_periods` | Minimum across all periods (weighted) | None | - | - | `flow_hours_max_over_periods` | Maximum across all periods (weighted) | None | - | $\Delta t_i$ | - | Timestep duration (hours) | - | + Parameters: `flow_hours_min` ($\text{FH}_\text{min}$), `flow_hours_max` ($\text{FH}_\text{max}$), `flow_hours_min_over_periods`, `flow_hours_max_over_periods` --- - ## Fixed Profile - - When a predetermined pattern is specified: + **Fixed profile** (when `fixed_relative_profile` specified): $$ - p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i}) + p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i}) $$ - | Python Parameter | Description | - |------------------|-------------| - | `fixed_relative_profile` | Array of relative flow rates (fraction of size) for each timestep | - - **Use case:** Renewable generation (solar, wind) or fixed demand patterns. + Parameters: `fixed_relative_profile` (array of relative flow rates) --- - ## On/Off Operation + **On/off operation** (when `on_off_parameters` specified): - When combined with [OnOffParameters](../features/OnOffParameters.md): + See [OnOffParameters](../features/OnOffParameters.md) for complete formulation. Adds constraints for: - Minimum/maximum consecutive on/off times - - Startup costs and switch limits - - Part-load restrictions - - **Mathematical Pattern:** [Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state) + - Startup/shutdown costs and limits + - Part-load restrictions ([Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state)) --- - ## Initial Conditions - - For flows with on/off parameters: - - | Python Parameter | Description | Default | - |------------------|-------------|---------| - | `previous_flow_rate` | Flow rate before optimization horizon | None (interpreted as 0/off) | + **Initial conditions** (optional): - **Use case:** Properly model startup/shutdown transitions at the beginning of the optimization horizon. + Parameters: `previous_flow_rate` (flow rate before optimization horizon, default: None/0) === "Use Cases" @@ -135,8 +86,7 @@ A Flow represents the transfer of energy or material between a Bus and a Compone ) ``` - **Variables created:** `flow_rate[t]` - + **Variables:** `flow_rate[t]` **Constraints:** $40 \leq p(t) \leq 100$ MW --- @@ -157,16 +107,12 @@ A Flow represents the transfer of energy or material between a Bus and a Compone ) ``` - **Variables created:** `flow_rate[t]`, `size`, `invest_binary` - - **Constraints:** - - - If investing: $10 \leq \text{size} \leq 100$ - - Flow bounds: $0 \leq p(t) \leq \text{size}$ + **Variables:** `flow_rate[t]`, `size`, `invest_binary` + **Constraints:** If investing: $10 \leq \text{size} \leq 100$; Flow bounds: $0 \leq p(t) \leq \text{size}$ --- - ## On/Off Operation with Constraints + ## On/Off Operation ```python from flixopt import Flow, OnOffParameters @@ -176,7 +122,6 @@ A Flow represents the transfer of energy or material between a Bus and a Compone bus='heating_network', size=50, # 50 kW thermal relative_minimum=0.3, # Minimum 15 kW when on - effects_per_flow_hour={'electricity_cost': 25}, on_off_parameters=OnOffParameters( effects_per_switch_on={'startup_cost': 100}, consecutive_on_hours_min=2, # Min run time @@ -185,18 +130,12 @@ A Flow represents the transfer of energy or material between a Bus and a Compone ) ``` - **Variables created:** `flow_rate[t]`, `on_off_state[t]`, `switch_on[t]`, `switch_off[t]` - - **Constraints:** - - - When on: $15 \leq p(t) \leq 50$ kW - - When off: $p(t) = 0$ - - Minimum 2 consecutive hours when on - - Minimum 1 hour off between runs + **Variables:** `flow_rate[t]`, `on_off_state[t]`, `switch_on[t]`, `switch_off[t]` + **Constraints:** When on: $15 \leq p(t) \leq 50$ kW; When off: $p(t) = 0$; Min 2h on, min 1h off --- - ## Fixed Profile (Renewable Generation) + ## Fixed Profile (Renewable) ```python import numpy as np @@ -210,8 +149,7 @@ A Flow represents the transfer of energy or material between a Bus and a Compone ) ``` - **Variables created:** `flow_rate[t]` (but values are fixed by profile) - + **Variables:** `flow_rate[t]` (fixed by profile) **Constraints:** $p(t) = 25 \cdot \text{profile}(t)$ MW --- @@ -225,19 +163,20 @@ A Flow represents the transfer of energy or material between a Bus and a Compone label='baseload_output', bus='electricity', size=200, # 200 MW - load_factor_min=0.7, # Must run at least 70% average capacity + load_factor_min=0.7, # Must run at least 70% average effects_per_flow_hour={'cost': 30}, ) ``` - **Variables created:** `flow_rate[t]`, `total_flow_hours` + **Variables:** `flow_rate[t]`, `total_flow_hours` + **Constraints:** $0 \leq p(t) \leq 200$ MW; $\sum_t p(t) \geq 0.7 \times 200 \times N_t$ - **Constraints:** +--- - - Flow bounds: $0 \leq p(t) \leq 200$ MW - - Load factor: $\sum_t p(t) \geq 0.7 \times 200 \times N_t$ +## Implementation ---- +- **Element Class:** [`Flow`][flixopt.elements.Flow] +- **Model Class:** [`FlowModel`][flixopt.elements.FlowModel] ## See Also From 4e69ebb9bedfad9b31820aa8aeeb6a52f53f2333 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:17:33 +0100 Subject: [PATCH 04/79] Improve organization by using tables --- .../mathematical-notation/elements/Flow.md | 63 +++---------------- 1 file changed, 10 insertions(+), 53 deletions(-) diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md index b7fc77e64..7d5260fea 100644 --- a/docs/user-guide/mathematical-notation/elements/Flow.md +++ b/docs/user-guide/mathematical-notation/elements/Flow.md @@ -16,59 +16,16 @@ A Flow represents the transfer of energy or material between a Bus and a Compone === "Constraints" - **Flow rate bounds** ([Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)): - - $$ - \text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) \leq p(\text{t}_{i}) \leq \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i}) - $$ - - Parameters: `size` ($\text P$), `relative_minimum` ($\text p^{\text{L}}_{\text{rel}}$, default: 0), `relative_maximum` ($\text p^{\text{U}}_{\text{rel}}$, default: 1) - - --- - - **Load factor** (when `load_factor_min` or `load_factor_max` specified): - - $$ - \text{LF}_\text{min} \cdot \text P \cdot N_t \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \leq \text{LF}_\text{max} \cdot \text P \cdot N_t - $$ - - Parameters: `load_factor_min` ($\text{LF}_\text{min}$, default: 0), `load_factor_max` ($\text{LF}_\text{max}$, default: 1) - - --- - - **Flow hours limits** (when `flow_hours_min` or `flow_hours_max` specified): - - $$ - \text{FH}_\text{min} \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{FH}_\text{max} - $$ - - Parameters: `flow_hours_min` ($\text{FH}_\text{min}$), `flow_hours_max` ($\text{FH}_\text{max}$), `flow_hours_min_over_periods`, `flow_hours_max_over_periods` - - --- - - **Fixed profile** (when `fixed_relative_profile` specified): - - $$ - p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i}) - $$ - - Parameters: `fixed_relative_profile` (array of relative flow rates) - - --- - - **On/off operation** (when `on_off_parameters` specified): - - See [OnOffParameters](../features/OnOffParameters.md) for complete formulation. Adds constraints for: - - - Minimum/maximum consecutive on/off times - - Startup/shutdown costs and limits - - Part-load restrictions ([Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state)) - - --- - - **Initial conditions** (optional): - - Parameters: `previous_flow_rate` (flow rate before optimization horizon, default: None/0) + | Constraint | Equation | Parameters | Active When | + |------------|----------|------------|-------------| + | **Flow rate bounds** | $\text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) \leq p(\text{t}_{i}) \leq \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ | `size` ($\text P$), `relative_minimum` ($\text p^{\text{L}}_{\text{rel}}$, default: 0), `relative_maximum` ($\text p^{\text{U}}_{\text{rel}}$, default: 1) | Always | + | **Load factor** | $\text{LF}_\text{min} \cdot \text P \cdot N_t \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \leq \text{LF}_\text{max} \cdot \text P \cdot N_t$ | `load_factor_min` ($\text{LF}_\text{min}$, default: 0), `load_factor_max` ($\text{LF}_\text{max}$, default: 1) | When `load_factor_min` or `load_factor_max` specified | + | **Flow hours limits** | $\text{FH}_\text{min} \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{FH}_\text{max}$ | `flow_hours_min`, `flow_hours_max`, `flow_hours_min_over_periods`, `flow_hours_max_over_periods` | When any flow hours parameter specified | + | **Fixed profile** | $p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i})$ | `fixed_relative_profile` (array) | When `fixed_relative_profile` specified | + | **On/off operation** | See [OnOffParameters](../features/OnOffParameters.md) | `on_off_parameters` | When `on_off_parameters` specified | + | **Initial conditions** | - | `previous_flow_rate` (default: None/0) | When `on_off_parameters` specified | + + **Mathematical Patterns:** [Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds), [Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state) === "Use Cases" From 464e685bc9e8900ff3e6bfd4ca7f5064d1ef2e2b Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:21:09 +0100 Subject: [PATCH 05/79] Improve organization by using tables and use eqref --- .../mathematical-notation/elements/Flow.md | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md index 7d5260fea..bc2f62afe 100644 --- a/docs/user-guide/mathematical-notation/elements/Flow.md +++ b/docs/user-guide/mathematical-notation/elements/Flow.md @@ -16,14 +16,18 @@ A Flow represents the transfer of energy or material between a Bus and a Compone === "Constraints" +
+ | Constraint | Equation | Parameters | Active When | |------------|----------|------------|-------------| - | **Flow rate bounds** | $\text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) \leq p(\text{t}_{i}) \leq \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ | `size` ($\text P$), `relative_minimum` ($\text p^{\text{L}}_{\text{rel}}$, default: 0), `relative_maximum` ($\text p^{\text{U}}_{\text{rel}}$, default: 1) | Always | - | **Load factor** | $\text{LF}_\text{min} \cdot \text P \cdot N_t \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \leq \text{LF}_\text{max} \cdot \text P \cdot N_t$ | `load_factor_min` ($\text{LF}_\text{min}$, default: 0), `load_factor_max` ($\text{LF}_\text{max}$, default: 1) | When `load_factor_min` or `load_factor_max` specified | - | **Flow hours limits** | $\text{FH}_\text{min} \leq \sum_{i=1}^{N_t} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{FH}_\text{max}$ | `flow_hours_min`, `flow_hours_max`, `flow_hours_min_over_periods`, `flow_hours_max_over_periods` | When any flow hours parameter specified | - | **Fixed profile** | $p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i})$ | `fixed_relative_profile` (array) | When `fixed_relative_profile` specified | - | **On/off operation** | See [OnOffParameters](../features/OnOffParameters.md) | `on_off_parameters` | When `on_off_parameters` specified | - | **Initial conditions** | - | `previous_flow_rate` (default: None/0) | When `on_off_parameters` specified | + | **Flow rate bounds** | $$\label{eq:flow_bounds} \text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) \leq p(\text{t}_{i}) \leq \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$$ | `size`, `relative_minimum` (default: 0), `relative_maximum` (default: 1) | Always | + | **Load factor** | $$\label{eq:flow_load_factor} \text{LF}_\text{min} \cdot \text P \cdot N_t \leq \sum_{i} p(\text{t}_{i}) \leq \text{LF}_\text{max} \cdot \text P \cdot N_t$$ | `load_factor_min` (default: 0), `load_factor_max` (default: 1) | `load_factor_min/max` specified | + | **Flow hours limits** | $$\label{eq:flow_hours} \text{FH}_\text{min} \leq \sum_{i} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{FH}_\text{max}$$ | `flow_hours_min`, `flow_hours_max`, `flow_hours_*_over_periods` | Any flow hours param specified | + | **Fixed profile** | $$\label{eq:flow_profile} p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i})$$ | `fixed_relative_profile` | `fixed_relative_profile` specified | + | **On/off operation** | See [OnOffParameters](../features/OnOffParameters.md) | `on_off_parameters` | `on_off_parameters` specified | + | **Initial conditions** | - | `previous_flow_rate` (default: None) | `on_off_parameters` specified | + +
**Mathematical Patterns:** [Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds), [Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state) @@ -44,7 +48,8 @@ A Flow represents the transfer of energy or material between a Bus and a Compone ``` **Variables:** `flow_rate[t]` - **Constraints:** $40 \leq p(t) \leq 100$ MW + + **Constraints:** $\eqref{eq:flow_bounds}$ with $40 \leq p(t) \leq 100$ MW --- @@ -65,7 +70,8 @@ A Flow represents the transfer of energy or material between a Bus and a Compone ``` **Variables:** `flow_rate[t]`, `size`, `invest_binary` - **Constraints:** If investing: $10 \leq \text{size} \leq 100$; Flow bounds: $0 \leq p(t) \leq \text{size}$ + + **Constraints:** $\eqref{eq:flow_bounds}$ with $0 \leq p(t) \leq \text{size}$, plus investment constraints --- @@ -88,7 +94,8 @@ A Flow represents the transfer of energy or material between a Bus and a Compone ``` **Variables:** `flow_rate[t]`, `on_off_state[t]`, `switch_on[t]`, `switch_off[t]` - **Constraints:** When on: $15 \leq p(t) \leq 50$ kW; When off: $p(t) = 0$; Min 2h on, min 1h off + + **Constraints:** $\eqref{eq:flow_bounds}$ plus on/off constraints from [OnOffParameters](../features/OnOffParameters.md) --- @@ -107,7 +114,8 @@ A Flow represents the transfer of energy or material between a Bus and a Compone ``` **Variables:** `flow_rate[t]` (fixed by profile) - **Constraints:** $p(t) = 25 \cdot \text{profile}(t)$ MW + + **Constraints:** $\eqref{eq:flow_profile}$ --- @@ -126,7 +134,8 @@ A Flow represents the transfer of energy or material between a Bus and a Compone ``` **Variables:** `flow_rate[t]`, `total_flow_hours` - **Constraints:** $0 \leq p(t) \leq 200$ MW; $\sum_t p(t) \geq 0.7 \times 200 \times N_t$ + + **Constraints:** $\eqref{eq:flow_bounds}$, $\eqref{eq:flow_load_factor}$ --- From f75944682ad1312a90ad88f72ecd3a2b28036a3b Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:22:36 +0100 Subject: [PATCH 06/79] Add symbol to parameter mapping --- .../mathematical-notation/elements/Flow.md | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md index bc2f62afe..d05fa9c90 100644 --- a/docs/user-guide/mathematical-notation/elements/Flow.md +++ b/docs/user-guide/mathematical-notation/elements/Flow.md @@ -16,18 +16,29 @@ A Flow represents the transfer of energy or material between a Bus and a Compone === "Constraints" -
- - | Constraint | Equation | Parameters | Active When | - |------------|----------|------------|-------------| - | **Flow rate bounds** | $$\label{eq:flow_bounds} \text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) \leq p(\text{t}_{i}) \leq \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$$ | `size`, `relative_minimum` (default: 0), `relative_maximum` (default: 1) | Always | - | **Load factor** | $$\label{eq:flow_load_factor} \text{LF}_\text{min} \cdot \text P \cdot N_t \leq \sum_{i} p(\text{t}_{i}) \leq \text{LF}_\text{max} \cdot \text P \cdot N_t$$ | `load_factor_min` (default: 0), `load_factor_max` (default: 1) | `load_factor_min/max` specified | - | **Flow hours limits** | $$\label{eq:flow_hours} \text{FH}_\text{min} \leq \sum_{i} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{FH}_\text{max}$$ | `flow_hours_min`, `flow_hours_max`, `flow_hours_*_over_periods` | Any flow hours param specified | - | **Fixed profile** | $$\label{eq:flow_profile} p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i})$$ | `fixed_relative_profile` | `fixed_relative_profile` specified | - | **On/off operation** | See [OnOffParameters](../features/OnOffParameters.md) | `on_off_parameters` | `on_off_parameters` specified | - | **Initial conditions** | - | `previous_flow_rate` (default: None) | `on_off_parameters` specified | - -
+ | Constraint | Equation | Active When | + |------------|----------|-------------| + | **Flow rate bounds** | $$\label{eq:flow_bounds} \text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) \leq p(\text{t}_{i}) \leq \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$$ | Always | + | **Load factor** | $$\label{eq:flow_load_factor} \text{LF}_\text{min} \cdot \text P \cdot N_t \leq \sum_{i} p(\text{t}_{i}) \leq \text{LF}_\text{max} \cdot \text P \cdot N_t$$ | `load_factor_min` or `load_factor_max` specified | + | **Flow hours limits** | $$\label{eq:flow_hours} \text{FH}_\text{min} \leq \sum_{i} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{FH}_\text{max}$$ | Any flow hours parameter specified | + | **Fixed profile** | $$\label{eq:flow_profile} p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i})$$ | `fixed_relative_profile` specified | + | **On/off operation** | See [OnOffParameters](../features/OnOffParameters.md) | `on_off_parameters` specified | + + ??? info "Symbol to Parameter Mapping" + | Symbol | Python Parameter | Description | Default | + |--------|------------------|-------------|---------| + | $\text P$ | `size` | Flow capacity | Required | + | $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i})$ | `relative_minimum` | Relative lower bound (fraction of size) | 0 | + | $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ | `relative_maximum` | Relative upper bound (fraction of size) | 1 | + | $\text{LF}_\text{min}$ | `load_factor_min` | Minimum average utilization (0-1) | 0 | + | $\text{LF}_\text{max}$ | `load_factor_max` | Maximum average utilization (0-1) | 1 | + | $\text{FH}_\text{min}$ | `flow_hours_min` | Minimum cumulative flow-hours | None | + | $\text{FH}_\text{max}$ | `flow_hours_max` | Maximum cumulative flow-hours | None | + | - | `flow_hours_min_over_periods` | Minimum flow-hours across all periods | None | + | - | `flow_hours_max_over_periods` | Maximum flow-hours across all periods | None | + | $\text{profile}(\text{t}_{i})$ | `fixed_relative_profile` | Array of relative flow rates | None | + | - | `previous_flow_rate` | Flow rate before optimization horizon | None | + | - | `on_off_parameters` | OnOffParameters instance | None | **Mathematical Patterns:** [Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds), [Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state) From 4df1fff245f31c344384ceb7fcfe26e8e1ad09b9 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:26:39 +0100 Subject: [PATCH 07/79] Changed to inline math --- docs/user-guide/mathematical-notation/elements/Flow.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md index d05fa9c90..c04aa3a25 100644 --- a/docs/user-guide/mathematical-notation/elements/Flow.md +++ b/docs/user-guide/mathematical-notation/elements/Flow.md @@ -18,10 +18,10 @@ A Flow represents the transfer of energy or material between a Bus and a Compone | Constraint | Equation | Active When | |------------|----------|-------------| - | **Flow rate bounds** | $$\label{eq:flow_bounds} \text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) \leq p(\text{t}_{i}) \leq \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$$ | Always | - | **Load factor** | $$\label{eq:flow_load_factor} \text{LF}_\text{min} \cdot \text P \cdot N_t \leq \sum_{i} p(\text{t}_{i}) \leq \text{LF}_\text{max} \cdot \text P \cdot N_t$$ | `load_factor_min` or `load_factor_max` specified | - | **Flow hours limits** | $$\label{eq:flow_hours} \text{FH}_\text{min} \leq \sum_{i} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{FH}_\text{max}$$ | Any flow hours parameter specified | - | **Fixed profile** | $$\label{eq:flow_profile} p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i})$$ | `fixed_relative_profile` specified | + | **Flow rate bounds** | $\label{eq:flow_bounds} \text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) \leq p(\text{t}_{i}) \leq \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ | Always | + | **Load factor** | $\label{eq:flow_load_factor} \text{LF}_\text{min} \cdot \text P \cdot N_t \leq \sum_{i} p(\text{t}_{i}) \leq \text{LF}_\text{max} \cdot \text P \cdot N_t$ | `load_factor_min` or `load_factor_max` specified | + | **Flow hours limits** | $\label{eq:flow_hours} \text{FH}_\text{min} \leq \sum_{i} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{FH}_\text{max}$ | Any flow hours parameter specified | + | **Fixed profile** | $\label{eq:flow_profile} p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i})$ | `fixed_relative_profile` specified | | **On/off operation** | See [OnOffParameters](../features/OnOffParameters.md) | `on_off_parameters` specified | ??? info "Symbol to Parameter Mapping" From 590befa31cbc14aec4aa0fc7788914fdba71556e Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:30:29 +0100 Subject: [PATCH 08/79] Use propre constraints with numbering --- .../mathematical-notation/elements/Flow.md | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md index c04aa3a25..84b8a0f87 100644 --- a/docs/user-guide/mathematical-notation/elements/Flow.md +++ b/docs/user-guide/mathematical-notation/elements/Flow.md @@ -16,13 +16,43 @@ A Flow represents the transfer of energy or material between a Bus and a Compone === "Constraints" - | Constraint | Equation | Active When | - |------------|----------|-------------| - | **Flow rate bounds** | $\label{eq:flow_bounds} \text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) \leq p(\text{t}_{i}) \leq \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ | Always | - | **Load factor** | $\label{eq:flow_load_factor} \text{LF}_\text{min} \cdot \text P \cdot N_t \leq \sum_{i} p(\text{t}_{i}) \leq \text{LF}_\text{max} \cdot \text P \cdot N_t$ | `load_factor_min` or `load_factor_max` specified | - | **Flow hours limits** | $\label{eq:flow_hours} \text{FH}_\text{min} \leq \sum_{i} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{FH}_\text{max}$ | Any flow hours parameter specified | - | **Fixed profile** | $\label{eq:flow_profile} p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i})$ | `fixed_relative_profile` specified | - | **On/off operation** | See [OnOffParameters](../features/OnOffParameters.md) | `on_off_parameters` specified | + **Flow rate bounds** (always active): + + $$\label{eq:flow_bounds} + \text P \cdot \text p^{\text{L}}_{\text{rel}}(\text{t}_{i}) \leq p(\text{t}_{i}) \leq \text P \cdot \text p^{\text{U}}_{\text{rel}}(\text{t}_{i}) + $$ + + --- + + **Load factor** (when `load_factor_min` or `load_factor_max` specified): + + $$\label{eq:flow_load_factor} + \text{LF}_\text{min} \cdot \text P \cdot N_t \leq \sum_{i} p(\text{t}_{i}) \leq \text{LF}_\text{max} \cdot \text P \cdot N_t + $$ + + --- + + **Flow hours limits** (when any flow hours parameter specified): + + $$\label{eq:flow_hours} + \text{FH}_\text{min} \leq \sum_{i} p(\text{t}_{i}) \cdot \Delta t_i \leq \text{FH}_\text{max} + $$ + + --- + + **Fixed profile** (when `fixed_relative_profile` specified): + + $$\label{eq:flow_profile} + p(\text{t}_{i}) = \text P \cdot \text{profile}(\text{t}_{i}) + $$ + + --- + + **On/off operation** (when `on_off_parameters` specified): + + See [OnOffParameters](../features/OnOffParameters.md) + + --- ??? info "Symbol to Parameter Mapping" | Symbol | Python Parameter | Description | Default | From 42375563efa590669abf7f9dd3213c3dcf1943ab Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:32:08 +0100 Subject: [PATCH 09/79] Move parameters into separate tab --- .../mathematical-notation/elements/Flow.md | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md index 84b8a0f87..8bf7da66a 100644 --- a/docs/user-guide/mathematical-notation/elements/Flow.md +++ b/docs/user-guide/mathematical-notation/elements/Flow.md @@ -54,24 +54,25 @@ A Flow represents the transfer of energy or material between a Bus and a Compone --- - ??? info "Symbol to Parameter Mapping" - | Symbol | Python Parameter | Description | Default | - |--------|------------------|-------------|---------| - | $\text P$ | `size` | Flow capacity | Required | - | $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i})$ | `relative_minimum` | Relative lower bound (fraction of size) | 0 | - | $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ | `relative_maximum` | Relative upper bound (fraction of size) | 1 | - | $\text{LF}_\text{min}$ | `load_factor_min` | Minimum average utilization (0-1) | 0 | - | $\text{LF}_\text{max}$ | `load_factor_max` | Maximum average utilization (0-1) | 1 | - | $\text{FH}_\text{min}$ | `flow_hours_min` | Minimum cumulative flow-hours | None | - | $\text{FH}_\text{max}$ | `flow_hours_max` | Maximum cumulative flow-hours | None | - | - | `flow_hours_min_over_periods` | Minimum flow-hours across all periods | None | - | - | `flow_hours_max_over_periods` | Maximum flow-hours across all periods | None | - | $\text{profile}(\text{t}_{i})$ | `fixed_relative_profile` | Array of relative flow rates | None | - | - | `previous_flow_rate` | Flow rate before optimization horizon | None | - | - | `on_off_parameters` | OnOffParameters instance | None | - **Mathematical Patterns:** [Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds), [Scaled Bounds with State](../modeling-patterns/bounds-and-states.md#scaled-bounds-with-state) +=== "Parameters" + + | Symbol | Python Parameter | Description | Default | + |--------|------------------|-------------|---------| + | $\text P$ | `size` | Flow capacity | Required | + | $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i})$ | `relative_minimum` | Relative lower bound (fraction of size) | 0 | + | $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ | `relative_maximum` | Relative upper bound (fraction of size) | 1 | + | $\text{LF}_\text{min}$ | `load_factor_min` | Minimum average utilization (0-1) | 0 | + | $\text{LF}_\text{max}$ | `load_factor_max` | Maximum average utilization (0-1) | 1 | + | $\text{FH}_\text{min}$ | `flow_hours_min` | Minimum cumulative flow-hours | None | + | $\text{FH}_\text{max}$ | `flow_hours_max` | Maximum cumulative flow-hours | None | + | - | `flow_hours_min_over_periods` | Minimum flow-hours across all periods | None | + | - | `flow_hours_max_over_periods` | Maximum flow-hours across all periods | None | + | $\text{profile}(\text{t}_{i})$ | `fixed_relative_profile` | Array of relative flow rates | None | + | - | `previous_flow_rate` | Flow rate before optimization horizon | None | + | - | `on_off_parameters` | OnOffParameters instance | None | + === "Use Cases" ## Basic Fixed Capacity Flow From ec478788f7a9be31d02a436718a8837254310311 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:34:59 +0100 Subject: [PATCH 10/79] Reorder parameters --- .../mathematical-notation/elements/Flow.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md index 8bf7da66a..bca6f855e 100644 --- a/docs/user-guide/mathematical-notation/elements/Flow.md +++ b/docs/user-guide/mathematical-notation/elements/Flow.md @@ -60,18 +60,19 @@ A Flow represents the transfer of energy or material between a Bus and a Compone | Symbol | Python Parameter | Description | Default | |--------|------------------|-------------|---------| + | $\text{FH}_\text{max}$ | `flow_hours_max` | Maximum cumulative flow-hours | None | + | $\text{FH}_\text{min}$ | `flow_hours_min` | Minimum cumulative flow-hours | None | + | $\text{LF}_\text{max}$ | `load_factor_max` | Maximum average utilization (0-1) | 1 | + | $\text{LF}_\text{min}$ | `load_factor_min` | Minimum average utilization (0-1) | 0 | | $\text P$ | `size` | Flow capacity | Required | | $\text p^{\text{L}}_{\text{rel}}(\text{t}_{i})$ | `relative_minimum` | Relative lower bound (fraction of size) | 0 | | $\text p^{\text{U}}_{\text{rel}}(\text{t}_{i})$ | `relative_maximum` | Relative upper bound (fraction of size) | 1 | - | $\text{LF}_\text{min}$ | `load_factor_min` | Minimum average utilization (0-1) | 0 | - | $\text{LF}_\text{max}$ | `load_factor_max` | Maximum average utilization (0-1) | 1 | - | $\text{FH}_\text{min}$ | `flow_hours_min` | Minimum cumulative flow-hours | None | - | $\text{FH}_\text{max}$ | `flow_hours_max` | Maximum cumulative flow-hours | None | - | - | `flow_hours_min_over_periods` | Minimum flow-hours across all periods | None | - | - | `flow_hours_max_over_periods` | Maximum flow-hours across all periods | None | | $\text{profile}(\text{t}_{i})$ | `fixed_relative_profile` | Array of relative flow rates | None | - | - | `previous_flow_rate` | Flow rate before optimization horizon | None | + | - | `fixed_relative_profile` | Array of relative flow rates | None | + | - | `flow_hours_max_over_periods` | Maximum flow-hours across all periods | None | + | - | `flow_hours_min_over_periods` | Minimum flow-hours across all periods | None | | - | `on_off_parameters` | OnOffParameters instance | None | + | - | `previous_flow_rate` | Flow rate before optimization horizon | None | === "Use Cases" From 159eed41981dcc7c9793dce8dfb324ffa5ac61ad Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:37:49 +0100 Subject: [PATCH 11/79] : Use the columns "symbol" and "python name" in the variables tab --- .../mathematical-notation/elements/Flow.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/user-guide/mathematical-notation/elements/Flow.md b/docs/user-guide/mathematical-notation/elements/Flow.md index bca6f855e..76d4a62ee 100644 --- a/docs/user-guide/mathematical-notation/elements/Flow.md +++ b/docs/user-guide/mathematical-notation/elements/Flow.md @@ -4,15 +4,15 @@ A Flow represents the transfer of energy or material between a Bus and a Compone === "Variables" - | Variable Name | Symbol | Description | Domain | Created When | - |---------------|--------|-------------|--------|--------------| - | **flow_rate** | $p(\text{t}_{i})$ | Flow rate at each timestep | $\mathbb{R}$ | Always | - | **size** | $\text P$ | Flow capacity (decision variable) | $\mathbb{R}_+$ | `size` is `InvestParameters` | - | **invest_binary** | $s_\text{invest}$ | Binary investment decision | $\{0,1\}$ | `size` is `InvestParameters` | - | **total_flow_hours** | - | Cumulative flow-hours per period | $\mathbb{R}_+$ | `flow_hours_min/max` or `load_factor_min/max` specified | - | **on_off_state** | $s(\text{t}_i)$ | Binary on/off state | $\{0,1\}$ | `on_off_parameters` specified | - | **switch_on** | - | Startup indicator | $\{0,1\}$ | `on_off_parameters` specified | - | **switch_off** | - | Shutdown indicator | $\{0,1\}$ | `on_off_parameters` specified | + | Symbol | Python Name | Description | Domain | Created When | + |--------|-------------|-------------|--------|--------------| + | $p(\text{t}_{i})$ | `flow_rate` | Flow rate at each timestep | $\mathbb{R}$ | Always | + | $\text P$ | `size` | Flow capacity (decision variable) | $\mathbb{R}_+$ | `size` is `InvestParameters` | + | $s_\text{invest}$ | `invest_binary` | Binary investment decision | $\{0,1\}$ | `size` is `InvestParameters` | + | - | `total_flow_hours` | Cumulative flow-hours per period | $\mathbb{R}_+$ | `flow_hours_min/max` or `load_factor_min/max` specified | + | $s(\text{t}_i)$ | `on_off_state` | Binary on/off state | $\{0,1\}$ | `on_off_parameters` specified | + | - | `switch_on` | Startup indicator | $\{0,1\}$ | `on_off_parameters` specified | + | - | `switch_off` | Shutdown indicator | $\{0,1\}$ | `on_off_parameters` specified | === "Constraints" From 16836c9414170eb055943341ff4735e3068e0ba2 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:43:40 +0100 Subject: [PATCH 12/79] Update Bus, Storage, and LinearConverter.md --- .../mathematical-notation/elements/Bus.md | 124 +++++----- .../elements/LinearConverter.md | 172 ++++++-------- .../mathematical-notation/elements/Storage.md | 214 +++++++++--------- flixopt/components.py | 35 +-- flixopt/elements.py | 12 +- 5 files changed, 274 insertions(+), 283 deletions(-) diff --git a/docs/user-guide/mathematical-notation/elements/Bus.md b/docs/user-guide/mathematical-notation/elements/Bus.md index 3d5a115a3..6f529d1e9 100644 --- a/docs/user-guide/mathematical-notation/elements/Bus.md +++ b/docs/user-guide/mathematical-notation/elements/Bus.md @@ -2,81 +2,53 @@ A Bus represents a node in the energy/material flow network where flow balance constraints ensure conservation (inflows equal outflows). -**Implementation:** +=== "Variables" -- **Element Class:** [`Bus`][flixopt.elements.Bus] -- **Model Class:** [`BusModel`][flixopt.elements.BusModel] - -**Related:** [`Flow`](Flow.md) · [`Storage`](Storage.md) · [`LinearConverter`](LinearConverter.md) - ---- - -=== "Core Formulation" + | Symbol | Python Name | Description | Domain | Created When | + |--------|-------------|-------------|--------|--------------| + | $p_{f_\text{in}}(\text{t}_i)$ | - | Flow rate of incoming flow $f_\text{in}$ at time $\text{t}_i$ | $\mathbb{R}$ | Always (from connected Flows) | + | $p_{f_\text{out}}(\text{t}_i)$ | - | Flow rate of outgoing flow $f_\text{out}$ at time $\text{t}_i$ | $\mathbb{R}$ | Always (from connected Flows) | + | $\phi_\text{in}(\text{t}_i)$ | `excess_input` | Missing inflow (shortage) at time $\text{t}_i$ | $\mathbb{R}_+$ | `excess_penalty_per_flow_hour` is specified | + | $\phi_\text{out}(\text{t}_i)$ | `excess_output` | Excess outflow (surplus) at time $\text{t}_i$ | $\mathbb{R}_+$ | `excess_penalty_per_flow_hour` is specified | - ## Nodal Balance Equation +=== "Constraints" - The fundamental constraint of a Bus is that all incoming flow rates must equal all outgoing flow rates at every timestep: + **Nodal balance equation** (always active): - $$ \label{eq:bus_balance} - \sum_{f_\text{in} \in \mathcal{F}_\text{in}} p_{f_\text{in}}(\text{t}_i) = - \sum_{f_\text{out} \in \mathcal{F}_\text{out}} p_{f_\text{out}}(\text{t}_i) + $$\label{eq:bus_balance} + \sum_{f_\text{in} \in \mathcal{F}_\text{in}} p_{f_\text{in}}(\text{t}_i) = + \sum_{f_\text{out} \in \mathcal{F}_\text{out}} p_{f_\text{out}}(\text{t}_i) $$ - ??? info "Variables" - | Symbol | Description | Domain | - |--------|-------------|--------| - | $p_{f_\text{in}}(\text{t}_i)$ | Flow rate of incoming flow $f_\text{in}$ at time $\text{t}_i$ | $\mathbb{R}$ | - | $p_{f_\text{out}}(\text{t}_i)$ | Flow rate of outgoing flow $f_\text{out}$ at time $\text{t}_i$ | $\mathbb{R}$ | - - ??? info "Sets" - | Symbol | Description | - |--------|-------------| - | $\mathcal{F}_\text{in}$ | Set of all incoming flows to the bus | - | $\mathcal{F}_\text{out}$ | Set of all outgoing flows from the bus | - - This strict equality ensures energy/material conservation at each node. + --- -=== "Advanced & Edge Cases" + **Modified balance with excess** (when `excess_penalty_per_flow_hour` specified): - ## Excess Penalty (Soft Constraints) - - When `excess_penalty_per_flow_hour` is specified, the Bus allows balance violations with a penalty cost. This creates a "soft" constraint useful for handling potential infeasibilities gracefully. - - ### Modified Balance Equation - - The balance equation becomes: - - $$ \label{eq:bus_balance_excess} - \sum_{f_\text{in} \in \mathcal{F}_\text{in}} p_{f_ \text{in}}(\text{t}_i) + \phi_\text{in}(\text{t}_i) = - \sum_{f_\text{out} \in \mathcal{F}_\text{out}} p_{f_\text{out}}(\text{t}_i) + \phi_\text{out}(\text{t}_i) + $$\label{eq:bus_balance_excess} + \sum_{f_\text{in} \in \mathcal{F}_\text{in}} p_{f_\text{in}}(\text{t}_i) + \phi_\text{in}(\text{t}_i) = + \sum_{f_\text{out} \in \mathcal{F}_\text{out}} p_{f_\text{out}}(\text{t}_i) + \phi_\text{out}(\text{t}_i) $$ - ??? info "Additional Variables" - | Symbol | Description | Domain | - |--------|-------------|--------| - | $\phi_\text{in}(\text{t}_i)$ | Missing inflow (shortage) at time $\text{t}_i$ | $\mathbb{R}_+ $ | - | $\phi_\text{out}(\text{t}_i)$ | Excess outflow (surplus) at time $\text{t}_i$ | $\mathbb{R}_+$ | + --- - ### Penalty Cost + **Penalty cost** (when `excess_penalty_per_flow_hour` specified): - The penalty term added to the objective function: - - $$ \label{eq:bus_penalty} - s_{b \rightarrow \Phi}(\text{t}_i) = - \text a_{b \rightarrow \Phi}(\text{t}_i) \cdot \Delta \text{t}_i - \cdot [ \phi_\text{in}(\text{t}_i) + \phi_\text{out}(\text{t}_i) ] + $$\label{eq:bus_penalty} + \Phi(\text{t}_i) = \text a_{b \rightarrow \Phi}(\text{t}_i) \cdot \Delta \text{t}_i \cdot [ \phi_\text{in}(\text{t}_i) + \phi_\text{out}(\text{t}_i) ] $$ - ??? info "Parameters" - | Symbol | Description | Units | - |--------|-------------|-------| - | $\text a_{b \rightarrow \Phi}(\text{t}_i)$ | Penalty coefficient (`excess_penalty_per_flow_hour`) | Cost per unit flow-hour | - | $\Delta \text{t}_i$ | Timestep duration | hours | - | $s_{b \rightarrow \Phi}(\text{t}_i)$ | Total penalty cost at time $\text{t}_i$ | Cost units | + **Mathematical Patterns:** [Basic Equality](../modeling-patterns/bounds-and-states.md) + +=== "Parameters" - **Use Case:** This soft constraint approach prevents model infeasibility when supply and demand cannot be perfectly balanced, making it easier to identify and diagnose problem areas in the energy system model. + | Symbol | Python Parameter | Description | Default | + |--------|------------------|-------------|---------| + | $\text a_{b \rightarrow \Phi}(\text{t}_i)$ | `excess_penalty_per_flow_hour` | Penalty coefficient for balance violations (cost per unit flow-hour) | 1e5 | + | $\Delta \text{t}_i$ | - | Timestep duration (hours) | From system time index | + | $\mathcal{F}_\text{in}$ | - | Set of all incoming flows to the bus | From connected Flows | + | $\mathcal{F}_\text{out}$ | - | Set of all outgoing flows from the bus | From connected Flows | -=== "Examples" +=== "Use Cases" ## Basic Bus with Strict Balance @@ -85,10 +57,15 @@ A Bus represents a node in the energy/material flow network where flow balance c electricity_grid = Bus( label='electricity_grid', + excess_penalty_per_flow_hour=None, # No imbalance allowed ) ``` - This creates a strict nodal balance: all electricity inflows must exactly equal all outflows at every timestep. + **Variables:** Flow rates from connected flows (no excess variables) + + **Constraints:** $\eqref{eq:bus_balance}$ enforces strict equality: all electricity inflows must exactly equal all outflows at every timestep. + + --- ## Bus with Excess Penalty @@ -101,10 +78,37 @@ A Bus represents a node in the energy/material flow network where flow balance c ) ``` + **Variables:** Flow rates + `excess_input` + `excess_output` + + **Constraints:** $\eqref{eq:bus_balance_excess}$ allows violations with penalty $\eqref{eq:bus_penalty}$ + This allows the model to violate the heat balance if necessary, but applies a penalty of 1000 cost units per kWh of unbalanced flow. Useful for debugging infeasible models or modeling emergency scenarios. + --- + + ## Time-Varying Penalty + + ```python + from flixopt import Bus + import numpy as np + + material_hub = Bus( + label='material_processing_hub', + excess_penalty_per_flow_hour=np.array([100, 200, 300, 500]), # Higher penalty during peak hours + ) + ``` + + **Variables:** Flow rates + `excess_input` + `excess_output` + + **Constraints:** $\eqref{eq:bus_balance_excess}$ with time-varying penalty $\eqref{eq:bus_penalty}$ where $\text a_{b \rightarrow \Phi}(\text{t}_i)$ varies by timestep. + --- +## Implementation + +- **Element Class:** [`Bus`][flixopt.elements.Bus] +- **Model Class:** [`BusModel`][flixopt.elements.BusModel] + ## See Also - **Elements:** [Flow](Flow.md) · [Storage](Storage.md) · [LinearConverter](LinearConverter.md) diff --git a/docs/user-guide/mathematical-notation/elements/LinearConverter.md b/docs/user-guide/mathematical-notation/elements/LinearConverter.md index 076ae3174..5cd4a57f8 100644 --- a/docs/user-guide/mathematical-notation/elements/LinearConverter.md +++ b/docs/user-guide/mathematical-notation/elements/LinearConverter.md @@ -2,108 +2,52 @@ A LinearConverter defines linear relationships (ratios) between incoming and outgoing flows, representing energy/material conversion processes with fixed or variable efficiencies. -**Implementation:** +=== "Variables" -- **Component Class:** [`LinearConverter`][flixopt.components.LinearConverter] -- **Model Class:** [`LinearConverterModel`][flixopt.components.LinearConverterModel] - -**Related:** [`Flow`](Flow.md) · [`Bus`](Bus.md) · [`Storage`](Storage.md) - ---- + | Symbol | Python Name | Description | Domain | Created When | + |--------|-------------|-------------|--------|--------------| + | $p_{f_\text{in}}(\text{t}_i)$ | - | Flow rate of incoming flow $f_\text{in}$ at time $\text{t}_i$ | $\mathbb{R}$ | Always (from input Flows) | + | $p_{f_\text{out}}(\text{t}_i)$ | - | Flow rate of outgoing flow $f_\text{out}$ at time $\text{t}_i$ | $\mathbb{R}$ | Always (from output Flows) | + | $\lambda_k$ | - | Piecewise lambda variables | $\mathbb{R}_+$ | `piecewise_conversion` is specified | -=== "Core Formulation" +=== "Constraints" - ## General Linear Ratio Constraint + **General linear ratio constraint** (when `conversion_factors` specified): - The fundamental constraint relates all incoming and outgoing flows through linear conversion factors: - - $$ \label{eq:Linear-Transformer-Ratio} - \sum_{f_{\text{in}} \in \mathcal F_{in}} \text a_{f_{\text{in}}}(\text{t}_i) \cdot p_{f_\text{in}}(\text{t}_i) = \sum_{f_{\text{out}} \in \mathcal F_{out}} \text b_{f_\text{out}}(\text{t}_i) \cdot p_{f_\text{out}}(\text{t}_i) + $$\label{eq:Linear-Transformer-Ratio} + \sum_{f_{\text{in}} \in \mathcal F_{in}} \text a_{f_{\text{in}}}(\text{t}_i) \cdot p_{f_\text{in}}(\text{t}_i) = \sum_{f_{\text{out}} \in \mathcal F_{out}} \text b_{f_\text{out}}(\text{t}_i) \cdot p_{f_\text{out}}(\text{t}_i) $$ - ??? info "Variables" - | Symbol | Description | Domain | - |--------|-------------|--------| - | $p_{f_\text{in}}(\text{t}_i)$ | Flow rate of incoming flow $f_\text{in}$ at time $\text{t}_i$ | $\mathbb{R}$ | - | $p_{f_\text{out}}(\text{t}_i)$ | Flow rate of outgoing flow $f_\text{out}$ at time $\text{t}_i$ | $\mathbb{R}$ | - - ??? info "Parameters" - | Symbol | Description | Interpretation | - |--------|-------------|----------------| - | $\text a_{f_\text{in}}(\text{t}_i)$ | Conversion factor for incoming flow $f_\text{in}$ | Weight/efficiency coefficient | - | $\text b_{f_\text{out}}(\text{t}_i)$ | Conversion factor for outgoing flow $f_\text{out}$ | Weight/efficiency coefficient | - - ??? info "Sets" - | Symbol | Description | - |--------|-------------| - | $\mathcal F_{in}$ | Set of all incoming flows | - | $\mathcal F_{out}$ | Set of all outgoing flows | + --- - ## Simplified Single-Input Single-Output Form + **Simplified single-input single-output** (special case of above): - For the common case of one incoming and one outgoing flow, equation $\eqref{eq:Linear-Transformer-Ratio}$ simplifies to: - - $$ \label{eq:Linear-Transformer-Ratio-simple} - \text a(\text{t}_i) \cdot p_{f_\text{in}}(\text{t}_i) = p_{f_\text{out}}(\text{t}_i) + $$\label{eq:Linear-Transformer-Ratio-simple} + \text a(\text{t}_i) \cdot p_{f_\text{in}}(\text{t}_i) = p_{f_\text{out}}(\text{t}_i) $$ - **Physical Interpretation:** - - - $\text a$ represents the **conversion efficiency** or **conversion ratio** - - For a heat pump: $\text a$ is the Coefficient of Performance (COP) - - For a boiler: $\text a$ is the thermal efficiency (typically 0.85-0.95) - - For a generator: $\text a$ is the electrical efficiency (typically 0.3-0.5) - -=== "Advanced & Edge Cases" - - ## Piecewise Linear Conversion Factors - - Conversion efficiencies often vary with operating conditions (e.g., partial load efficiency, temperature-dependent COP). This is modeled using [Piecewise](../features/Piecewise.md) linear approximations. - - **Example:** Heat pump COP as a function of ambient temperature or load fraction. - - See [Piecewise](../features/Piecewise.md) for the mathematical formulation of piecewise linear relationships. - - ## Multiple Inputs/Outputs - - LinearConverters can model complex multi-flow processes: - - - **CHP (Combined Heat and Power):** 1 fuel input → 2 outputs (electricity + heat) - - **Heat pump with auxiliary:** 2 inputs (electricity + ambient heat) → 1 output (useful heat) - - **Multi-fuel boiler:** Multiple fuel inputs → 1 heat output - - Each flow has its own conversion factor $\text a_{f}$ or $\text b_{f}$ defining the ratio. - - ## Time-Varying Conversion Factors + Where $\text a$ represents the conversion efficiency or conversion ratio (COP for heat pumps, thermal efficiency for boilers, electrical efficiency for generators). - Conversion factors can be time-dependent $\text a(\text{t}_i)$ to model: + --- - - Seasonal efficiency variations - - Temperature-dependent performance - - Degradation over time - - Scheduled maintenance periods + **Piecewise linear conversion** (when `piecewise_conversion` specified): - ## Investment Sizing + See [Piecewise](../features/Piecewise.md) for the detailed mathematical formulation of piecewise linear relationships between flows. - When using [InvestParameters](../features/InvestParameters.md), the converter size becomes an optimization variable. Flow sizes are then linked to the converter's optimized capacity. + **Mathematical Patterns:** [Linear Equality Constraints](../modeling-patterns/bounds-and-states.md), [Piecewise Linear Approximations](../features/Piecewise.md) - ## On/Off Operation +=== "Parameters" - Combining with [OnOffParameters](../features/OnOffParameters.md) allows modeling: + | Symbol | Python Parameter | Description | Default | + |--------|------------------|-------------|---------| + | $\mathcal F_{in}$ | `inputs` | Set of all incoming flows | Required | + | $\mathcal F_{out}$ | `outputs` | Set of all outgoing flows | Required | + | $\text a_{f_\text{in}}(\text{t}_i)$ | `conversion_factors` | Conversion factor for incoming flow (weight/efficiency coefficient) | None | + | $\text b_{f_\text{out}}(\text{t}_i)$ | `conversion_factors` | Conversion factor for outgoing flow (weight/efficiency coefficient) | None | + | - | `on_off_parameters` | OnOffParameters for on/off operation | None | + | - | `piecewise_conversion` | PiecewiseConversion for non-linear behavior | None | - - Minimum run times - - Startup costs - - Part-load restrictions (minimum load when operating) - -=== "Mathematical Patterns" - - LinearConverter formulation relies on: - - - **Linear equality constraints** - Enforcing the ratio relationship - - **[Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)** - For flow rate bounds - - **[Piecewise Linear Approximations](../features/Piecewise.md)** - For non-constant conversion factors - -=== "Examples" +=== "Use Cases" ## Simple Boiler (Single Input/Output) @@ -114,10 +58,16 @@ A LinearConverter defines linear relationships (ratios) between incoming and out label='gas_boiler', inputs=[Flow(label='gas_in', bus='natural_gas', size=100)], outputs=[Flow(label='heat_out', bus='heating', size=90)], - conversion_factors={('gas_in', 'heat_out'): 0.9}, # 90% efficiency + conversion_factors=[{'gas_in': 0.9, 'heat_out': 1}], # 90% efficiency ) ``` + **Variables:** Flow rates from input and output flows + + **Constraints:** $\eqref{eq:Linear-Transformer-Ratio-simple}$ with $0.9 \cdot p_\text{gas}(t) = p_\text{heat}(t)$, representing 90% thermal efficiency + + --- + ## CHP Plant (One Input, Two Outputs) ```python @@ -130,36 +80,51 @@ A LinearConverter defines linear relationships (ratios) between incoming and out Flow(label='electricity_out', bus='electricity', size=35), Flow(label='heat_out', bus='heating', size=55), ], - conversion_factors={ - ('fuel_in', 'electricity_out'): 0.35, # 35% electrical efficiency - ('fuel_in', 'heat_out'): 0.55, # 55% thermal efficiency - }, + conversion_factors=[ + {'fuel_in': 0.35, 'electricity_out': 1}, # 35% electrical efficiency + {'fuel_in': 0.55, 'heat_out': 1}, # 55% thermal efficiency + ], ) ``` + **Variables:** Flow rates from one input and two output flows + + **Constraints:** Two instances of $\eqref{eq:Linear-Transformer-Ratio}$: + - $0.35 \cdot p_\text{fuel}(t) = p_\text{elec}(t)$ + - $0.55 \cdot p_\text{fuel}(t) = p_\text{heat}(t)$ + + --- + ## Heat Pump with Temperature-Dependent COP ```python - from flixopt import LinearConverter, Flow, Piecewise, Piece - import numpy as np + from flixopt import LinearConverter, Flow, Piecewise, Piece, PiecewiseConversion # COP varies from 2.5 (cold) to 4.0 (warm) cop_curve = Piecewise( [ - Piece((-10, 2.5), (0, 3.0)), # -10°C to 0°C - Piece((0, 3.0), (10, 3.5)), # 0°C to 10°C - Piece((10, 3.5), (20, 4.0)), # 10°C to 20°C + Piece((0, 0), (100, 250)), # 0-100 kW elec → 0-250 kW heat (COP 2.5) + Piece((100, 250), (200, 600)), # 100-200 kW elec → 250-600 kW heat (COP 3.5) ] ) heat_pump = LinearConverter( label='heat_pump', - inputs=[Flow(label='electricity_in', bus='electricity', size=25)], - outputs=[Flow(label='heat_out', bus='heating', size=100)], - conversion_factors={('electricity_in', 'heat_out'): cop_curve}, + inputs=[Flow(label='electricity_in', bus='electricity', size=200)], + outputs=[Flow(label='heat_out', bus='heating', size=600)], + piecewise_conversion=PiecewiseConversion( + origin_flow='electricity_in', + piecewise_shares={'heat_out': cop_curve}, + ), ) ``` + **Variables:** Flow rates + piecewise lambda variables $\lambda_k$ + + **Constraints:** Piecewise constraints linking electricity input to heat output with variable COP (see [Piecewise](../features/Piecewise.md)) + + --- + ## Converter with Investment Decision ```python @@ -177,10 +142,14 @@ A LinearConverter defines linear relationships (ratios) between incoming and out ), )], outputs=[Flow(label='h2_out', bus='hydrogen', size=1)], # Sized by ratio - conversion_factors={('electricity_in', 'h2_out'): 0.65}, # 65% efficiency + conversion_factors=[{'electricity_in': 0.65, 'h2_out': 1}], # 65% efficiency ) ``` + **Variables:** Flow rates + `size` variable for investment + + **Constraints:** $\eqref{eq:Linear-Transformer-Ratio-simple}$ plus investment constraints from [InvestParameters](../features/InvestParameters.md) + --- ## Specialized LinearConverter Classes @@ -197,6 +166,11 @@ These provide more intuitive interfaces for common applications. --- +## Implementation + +- **Component Class:** [`LinearConverter`][flixopt.components.LinearConverter] +- **Model Class:** [`LinearConverterModel`][flixopt.components.LinearConverterModel] + ## See Also - **Elements:** [Flow](Flow.md) · [Bus](Bus.md) · [Storage](Storage.md) diff --git a/docs/user-guide/mathematical-notation/elements/Storage.md b/docs/user-guide/mathematical-notation/elements/Storage.md index 02443c3f4..27e19d1e9 100644 --- a/docs/user-guide/mathematical-notation/elements/Storage.md +++ b/docs/user-guide/mathematical-notation/elements/Storage.md @@ -2,125 +2,82 @@ A Storage component represents energy or material accumulation with charging/discharging flows, state of charge tracking, and efficiency losses. -**Implementation:** +=== "Variables" -- **Component Class:** [`Storage`][flixopt.components.Storage] -- **Model Class:** [`StorageModel`][flixopt.components.StorageModel] + | Symbol | Python Name | Description | Domain | Created When | + |--------|-------------|-------------|--------|--------------| + | $c(\text{t}_i)$ | `charge_state` | State of charge at time $\text{t}_i$ | $\mathbb{R}_+$ | Always | + | $p_{f_\text{in}}(\text{t}_i)$ | - | Input flow rate (charging power) at time $\text{t}_i$ | $\mathbb{R}_+$ | Always (from `charging` Flow) | + | $p_{f_\text{out}}(\text{t}_i)$ | - | Output flow rate (discharging power) at time $\text{t}_i$ | $\mathbb{R}_+$ | Always (from `discharging` Flow) | + | - | `netto_discharge` | Net discharge rate (discharge - charge) | $\mathbb{R}$ | Always | + | $\text C$ | `size` | Storage capacity (decision variable) | $\mathbb{R}_+$ | `capacity_in_flow_hours` is `InvestParameters` | -**Related:** [`Flow`](Flow.md) · [`Bus`](Bus.md) · [`LinearConverter`](LinearConverter.md) - ---- +=== "Constraints" -=== "Core Formulation" + **State of charge bounds** (always active): - ## State of Charge Bounds - - The state of charge $c(\text{t}_i)$ is bounded by the storage size and relative limits: - - $$ \label{eq:Storage_Bounds} - \text C \cdot \text c^{\text{L}}_{\text{rel}}(\text t_{i}) - \leq c(\text{t}_i) \leq - \text C \cdot \text c^{\text{U}}_{\text{rel}}(\text t_{i}) + $$\label{eq:Storage_Bounds} + \text C \cdot \text c^{\text{L}}_{\text{rel}}(\text t_{i}) + \leq c(\text{t}_i) \leq + \text C \cdot \text c^{\text{U}}_{\text{rel}}(\text t_{i}) $$ - ??? info "Variables" - | Symbol | Description | Domain | - |--------|-------------|--------| - | $c(\text{t}_i)$ | State of charge at time $\text{t}_i$ | $\mathbb{R}_+$ | - | $\text C$ | Storage capacity | $\mathbb{R}_+$ or decision variable (with [InvestParameters](../features/InvestParameters.md)) | - - ??? info "Parameters" - | Symbol | Description | Typical Value | - |--------|-------------|---------------| - | $\text c^{\text{L}}_{\text{rel}}(\text t_{i})$ | Relative lower bound at time $\text{t}_i$ | 0 | - | $\text c^{\text{U}}_{\text{rel}}(\text t_{i})$ | Relative upper bound at time $\text{t}_i$ | 1 | + --- - ### Simplified Form + **Storage balance equation** (always active): - With standard bounds $\text c^{\text{L}}_{\text{rel}} = 0$ and $\text c^{\text{U}}_{\text{rel}} = 1$, equation $\eqref{eq:Storage_Bounds}$ simplifies to: - - $$ 0 \leq c(\text t_{i}) \leq \text C $$ - - ## Storage Balance Equation - - The state of charge evolves according to charging/discharging flows and self-discharge losses: - - $$ + $$\label{eq:storage_balance} \begin{align} - c(\text{t}_{i+1}) &= c(\text{t}_{i}) \cdot (1-\dot{\text{c}}_\text{rel,loss}(\text{t}_i))^{\Delta \text{t}_{i}} \nonumber \\ - &\quad + p_{f_\text{in}}(\text{t}_i) \cdot \Delta \text{t}_i \cdot \eta_\text{in}(\text{t}_i) \nonumber \\ - &\quad - p_{f_\text{out}}(\text{t}_i) \cdot \Delta \text{t}_i / \eta_\text{out}(\text{t}_i) - \label{eq:storage_balance} + c(\text{t}_{i+1}) &= c(\text{t}_{i}) \cdot (1-\dot{\text{c}}_\text{rel,loss}(\text{t}_i))^{\Delta \text{t}_{i}} \nonumber \\ + &\quad + p_{f_\text{in}}(\text{t}_i) \cdot \Delta \text{t}_i \cdot \eta_\text{in}(\text{t}_i) \nonumber \\ + &\quad - p_{f_\text{out}}(\text{t}_i) \cdot \Delta \text{t}_i / \eta_\text{out}(\text{t}_i) \end{align} $$ - ??? info "Flow Variables" - | Symbol | Description | Domain | - |--------|-------------|--------| - | $p_{f_\text{in}}(\text{t}_i)$ | Input flow rate (charging power) at time $\text{t}_i$ | $\mathbb{R}_+$ | - | $p_{f_\text{out}}(\text{t}_i)$ | Output flow rate (discharging power) at time $\text{t}_i$ | $\mathbb{R}_+$ | - - ??? info "Efficiency Parameters" - | Symbol | Description | Typical Range | Units | - |--------|-------------|---------------|-------| - | $\eta_\text{in}(\text{t}_i)$ | Charging efficiency | 0.85-0.98 | dimensionless | - | $\eta_\text{out}(\text{t}_i)$ | Discharging efficiency | 0.85-0.98 | dimensionless | - | $\dot{\text{c}}_\text{rel,loss}(\text{t}_i)$ | Relative self-discharge rate | 0-0.05 | per hour | - | $\Delta \text{t}_{i}$ | Timestep duration | - | hours | + --- - **Physical Interpretation:** - - - The first term represents self-discharge (exponential decay) - - The second term adds energy from charging (accounting for charging losses) - - The third term subtracts energy from discharging (accounting for discharging losses) - -=== "Advanced & Edge Cases" - - ## Initial State of Charge - - The storage must be initialized with a starting charge state: - - - **Parameter:** `initial_charge_state` sets $c(\text{t}_0)$ - - **Default:** Often set to some fraction of capacity (e.g., 0.5 × $\text C$) - - ## Final State of Charge Constraints - - Optional bounds on the state of charge at the end of the optimization horizon: + **Initial charge state** (when `initial_charge_state` specified): + $$\label{eq:storage_initial} + c(\text{t}_0) = \text{c}_\text{initial} $$ - \text c^{\text{L}}_{\text{final}} \leq c(\text{t}_\text{end}) \leq \text c^{\text{U}}_{\text{final}} - $$ - - **Use Case:** Ensure the storage ends with sufficient charge for the next optimization period, or enforce cyclic conditions. - - **Parameters:** - - `minimal_final_charge_state`: Lower bound $\text c^{\text{L}}_{\text{final}}$ - - `maximal_final_charge_state`: Upper bound $\text c^{\text{U}}_{\text{final}}$ - - ## Investment Sizing - - When using [InvestParameters](../features/InvestParameters.md), the storage capacity $\text C$ becomes an optimization variable: - - - Storage size is determined by the optimizer to minimize total system cost - - Flow capacities (charging/discharging rates) can be independently sized or linked to storage capacity - ## Prevent Simultaneous Charging/Discharging + Or for cyclic condition (`initial_charge_state='equals_final'`): - Some storage types (e.g., batteries with shared converters) cannot charge and discharge simultaneously. This is modeled using [OnOffParameters](../features/OnOffParameters.md) on the input/output flows with appropriate constraints. - - ## Variable Timesteps - - The formulation naturally handles variable timestep durations $\Delta \text{t}_i$ through the balance equation $\eqref{eq:storage_balance}$. + $$\label{eq:storage_cyclic} + c(\text{t}_0) = c(\text{t}_\text{end}) + $$ -=== "Mathematical Patterns" + --- - Storage formulation builds on the following modeling patterns: + **Final charge state bounds** (when `minimal_final_charge_state` or `maximal_final_charge_state` specified): - - **[Basic Bounds](../modeling-patterns/bounds-and-states.md#basic-bounds)** - Charge state bounds (equation $\eqref{eq:Storage_Bounds}$) - - **[Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)** - Flow rate bounds relative to storage size - - **[Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state)** - When using [InvestParameters](../features/InvestParameters.md) for capacity investment + $$\label{eq:storage_final} + \text c^{\text{L}}_{\text{final}} \leq c(\text{t}_\text{end}) \leq \text c^{\text{U}}_{\text{final}} + $$ -=== "Examples" + **Mathematical Patterns:** [Basic Bounds](../modeling-patterns/bounds-and-states.md#basic-bounds), [Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds) + +=== "Parameters" + + | Symbol | Python Parameter | Description | Default | + |--------|------------------|-------------|---------| + | $\text C$ | `capacity_in_flow_hours` | Storage capacity | Required | + | $\text c^{\text{L}}_{\text{rel}}(\text t_{i})$ | `relative_minimum_charge_state` | Relative lower bound (fraction of capacity) | 0 | + | $\text c^{\text{U}}_{\text{rel}}(\text t_{i})$ | `relative_maximum_charge_state` | Relative upper bound (fraction of capacity) | 1 | + | $\text{c}_\text{initial}$ | `initial_charge_state` | Charge at start | 0 | + | $\text c^{\text{L}}_{\text{final}}$ | `minimal_final_charge_state` | Minimum absolute charge required at end | None | + | $\text c^{\text{U}}_{\text{final}}$ | `maximal_final_charge_state` | Maximum absolute charge allowed at end | None | + | $\Delta \text{t}_{i}$ | - | Timestep duration | hours | + | $\dot{\text{c}}_\text{rel,loss}(\text{t}_i)$ | `relative_loss_per_hour` | Relative self-discharge rate per hour | 0 | + | $\eta_\text{in}(\text{t}_i)$ | `eta_charge` | Charging efficiency (0-1) | 1 | + | $\eta_\text{out}(\text{t}_i)$ | `eta_discharge` | Discharging efficiency (0-1) | 1 | + | - | `balanced` | If True, forces charging and discharging flows to have equal sizes | False | + | - | `prevent_simultaneous_charge_and_discharge` | Prevents charging and discharging simultaneously | True | + | - | `relative_minimum_final_charge_state` | Minimum relative charge at end | None | + | - | `relative_maximum_final_charge_state` | Maximum relative charge at end | None | + +=== "Use Cases" ## Basic Battery Storage @@ -129,8 +86,8 @@ A Storage component represents energy or material accumulation with charging/dis battery = Storage( label='battery', - inputs=[Flow(label='charge', bus='electricity', size=50)], # 50 kW charging - outputs=[Flow(label='discharge', bus='electricity', size=50)], # 50 kW discharging + charging=Flow(label='charge', bus='electricity', size=50), # 50 kW charging + discharging=Flow(label='discharge', bus='electricity', size=50), # 50 kW discharging capacity_in_flow_hours=200, # 200 kWh capacity initial_charge_state=100, # Start at 100 kWh (50% SOC) eta_charge=0.95, # 95% charging efficiency @@ -139,6 +96,12 @@ A Storage component represents energy or material accumulation with charging/dis ) ``` + **Variables:** `charge_state[t]`, flow rates, `netto_discharge[t]` + + **Constraints:** $\eqref{eq:Storage_Bounds}$ with $0 \leq c(t) \leq 200$ kWh, $\eqref{eq:storage_balance}$, $\eqref{eq:storage_initial}$ with $c(t_0) = 100$ kWh + + --- + ## Thermal Storage with Final State Constraint ```python @@ -146,8 +109,8 @@ A Storage component represents energy or material accumulation with charging/dis thermal_storage = Storage( label='heat_tank', - inputs=[Flow(label='heat_in', bus='heating', size=100)], - outputs=[Flow(label='heat_out', bus='heating', size=100)], + charging=Flow(label='heat_in', bus='heating', size=100), + discharging=Flow(label='heat_out', bus='heating', size=100), capacity_in_flow_hours=500, # 500 kWh thermal capacity initial_charge_state=250, # Start half-full minimal_final_charge_state=250, # End at least half-full @@ -157,6 +120,12 @@ A Storage component represents energy or material accumulation with charging/dis ) ``` + **Variables:** `charge_state[t]`, flow rates, `netto_discharge[t]` + + **Constraints:** $\eqref{eq:Storage_Bounds}$, $\eqref{eq:storage_balance}$, $\eqref{eq:storage_initial}$, $\eqref{eq:storage_final}$ with $c(t_\text{end}) \geq 250$ kWh + + --- + ## Storage with Investment Decision ```python @@ -164,8 +133,8 @@ A Storage component represents energy or material accumulation with charging/dis optimized_battery = Storage( label='battery_investment', - inputs=[Flow(label='charge', bus='electricity', size=100)], - outputs=[Flow(label='discharge', bus='electricity', size=100)], + charging=Flow(label='charge', bus='electricity', size=100), + discharging=Flow(label='discharge', bus='electricity', size=100), capacity_in_flow_hours=InvestParameters( minimum_size=0, # Can choose not to build maximum_size=1000, # Up to 1 MWh @@ -177,11 +146,42 @@ A Storage component represents energy or material accumulation with charging/dis ) ``` + **Variables:** `charge_state[t]`, flow rates, `netto_discharge[t]`, `size` (decision variable) + + **Constraints:** $\eqref{eq:Storage_Bounds}$ with variable $\text C$, $\eqref{eq:storage_balance}$, plus investment constraints from [InvestParameters](../features/InvestParameters.md) + + --- + + ## Cyclic Storage Condition + + ```python + from flixopt import Storage, Flow + + pumped_hydro = Storage( + label='pumped_hydro', + charging=Flow(label='pump', bus='electricity', size=100), + discharging=Flow(label='turbine', bus='electricity', size=120), + capacity_in_flow_hours=10000, # 10 MWh + initial_charge_state='equals_final', # End with same charge as start + eta_charge=0.85, # Pumping efficiency + eta_discharge=0.90, # Turbine efficiency + relative_loss_per_hour=0.0001, # Minimal evaporation + ) + ``` + + **Variables:** `charge_state[t]`, flow rates, `netto_discharge[t]` + + **Constraints:** $\eqref{eq:Storage_Bounds}$, $\eqref{eq:storage_balance}$, $\eqref{eq:storage_cyclic}$ enforcing $c(t_0) = c(t_\text{end})$ + --- +## Implementation + +- **Component Class:** [`Storage`][flixopt.components.Storage] +- **Model Class:** [`StorageModel`][flixopt.components.StorageModel] + ## See Also -- **Elements:** [Flow](Flow.md) · [Bus](Bus.md) +- **Elements:** [Flow](Flow.md) · [Bus](Bus.md) · [LinearConverter](LinearConverter.md) - **Features:** [InvestParameters](../features/InvestParameters.md) · [OnOffParameters](../features/OnOffParameters.md) -- **Patterns:** [Modeling Patterns](../modeling-patterns/index.md) -- **Components:** [LinearConverter](LinearConverter.md) +- **Patterns:** [Modeling Patterns](../modeling-patterns/index.md) · [Bounds and States](../modeling-patterns/bounds-and-states.md) diff --git a/flixopt/components.py b/flixopt/components.py index cf6cb4082..51e1ee118 100644 --- a/flixopt/components.py +++ b/flixopt/components.py @@ -41,8 +41,7 @@ class LinearConverter(Component): behavior approximated through piecewise linear segments. Mathematical Formulation: - See the complete mathematical model in the documentation: - [LinearConverter](../user-guide/mathematical-notation/elements/LinearConverter.md) + See Args: label: The label of the Element. Used to identify it in the FlowSystem. @@ -260,18 +259,7 @@ class Storage(Component): and investment-optimized storage systems with comprehensive techno-economic modeling. Mathematical Formulation: - See the complete mathematical model in the documentation: - [Storage](../user-guide/mathematical-notation/elements/Storage.md) - - - Equation (1): Charge state bounds - - Equation (3): Storage balance (charge state evolution) - - Variable Mapping: - - ``capacity_in_flow_hours`` → C (storage capacity) - - ``charge_state`` → c(t_i) (state of charge at time t_i) - - ``relative_loss_per_hour`` → ċ_rel,loss (self-discharge rate) - - ``eta_charge`` → η_in (charging efficiency) - - ``eta_discharge`` → η_out (discharging efficiency) + See Args: label: Element identifier used in the FlowSystem. @@ -787,6 +775,16 @@ def create_transmission_equation(self, name: str, in_flow: Flow, out_flow: Flow) class LinearConverterModel(ComponentModel): + """Mathematical model implementation for LinearConverter components. + + Creates optimization constraints for linear conversion relationships between + input and output flows, supporting both simple conversion factors and piecewise + non-linear approximations. + + Mathematical Formulation: + See + """ + element: LinearConverter def __init__(self, model: FlowSystemModel, element: LinearConverter): @@ -835,7 +833,14 @@ def _do_modeling(self): class StorageModel(ComponentModel): - """Submodel of Storage""" + """Mathematical model implementation for Storage components. + + Creates optimization variables and constraints for charge state tracking, + storage balance equations, and optional investment sizing. + + Mathematical Formulation: + See + """ element: Storage diff --git a/flixopt/elements.py b/flixopt/elements.py index da4839b70..eeb752b00 100644 --- a/flixopt/elements.py +++ b/flixopt/elements.py @@ -196,8 +196,7 @@ class Bus(Element): or material flows between different Components. Mathematical Formulation: - See the complete mathematical model in the documentation: - [Bus](../user-guide/mathematical-notation/elements/Bus.md) + See Args: label: The label of the Element. Used to identify it in the FlowSystem. @@ -939,6 +938,15 @@ def previous_states(self) -> xr.DataArray | None: class BusModel(ElementModel): + """Mathematical model implementation for Bus elements. + + Creates optimization variables and constraints for nodal balance equations, + and optional excess/deficit variables with penalty costs. + + Mathematical Formulation: + See + """ + element: Bus # Type hint def __init__(self, model: FlowSystemModel, element: Bus): From e4711b4388136a450f84dcecfd7894ee4f053730 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:48:51 +0100 Subject: [PATCH 13/79] Update InvestParameters and OnOffParameters.md --- .../features/InvestParameters.md | 258 +++++------- .../features/OnOffParameters.md | 380 ++++++------------ flixopt/features.py | 20 +- flixopt/interface.py | 6 +- 4 files changed, 255 insertions(+), 409 deletions(-) diff --git a/docs/user-guide/mathematical-notation/features/InvestParameters.md b/docs/user-guide/mathematical-notation/features/InvestParameters.md index 9304c5918..3271a4009 100644 --- a/docs/user-guide/mathematical-notation/features/InvestParameters.md +++ b/docs/user-guide/mathematical-notation/features/InvestParameters.md @@ -2,210 +2,110 @@ InvestParameters enable investment decision modeling in optimization, supporting both binary (invest/don't invest) and continuous sizing choices with comprehensive cost modeling. -**Implementation:** +=== "Variables" -- **Feature Class:** [`InvestParameters`][flixopt.interface.InvestParameters] -- **Used by:** [`Flow`][flixopt.elements.Flow] · [`Storage`][flixopt.components.Storage] · [`LinearConverter`][flixopt.components.LinearConverter] - -**Related:** [`OnOffParameters`](OnOffParameters.md) · [`Piecewise`](Piecewise.md) - ---- - -=== "Core Formulation" + | Symbol | Python Name | Description | Domain | Created When | + |--------|-------------|-------------|--------|--------------| + | $v_\text{invest}$ | `size` | Investment size (continuous or fixed) | $\mathbb{R}_+$ | Always | + | $s_\text{invest}$ | `invested` | Binary investment decision | $\{0,1\}$ | `mandatory=False` (optional investment) | - ## Binary Investment Decision +=== "Constraints" - Fixed-size investment creating a yes/no decision (e.g., install a 100 kW generator): + **Binary investment decision** (when `fixed_size` specified): $$\label{eq:invest_binary} v_\text{invest} = s_\text{invest} \cdot \text{size}_\text{fixed} $$ - ??? info "Variables" - | Symbol | Description | Domain | - |--------|-------------|--------| - | $s_\text{invest}$ | Binary investment decision | $\{0, 1\}$ | - | $v_\text{invest}$ | Resulting investment size | $\mathbb{R}_+$ | - - ??? info "Parameters" - | Symbol | Description | - |--------|-------------| - | $\text{size}_\text{fixed}$ | Predefined component size | + --- - **Behavior:** - - - $s_\text{invest} = 0$: no investment ($v_\text{invest} = 0$) - - $s_\text{invest} = 1$: invest at fixed size ($v_\text{invest} = \text{size}_\text{fixed}$) - - ## Continuous Sizing Decision - - Variable-size investment with bounds (e.g., battery capacity from 10-1000 kWh): + **Continuous sizing decision** (when `minimum_size` and `maximum_size` specified): $$\label{eq:invest_continuous} s_\text{invest} \cdot \text{size}_\text{min} \leq v_\text{invest} \leq s_\text{invest} \cdot \text{size}_\text{max} $$ - ??? info "Variables" - | Symbol | Description | Domain | - |--------|-------------|--------| - | $s_\text{invest}$ | Binary investment decision | $\{0, 1\}$ | - | $v_\text{invest}$ | Investment size (continuous) | $\mathbb{R}_+$ | - - ??? info "Parameters" - | Symbol | Description | - |--------|-------------| - | $\text{size}_\text{min}$ | Minimum investment size (if investing) | - | $\text{size}_\text{max}$ | Maximum investment size | - - **Behavior:** + When `mandatory=False`: $s_\text{invest} = 0$ means no investment ($v_\text{invest} = 0$), $s_\text{invest} = 1$ means invest with size in $[\text{size}_\text{min}, \text{size}_\text{max}]$ - - $s_\text{invest} = 0$: no investment ($v_\text{invest} = 0$) - - $s_\text{invest} = 1$: invest with size in $[\text{size}_\text{min}, \text{size}_\text{max}]$ + --- - This uses the **[Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state)** pattern. + **Mandatory investment** (when `mandatory=True`): - ## Investment Effects (Costs) + $$\label{eq:invest_mandatory} + s_\text{invest} = 1 + $$ - ### Fixed Effects + --- - One-time effects incurred if investment is made, independent of size: + **Investment effects - Fixed** (when `effects_of_investment` specified): $$\label{eq:invest_fixed_effects} E_{e,\text{fix}} = s_\text{invest} \cdot \text{fix}_e $$ - **Examples:** Fixed installation costs (permits, grid connection), one-time environmental impacts. + One-time effects incurred if investment is made, independent of size (permits, grid connection, one-time environmental impacts). - ### Specific Effects (Per-Unit Costs) + --- - Effects proportional to investment size: + **Investment effects - Specific** (when `effects_of_investment_per_size` specified): $$\label{eq:invest_specific_effects} E_{e,\text{spec}} = v_\text{invest} \cdot \text{spec}_e $$ - **Examples:** Equipment costs (€/kW), material requirements (kg steel/kW), recurring maintenance (€/kW/year). - - ### Total Investment Effects - - $$\label{eq:invest_total_effects} - E_{e,\text{invest}} = E_{e,\text{fix}} + E_{e,\text{spec}} + E_{e,\text{pw}} + E_{e,\text{retirement}} - $$ - - Where $E_{e,\text{pw}}$ is the piecewise contribution (see Advanced tab) and $E_{e,\text{retirement}}$ are retirement effects (see Advanced tab). - -=== "Advanced & Edge Cases" + Effects proportional to investment size (equipment costs €/kW, material requirements kg/kW, recurring maintenance €/kW/year). - ## Optional vs. Mandatory Investment + --- - The `mandatory` parameter controls whether investment is required: - - **Optional Investment** (`mandatory=False`, default): - $$\label{eq:invest_optional} - s_\text{invest} \in \{0, 1\} - $$ - - The optimization can freely choose to invest or not. - - **Mandatory Investment** (`mandatory=True`): - $$\label{eq:invest_mandatory} - s_\text{invest} = 1 - $$ - - The investment must occur (useful for mandatory upgrades or replacements). - - ## Retirement Effects - - Effects incurred if investment is NOT made (when retiring/not replacing existing equipment): + **Retirement effects** (when `effects_of_retirement` specified and `mandatory=False`): $$\label{eq:invest_retirement_effects} E_{e,\text{retirement}} = (1 - s_\text{invest}) \cdot \text{retirement}_e $$ - **Behavior:** + Effects incurred if investment is NOT made (demolition/disposal costs, decommissioning, penalties, opportunity costs). - - $s_\text{invest} = 0$: retirement effects are incurred - - $s_\text{invest} = 1$: no retirement effects + --- - **Examples:** Demolition/disposal costs, decommissioning expenses, contractual penalties, opportunity costs. - - ## Piecewise Effects (Economies of Scale) - - Non-linear effect relationships using piecewise linear approximations: + **Piecewise effects** (when `piecewise_effects_of_investment` specified): $$\label{eq:invest_piecewise_effects} E_{e,\text{pw}} = \sum_{k=1}^{K} \lambda_k \cdot r_{e,k} $$ Subject to: - $$ - v_\text{invest} = \sum_{k=1}^{K} \lambda_k \cdot v_k - $$ - - ??? info "Piecewise Variables" - | Symbol | Description | - |--------|-------------| - | $\lambda_k$ | Piecewise lambda variables (see [Piecewise](Piecewise.md)) | - | $r_{e,k}$ | Effect rate at piece $k$ | - | $v_k$ | Size points defining the pieces | - - **Use cases:** Economies of scale (bulk discounts), technology learning curves, threshold effects. - - See [Piecewise](Piecewise.md) for detailed mathematical formulation. - - ## Integration with Component Sizing - - Investment parameters modify component sizing: - **Without Investment:** $$ - \text{size} = \text{size}_\text{nominal} - $$ - - **With Investment:** - $$ - \text{size} = v_\text{invest} - $$ - - This size variable then appears in component constraints. For example, flow rate bounds become: - - $$ - v_\text{invest} \cdot \text{rel}_\text{lower} \leq p(t) \leq v_\text{invest} \cdot \text{rel}_\text{upper} + v_\text{invest} = \sum_{k=1}^{K} \lambda_k \cdot v_k $$ - Using the **[Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)** pattern. - - ## Cost Annualization + Non-linear effect relationships using piecewise linear approximations for economies of scale, technology learning curves, or threshold effects. See [Piecewise](Piecewise.md) for detailed formulation. - **Important:** All investment cost values must be properly weighted to match the optimization model's time horizon. + --- - For long-term investments, costs should be annualized: - - $$\label{eq:annualization} - \text{cost}_\text{annual} = \frac{\text{cost}_\text{capital} \cdot r}{1 - (1 + r)^{-n}} - $$ + **Total investment effects**: - ??? info "Annualization Parameters" - | Symbol | Description | - |--------|-------------| - | $\text{cost}_\text{capital}$ | Upfront investment cost | - | $r$ | Discount rate | - | $n$ | Equipment lifetime (years) | - - **Example:** €1,000,000 equipment with 20-year life and 5% discount rate - $$ - \text{cost}_\text{annual} = \frac{1{,}000{,}000 \cdot 0.05}{1 - (1.05)^{-20}} \approx €80{,}243/\text{year} + $$\label{eq:invest_total_effects} + E_{e,\text{invest}} = E_{e,\text{fix}} + E_{e,\text{spec}} + E_{e,\text{pw}} + E_{e,\text{retirement}} $$ -=== "Mathematical Patterns" + **Mathematical Patterns:** [Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state), [Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds), [Piecewise Linear Approximations](Piecewise.md) - InvestParameters relies on: +=== "Parameters" - - **[Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state)** - For continuous sizing with binary investment decision - - **[Scaled Bounds](../modeling-patterns/bounds-and-states.md#scaled-bounds)** - For linking investment size to component constraints - - **[Piecewise Linear Approximations](Piecewise.md)** - For non-linear cost structures + | Symbol | Python Parameter | Description | Default | + |--------|------------------|-------------|---------| + | $\text{fix}_e$ | `effects_of_investment` | Fixed effects if investment is made | None | + | $\text{retirement}_e$ | `effects_of_retirement` | Effects if NOT investing | None | + | $\text{size}_\text{fixed}$ | `fixed_size` | Predefined component size (binary decision) | None | + | $\text{size}_\text{max}$ | `maximum_size` | Maximum investment size | CONFIG.Modeling.big | + | $\text{size}_\text{min}$ | `minimum_size` | Minimum investment size (if investing) | CONFIG.Modeling.epsilon | + | $\text{spec}_e$ | `effects_of_investment_per_size` | Per-unit effects proportional to size | None | + | - | `linked_periods` | Describes which periods are linked for multi-period optimization | None | + | - | `mandatory` | If True, investment must occur | False | + | - | `piecewise_effects_of_investment` | PiecewiseEffects for non-linear cost structures | None | -=== "Examples" +=== "Use Cases" ## Binary Investment (Solar Panels) @@ -224,6 +124,12 @@ InvestParameters enable investment decision modeling in optimization, supporting ) ``` + **Variables:** `size`, `invested` (binary) + + **Constraints:** $\eqref{eq:invest_binary}$ with $v_\text{invest} = s_\text{invest} \cdot 100$ kW, $\eqref{eq:invest_fixed_effects}$ with fixed cost of €25,000, $\eqref{eq:invest_specific_effects}$ with €1,200/kW + + --- + ## Continuous Sizing (Battery) ```python @@ -231,8 +137,8 @@ InvestParameters enable investment decision modeling in optimization, supporting battery = Storage( label='battery_storage', - inputs=[Flow(label='charge', bus='electricity', size=100)], - outputs=[Flow(label='discharge', bus='electricity', size=100)], + charging=Flow(label='charge', bus='electricity', size=100), + discharging=Flow(label='discharge', bus='electricity', size=100), capacity_in_flow_hours=InvestParameters( minimum_size=10, # Minimum 10 kWh if investing maximum_size=1000, # Maximum 1 MWh @@ -242,6 +148,12 @@ InvestParameters enable investment decision modeling in optimization, supporting ) ``` + **Variables:** `size` (continuous), `invested` (binary) + + **Constraints:** $\eqref{eq:invest_continuous}$ with $s_\text{invest} \cdot 10 \leq v_\text{invest} \leq s_\text{invest} \cdot 1000$ kWh, $\eqref{eq:invest_fixed_effects}$, $\eqref{eq:invest_specific_effects}$ + + --- + ## With Retirement Costs (Replacement Decision) ```python @@ -261,10 +173,16 @@ InvestParameters enable investment decision modeling in optimization, supporting ), )], outputs=[Flow(label='heat_out', bus='heating', size=1)], - conversion_factors={('gas_in', 'heat_out'): 0.9}, + conversion_factors=[{'gas_in': 0.9, 'heat_out': 1}], ) ``` + **Variables:** `size`, `invested` + + **Constraints:** $\eqref{eq:invest_continuous}$, $\eqref{eq:invest_fixed_effects}$, $\eqref{eq:invest_specific_effects}$, $\eqref{eq:invest_retirement_effects}$ with €8,000 cost if $s_\text{invest} = 0$ + + --- + ## Economies of Scale (Piecewise Costs) ```python @@ -281,15 +199,21 @@ InvestParameters enable investment decision modeling in optimization, supporting ]), piecewise_shares={ 'cost': Piecewise([ - Piece((0, 800), (100, 750)), # €800-750/kWh (small) - Piece((100, 750), (500, 600)), # €750-600/kWh (medium) - Piece((500, 600), (1000, 500)), # €600-500/kWh (large, bulk discount) + Piece((0, 0), (100, 80000)), # €800/kWh (small) + Piece((100, 80000), (500, 350000)), # €750-€600/kWh (medium) + Piece((500, 350000), (1000, 850000)), # €600-€500/kWh (large, bulk discount) ]) }, ), ) ``` + **Variables:** `size`, `invested`, piecewise lambda variables $\lambda_k$ + + **Constraints:** $\eqref{eq:invest_continuous}$, $\eqref{eq:invest_piecewise_effects}$ representing decreasing cost per kWh with scale (bulk discount from €800/kWh to €500/kWh) + + --- + ## Mandatory Investment (Upgrade Required) ```python @@ -307,8 +231,38 @@ InvestParameters enable investment decision modeling in optimization, supporting ) ``` + **Variables:** `size` (no binary variable since mandatory) + + **Constraints:** $\eqref{eq:invest_mandatory}$ forcing $s_\text{invest} = 1$, $\eqref{eq:invest_continuous}$ simplified to $100 \leq v_\text{invest} \leq 500$ kW + +--- + +## Cost Annualization + +**Important:** All investment cost values must be properly weighted to match the optimization model's time horizon. + +For long-term investments, costs should be annualized: + +$$\label{eq:annualization} +\text{cost}_\text{annual} = \frac{\text{cost}_\text{capital} \cdot r}{1 - (1 + r)^{-n}} +$$ + +Where $r$ is the discount rate and $n$ is the equipment lifetime (years). + +**Example:** €1,000,000 equipment with 20-year life and 5% discount rate: + +$$ +\text{cost}_\text{annual} = \frac{1{,}000{,}000 \cdot 0.05}{1 - (1.05)^{-20}} \approx €80{,}243/\text{year} +$$ + --- +## Implementation + +- **Feature Class:** [`InvestParameters`][flixopt.interface.InvestParameters] +- **Model Class:** [`InvestmentModel`][flixopt.features.InvestmentModel] +- **Used by:** [`Flow`](../elements/Flow.md) · [`Storage`](../elements/Storage.md) · [`LinearConverter`](../elements/LinearConverter.md) + ## See Also - **Elements:** [Flow](../elements/Flow.md) · [Storage](../elements/Storage.md) · [LinearConverter](../elements/LinearConverter.md) diff --git a/docs/user-guide/mathematical-notation/features/OnOffParameters.md b/docs/user-guide/mathematical-notation/features/OnOffParameters.md index 6bf40fec9..cf07279d8 100644 --- a/docs/user-guide/mathematical-notation/features/OnOffParameters.md +++ b/docs/user-guide/mathematical-notation/features/OnOffParameters.md @@ -1,307 +1,191 @@ # OnOffParameters -[`OnOffParameters`][flixopt.interface.OnOffParameters] model equipment that operates in discrete on/off states rather than continuous operation. This captures realistic operational constraints including startup costs, minimum run times, cycling limitations, and maintenance scheduling. +OnOffParameters define operational constraints and effects for binary on/off equipment behavior, capturing realistic operational limits and associated costs. -## Binary State Variable +=== "Variables" -Equipment operation is modeled using a binary state variable: + | Symbol | Python Name | Description | Domain | Created When | + |--------|-------------|-------------|--------|--------------| + | $s(\text{t}_i)$ | `on` | Binary on/off state at time $\text{t}_i$ | $\{0,1\}$ | Always | + | - | `off` | Binary off state (complement of `on`) | $\{0,1\}$ | Any consecutive off constraints specified | + | - | `switch_on` | Startup indicator at time $\text{t}_i$ | $\{0,1\}$ | `switch_on_max` or `effects_per_switch_on` or consecutive constraints specified | + | - | `switch_off` | Shutdown indicator at time $\text{t}_i$ | $\{0,1\}$ | `switch_on_max` or `effects_per_switch_on` or consecutive constraints specified | + | - | `on_hours_total` | Total operating hours in period | $\mathbb{R}_+$ | `on_hours_min` or `on_hours_max` specified | + | - | `switch_count` | Count of startups in period | $\mathbb{Z}_+$ | `switch_on_max` specified | + | - | `consecutive_on_hours` | Duration tracking for minimum/maximum run times | $\mathbb{R}_+$ | `consecutive_on_hours_min` or `consecutive_on_hours_max` specified | + | - | `consecutive_off_hours` | Duration tracking for minimum/maximum off times | $\mathbb{R}_+$ | `consecutive_off_hours_min` or `consecutive_off_hours_max` specified | -$$\label{eq:onoff_state} -s(t) \in \{0, 1\} \quad \forall t -$$ +=== "Constraints" -With: -- $s(t) = 1$: equipment is operating (on state) -- $s(t) = 0$: equipment is shutdown (off state) + **Complementary on/off states** (when off state needed): -This state variable controls the equipment's operational constraints and modifies flow bounds using the **bounds with state** pattern from [Bounds and States](../modeling-patterns/bounds-and-states.md#bounds-with-state). + $$\label{eq:onoff_complementary} + s(\text{t}_i) + s_\text{off}(\text{t}_i) = 1 + $$ ---- - -## State Transitions and Switching - -State transitions are tracked using switch variables (see [State Transitions](../modeling-patterns/state-transitions.md#binary-state-transitions)): - -$$\label{eq:onoff_transitions} -s^\text{on}(t) - s^\text{off}(t) = s(t) - s(t-1) \quad \forall t > 0 -$$ - -$$\label{eq:onoff_switch_exclusivity} -s^\text{on}(t) + s^\text{off}(t) \leq 1 \quad \forall t -$$ - -With: -- $s^\text{on}(t) \in \{0, 1\}$: equals 1 when switching from off to on (startup) -- $s^\text{off}(t) \in \{0, 1\}$: equals 1 when switching from on to off (shutdown) - -**Behavior:** -- Off → On: $s^\text{on}(t) = 1, s^\text{off}(t) = 0$ -- On → Off: $s^\text{on}(t) = 0, s^\text{off}(t) = 1$ -- No change: $s^\text{on}(t) = 0, s^\text{off}(t) = 0$ - ---- - -## Effects and Costs + --- -### Switching Effects + **Total operating hours** (when `on_hours_min` or `on_hours_max` specified): -Effects incurred when equipment starts up: + $$\label{eq:onoff_total_hours} + H_\text{min} \leq \sum_i s(\text{t}_i) \cdot \Delta \text{t}_i \leq H_\text{max} + $$ -$$\label{eq:onoff_switch_effects} -E_{e,\text{switch}} = \sum_{t} s^\text{on}(t) \cdot \text{effect}_{e,\text{switch}} -$$ + --- -With: -- $\text{effect}_{e,\text{switch}}$ being the effect value per startup event + **State transitions** (when switch tracking enabled): -**Examples:** -- Startup fuel consumption -- Wear and tear costs -- Labor costs for startup procedures -- Inrush power demands + $$\label{eq:onoff_transitions} + \text{switch\_on}(\text{t}_i) - \text{switch\_off}(\text{t}_i) = s(\text{t}_i) - s(\text{t}_{i-1}) + $$ ---- - -### Running Effects + --- -Effects incurred while equipment is operating: + **Maximum startup count** (when `switch_on_max` specified): -$$\label{eq:onoff_running_effects} -E_{e,\text{run}} = \sum_{t} s(t) \cdot \Delta t \cdot \text{effect}_{e,\text{run}} -$$ + $$\label{eq:onoff_switch_count} + \sum_i \text{switch\_on}(\text{t}_i) \leq N_\text{switch,max} + $$ -With: -- $\text{effect}_{e,\text{run}}$ being the effect rate per operating hour -- $\Delta t$ being the time step duration + --- -**Examples:** -- Fixed operating and maintenance costs -- Auxiliary power consumption -- Consumable materials -- Emissions while running + **Minimum consecutive on time** (when `consecutive_on_hours_min` specified): ---- + $$\label{eq:onoff_min_on} + \text{If } s(\text{t}_{i-1}) = 0 \text{ and } s(\text{t}_i) = 1, \text{ then } \sum_{j=i}^{i+k} s(\text{t}_j) \cdot \Delta \text{t}_j \geq T_\text{on,min} + $$ -## Operating Hour Constraints + --- -### Total Operating Hours + **Maximum consecutive on time** (when `consecutive_on_hours_max` specified): -Bounds on total operating time across the planning horizon: + $$\label{eq:onoff_max_on} + \sum_{j=i}^{i+k} s(\text{t}_j) \cdot \Delta \text{t}_j \leq T_\text{on,max} + $$ -$$\label{eq:onoff_total_hours} -h_\text{min} \leq \sum_{t} s(t) \cdot \Delta t \leq h_\text{max} -$$ + --- -With: -- $h_\text{min}$ being the minimum total operating hours -- $h_\text{max}$ being the maximum total operating hours + **Minimum consecutive off time** (when `consecutive_off_hours_min` specified): -**Use cases:** -- Minimum runtime requirements (contracts, maintenance) -- Maximum runtime limits (fuel availability, permits, equipment life) - ---- + $$\label{eq:onoff_min_off} + \text{If } s(\text{t}_{i-1}) = 1 \text{ and } s(\text{t}_i) = 0, \text{ then } \sum_{j=i}^{i+k} (1-s(\text{t}_j)) \cdot \Delta \text{t}_j \geq T_\text{off,min} + $$ -### Consecutive Operating Hours + --- -**Minimum Consecutive On-Time:** + **Effects per switch on** (when `effects_per_switch_on` specified): -Enforces minimum runtime once started using duration tracking (see [Duration Tracking](../modeling-patterns/duration-tracking.md#minimum-duration-constraints)): + $$\label{eq:onoff_switch_effects} + E_{e,\text{switch}} = \sum_i \text{switch\_on}(\text{t}_i) \cdot c_{e,\text{switch}} + $$ -$$\label{eq:onoff_min_on_duration} -d^\text{on}(t) \geq (s(t-1) - s(t)) \cdot h^\text{on}_\text{min} \quad \forall t > 0 -$$ + --- -With: -- $d^\text{on}(t)$ being the consecutive on-time duration at time $t$ -- $h^\text{on}_\text{min}$ being the minimum required on-time + **Effects per running hour** (when `effects_per_running_hour` specified): -**Behavior:** -- When shutting down at time $t$: enforces equipment was on for at least $h^\text{on}_\text{min}$ prior to the switch -- Prevents short cycling and frequent startups + $$\label{eq:onoff_running_effects} + E_{e,\text{run}} = \sum_i s(\text{t}_i) \cdot \Delta \text{t}_i \cdot c_{e,\text{run}} + $$ -**Maximum Consecutive On-Time:** + **Mathematical Patterns:** [State Transitions](../modeling-patterns/bounds-and-states.md), [Duration Tracking](../modeling-patterns/bounds-and-states.md) -Limits continuous operation before requiring shutdown: +=== "Parameters" -$$\label{eq:onoff_max_on_duration} -d^\text{on}(t) \leq h^\text{on}_\text{max} \quad \forall t -$$ + | Symbol | Python Parameter | Description | Default | + |--------|------------------|-------------|---------| + | $c_{e,\text{run}}$ | `effects_per_running_hour` | Ongoing effects while equipment operates | None | + | $c_{e,\text{switch}}$ | `effects_per_switch_on` | Effects for each startup | None | + | $\Delta \text{t}_i$ | - | Timestep duration (hours) | From system | + | $H_\text{max}$ | `on_hours_max` | Maximum total operating hours per period | None | + | $H_\text{min}$ | `on_hours_min` | Minimum total operating hours per period | None | + | $N_\text{switch,max}$ | `switch_on_max` | Maximum number of startups per period | None | + | $T_\text{off,min}$ | `consecutive_off_hours_min` | Minimum continuous shutdown duration | None | + | $T_\text{on,max}$ | `consecutive_on_hours_max` | Maximum continuous operating duration | None | + | $T_\text{on,min}$ | `consecutive_on_hours_min` | Minimum continuous operating duration | None | + | - | `consecutive_off_hours_max` | Maximum continuous shutdown duration | None | + | - | `force_switch_on` | Create switch variables even without max constraint | False | -**Use cases:** -- Mandatory maintenance intervals -- Process batch time limits -- Thermal cycling requirements +=== "Use Cases" ---- + ## Power Plant with Startup Costs -### Consecutive Shutdown Hours + ```python + from flixopt import Flow, OnOffParameters -**Minimum Consecutive Off-Time:** + generator = Flow( + label='power_output', + bus='electricity', + size=100, # 100 MW + on_off_parameters=OnOffParameters( + effects_per_switch_on={'cost': 25000}, # €25k startup cost + consecutive_on_hours_min=8, # Must run 8+ hours once started + consecutive_off_hours_min=4, # Must stay off 4+ hours + ), + ) + ``` -Enforces minimum shutdown duration before restarting: + **Variables:** `on[t]`, `switch_on[t]`, `switch_off[t]`, `consecutive_on_hours`, `consecutive_off_hours` -$$\label{eq:onoff_min_off_duration} -d^\text{off}(t) \geq (s(t) - s(t-1)) \cdot h^\text{off}_\text{min} \quad \forall t > 0 -$$ + **Constraints:** $\eqref{eq:onoff_transitions}$, $\eqref{eq:onoff_min_on}$ with 8h minimum, $\eqref{eq:onoff_min_off}$ with 4h minimum, $\eqref{eq:onoff_switch_effects}$ with €25k per startup -With: -- $d^\text{off}(t)$ being the consecutive off-time duration at time $t$ -- $h^\text{off}_\text{min}$ being the minimum required off-time + --- -**Use cases:** -- Cooling periods -- Maintenance requirements -- Process stabilization + ## Industrial Process with Cycling Limits -**Maximum Consecutive Off-Time:** + ```python + from flixopt import Flow, OnOffParameters -Limits shutdown duration before mandatory restart: + batch_reactor = Flow( + label='process_output', + bus='production', + size=50, + on_off_parameters=OnOffParameters( + effects_per_switch_on={'setup_cost': 1500}, + effects_per_running_hour={'utilities': 200}, + consecutive_on_hours_min=12, # 12h minimum batch + switch_on_max=20, # Max 20 batches per period + ), + ) + ``` -$$\label{eq:onoff_max_off_duration} -d^\text{off}(t) \leq h^\text{off}_\text{max} \quad \forall t -$$ + **Variables:** `on[t]`, `switch_on[t]`, `switch_off[t]`, `switch_count`, `consecutive_on_hours` -**Use cases:** -- Equipment preservation requirements -- Process stability needs -- Contractual minimum activity levels + **Constraints:** $\eqref{eq:onoff_transitions}$, $\eqref{eq:onoff_switch_count}$ with max 20 startups, $\eqref{eq:onoff_min_on}$ with 12h minimum, $\eqref{eq:onoff_switch_effects}$, $\eqref{eq:onoff_running_effects}$ ---- + --- -## Cycling Limits + ## HVAC with Operating Hour Limits -Maximum number of startups across the planning horizon: + ```python + from flixopt import Flow, OnOffParameters -$$\label{eq:onoff_max_switches} -\sum_{t} s^\text{on}(t) \leq n_\text{max} -$$ + chiller = Flow( + label='cooling_output', + bus='chilled_water', + size=500, # 500 kW cooling + on_off_parameters=OnOffParameters( + on_hours_min=2000, # Minimum 2000h/year utilization + on_hours_max=5000, # Maximum 5000h/year (maintenance limit) + consecutive_on_hours_max=18, # Max 18h continuous operation + ), + ) + ``` -With: -- $n_\text{max}$ being the maximum allowed number of startups + **Variables:** `on[t]`, `on_hours_total`, `consecutive_on_hours` -**Use cases:** -- Preventing excessive equipment wear -- Grid stability requirements -- Operational complexity limits -- Maintenance budget constraints - ---- - -## Integration with Flow Bounds - -OnOffParameters modify flow rate bounds by coupling them to the on/off state. - -**Without OnOffParameters** (continuous operation): -$$ -P \cdot \text{rel}_\text{lower} \leq p(t) \leq P \cdot \text{rel}_\text{upper} -$$ - -**With OnOffParameters** (binary operation): -$$ -s(t) \cdot P \cdot \max(\varepsilon, \text{rel}_\text{lower}) \leq p(t) \leq s(t) \cdot P \cdot \text{rel}_\text{upper} -$$ - -Using the **bounds with state** pattern from [Bounds and States](../modeling-patterns/bounds-and-states.md#bounds-with-state). - -**Behavior:** -- When $s(t) = 0$: flow is forced to zero -- When $s(t) = 1$: flow follows normal bounds - ---- - -## Complete Formulation Summary - -For equipment with OnOffParameters, the complete constraint system includes: - -1. **State variable:** $s(t) \in \{0, 1\}$ -2. **Switch tracking:** $s^\text{on}(t) - s^\text{off}(t) = s(t) - s(t-1)$ -3. **Switch exclusivity:** $s^\text{on}(t) + s^\text{off}(t) \leq 1$ -4. **Duration tracking:** - - On-duration: $d^\text{on}(t)$ following duration tracking pattern - - Off-duration: $d^\text{off}(t)$ following duration tracking pattern -5. **Minimum on-time:** $d^\text{on}(t) \geq (s(t-1) - s(t)) \cdot h^\text{on}_\text{min}$ -6. **Maximum on-time:** $d^\text{on}(t) \leq h^\text{on}_\text{max}$ -7. **Minimum off-time:** $d^\text{off}(t) \geq (s(t) - s(t-1)) \cdot h^\text{off}_\text{min}$ -8. **Maximum off-time:** $d^\text{off}(t) \leq h^\text{off}_\text{max}$ -9. **Total hours:** $h_\text{min} \leq \sum_t s(t) \cdot \Delta t \leq h_\text{max}$ -10. **Cycling limit:** $\sum_t s^\text{on}(t) \leq n_\text{max}$ -11. **Flow bounds:** $s(t) \cdot P \cdot \text{rel}_\text{lower} \leq p(t) \leq s(t) \cdot P \cdot \text{rel}_\text{upper}$ + **Constraints:** $\eqref{eq:onoff_total_hours}$ with 2000-5000h bounds, $\eqref{eq:onoff_max_on}$ with 18h maximum --- ## Implementation -**Python Class:** [`OnOffParameters`][flixopt.interface.OnOffParameters] - -**Key Parameters:** -- `effects_per_switch_on`: Costs per startup event -- `effects_per_running_hour`: Costs per hour of operation -- `on_hours_min`, `on_hours_max`: Total runtime bounds -- `consecutive_on_hours_min`, `consecutive_on_hours_max`: Consecutive runtime bounds -- `consecutive_off_hours_min`, `consecutive_off_hours_max`: Consecutive shutdown bounds -- `switch_on_max`: Maximum number of startups -- `force_switch_on`: Create switch variables even without limits (for tracking) - -See the [`OnOffParameters`][flixopt.interface.OnOffParameters] API documentation for complete parameter list and usage examples. - -**Mathematical Patterns Used:** -- [State Transitions](../modeling-patterns/state-transitions.md#binary-state-transitions) - Switch tracking -- [Duration Tracking](../modeling-patterns/duration-tracking.md) - Consecutive time constraints -- [Bounds with State](../modeling-patterns/bounds-and-states.md#bounds-with-state) - Flow control - -**Used in:** -- [`Flow`][flixopt.elements.Flow] - On/off operation for flows -- All components supporting discrete operational states - ---- - -## Examples - -### Power Plant with Startup Costs -```python -power_plant = OnOffParameters( - effects_per_switch_on={'startup_cost': 25000}, # €25k per startup - effects_per_running_hour={'fixed_om': 125}, # €125/hour while running - consecutive_on_hours_min=8, # Minimum 8-hour run - consecutive_off_hours_min=4, # 4-hour cooling period - on_hours_max=6000, # Annual limit -) -``` - -### Batch Process with Cycling Limits -```python -batch_reactor = OnOffParameters( - effects_per_switch_on={'setup_cost': 1500}, - consecutive_on_hours_min=12, # 12-hour minimum batch - consecutive_on_hours_max=24, # 24-hour maximum batch - consecutive_off_hours_min=6, # Cleaning time - switch_on_max=200, # Max 200 batches -) -``` - -### HVAC with Cycle Prevention -```python -hvac = OnOffParameters( - effects_per_switch_on={'compressor_wear': 0.5}, - consecutive_on_hours_min=1, # Prevent short cycling - consecutive_off_hours_min=0.5, # 30-min minimum off - switch_on_max=2000, # Limit compressor starts -) -``` - -### Backup Generator with Testing Requirements -```python -backup_gen = OnOffParameters( - effects_per_switch_on={'fuel_priming': 50}, # L diesel - consecutive_on_hours_min=0.5, # 30-min test duration - consecutive_off_hours_max=720, # Test every 30 days - on_hours_min=26, # Weekly testing requirement -) -``` - ---- +- **Feature Class:** [`OnOffParameters`][flixopt.interface.OnOffParameters] +- **Model Class:** [`OnOffModel`][flixopt.features.OnOffModel] +- **Used by:** [`Flow`](../elements/Flow.md) · [`LinearConverter`](../elements/LinearConverter.md) -## Notes +## See Also -**Time Series Boundary:** The final time period constraints for consecutive_on_hours_min/max and consecutive_off_hours_min/max are not enforced at the end of the planning horizon. This allows optimization to end with ongoing campaigns that may be shorter/longer than specified, as they extend beyond the modeled period. +- **Elements:** [Flow](../elements/Flow.md) · [LinearConverter](../elements/LinearConverter.md) +- **Features:** [InvestParameters](InvestParameters.md) +- **Patterns:** [Bounds and States](../modeling-patterns/bounds-and-states.md) +- **System-Level:** [Effects, Penalty & Objective](../effects-penalty-objective.md) diff --git a/flixopt/features.py b/flixopt/features.py index 8c4bf7c70..205466456 100644 --- a/flixopt/features.py +++ b/flixopt/features.py @@ -22,16 +22,19 @@ class InvestmentModel(Submodel): - """ - This feature model is used to model the investment of a variable. - It applies the corresponding bounds to the variable and the on/off state of the variable. + """Mathematical model implementation for investment decisions. + + Creates optimization variables and constraints for investment sizing decisions, + supporting both binary and continuous sizing with comprehensive effect modeling. + + Mathematical Formulation: + See Args: model: The optimization model instance label_of_element: The label of the parent (Element). Used to construct the full label of the model. parameters: The parameters of the feature model. label_of_model: The label of the model. This is needed to construct the full label of the model. - """ parameters: InvestParameters @@ -145,7 +148,14 @@ def invested(self) -> linopy.Variable | None: class OnOffModel(Submodel): - """OnOff model using factory patterns""" + """Mathematical model implementation for on/off operational constraints. + + Creates optimization variables and constraints for binary state modeling, + state transitions, duration tracking, and operational effects. + + Mathematical Formulation: + See + """ def __init__( self, diff --git a/flixopt/interface.py b/flixopt/interface.py index 55ac03b6b..04e837a6b 100644 --- a/flixopt/interface.py +++ b/flixopt/interface.py @@ -706,8 +706,7 @@ class InvestParameters(Interface): - **Divestment Effects**: Penalties for not investing (demolition, opportunity costs) Mathematical Formulation: - See the complete mathematical model in the documentation: - [InvestParameters](../user-guide/mathematical-notation/features/InvestParameters.md) + See Args: fixed_size: Creates binary decision at this exact size. None allows continuous sizing. @@ -1137,8 +1136,7 @@ class OnOffParameters(Interface): - **Process Equipment**: Compressors, pumps with operational constraints Mathematical Formulation: - See the complete mathematical model in the documentation: - [OnOffParameters](../user-guide/mathematical-notation/features/OnOffParameters.md) + See Args: effects_per_switch_on: Costs or impacts incurred for each transition from From 90a44da07edb9013c3c3ff2570c1934d595580a2 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:51:33 +0100 Subject: [PATCH 14/79] Update Piecewise.md --- .../features/Piecewise.md | 247 +++++++++++++++--- flixopt/features.py | 9 + flixopt/interface.py | 10 +- 3 files changed, 233 insertions(+), 33 deletions(-) diff --git a/docs/user-guide/mathematical-notation/features/Piecewise.md b/docs/user-guide/mathematical-notation/features/Piecewise.md index 688ac8cea..ada217c09 100644 --- a/docs/user-guide/mathematical-notation/features/Piecewise.md +++ b/docs/user-guide/mathematical-notation/features/Piecewise.md @@ -1,49 +1,234 @@ # Piecewise -A Piecewise is a collection of [`Pieces`][flixopt.interface.Piece], which each define a valid range for a variable $v$ +Piecewise enables modeling of non-linear relationships through piecewise linear approximations while maintaining problem linearity, consisting of a collection of Pieces that define valid ranges for variables. -$$ \label{eq:active_piece} - \beta_\text{k} = \lambda_\text{0, k} + \lambda_\text{1, k} -$$ +=== "Variables" -$$ \label{eq:piece} - v_\text{k} = \lambda_\text{0, k} * \text{v}_{\text{start,k}} + \lambda_\text{1,k} * \text{v}_{\text{end,k}} -$$ + | Symbol | Python Name | Description | Domain | Created When | + |--------|-------------|-------------|--------|--------------| + | $\beta_k$ | `beta` (per Piece) | Binary variable indicating if piece $k$ is active | $\{0,1\}$ | Always (for each Piece) | + | $\beta_\text{zero}$ | `zero_point` | Binary variable allowing all variables to be zero | $\{0,1\}$ | `zero_point=True` specified | + | $\lambda_{0,k}$ | `lambda0` (per Piece) | Fraction of start point $\text{v}_{\text{start},k}$ that is active | $[0,1]$ | Always (for each Piece) | + | $\lambda_{1,k}$ | `lambda1` (per Piece) | Fraction of end point $\text{v}_{\text{end},k}$ that is active | $[0,1]$ | Always (for each Piece) | -$$ \label{eq:piecewise_in_pieces} -\sum_{k=1}^k \beta_{k} = 1 -$$ +=== "Constraints" -With: + **Active piece definition** (always active for each piece): -- $v$: The variable to be defined by the Piecewise -- $\text{v}_{\text{start,k}}$: the start point of the piece for variable $v$ -- $\text{v}_{\text{end,k}}$: the end point of the piece for variable $v$ -- $\beta_\text{k} \in \{0, 1\}$: defining wether the Piece $k$ is active -- $\lambda_\text{0,k} \in [0, 1]$: A variable defining the fraction of $\text{v}_{\text{start,k}}$ that is active -- $\lambda_\text{1,k} \in [0, 1]$: A variable defining the fraction of $\text{v}_{\text{end,k}}$ that is active + $$\label{eq:active_piece} + \beta_k = \lambda_{0,k} + \lambda_{1,k} + $$ -Which can also be described as $v \in 0 \cup [\text{v}_\text{start}, \text{v}_\text{end}]$. + Binary variable $\beta_k$ is 1 if piece $k$ is active (either $\lambda_{0,k}$ or $\lambda_{1,k}$ is non-zero), 0 otherwise. -Instead of \eqref{eq:piecewise_in_pieces}, the following constraint is used to also allow all variables to be zero: + --- -$$ \label{eq:piecewise_in_pieces_zero} -\sum_{k=1}^k \beta_{k} = \beta_\text{zero} -$$ + **Variable definition through piece** (always active for each variable): -With: + $$\label{eq:piece} + v_k = \lambda_{0,k} \cdot \text{v}_{\text{start},k} + \lambda_{1,k} \cdot \text{v}_{\text{end},k} + $$ -- $\beta_\text{zero} \in \{0, 1\}$. + The variable value is the weighted sum of the piece's start and end points. -Which can also be described as $v \in \{0\} \cup [\text{v}_{\text{start_k}}, \text{v}_{\text{end_k}}]$ + --- + **Single active piece** (when `zero_point=False` or not specified): -## Combining multiple Piecewises + $$\label{eq:piecewise_in_pieces} + \sum_{k=1}^K \beta_k = 1 + $$ -Piecewise allows representing non-linear relationships. -This is a powerful technique in linear optimization to model non-linear behaviors while maintaining the problem's linearity. + Exactly one piece must be active. This ensures $v \in [\text{v}_{\text{start},k}, \text{v}_{\text{end},k}]$ for some $k$. -Therefore, each Piecewise must have the same number of Pieces $k$. + --- -The variables described in [Piecewise](#piecewise) are created for each Piece, but nor for each Piecewise. -Rather, \eqref{eq:piece} is the only constraint that is created for each Piecewise, using the start and endpoints $\text{v}_{\text{start,k}}$ and $\text{v}_{\text{end,k}}$ of each Piece for the corresponding variable $v$ + **Optional zero with single active piece** (when `zero_point=True`): + + $$\label{eq:piecewise_in_pieces_zero} + \sum_{k=1}^K \beta_k = \beta_\text{zero} + $$ + + Either one piece is active ($\beta_\text{zero} = 1$) or all are inactive ($\beta_\text{zero} = 0$, forcing all $\lambda$ to zero). This allows $v \in \{0\} \cup [\text{v}_{\text{start},k}, \text{v}_{\text{end},k}]$. + + --- + + **Combined piecewise relationship** (when multiple variables share pieces): + + $$\label{eq:piecewise_combined} + \begin{align} + v_1 &= \sum_{k=1}^K (\lambda_{0,k} \cdot \text{v}_{1,\text{start},k} + \lambda_{1,k} \cdot \text{v}_{1,\text{end},k}) \\ + v_2 &= \sum_{k=1}^K (\lambda_{0,k} \cdot \text{v}_{2,\text{start},k} + \lambda_{1,k} \cdot \text{v}_{2,\text{end},k}) + \end{align} + $$ + + Multiple variables share the same $\lambda$ and $\beta$ variables, creating coupled non-linear relationships. + + **Mathematical Patterns:** [SOS Type 2 Constraints](../modeling-patterns/bounds-and-states.md), [Piecewise Linear Approximation](../modeling-patterns/bounds-and-states.md) + +=== "Parameters" + + | Symbol | Python Parameter | Description | Default | + |--------|------------------|-------------|---------| + | $K$ | - | Number of pieces | From `pieces` list length | + | $\text{v}_{\text{end},k}$ | - | End point of piece $k$ | From `Piece.end` | + | $\text{v}_{\text{start},k}$ | - | Start point of piece $k$ | From `Piece.start` | + | - | `pieces` | List of Piece objects defining the linear segments | Required | + | - | `zero_point` | Allow all variables to be zero | False | + +=== "Use Cases" + + ## Continuous Efficiency Curve (Touching Pieces) + + ```python + from flixopt import Piecewise, Piece + + efficiency_curve = Piecewise([ + Piece((0, 0), (25, 25)), # Low load: 0-25 MW + Piece((25, 25), (75, 75)), # Medium load: 25-75 MW (touches at 25) + Piece((75, 75), (100, 100)), # High load: 75-100 MW (touches at 75) + ]) + ``` + + **Variables:** $\beta_1, \beta_2, \beta_3$ (piece indicators), $\lambda_{0,1}, \lambda_{1,1}, ..., \lambda_{0,3}, \lambda_{1,3}$ (lambda variables) + + **Constraints:** $\eqref{eq:active_piece}$ for each piece, $\eqref{eq:piece}$ defining $v$, $\eqref{eq:piecewise_in_pieces}$ ensuring exactly one piece active + + **Behavior:** Creates smooth continuous function without gaps, allowing operation anywhere from 0-100 MW. + + --- + + ## Forbidden Operating Range (Gap Between Pieces) + + ```python + from flixopt import Piecewise, Piece + + turbine_operation = Piecewise([ + Piece((0, 0), (0, 0)), # Off state (point) + Piece((40, 40), (100, 100)), # Operating range (gap: 0-40 forbidden) + ]) + ``` + + **Variables:** $\beta_1, \beta_2$, $\lambda_{0,1}, \lambda_{1,1}, \lambda_{0,2}, \lambda_{1,2}$ + + **Constraints:** $\eqref{eq:active_piece}$, $\eqref{eq:piece}$, $\eqref{eq:piecewise_in_pieces}$ + + **Behavior:** Equipment must be either completely off (0) or operating between 40-100 MW. The range 0-40 MW is forbidden due to the gap. + + --- + + ## Variable COP Heat Pump (Two Coupled Variables) + + ```python + from flixopt import LinearConverter, Flow, Piecewise, Piece, PiecewiseConversion + + # COP varies: 2.5 at low load to 4.0 at high load + electricity_to_heat = Piecewise([ + Piece((0, 0), (50, 125)), # 0-50 kW elec → 0-125 kW heat (COP 2.5) + Piece((50, 125), (100, 350)), # 50-100 kW elec → 125-350 kW heat (COP 3.5-4.5) + ]) + + heat_pump = LinearConverter( + label='heat_pump', + inputs=[Flow(label='electricity_in', bus='electricity', size=100)], + outputs=[Flow(label='heat_out', bus='heating', size=350)], + piecewise_conversion=PiecewiseConversion( + origin_flow='electricity_in', + piecewise_shares={'heat_out': electricity_to_heat}, + ), + ) + ``` + + **Variables:** Shared $\beta_1, \beta_2$, $\lambda_{0,1}, \lambda_{1,1}, \lambda_{0,2}, \lambda_{1,2}$ for both electricity and heat + + **Constraints:** $\eqref{eq:piecewise_combined}$ coupling electricity input to heat output with variable COP + + **Behavior:** Electricity input and heat output are coupled through shared lambda variables, modeling load-dependent COP. + + --- + + ## Optional Operation with Zero Point + + ```python + from flixopt import Piecewise, Piece + + optional_operation = Piecewise( + pieces=[ + Piece((10, 10), (50, 50)), # Low operating range + Piece((50, 50), (100, 100)), # High operating range + ], + zero_point=True, # Allow complete shutdown + ) + ``` + + **Variables:** $\beta_1, \beta_2$, $\beta_\text{zero}$, $\lambda_{0,1}, \lambda_{1,1}, \lambda_{0,2}, \lambda_{1,2}$ + + **Constraints:** $\eqref{eq:active_piece}$, $\eqref{eq:piece}$, $\eqref{eq:piecewise_in_pieces_zero}$ with zero point + + **Behavior:** Equipment can be completely off ($v=0$, $\beta_\text{zero}=0$) or operating in 10-100 range ($\beta_\text{zero}=1$, one piece active). + + --- + + ## Economies of Scale (Investment Costs) + + ```python + from flixopt import InvestParameters, Piecewise, Piece, PiecewiseEffects + + # Cost per kWh decreases with scale + battery_cost = InvestParameters( + minimum_size=10, + maximum_size=1000, + piecewise_effects_of_investment=PiecewiseEffects( + piecewise_origin=Piecewise([ + Piece((0, 0), (100, 100)), # Small + Piece((100, 100), (500, 500)), # Medium + Piece((500, 500), (1000, 1000)), # Large + ]), + piecewise_shares={ + 'cost': Piecewise([ + Piece((0, 0), (100, 80000)), # €800/kWh + Piece((100, 80000), (500, 350000)), # €750-600/kWh + Piece((500, 350000), (1000, 850000)), # €600-500/kWh (bulk discount) + ]) + }, + ), + ) + ``` + + **Variables:** Shared $\beta_k$, $\lambda_{0,k}$, $\lambda_{1,k}$ for size and cost + + **Constraints:** $\eqref{eq:piecewise_combined}$ coupling size to cost with decreasing unit cost + + **Behavior:** Investment size and total cost are coupled through piecewise relationship modeling economies of scale. + +--- + +## Piece Relationship Patterns + +### Touching Pieces (Continuous Function) +Pieces that share boundary points create smooth, continuous functions without gaps or overlaps. +**Use case:** Efficiency curves, performance maps + +### Gaps Between Pieces (Forbidden Regions) +Non-contiguous pieces with gaps represent forbidden operating regions. +**Use case:** Minimum load requirements, safety zones, equipment limitations + +### Overlapping Pieces (Flexible Operation) +Pieces with overlapping domains provide optimization flexibility, allowing the solver to choose which segment to operate in. +**Use case:** Multiple operating modes, flexible efficiency options + +--- + +## Implementation + +- **Feature Class:** [`Piecewise`][flixopt.interface.Piecewise] +- **Model Class:** [`PiecewiseModel`][flixopt.features.PiecewiseModel] +- **Helper Class:** [`Piece`][flixopt.interface.Piece] +- **Used by:** [`LinearConverter`](../elements/LinearConverter.md) (via `PiecewiseConversion`) · [`InvestParameters`](InvestParameters.md) (via `PiecewiseEffects`) + +## See Also + +- **Elements:** [LinearConverter](../elements/LinearConverter.md) +- **Features:** [InvestParameters](InvestParameters.md) +- **Patterns:** [Modeling Patterns](../modeling-patterns/index.md) +- **System-Level:** [Effects, Penalty & Objective](../effects-penalty-objective.md) diff --git a/flixopt/features.py b/flixopt/features.py index 205466456..27ba84c1d 100644 --- a/flixopt/features.py +++ b/flixopt/features.py @@ -378,6 +378,15 @@ def _do_modeling(self): class PiecewiseModel(Submodel): + """Mathematical model implementation for piecewise linear approximations. + + Creates optimization variables and constraints for piecewise linear relationships, + including lambda variables, piece activation binaries, and coupling constraints. + + Mathematical Formulation: + See + """ + def __init__( self, model: FlowSystemModel, diff --git a/flixopt/interface.py b/flixopt/interface.py index 04e837a6b..db94dba76 100644 --- a/flixopt/interface.py +++ b/flixopt/interface.py @@ -82,8 +82,14 @@ def transform_data(self, name_prefix: str = '') -> None: @register_class_for_io class Piecewise(Interface): - """ - Define a Piecewise, consisting of a list of Pieces. + """Define piecewise linear approximations for modeling non-linear relationships. + + Enables modeling of non-linear relationships through piecewise linear segments + while maintaining problem linearity. Consists of a collection of Pieces that + define valid ranges for variables. + + Mathematical Formulation: + See Args: pieces: list of Piece objects defining the linear segments. The arrangement From 2af6bbce6731b011948ac21dae4182c5550c78cc Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:13:19 +0100 Subject: [PATCH 15/79] Compact effects-penalty-objective.md --- .../effects-penalty-objective.md | 448 +++++++++++------- flixopt/effects.py | 25 +- 2 files changed, 294 insertions(+), 179 deletions(-) diff --git a/docs/user-guide/mathematical-notation/effects-penalty-objective.md b/docs/user-guide/mathematical-notation/effects-penalty-objective.md index 0759ef5ee..12c240d04 100644 --- a/docs/user-guide/mathematical-notation/effects-penalty-objective.md +++ b/docs/user-guide/mathematical-notation/effects-penalty-objective.md @@ -1,199 +1,331 @@ # Effects, Penalty & Objective -## Effects +Effects quantify system-wide impacts like costs, emissions, or resource consumption, aggregating contributions from Elements across the FlowSystem. One Effect serves as the optimization objective, while others can be tracked and constrained. -[`Effects`][flixopt.effects.Effect] are used to quantify system-wide impacts like costs, emissions, or resource consumption. These arise from **shares** contributed by **Elements** such as [Flows](elements/Flow.md), [Storage](elements/Storage.md), and other components. +=== "Variables" -**Example:** + | Symbol | Python Name | Description | Domain | Created When | + |--------|-------------|-------------|--------|--------------| + | $E_{e,\text{per}}$ | `(periodic)\|total` | Total periodic effect (time-independent) | $\mathbb{R}$ | Always (per Effect) | + | $E_{e,\text{temp}}(\text{t}_i)$ | `(temporal)\|total` | Temporal effect at time $\text{t}_i$ | $\mathbb{R}$ | Always (per Effect) | + | $E_e$ | `total` | Total effect (periodic + temporal combined) | $\mathbb{R}$ | Always (per Effect) | + | $\Phi$ | `penalty` | Penalty term for constraint violations | $\mathbb{R}_+$ | Always (system-wide) | + | - | `total_over_periods` | Weighted sum of total effect across all periods | $\mathbb{R}$ | `minimum_over_periods` or `maximum_over_periods` specified | -[`Flows`][flixopt.elements.Flow] have an attribute `effects_per_flow_hour` that defines the effect contribution per flow-hour: -- Costs (€/kWh) -- Emissions (kg CO₂/kWh) -- Primary energy consumption (kWh_primary/kWh) +=== "Constraints" -Effects are categorized into two domains: + **Element shares to periodic effects**: -1. **Temporal effects** - Time-dependent contributions (e.g., operational costs, hourly emissions) -2. **Periodic effects** - Time-independent contributions (e.g., investment costs, fixed annual fees) + $$\label{eq:Share_periodic} + s_{l \rightarrow e, \text{per}} = \sum_{v \in \mathcal{V}_{l, \text{per}}} v \cdot \text{a}_{v \rightarrow e} + $$ -### Multi-Dimensional Effects + Where $\mathcal{V}_{l, \text{per}}$ are periodic (investment-related) variables of element $l$, and $\text{a}_{v \rightarrow e}$ is the effect factor (e.g., €/kW). -**The formulations below are written with time index $\text{t}_i$ only, but automatically expand when periods and/or scenarios are present.** + --- -When the FlowSystem has additional dimensions (see [Dimensions](dimensions.md)): + **Element shares to temporal effects**: -- **Temporal effects** are indexed by all present dimensions: $E_{e,\text{temp}}(\text{t}_i, y, s)$ -- **Periodic effects** are indexed by period only (scenario-independent within a period): $E_{e,\text{per}}(y)$ -- Effects are aggregated with dimension weights in the objective function + $$\label{eq:Share_temporal} + s_{l \rightarrow e, \text{temp}}(\text{t}_i) = \sum_{v \in \mathcal{V}_{l,\text{temp}}} v(\text{t}_i) \cdot \text{a}_{v \rightarrow e}(\text{t}_i) + $$ -For complete details on how dimensions affect effects and the objective, see [Dimensions](dimensions.md). + Where $\mathcal{V}_{l, \text{temp}}$ are temporal (operational) variables, and $\text{a}_{v \rightarrow e}(\text{t}_i)$ is the time-varying effect factor (e.g., €/kWh). ---- + --- -## Effect Formulation + **Total periodic effect**: -### Shares from Elements + $$\label{eq:Effect_periodic} + E_{e, \text{per}} = + \sum_{l \in \mathcal{L}} s_{l \rightarrow e,\text{per}} + + \sum_{x \in \mathcal{E}\backslash e} E_{x, \text{per}} \cdot \text{r}_{x \rightarrow e,\text{per}} + $$ -Each element $l$ contributes shares to effect $e$ in both temporal and periodic domains: + Aggregates element periodic shares plus cross-effect contributions (e.g., CO₂ → costs via carbon pricing). -**Periodic shares** (time-independent): -$$ \label{eq:Share_periodic} -s_{l \rightarrow e, \text{per}} = \sum_{v \in \mathcal{V}_{l, \text{per}}} v \cdot \text{a}_{v \rightarrow e} -$$ + --- -**Temporal shares** (time-dependent): -$$ \label{eq:Share_temporal} -s_{l \rightarrow e, \text{temp}}(\text{t}_i) = \sum_{v \in \mathcal{V}_{l,\text{temp}}} v(\text{t}_i) \cdot \text{a}_{v \rightarrow e}(\text{t}_i) -$$ + **Total temporal effect**: -Where: + $$\label{eq:Effect_temporal} + E_{e, \text{temp}}(\text{t}_{i}) = + \sum_{l \in \mathcal{L}} s_{l \rightarrow e, \text{temp}}(\text{t}_i) + + \sum_{x \in \mathcal{E}\backslash e} E_{x, \text{temp}}(\text{t}_i) \cdot \text{r}_{x \rightarrow {e},\text{temp}}(\text{t}_i) + $$ -- $\text{t}_i$ is the time step -- $\mathcal{V}_l$ is the set of all optimization variables of element $l$ -- $\mathcal{V}_{l, \text{per}}$ is the subset of periodic (investment-related) variables -- $\mathcal{V}_{l, \text{temp}}$ is the subset of temporal (operational) variables -- $v$ is an optimization variable -- $v(\text{t}_i)$ is the variable value at timestep $\text{t}_i$ -- $\text{a}_{v \rightarrow e}$ is the effect factor (e.g., €/kW for investment, €/kWh for operation) -- $s_{l \rightarrow e, \text{per}}$ is the periodic share of element $l$ to effect $e$ -- $s_{l \rightarrow e, \text{temp}}(\text{t}_i)$ is the temporal share of element $l$ to effect $e$ + Aggregates element temporal shares plus cross-effect contributions at each timestep. -**Examples:** -- **Periodic share**: Investment cost = $\text{size} \cdot \text{specific\_cost}$ (€/kW) -- **Temporal share**: Operational cost = $\text{flow\_rate}(\text{t}_i) \cdot \text{price}(\text{t}_i)$ (€/kWh) + --- ---- + **Total temporal effect (sum over time)**: -### Cross-Effect Contributions + $$\label{eq:Effect_temporal_total} + E_{e,\text{temp},\text{tot}} = \sum_{i=1}^n E_{e,\text{temp}}(\text{t}_{i}) + $$ -Effects can contribute shares to other effects, enabling relationships like carbon pricing or resource accounting. + --- -An effect $x$ can contribute to another effect $e \in \mathcal{E}\backslash x$ via conversion factors: + **Total combined effect**: -**Example:** CO₂ emissions (kg) → Monetary costs (€) -- Effect $x$: "CO₂ emissions" (unit: kg) -- Effect $e$: "costs" (unit: €) -- Factor $\text{r}_{x \rightarrow e}$: CO₂ price (€/kg) + $$\label{eq:Effect_Total} + E_{e} = E_{e,\text{per}} + E_{e,\text{temp},\text{tot}} + $$ -**Note:** Circular references must be avoided. + --- -### Total Effect Calculation + **Penalty term**: -**Periodic effects** aggregate element shares and cross-effect contributions: + $$\label{eq:Penalty} + \Phi = \sum_{l \in \mathcal{L}} \left( s_{l \rightarrow \Phi} +\sum_{\text{t}_i \in \mathcal{T}} s_{l \rightarrow \Phi}(\text{t}_{i}) \right) + $$ -$$ \label{eq:Effect_periodic} -E_{e, \text{per}} = -\sum_{l \in \mathcal{L}} s_{l \rightarrow e,\text{per}} + -\sum_{x \in \mathcal{E}\backslash e} E_{x, \text{per}} \cdot \text{r}_{x \rightarrow e,\text{per}} -$$ + Accumulates penalty shares from elements (primarily from [Bus](elements/Bus.md) via `excess_penalty_per_flow_hour`). -**Temporal effects** at each timestep: + --- -$$ \label{eq:Effect_temporal} -E_{e, \text{temp}}(\text{t}_{i}) = -\sum_{l \in \mathcal{L}} s_{l \rightarrow e, \text{temp}}(\text{t}_i) + -\sum_{x \in \mathcal{E}\backslash e} E_{x, \text{temp}}(\text{t}_i) \cdot \text{r}_{x \rightarrow {e},\text{temp}}(\text{t}_i) -$$ + **Objective function**: -**Total temporal effects** (sum over all timesteps): + $$\label{eq:Objective} + \min \left( E_{\Omega} + \Phi \right) + $$ -$$\label{eq:Effect_temporal_total} -E_{e,\text{temp},\text{tot}} = \sum_{i=1}^n E_{e,\text{temp}}(\text{t}_{i}) -$$ + Where $E_{\Omega}$ is the chosen objective effect (designated with `is_objective=True`). -**Total effect** (combining both domains): + --- -$$ \label{eq:Effect_Total} -E_{e} = E_{e,\text{per}} + E_{e,\text{temp},\text{tot}} -$$ + **Effect bounds - Temporal total** (when `minimum_temporal` or `maximum_temporal` specified): -Where: + $$\label{eq:Bounds_Temporal_Total} + E_{e,\text{temp}}^\text{L} \leq E_{e,\text{temp},\text{tot}} \leq E_{e,\text{temp}}^\text{U} + $$ -- $\mathcal{L}$ is the set of all elements in the FlowSystem -- $\mathcal{E}$ is the set of all effects -- $\text{r}_{x \rightarrow e, \text{per}}$ is the periodic conversion factor from effect $x$ to effect $e$ -- $\text{r}_{x \rightarrow e, \text{temp}}(\text{t}_i)$ is the temporal conversion factor + --- ---- + **Effect bounds - Temporal per timestep** (when `minimum_per_hour` or `maximum_per_hour` specified): -### Constraining Effects + $$\label{eq:Bounds_Timestep} + E_{e,\text{temp}}^\text{L}(\text{t}_i) \leq E_{e,\text{temp}}(\text{t}_i) \leq E_{e,\text{temp}}^\text{U}(\text{t}_i) + $$ -Effects can be bounded to enforce limits on costs, emissions, or other impacts: + --- -**Total bounds** (apply to $E_{e,\text{per}}$, $E_{e,\text{temp},\text{tot}}$, or $E_e$): + **Effect bounds - Periodic** (when `minimum_periodic` or `maximum_periodic` specified): -$$ \label{eq:Bounds_Total} -E^\text{L} \leq E \leq E^\text{U} -$$ + $$\label{eq:Bounds_Periodic} + E_{e,\text{per}}^\text{L} \leq E_{e,\text{per}} \leq E_{e,\text{per}}^\text{U} + $$ -**Temporal bounds per timestep:** + --- -$$ \label{eq:Bounds_Timestep} -E_{e,\text{temp}}^\text{L}(\text{t}_i) \leq E_{e,\text{temp}}(\text{t}_i) \leq E_{e,\text{temp}}^\text{U}(\text{t}_i) -$$ + **Effect bounds - Total combined** (when `minimum_total` or `maximum_total` specified): -**Implementation:** See [`Effect`][flixopt.effects.Effect] parameters: -- `minimum_temporal`, `maximum_temporal` - Total temporal bounds -- `minimum_per_hour`, `maximum_per_hour` - Hourly temporal bounds -- `minimum_periodic`, `maximum_periodic` - Periodic bounds -- `minimum_total`, `maximum_total` - Combined total bounds + $$\label{eq:Bounds_Total} + E_e^\text{L} \leq E_e \leq E_e^\text{U} + $$ ---- + --- -## Penalty + **Effect bounds - Across all periods** (when `minimum_over_periods` or `maximum_over_periods` specified): -In addition to user-defined [Effects](#effects), every FlixOpt model includes a **Penalty** term $\Phi$ to: -- Prevent infeasible problems -- Simplify troubleshooting by allowing constraint violations with high cost + $$\label{eq:Bounds_Over_Periods} + E_e^\text{L,periods} \leq \sum_{y \in \mathcal{Y}} w_y \cdot E_e(y) \leq E_e^\text{U,periods} + $$ -Penalty shares originate from elements, similar to effect shares: + **Mathematical Patterns:** Effect Aggregation, Weighted Objectives -$$ \label{eq:Penalty} -\Phi = \sum_{l \in \mathcal{L}} \left( s_{l \rightarrow \Phi} +\sum_{\text{t}_i \in \mathcal{T}} s_{l \rightarrow \Phi}(\text{t}_{i}) \right) -$$ +=== "Parameters" -Where: + | Symbol | Python Parameter | Description | Default | + |--------|------------------|-------------|---------| + | $\mathcal{E}$ | - | Set of all effects in the system | From registered Effects | + | $\mathcal{L}$ | - | Set of all elements in the FlowSystem | From all Elements | + | $\mathcal{T}$ | - | Set of all timesteps | From system time index | + | $\mathcal{V}_{l, \text{per}}$ | - | Subset of periodic (investment) variables for element $l$ | Element-dependent | + | $\mathcal{V}_{l, \text{temp}}$ | - | Subset of temporal (operational) variables for element $l$ | Element-dependent | + | $\mathcal{Y}$ | - | Set of periods | From system dimensions | + | $\text{a}_{v \rightarrow e}$ | - | Effect factor (e.g., €/kW for investment) | Element-specific | + | $\text{a}_{v \rightarrow e}(\text{t}_i)$ | - | Time-varying effect factor (e.g., €/kWh) | Element-specific | + | $\text{r}_{x \rightarrow e, \text{per}}$ | `share_from_periodic` | Periodic conversion factor from effect $x$ to $e$ | None | + | $\text{r}_{x \rightarrow e, \text{temp}}(\text{t}_i)$ | `share_from_temporal` | Temporal conversion factor from effect $x$ to $e$ | None | + | $w_y$ | `weights` | Period weights (e.g., discount factors) | From FlowSystem or Effect-specific | + | - | `description` | Descriptive name for the effect | None | + | - | `is_objective` | If True, this effect is the optimization objective | False | + | - | `is_standard` | If True, allows direct value input without effect dictionaries | False | + | - | `maximum_over_periods` | Maximum weighted sum across all periods | None | + | - | `maximum_per_hour` | Maximum contribution per timestep | None | + | - | `maximum_periodic` | Maximum periodic contribution | None | + | - | `maximum_temporal` | Maximum total temporal contribution | None | + | - | `maximum_total` | Maximum total effect (periodic + temporal) | None | + | - | `minimum_over_periods` | Minimum weighted sum across all periods | None | + | - | `minimum_per_hour` | Minimum contribution per timestep | None | + | - | `minimum_periodic` | Minimum periodic contribution | None | + | - | `minimum_temporal` | Minimum total temporal contribution | None | + | - | `minimum_total` | Minimum total effect (periodic + temporal) | None | + | - | `unit` | Unit of the effect (informative only) | None | -- $\mathcal{L}$ is the set of all elements -- $\mathcal{T}$ is the set of all timesteps -- $s_{l \rightarrow \Phi}$ is the penalty share from element $l$ +=== "Use Cases" -**Current usage:** Penalties primarily occur in [Buses](elements/Bus.md) via the `excess_penalty_per_flow_hour` parameter, which allows nodal imbalances at a high cost. + ## Basic Cost Objective ---- + ```python + from flixopt import Effect, Flow -## Objective Function + # Define cost effect + cost = Effect( + label='system_costs', + unit='€', + is_objective=True, # This is what we minimize + ) -The optimization objective minimizes the chosen effect plus any penalties: + # Elements contribute via effects parameters + generator = Flow( + label='electricity_out', + bus='grid', + size=100, + effects_per_flow_hour={'system_costs': 45}, # €45/MWh operational cost + ) + ``` + + **Variables:** $E_{\text{costs},\text{per}}$, $E_{\text{costs},\text{temp}}(\text{t}_i)$, $E_{\text{costs}}$ + + **Constraints:** $\eqref{eq:Share_temporal}$ accumulating operational costs, $\eqref{eq:Effect_temporal}$ aggregating across elements, $\eqref{eq:Effect_Total}$ combining domains, $\eqref{eq:Objective}$ minimizing total cost + + --- + + ## Multi-Criteria: Cost with Emission Constraint + + ```python + from flixopt import Effect, Flow + + # Cost objective + cost = Effect( + label='costs', + unit='€', + is_objective=True, + ) + + # CO₂ emissions as constraint + co2 = Effect( + label='co2_emissions', + unit='kg', + maximum_temporal=100000, # Max 100 tons CO₂ per period + ) + + # Generator with both effects + generator = Flow( + label='electricity_out', + bus='grid', + size=100, + effects_per_flow_hour={ + 'costs': 45, # €45/MWh + 'co2_emissions': 0.8, # 0.8 kg CO₂/kWh + }, + ) + ``` + + **Variables:** $E_{\text{costs}}$, $E_{\text{CO}_2}$, $\Phi$ + + **Constraints:** $\eqref{eq:Objective}$ minimizing costs, $\eqref{eq:Bounds_Temporal_Total}$ with $E_{\text{CO}_2,\text{temp},\text{tot}} \leq 100{,}000$ kg + + **Behavior:** ε-constraint method - optimize one objective while constraining another + + --- + + ## Cross-Effect: Carbon Pricing + + ```python + from flixopt import Effect + + # CO₂ emissions + co2 = Effect( + label='co2_emissions', + unit='kg', + ) + + # Cost with carbon pricing + cost = Effect( + label='costs', + unit='€', + is_objective=True, + share_from_temporal={'co2_emissions': 0.05}, # €50/ton CO₂ + ) + ``` + + **Variables:** $E_{\text{CO}_2,\text{temp}}(\text{t}_i)$, $E_{\text{costs},\text{temp}}(\text{t}_i)$ + + **Constraints:** $\eqref{eq:Effect_temporal}$ with cross-effect term: $E_{\text{costs},\text{temp}}(\text{t}_i) = s_{\text{costs}}(\text{t}_i) + E_{\text{CO}_2,\text{temp}}(\text{t}_i) \cdot 0.05$ + + **Behavior:** Weighted sum method - CO₂ emissions automatically converted to costs + + --- -$$ \label{eq:Objective} -\min \left( E_{\Omega} + \Phi \right) -$$ + ## Investment Constraints -Where: + ```python + from flixopt import Effect, Flow, InvestParameters + + # Investment budget constraint + capex = Effect( + label='capital_costs', + unit='€', + maximum_periodic=5_000_000, # €5M investment budget + ) + + cost = Effect( + label='total_costs', + unit='€', + is_objective=True, + ) + + # Battery with investment + battery_flow = Flow( + label='storage_capacity', + bus='electricity', + size=InvestParameters( + minimum_size=0, + maximum_size=1000, + effects_of_investment_per_size={'capital_costs': 600}, # €600/kWh + effects_of_investment_per_size={'total_costs': 80}, # Annualized: €80/kWh/year + ), + ) + ``` -- $E_{\Omega}$ is the chosen **objective effect** (see $\eqref{eq:Effect_Total}$) -- $\Phi$ is the [penalty](#penalty) term + **Variables:** $E_{\text{capex},\text{per}}$, $E_{\text{costs},\text{per}}$, $E_{\text{costs},\text{temp}}(\text{t}_i)$ -One effect must be designated as the objective via `is_objective=True`. + **Constraints:** $\eqref{eq:Share_periodic}$ from investment, $\eqref{eq:Bounds_Periodic}$ with $E_{\text{capex},\text{per}} \leq 5{,}000{,}000$ €, $\eqref{eq:Objective}$ minimizing total costs -### Multi-Criteria Optimization + --- + + ## Hourly Peak Constraint + + ```python + from flixopt import Effect, Flow + + # Peak power limit + peak_power = Effect( + label='grid_power', + unit='kW', + maximum_per_hour=500, # Max 500 kW at any timestep + ) -This formulation supports multiple optimization approaches: + grid_import = Flow( + label='grid_import', + bus='electricity', + size=1000, + effects_per_flow_hour={'grid_power': 1}, # 1 kW per kW flow + ) + ``` -**1. Weighted Sum Method** -- The objective effect can incorporate other effects via cross-effect factors -- Example: Minimize costs while including carbon pricing: $\text{CO}_2 \rightarrow \text{costs}$ + **Variables:** $E_{\text{peak},\text{temp}}(\text{t}_i)$ -**2. ε-Constraint Method** -- Optimize one effect while constraining others -- Example: Minimize costs subject to $\text{CO}_2 \leq 1000$ kg + **Constraints:** $\eqref{eq:Bounds_Timestep}$ with $E_{\text{peak},\text{temp}}(\text{t}_i) \leq 500$ kW for all $\text{t}_i$ --- -## Objective with Multiple Dimensions +## Multi-Dimensional Objective -When the FlowSystem includes **periods** and/or **scenarios** (see [Dimensions](dimensions.md)), the objective aggregates effects across all dimensions using weights. +When the FlowSystem includes **periods** and/or **scenarios** (see [Dimensions](dimensions.md)), the objective aggregates effects using weights. ### Time Only (Base Case) @@ -201,13 +333,6 @@ $$ \min \quad E_{\Omega} + \Phi = \sum_{\text{t}_i \in \mathcal{T}} E_{\Omega,\text{temp}}(\text{t}_i) + E_{\Omega,\text{per}} + \Phi $$ -Where: -- Temporal effects sum over time: $\sum_{\text{t}_i} E_{\Omega,\text{temp}}(\text{t}_i)$ -- Periodic effects are constant: $E_{\Omega,\text{per}}$ -- Penalty sums over time: $\Phi = \sum_{\text{t}_i} \Phi(\text{t}_i)$ - ---- - ### Time + Scenario $$ @@ -215,18 +340,9 @@ $$ $$ Where: -- $\mathcal{S}$ is the set of scenarios -- $w_s$ is the weight for scenario $s$ (typically scenario probability) -- Periodic effects are **shared across scenarios**: $E_{\Omega,\text{per}}$ (same for all $s$) +- Periodic effects are **shared across scenarios**: $E_{\Omega,\text{per}}$ - Temporal effects are **scenario-specific**: $E_{\Omega,\text{temp}}(s) = \sum_{\text{t}_i} E_{\Omega,\text{temp}}(\text{t}_i, s)$ -- Penalties are **scenario-specific**: $\Phi(s) = \sum_{\text{t}_i} \Phi(\text{t}_i, s)$ - -**Interpretation:** -- Investment decisions (periodic) made once, used across all scenarios -- Operations (temporal) differ by scenario -- Objective balances expected value across scenarios - ---- +- $w_s$ is the scenario weight (typically probability) ### Time + Period @@ -234,38 +350,22 @@ $$ \min \quad \sum_{y \in \mathcal{Y}} w_y \cdot \left( E_{\Omega}(y) + \Phi(y) \right) $$ -Where: -- $\mathcal{Y}$ is the set of periods (e.g., years) -- $w_y$ is the weight for period $y$ (typically annual discount factor) -- Each period $y$ has **independent** periodic and temporal effects -- Each period $y$ has **independent** investment and operational decisions - ---- +Where $w_y$ is the period weight (typically annual discount factor). -### Time + Period + Scenario (Full Multi-Dimensional) +### Time + Period + Scenario (Full) $$ \min \quad \sum_{y \in \mathcal{Y}} \left[ w_y \cdot E_{\Omega,\text{per}}(y) + \sum_{s \in \mathcal{S}} w_{y,s} \cdot \left( E_{\Omega,\text{temp}}(y,s) + \Phi(y,s) \right) \right] $$ -Where: -- $\mathcal{S}$ is the set of scenarios -- $\mathcal{Y}$ is the set of periods -- $w_y$ is the period weight (for periodic effects) -- $w_{y,s}$ is the combined period-scenario weight (for temporal effects) -- **Periodic effects** $E_{\Omega,\text{per}}(y)$ are period-specific but **scenario-independent** -- **Temporal effects** $E_{\Omega,\text{temp}}(y,s) = \sum_{\text{t}_i} E_{\Omega,\text{temp}}(\text{t}_i, y, s)$ are **fully indexed** -- **Penalties** $\Phi(y,s)$ are **fully indexed** - **Key Principle:** -- Scenarios and periods are **operationally independent** (no energy/resource exchange) -- Coupled **only through the weighted objective function** -- **Periodic effects within a period are shared across all scenarios** (investment made once per period) -- **Temporal effects are independent per scenario** (different operations under different conditions) +- **Periodic effects within a period are shared across all scenarios** (investment made once) +- **Temporal effects are independent per scenario** (different operations) +- Scenarios and periods are **operationally independent**, coupled only through the weighted objective --- -## Summary +## Summary Table | Concept | Formulation | Time Dependency | Dimension Indexing | |---------|-------------|-----------------|-------------------| @@ -278,9 +378,15 @@ Where: --- +## Implementation + +- **Element Class:** [`Effect`][flixopt.effects.Effect] +- **Model Class:** [`EffectModel`][flixopt.effects.EffectModel] +- **Collection Class:** [`EffectCollection`][flixopt.effects.EffectCollection] + ## See Also -- [Dimensions](dimensions.md) - Complete explanation of multi-dimensional modeling -- [Flow](elements/Flow.md) - Temporal effect contributions via `effects_per_flow_hour` -- [InvestParameters](features/InvestParameters.md) - Periodic effect contributions via investment -- [Effect API][flixopt.effects.Effect] - Implementation details and parameters +- **System-Level:** [Dimensions](dimensions.md) - Multi-dimensional modeling with periods and scenarios +- **Elements:** [Flow](elements/Flow.md) - Temporal contributions via `effects_per_flow_hour` +- **Features:** [InvestParameters](features/InvestParameters.md) - Periodic contributions via investment effects +- **Components:** [Bus](elements/Bus.md) - Penalty contributions via `excess_penalty_per_flow_hour` diff --git a/flixopt/effects.py b/flixopt/effects.py index 02181920a..7295bd73f 100644 --- a/flixopt/effects.py +++ b/flixopt/effects.py @@ -29,16 +29,15 @@ @register_class_for_io class Effect(Element): - """ - Represents system-wide impacts like costs, emissions, resource consumption, or other effects. + """Represents system-wide impacts like costs, emissions, or resource consumption. - Effects capture the broader impacts of system operation and investment decisions beyond - the primary energy/material flows. Each Effect accumulates contributions from Components, - Flows, and other system elements. One Effect is typically chosen as the optimization - objective, while others can serve as constraints or tracking metrics. + Effects quantify impacts aggregating contributions from Elements across the FlowSystem. + One Effect serves as the optimization objective, while others can be constrained or tracked. + Supports operational and investment contributions, cross-effect relationships (e.g., carbon + pricing), and flexible constraint formulation. - Effects support comprehensive modeling including operational and investment contributions, - cross-effect relationships (e.g., carbon pricing), and flexible constraint formulation. + Mathematical Formulation: + See Args: label: The label of the Element. Used to identify it in the FlowSystem. @@ -470,6 +469,16 @@ def _plausibility_checks(self) -> None: class EffectModel(ElementModel): + """Mathematical model implementation for Effects. + + Creates optimization variables and constraints for effect aggregation, + including periodic and temporal tracking, cross-effect contributions, + and effect bounds. + + Mathematical Formulation: + See + """ + element: Effect # Type hint def __init__(self, model: FlowSystemModel, element: Effect): From 4b8a55aa3e2ff8c3e8053f534ef751445112dcd1 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:17:52 +0100 Subject: [PATCH 16/79] Allow toc level 3 --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 61d33e233..bfc7c154a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -128,7 +128,7 @@ markdown_extensions: - toc: permalink: true permalink_title: Anchor link to this section - toc_depth: 2 + toc_depth: 3 title: On this page # Code blocks From 7cf295d725a15987a410ec90da9018b7cbd8ba9a Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:24:13 +0100 Subject: [PATCH 17/79] Add toc to homepage --- docs/index.md | 1 - docs/stylesheets/extra.css | 12 +++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/index.md b/docs/index.md index c9b01f284..1ae064704 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,7 +1,6 @@ --- title: Home hide: - - navigation - toc --- diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 68f23f363..6d8f3323d 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -511,6 +511,9 @@ background: linear-gradient(135deg, rgba(0, 150, 136, 0.1) 0%, rgba(0, 121, 107, 0.1) 100%); border-radius: 1rem; margin-bottom: 3rem; + margin-left: auto; + margin-right: auto; + max-width: 1200px; } .hero-section h1 { @@ -542,7 +545,8 @@ display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 2rem; - margin: 3rem 0; + margin: 3rem auto; + max-width: 1200px; } .feature-card { @@ -611,17 +615,19 @@ } .architecture-section { - margin: 4rem 0; + margin: 4rem auto; padding: 2rem; background: var(--md-code-bg-color); border-radius: 0.75rem; + max-width: 1200px; } .quick-links { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; - margin: 3rem 0; + margin: 3rem auto; + max-width: 1200px; } .quick-link-card { From 9c84ec6a86e4888386d4daacdd293bec4d38b8df Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:26:53 +0100 Subject: [PATCH 18/79] Replace ustom css with mkdocs material stuff --- docs/index.md | 106 ++++++++++++++++++------------------- docs/stylesheets/extra.css | 80 ---------------------------- 2 files changed, 51 insertions(+), 135 deletions(-) diff --git a/docs/index.md b/docs/index.md index 1ae064704..6ebfd95fb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,36 +22,44 @@ hide: ## :material-map-marker-path: Quick Navigation -