Skip to content

Effects & Dimensions

Effects track metrics (costs, CO₂, energy). Dimensions define the structure over which effects aggregate.

Defining Effects

costs = fx.Effect(label='costs', unit='€', is_objective=True)
co2 = fx.Effect(label='co2', unit='kg')

flow_system.add_elements(costs, co2)

One effect is the objective (minimized). Others are tracked or constrained.


Effect Types

Accumulated over timesteps — operational costs, emissions, energy:

  • Per flow hour: \(E(t) = p(t) \cdot c \cdot \Delta t\)
  • Per event (startup): \(E(t) = s^{start}(t) \cdot c\)
fx.Flow(..., effects_per_flow_hour={'costs': 50})  # €50/MWh

Time-independent — investment costs, fixed fees:

\(E_{per} = P \cdot c_{inv}\)

fx.InvestParameters(effects_of_investment_per_size={'costs': 200})  # €200/kW

Sum of periodic and temporal components.


Where Effects Are Contributed

fx.Flow(
    effects_per_flow_hour={'costs': 50, 'co2': 0.2},  # Per MWh
)
fx.StatusParameters(
    effects_per_startup={'costs': 1000},      # Per startup event
    effects_per_active_hour={'costs': 10},    # Per hour while running
)
fx.InvestParameters(
    effects_of_investment={'costs': 50000},           # Fixed if investing
    effects_of_investment_per_size={'costs': 800},    # Per kW installed
    effects_of_retirement={'costs': 10000},           # If NOT investing
)
fx.Bus(
    excess_penalty_per_flow_hour=1e6,    # Penalty for excess
    shortage_penalty_per_flow_hour=1e6,  # Penalty for shortage
)

Dimensions

The model operates across three dimensions:

The basic time resolution — always required:

flow_system = fx.FlowSystem(
    timesteps=pd.date_range('2024-01-01', periods=8760, freq='h'),
)

All variables and constraints are indexed by time. Temporal effects sum over timesteps.

Represent uncertainty (weather, prices). Operations vary per scenario, investments are shared:

flow_system = fx.FlowSystem(
    timesteps=pd.date_range('2024-01-01', periods=8760, freq='h'),
    scenarios=pd.Index(['sunny_year', 'cloudy_year']),
    scenario_weights=[0.7, 0.3],
)

Scenarios are independent — no energy or information exchange between them.

Sequential time blocks (years) for multi-period planning:

flow_system = fx.FlowSystem(
    timesteps=pd.date_range('2024-01-01', periods=8760, freq='h'),
    periods=pd.Index([2025, 2030]),
)

Periods are independent — each has its own investment decisions.


Objective Function

The objective aggregates effects across all dimensions with weights:

Single period, no scenarios:

\[\min \quad E_{per} + \sum_t E_{temp}(t)\]

Investment decided once, operations weighted by probability:

\[\min \quad E_{per} + \sum_s w_s \cdot \sum_t E_{temp}(t, s)\]
  • \(w_s\) — scenario weight (probability)

Multi-year planning with discounting:

\[\min \quad \sum_y w_y \cdot \left( E_{per}(y) + \sum_t E_{temp}(t, y) \right)\]
  • \(w_y\) — period weight (duration or discount factor)

Periods × Scenarios:

\[\min \quad \sum_y w_y \cdot \left( E_{per}(y) + \sum_s w_s \cdot \sum_t E_{temp}(t, y, s) \right)\]

The penalty effect is always included: \(\min \quad E_{objective} + E_{penalty}\)


Weights

Provided explicitly — typically probabilities:

scenario_weights=[0.6, 0.4]

Default: equal weights, normalized to sum to 1.

Computed automatically from period index (interval sizes):

periods = pd.Index([2020, 2025, 2030])
# → weights: [5, 5, 5] (5-year intervals)

When both present:

\(w_{y,s} = w_y \cdot w_s\)


Constraints on Effects

Bound on aggregated effect (temporal + periodic) per period:

fx.Effect(label='co2', unit='kg', maximum_total=100_000)

Bound at each timestep:

fx.Effect(label='peak', unit='kW', maximum_per_hour=500)

Bound on periodic component only:

fx.Effect(label='capex', unit='€', maximum_periodic=1_000_000)

Bound on temporal component only:

fx.Effect(label='opex', unit='€', maximum_temporal=500_000)

Bound across all periods (weighted sum):

fx.Effect(label='co2', unit='kg', maximum_over_periods=1_000_000)

Cross-Effects

Effects can contribute to each other (e.g., carbon pricing):

co2 = fx.Effect(label='co2', unit='kg')

costs = fx.Effect(
    label='costs', unit='€', is_objective=True,
    share_from_temporal={'co2': 0.08},  # €80/tonne
)

Penalty Effect

A built-in Penalty effect enables soft constraints and prevents infeasibility:

fx.StatusParameters(effects_per_startup={'Penalty': 1})
fx.Bus(label='heat', excess_penalty_per_flow_hour=1e5)

Penalty is weighted identically to the objective effect across all dimensions.


Shared vs Independent Decisions

By default, investment decisions are shared across scenarios within a period:

  • Build capacity once → operate differently per scenario
  • Reflects real-world investment under uncertainty
\[P(y) \quad \text{(one decision per period, used in all scenarios)}\]

By default, operational decisions are independent per scenario:

\[p(t, y, s) \quad \text{(different for each scenario)}\]

Use Cases

Limit total CO₂ emissions across all years:

co2 = fx.Effect(
    label='co2', unit='kg',
    maximum_over_periods=1_000_000,  # 1000 tonnes total
)

# Contribute emissions from gas consumption
gas_flow = fx.Flow(
    label='gas', bus=gas_bus,
    effects_per_flow_hour={'co2': 0.2},  # 0.2 kg/kWh
)

Cap annual investment spending:

capex = fx.Effect(
    label='capex', unit='€',
    maximum_periodic=5_000_000,  # €5M per period
)

battery = fx.Storage(
    ...,
    capacity=fx.InvestParameters(
        effects_of_investment_per_size={'capex': 600},  # €600/kWh
    ),
)

Track and limit peak power:

peak = fx.Effect(
    label='peak', unit='kW',
    maximum_per_hour=1000,  # Grid connection limit
)

grid_import = fx.Flow(
    label='import', bus=elec_bus,
    effects_per_flow_hour={'peak': 1},  # Track instantaneous power
)

Add CO₂ cost to objective automatically:

co2 = fx.Effect(label='co2', unit='kg')

costs = fx.Effect(
    label='costs', unit='€', is_objective=True,
    share_from_temporal={'co2': 0.08},  # €80/tonne carbon price
)

# Now any CO₂ contribution automatically adds to costs

Limit total land area for installations:

land = fx.Effect(
    label='land', unit='m²',
    maximum_periodic=50_000,  # 5 hectares max
)

pv = fx.Source(
    ...,
    output=fx.Flow(
        ...,
        invest_parameters=fx.InvestParameters(
            effects_of_investment_per_size={'land': 5},  # 5 m²/kWp
        ),
    ),
)

Track multiple objectives, optimize one:

costs = fx.Effect(label='costs', unit='€', is_objective=True)
co2 = fx.Effect(label='co2', unit='kg')
primary_energy = fx.Effect(label='PE', unit='kWh')

# All are tracked, costs is minimized
# Use maximum_total on co2 for ε-constraint method

Reference

Symbol Type Description
\(E_{temp}(t)\) \(\mathbb{R}\) Temporal effect at timestep \(t\)
\(E_{per}\) \(\mathbb{R}\) Periodic effect (per period)
\(E\) \(\mathbb{R}\) Total effect (\(E_{per} + \sum_t E_{temp}(t)\))
\(w_s\) \(\mathbb{R}_{\geq 0}\) Scenario weight (probability)
\(w_y\) \(\mathbb{R}_{> 0}\) Period weight (duration/discount)
\(p(t)\) \(\mathbb{R}_{\geq 0}\) Flow rate at timestep \(t\)
\(s^{start}(t)\) \(\{0, 1\}\) Startup indicator
\(P\) \(\mathbb{R}_{\geq 0}\) Investment size
\(c\) \(\mathbb{R}\) Effect coefficient
\(\Delta t\) \(\mathbb{R}_{> 0}\) Timestep duration (hours)
Constraint Python Scope
Total limit maximum_total Per period
Timestep limit maximum_per_hour Each timestep
Periodic limit maximum_periodic Per period (periodic only)
Temporal limit maximum_temporal Per period (temporal only)
Global limit maximum_over_periods Across all periods

Classes: Effect, EffectCollection