Skip to content

Re-using of Elements #196

Closed
Closed

Description

@baumbude

Früher konnte man Elements beliebig oft verwenden.
Beispiel:
flow_system_ohne_Eigenverbrauch.add_elements(cost_effect, bus_el, battery, source, sink)
flow_system_mit_Eigenverbrauch.add_elements(cost_effect, bus_el, battery, verbraucher, source_sink)

Das scheint nicht mehr zu gehen (mindestens Konflikt mit timeseries-collection).

Es hat bei der Modellerstellung enorme Vorteile. Klar es gibt auch Nachteile für den Zugriff nachher, aber eigentlich nicht, wenn man konsequent mit dem results-Objekt arbeitet.

Wir sollten einfach mal PRO und CONS diskutieren.

Activity

  1. FBumann commented on Mar 25, 2025

    @FBumann
    Member

    Gerne.

    Kannst du mal ein Skript teilen damit ich weis was nicht geht?

  2. FBumann commented on Mar 25, 2025

    @FBumann
    Member

    In meinem ersten Test geht das auf jeden fall.

    """
    This script shows how to use the flixOpt framework to model a more complex energy system.
    """
    
    import numpy as np
    import pandas as pd
    from rich.pretty import pprint  # Used for pretty printing
    
    import flixOpt as fx
    
    if __name__ == '__main__':
        # --- Experiment Options ---
        # Configure options for testing various parameters and behaviors
        check_penalty = False
        excess_penalty = 1e5
        use_chp_with_segments = False
        time_indices = None  # Define specific time steps for custom calculations, or use the entire series
    
        # --- Define Demand and Price Profiles ---
        # Input data for electricity and heat demands, as well as electricity price
        electricity_demand = np.array([70, 80, 90, 90, 90, 90, 90, 90, 90])
        heat_demand = (
            np.array([30, 0, 90, 110, 2000, 20, 20, 20, 20])
            if check_penalty
            else np.array([30, 0, 90, 110, 110, 20, 20, 20, 20])
        )
        electricity_price = np.array([40, 40, 40, 40, 40, 40, 40, 40, 40])
    
        # --- Define the Flow System, that will hold all elements, and the time steps you want to model ---
        timesteps = pd.date_range('2020-01-01', periods=len(heat_demand), freq='h')
        flow_system = fx.FlowSystem(timesteps)  # Create FlowSystem
    
        # --- Define Energy Buses ---
        # Represent node balances (inputs=outputs) for the different energy carriers (electricity, heat, gas) in the system
        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),
        )
    
        # --- Define Effects ---
        # Specify effects related to costs, CO2 emissions, and primary energy consumption
        Costs = fx.Effect('costs', '€', 'Kosten', is_standard=True, is_objective=True)
        CO2 = fx.Effect('CO2', 'kg', 'CO2_e-Emissionen', specific_share_to_other_effects_operation={Costs.label: 0.2})
        PE = fx.Effect('PE', 'kWh_PE', 'Primärenergie', maximum_total=3.5e3)
    
        # --- Define Components ---
        # 1. Define Boiler Component
        # A gas boiler that converts fuel into thermal output, with investment and on-off parameters
        Gaskessel = fx.linear_converters.Boiler(
            'Kessel',
            eta=0.5,  # Efficiency ratio
            on_off_parameters=fx.OnOffParameters(effects_per_running_hour={Costs.label: 0, CO2.label: 1000}),  # CO2 emissions per hour
            Q_th=fx.Flow(
                label='Q_th',  # Thermal output
                bus='Fernwärme',  # Linked bus
                size=fx.InvestParameters(
                    fix_effects=1000,  # Fixed investment costs
                    fixed_size=50,  # Fixed size
                    optional=False,  # Forced investment
                    specific_effects={Costs.label: 10, PE.label: 2},  # Specific costs
                ),
                load_factor_max=1.0,  # Maximum load factor (50 kW)
                load_factor_min=0.1,  # Minimum load factor (5 kW)
                relative_minimum=5 / 50,  # Minimum part load
                relative_maximum=1,  # Maximum part load
                previous_flow_rate=50,  # Previous flow rate
                flow_hours_total_max=1e6,  # Total energy flow limit
                on_off_parameters=fx.OnOffParameters(
                    on_hours_total_min=0,  # Minimum operating hours
                    on_hours_total_max=1000,  # Maximum operating hours
                    consecutive_on_hours_max=10,  # Max consecutive operating hours
                    consecutive_on_hours_min=np.array(
                        [1, 1, 1, 1, 1, 2, 2, 2, 2]
                    ),  # min consecutive operation hoursconsecutive_off_hours_max=10,  # Max consecutive off hours
                    effects_per_switch_on=0.01,  # Cost per switch-on
                    switch_on_total_max=1000,  # Max number of starts
                ),
            ),
            Q_fu=fx.Flow(label='Q_fu', bus='Gas', size=200),
        )
    
        # 2. Define CHP Unit
        # Combined Heat and Power unit that generates both electricity and heat from fuel
        bhkw = fx.linear_converters.CHP(
            'BHKW2',
            eta_th=0.5,
            eta_el=0.4,
            on_off_parameters=fx.OnOffParameters(effects_per_switch_on=0.01),
            P_el=fx.Flow('P_el', bus='Strom', size=60, relative_minimum=5 / 60),
            Q_th=fx.Flow('Q_th', bus='Fernwärme', size=1e3),
            Q_fu=fx.Flow('Q_fu', bus='Gas', size=1e3, previous_flow_rate=20),  # The CHP was ON previously
        )
    
        # 3. Define CHP with Linear Segments
        # This CHP unit uses linear segments for more dynamic behavior over time
        P_el = fx.Flow('P_el', bus='Strom', size=60, previous_flow_rate=20)
        Q_th = fx.Flow('Q_th', bus='Fernwärme')
        Q_fu = fx.Flow('Q_fu', bus='Gas')
        segmented_conversion_factors = {
            P_el.label: [(5, 30), (40, 60)],  # Similar to eta_th, each factor here can be an array
            Q_th.label: [(6, 35), (45, 100)],
            Q_fu.label: [(12, 70), (90, 200)],
        }
    
        bhkw_2 = fx.LinearConverter(
            'BHKW2',
            inputs=[Q_fu],
            outputs=[P_el, Q_th],
            segmented_conversion_factors=segmented_conversion_factors,
            on_off_parameters=fx.OnOffParameters(effects_per_switch_on=0.01),
        )
    
        # 4. Define Storage Component
        # Storage with variable size and segmented investment effects
        segmented_investment_effects = (
            [(5, 25), (25, 100)],  # Investment size
            {
                Costs.label: [(50, 250), (250, 800)],  # Investment costs
                PE.label: [(5, 25), (25, 100)],  # Primary energy costs
            },
        )
    
        speicher = fx.Storage(
            'Speicher',
            charging=fx.Flow('Q_th_load', bus='Fernwärme', size=1e4),
            discharging=fx.Flow('Q_th_unload', bus='Fernwärme', size=1e4),
            capacity_in_flow_hours=fx.InvestParameters(
                effects_in_segments=segmented_investment_effects,  # Investment effects
                optional=False,  # Forced investment
                minimum_size=0,
                maximum_size=1000,  # Optimizing between 0 and 1000 kWh
            ),
            initial_charge_state=0,  # Initial charge state
            maximal_final_charge_state=10,  # Maximum final charge state
            eta_charge=0.9,
            eta_discharge=1,  # Charge/discharge efficiency
            relative_loss_per_hour=0.08,  # Energy loss per hour, relative to current charge state
            prevent_simultaneous_charge_and_discharge=True,  # Prevent simultaneous charge/discharge
        )
    
        # 5. Define Sinks and Sources
        # 5.a) Heat demand profile
        Waermelast = fx.Sink(
            'Wärmelast',
            sink=fx.Flow(
                'Q_th_Last',  # Heat sink
                bus='Fernwärme',  # Linked bus
                size=1,
                fixed_relative_profile=heat_demand,  # Fixed demand profile
            ),
        )
    
        # 5.b) Gas tariff
        Gasbezug = fx.Source(
            'Gastarif',
            source=fx.Flow(
                'Q_Gas',
                bus='Gas',  # Gas source
                size=1000,  # Nominal size
                effects_per_flow_hour={Costs.label: 0.04, CO2.label: 0.3},
            ),
        )
    
        # 5.c) Feed-in of electricity
        Stromverkauf = fx.Sink(
            'Einspeisung',
            sink=fx.Flow(
                'P_el',
                bus='Strom',  # Feed-in tariff for electricity
                effects_per_flow_hour=-1 * electricity_price,  # Negative price for feed-in
            ),
        )
    
        # --- Build FlowSystem ---
        # Select components to be included in the flow system
        flow_system.add_elements(Costs, CO2, PE, Gaskessel, Waermelast, Gasbezug, Stromverkauf, speicher)
        flow_system.add_elements(bhkw_2) if use_chp_with_segments else flow_system.add_elements(bhkw)
    
        pprint(flow_system)  # Get a string representation of the FlowSystem
    
        flow_system2 = fx.FlowSystem(timesteps)
        flow_system2.add_elements(*list(flow_system.components.values()))
        flow_system2.add_elements(*list(flow_system.buses.values()))
        flow_system2.add_elements(*list(flow_system.effects.effects.values()))
    
        # --- Solve FlowSystem ---
        calculation = fx.FullCalculation('complex example', flow_system, time_indices)
        calculation.do_modeling()
    
        calculation.solve(fx.solvers.HighsSolver(0.01, 60))
    
        calculation2 = fx.FullCalculation('complex example 2', flow_system2, time_indices)
        calculation2.do_modeling()
    
        calculation2.solve(fx.solvers.HighsSolver(0.01, 60))
        
        assert np.isclose(calculation2.results.objective, calculation.results.objective, rtol=1e-5, atol=0.1)
    
        # --- Results ---
        # You can analyze results directly or save them to file and reload them later.
        calculation.results.to_file()
    
        # But let's plot some results anyway
        calculation.results.plot_heatmap('BHKW2(Q_th)|flow_rate')
        calculation.results['BHKW2'].plot_node_balance()
        calculation.results['Speicher'].plot_charge_state()
  3. FBumann commented on Mar 28, 2025

    @FBumann
    Member

    @baumbude kannst du hier nochmal reinschauen und sagen was nicht funktioniert?

  4. baumbude commented on Apr 2, 2025

    @baumbude
    CollaboratorAuthor

    Ja @FBumann , es klappt anscheinend nur nicht, wenn sich die timesteps-Reihe ändert:
    Hier Minimalbeispiel:

    importt flixOpt as fx
    import pandas as pd
    import random
    
    type = 'both'
    # type = 'first'
    # type = 'second'
    
    
    aSolver = fx.solvers.HighsSolver(0.002, 20)
    price_values = [random.randint(-300, 500) for _ in range(25)]
    
    price_1 = pd.DataFrame(price_values, index=pd.date_range(start='2025-01-01', periods=10, freq='h'))
    price_2 = pd.DataFrame(price_values, index=pd.date_range(start='2023-01-01', periods=10, freq='h'))
    
    bus_el = fx.Bus('electricity')
    cost_effect = fx.Effect('costs', '€', 'Cost', is_standard=True, is_objective=True)
    
    source = fx.Source('DA_market',
              source=fx.Flow(label='Einkauf',
                       bus=bus_el.label,
                       effects_per_flow_hour=price_1,
                       ),
              )
    
    # -----------------------------------------------------
    # -------------------- Zeitreihe 1  -------------------
    
    if type in ['both','first']:
    
        flow_system = fx.FlowSystem(timesteps=price_1.index)
        flow_system.add_elements(cost_effect, bus_el)
        flow_system.add_elements(source)
    
        # --- Define, model and solve a Calculation ---
        calculation = fx.FullCalculation('Battery_onlyDA_2023', flow_system)
        calculation.do_modeling()
        calculation.solve(aSolver)
    
    
    # -----------------------------------------------------
    # -------------------- Zeitreihe 2  -------------------
    
    if type in ['both','second']:
        flow_system24 = fx.FlowSystem(timesteps=price_2.index)
        source.source.effects_per_flow_hour = -price_2
        flow_system24.add_elements(cost_effect, bus_el)
        flow_system24.add_elements(source)
    
        # --- Define, model and solve a Calculation ---
        calculation = fx.FullCalculation('Battery_onlyDA_2024', flow_system24)
        calculation.do_modeling()
        calculation.solve(aSolver)
  5. FBumann commented on Apr 2, 2025

    @FBumann
    Member

    Ist das etwas was wir brauchen?
    Dafür müsste man immer die _backup_data aus den timeseries rausholen wenn man Elemente zu einem neuen FlowSystem hinzufügt.

    Wäre denkbar umzusetzen.

  6. FBumann commented on Apr 2, 2025

    @FBumann
    Member

    Ist das etwas was wir brauchen? Dafür müsste man immer die _backup_data aus den timeseries rausholen wenn man Elemente zu einem neuen FlowSystem hinzufügt.

    Wäre denkbar umzusetzen.

    @baumbude Ich nehms zurück.
    Das beisst sich sehr mit dem indexing (active_timesteps).
    Das beides möglich zu machen wird sehr schwierig glaube ich.

  7. added
    Not in FocusThis seems reasonable, but we are not working on it at the moment.
    on Sep 10, 2025
  8. linked a pull request that will close this issueV3.0.0/invest #294on Sep 11, 2025
  9. FBumann commented on Oct 13, 2025

    @FBumann
    Member

    Closed by #348

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Not in FocusThis seems reasonable, but we are not working on it at the moment.discussIdeas or bringing up confusing things.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions