Skip to content

Optimization Modes Comparison

Note: This example relies on time series data. You can find it in the examples folder of the FlixOpt repository.

"""
This script demonstrates how to use the different calculation types in the flixopt framework
to model the same energy system. The results will be compared to each other.
"""

import pathlib

import pandas as pd
import xarray as xr

import flixopt as fx


# Get solutions for plotting for different optimizations
def get_solutions(optimizations: list, variable: str) -> xr.Dataset:
    dataarrays = []
    for optimization in optimizations:
        if optimization.name == 'Segmented':
            dataarrays.append(optimization.results.solution_without_overlap(variable).rename(optimization.name))
        else:
            dataarrays.append(optimization.results.solution[variable].rename(optimization.name))
    return xr.merge(dataarrays, join='outer')


if __name__ == '__main__':
    fx.CONFIG.exploring()

    # Calculation Types
    full, segmented, aggregated = True, True, True

    # Segmented Properties
    segment_length, overlap_length = 96, 1

    # Aggregated Properties
    clustering_parameters = fx.ClusteringParameters(
        hours_per_period=6,
        nr_of_periods=4,
        fix_storage_flows=False,
        aggregate_data_and_fix_non_binary_vars=True,
        percentage_of_period_freedom=0,
        penalty_of_period_freedom=0,
    )
    keep_extreme_periods = True
    excess_penalty = 1e5  # or set to None if not needed

    # Data Import
    data_import = pd.read_csv(
        pathlib.Path(__file__).parent.parent / 'resources' / 'Zeitreihen2020.csv', index_col=0
    ).sort_index()
    filtered_data = data_import['2020-01-01':'2020-01-07 23:45:00']
    # filtered_data = data_import[0:500]  # Alternatively filter by index

    filtered_data.index = pd.to_datetime(filtered_data.index)
    timesteps = filtered_data.index

    # Access specific columns and convert to 1D-numpy array
    electricity_demand = filtered_data['P_Netz/MW'].to_numpy()
    heat_demand = filtered_data['Q_Netz/MW'].to_numpy()
    electricity_price = filtered_data['Strompr.€/MWh'].to_numpy()
    gas_price = filtered_data['Gaspr.€/MWh'].to_numpy()

    # TimeSeriesData objects
    TS_heat_demand = fx.TimeSeriesData(heat_demand)
    TS_electricity_demand = fx.TimeSeriesData(electricity_demand, clustering_weight=0.7)
    TS_electricity_price_sell = fx.TimeSeriesData(-(electricity_price - 0.5), clustering_group='p_el')
    TS_electricity_price_buy = fx.TimeSeriesData(electricity_price + 0.5, clustering_group='p_el')

    flow_system = fx.FlowSystem(timesteps)
    flow_system.add_elements(
        fx.Bus('Strom', excess_penalty_per_flow_hour=excess_penalty),
        fx.Bus('Fernwärme', excess_penalty_per_flow_hour=excess_penalty),
        fx.Bus('Gas', excess_penalty_per_flow_hour=excess_penalty),
        fx.Bus('Kohle', excess_penalty_per_flow_hour=excess_penalty),
    )

    # Effects
    costs = fx.Effect('costs', '€', 'Kosten', is_standard=True, is_objective=True)
    CO2 = fx.Effect('CO2', 'kg', 'CO2_e-Emissionen')
    PE = fx.Effect('PE', 'kWh_PE', 'Primärenergie')

    # Component Definitions

    # 1. Boiler
    a_gaskessel = fx.linear_converters.Boiler(
        'Kessel',
        thermal_efficiency=0.85,
        thermal_flow=fx.Flow(label='Q_th', bus='Fernwärme'),
        fuel_flow=fx.Flow(
            label='Q_fu',
            bus='Gas',
            size=95,
            relative_minimum=12 / 95,
            previous_flow_rate=20,
            on_off_parameters=fx.OnOffParameters(effects_per_switch_on=1000),
        ),
    )

    # 2. CHP
    a_kwk = fx.linear_converters.CHP(
        'BHKW2',
        thermal_efficiency=0.58,
        electrical_efficiency=0.22,
        on_off_parameters=fx.OnOffParameters(effects_per_switch_on=24000),
        electrical_flow=fx.Flow('P_el', bus='Strom', size=200),
        thermal_flow=fx.Flow('Q_th', bus='Fernwärme', size=200),
        fuel_flow=fx.Flow('Q_fu', bus='Kohle', size=288, relative_minimum=87 / 288, previous_flow_rate=100),
    )

    # 3. Storage
    a_speicher = fx.Storage(
        'Speicher',
        capacity_in_flow_hours=684,
        initial_charge_state=137,
        minimal_final_charge_state=137,
        maximal_final_charge_state=158,
        eta_charge=1,
        eta_discharge=1,
        relative_loss_per_hour=0.001,
        prevent_simultaneous_charge_and_discharge=True,
        charging=fx.Flow('Q_th_load', size=137, bus='Fernwärme'),
        discharging=fx.Flow('Q_th_unload', size=158, bus='Fernwärme'),
    )

    # 4. Sinks and Sources
    # Heat Load Profile
    a_waermelast = fx.Sink(
        'Wärmelast', inputs=[fx.Flow('Q_th_Last', bus='Fernwärme', size=1, fixed_relative_profile=TS_heat_demand)]
    )

    # Electricity Feed-in
    a_strom_last = fx.Sink(
        'Stromlast', inputs=[fx.Flow('P_el_Last', bus='Strom', size=1, fixed_relative_profile=TS_electricity_demand)]
    )

    # Gas Tariff
    a_gas_tarif = fx.Source(
        'Gastarif',
        outputs=[
            fx.Flow('Q_Gas', bus='Gas', size=1000, effects_per_flow_hour={costs.label: gas_price, CO2.label: 0.3})
        ],
    )

    # Coal Tariff
    a_kohle_tarif = fx.Source(
        'Kohletarif',
        outputs=[fx.Flow('Q_Kohle', bus='Kohle', size=1000, effects_per_flow_hour={costs.label: 4.6, CO2.label: 0.3})],
    )

    # Electricity Tariff and Feed-in
    a_strom_einspeisung = fx.Sink(
        'Einspeisung', inputs=[fx.Flow('P_el', bus='Strom', size=1000, effects_per_flow_hour=TS_electricity_price_sell)]
    )

    a_strom_tarif = fx.Source(
        'Stromtarif',
        outputs=[
            fx.Flow(
                'P_el',
                bus='Strom',
                size=1000,
                effects_per_flow_hour={costs.label: TS_electricity_price_buy, CO2.label: 0.3},
            )
        ],
    )

    # Flow System Setup
    flow_system.add_elements(costs, CO2, PE)
    flow_system.add_elements(
        a_gaskessel,
        a_waermelast,
        a_strom_last,
        a_gas_tarif,
        a_kohle_tarif,
        a_strom_einspeisung,
        a_strom_tarif,
        a_kwk,
        a_speicher,
    )
    flow_system.plot_network()

    # Optimizations
    optimizations: list[fx.Optimization | fx.ClusteredOptimization | fx.SegmentedOptimization] = []

    if full:
        optimization = fx.Optimization('Full', flow_system.copy())
        optimization.do_modeling()
        optimization.solve(fx.solvers.HighsSolver(0.01 / 100, 60))
        optimizations.append(optimization)

    if segmented:
        optimization = fx.SegmentedOptimization('Segmented', flow_system.copy(), segment_length, overlap_length)
        optimization.do_modeling_and_solve(fx.solvers.HighsSolver(0.01 / 100, 60))
        optimizations.append(optimization)

    if aggregated:
        if keep_extreme_periods:
            clustering_parameters.time_series_for_high_peaks = [TS_heat_demand]
            clustering_parameters.time_series_for_low_peaks = [TS_electricity_demand, TS_heat_demand]
        optimization = fx.ClusteredOptimization('Aggregated', flow_system.copy(), clustering_parameters)
        optimization.do_modeling()
        optimization.solve(fx.solvers.HighsSolver(0.01 / 100, 60))
        optimizations.append(optimization)

    # --- Plotting for comparison ---
    fx.plotting.with_plotly(
        get_solutions(optimizations, 'Speicher|charge_state'),
        mode='line',
        title='Charge State Comparison',
        ylabel='Charge state',
        xlabel='Time in h',
    ).write_html('results/Charge State.html')

    fx.plotting.with_plotly(
        get_solutions(optimizations, 'BHKW2(Q_th)|flow_rate'),
        mode='line',
        title='BHKW2(Q_th) Flow Rate Comparison',
        ylabel='Flow rate',
        xlabel='Time in h',
    ).write_html('results/BHKW2 Thermal Power.html')

    fx.plotting.with_plotly(
        get_solutions(optimizations, 'costs(temporal)|per_timestep'),
        mode='line',
        title='Operation Cost Comparison',
        ylabel='Costs [€]',
        xlabel='Time in h',
    ).write_html('results/Operation Costs.html')

    fx.plotting.with_plotly(
        get_solutions(optimizations, 'costs(temporal)|per_timestep').sum('time'),
        mode='stacked_bar',
        title='Total Cost Comparison',
        ylabel='Costs [€]',
    ).update_layout(barmode='group').write_html('results/Total Costs.html')

    fx.plotting.with_plotly(
        pd.DataFrame(
            [calc.durations for calc in optimizations], index=[calc.name for calc in optimizations]
        ).to_xarray(),
        mode='stacked_bar',
    ).update_layout(title='Duration Comparison', xaxis_title='Optimization type', yaxis_title='Time (s)').write_html(
        'results/Speed Comparison.html'
    )