Transformation

As laid out in Introduction and Purpose tessif aims to provide a simple yet powerful interface for analysing energy supply systems using different models. Hence a common task performed with tessif is to parameterize an energy system once using tessif's energy system and then transforming it into the different kinds of supported models to conduct simulations.

Following sections explain how such transfomations can be archieved using singular models while also giving some examples. For comparing multiple models on the same energy system, refer to the respective example hub section.

See here for examples on pure model code

Note

When aiming to perform large scale investigations, involving many different kinds of parameterized energy systems it is much more convenient to use the simulate wrappers directly providing from_tessif=True as argument, as can be seen here here.

Oemof

Tessif energy systems are transformed into oemof energy systems using tessif.transform.es2es.omf.transform().

The concept of oemof's energy system and tessif's energy system are quite similar in the sense that both apply a generalization approach when modeling an energy system.

Transforming data sets from tessif to oemof is therefore quite simple. And in case none of oemof’s more specialized components are used, no data loss is to be expected.

Following examples illustrate how instances of tessif energy systems can be transformed into oemof energy systems

Minimum Working Example

Note

A utility wrapper returning the tessif energy system as well as the transformed oemof energy system can be found in: tessif/examples/transformation/omf.py

Utilizing the example hub’s create_mwe() utility for conveniently accessing basic tessif energy system instance:

  1. Create the mwe:

    >>> from tessif.examples.data.tsf.py_hard import create_mwe
    >>> tessif_es = create_mwe()
    
  2. Transform the tessif energy system:

    >>> from tessif.transform.es2es.omf import transform
    >>> oemof_es = transform(tessif_es)
    
  3. Show node labels using the omeof interface as well as tessif's uid concept:

    >>> for node in oemof_es.nodes:
    ...     print(node.label.name)
    Pipeline
    Powerline
    Gas Station
    Demand
    Generator
    Battery
    
  4. Simulate the oemof energy system:

    >>> import tessif.simulate as simulate
    >>> optimized_oemof_es = simulate.omf_from_es(oemof_es)
    
  5. Extract some results:

    >>> import tessif.transform.es2mapping.omf as transform_oemof_results
    >>> load_results = transform_oemof_results.LoadResultier(
    ...     optimized_oemof_es)
    >>> print(load_results.node_load['Powerline'])
    Powerline            Battery  Generator  Battery  Demand
    1990-07-13 00:00:00    -10.0       -0.0      0.0    10.0
    1990-07-13 01:00:00     -0.0      -10.0      0.0    10.0
    1990-07-13 02:00:00     -0.0      -10.0      0.0    10.0
    1990-07-13 03:00:00     -0.0      -10.0      0.0    10.0
    

Constraints Types

Following sections provide overview and examples on how tessif transforms different constraint types, so oemof can interprete them.

The code examples illustrated makes use of tessif components as well as the corresponding oemof transformation module:

>>> # prepare a list of oemof and tessif busses, to be used later on
>>> import tessif.model.components as tessif_components
>>> import tessif.transform.es2es.omf as tsf_to_omf
>>> tessif_bus = tessif_components.Bus(
...     name='my_bus',
...     inputs=('my_source.power', 'my_transformer.power'),
...     outputs=('my_sink.power', 'my_transformer.fuel'))
>>> oemof_bus = list(tsf_to_omf.generate_oemof_busses((tessif_bus,)))[0]

Optimization Parameters

oemof energy systems components formulate flow rate specific cost parameters subject to global minimization bound to the individual component’s oemof.solph.network.Flow:

  • oemof.solph.network.Flow.variable_costs

Example

Parameterize and transform a tessif source object:

>>> tessif_source = tessif_components.Source(
...     name='my_source', outputs=('power',),
...     flow_costs={'power': 42})
>>> oemof_source = list(tsf_to_omf.generate_oemof_sources(
...     sources=(tessif_source,),
...     tessif_busses=(tessif_bus,),
...     oemof_busses=(oemof_bus,)))[0]

To see optimization parameter conversion:

>>> for outflow in oemof_source.outputs.values():
...     print(outflow.variable_costs[0])
42

Linear Constraints

oemof energy systems components possibly formulate a number of linear constraints. These include:

  • oemof.solph.network.Flow.nominal_value

  • oemof.solph.network.Flow.summed_min

  • oemof.solph.network.Flow.summed_max

  • oemof.solph.network.Flow.min

  • oemof.solph.network.Flow.max

  • oemof.solph.network.Flow.positive_gradient

  • oemof.solph.network.Flow.negative_gradient

Refer to oemofs source code, cause currently the documentation is in shambles.

Example

Parameterise and transform a tessif sink object:

>>> tessif_sink = tessif_components.Sink(
...     name='my_sink', inputs=('power',),
...     accumulated_amounts={'power': (42, 84)},
...     flow_rates={'power': (0, 42)},
...     flow_gradients={'power': (0, 100)},
...     gradient_costs={'power': (1, 1)},
...     )
>>> oemof_sink = list(tsf_to_omf.generate_oemof_sinks(
...     sinks=(tessif_sink,),
...     tessif_busses=(tessif_bus,),
...     oemof_busses=(oemof_bus,)))[0]

To see linear constraint parameter conversions:

>>> for otpt in oemof_sink.inputs.values():
...     print('nominal_value:', otpt.nominal_value)
...     print('min:', otpt.min[0])
...     print('max:', otpt.max[0])
...     print('summed_max:', otpt.summed_max)
...     print('summed_min:', otpt.summed_min)
...     print('positive_gradient:', otpt.positive_gradient['ub'][0])
...     print('positive_gradient_costs:', otpt.positive_gradient['costs'])
...     print('negative_gradient:', otpt.negative_gradient['ub'][0])
...     print('negative_gradient_costs:', otpt.negative_gradient['costs'])
nominal_value: 42
min: 0.0
max: 1.0
summed_max: 2.0
summed_min: 1.0
positive_gradient: 0
positive_gradient_costs: 1
negative_gradient: 100
negative_gradient_costs: 1

In the context of Tessif some linear parameters are subject to global constraints but bound to the individual component’s oemof.solph.network.Flow.

These are handled by tessif’s transformation utilities In particular by:

And are accessible via the energy system.

Expansion Constraints

oemof energy systems components can formulate a number of expansion problem constraints, but only if a dedicated oemof.solph.options.Investment object is created. These expansion problem parameters include:

  • oemof.solph.options.Investment.maximum

  • oemof.solph.options.Investment.minimum

  • oemof.solph.options.Investment.existing

  • oemof.solph.options.Investment.ep_costs

Example

Parameterise and transform a tessif source object:

>>> tessif_source = tessif_components.Source(
...     name='my_source', outputs=('power',),
...     flow_rates={'power': (0, 30)},
...     expandable={'power': True},
...     expansion_costs={'power': 5},
...     expansion_limits={'power': (30, 100)}
...     )
>>> oemof_source = list(tsf_to_omf.generate_oemof_sources(
...     sources=(tessif_source,),
...     tessif_busses=(tessif_bus,),
...     oemof_busses=(oemof_bus,)))[0]

To see expansion problem parameter conversions:

>>> for otpt in oemof_source.outputs.values():
...    print('ep_costs:', otpt.investment.ep_costs)
...    print('existing:', otpt.investment.existing)
...    print('maximum:', otpt.investment.maximum)
...    print('minimum:', otpt.investment.minimum)
ep_costs: 5
existing: 30
maximum: 70
minimum: 0

Note

Note how oemof uses minimum/maximum expansion constraints as additional installed capacity, whereas tessif defines them as absolute values.

Mixed Integer Linear Constraints

oemof energy systems components possibly formulate a number of mixed integer linear problem (milp) constraints, but only if a dedicated oemof.solph.options.NonConvex object is created. These milp parameters include:

  • oemof.solph.options.NonConvex.startup_costs

  • oemof.solph.options.NonConvex.shutdown_costs

  • oemof.solph.options.NonConvex.activity_costs

  • oemof.solph.options.NonConvex.minimum_uptime

  • oemof.solph.options.NonConvex.minimum_downtime

  • oemof.solph.options.NonConvex.maximum_startups

  • oemof.solph.options.NonConvex.maximum_shutdowns

  • oemof.solph.options.NonConvex.initial_status

Example

Parameterise and transform a tessif transformer object:

>>> tessif_transformer = tessif_components.Transformer(
...     name='my_transformer', inputs=('fuel',), outputs=('power',),
...     conversions={('fuel', 'power'): 0.42},
...     milp={'power': True, 'fuel': False},
...     number_of_status_changes=(13, 15),
...     costs_for_being_active=7,
...     status_changing_costs=(9, 1),
...     status_inertia=(0, 2),
...     initial_status=(1),
...     )
>>> oemof_transformer = list(tsf_to_omf.generate_oemof_transformers(
...     transformers=(tessif_transformer,),
...     tessif_busses=(tessif_bus,),
...     oemof_busses=(oemof_bus,)))[0]

To see mixed integer linear problem parameter conversions:

>>> for otpt in oemof_transformer.outputs.values():
...     print('startup_costs:', otpt.nonconvex.startup_costs[0])
...     print('shutdown_costs:', otpt.nonconvex.shutdown_costs[0])
...     print('activity_costs:', otpt.nonconvex.activity_costs[0])
...     print('minimum_uptime:', otpt.nonconvex.minimum_uptime)
...     print('minimum_downtime:', otpt.nonconvex.minimum_downtime)
...     print('maximum_startups:', otpt.nonconvex.maximum_startups)
...     print('maximum_shutdowns:', otpt.nonconvex.maximum_shutdowns)
...     print('initial_status:', otpt.nonconvex.initial_status)
startup_costs: 9
shutdown_costs: 1
activity_costs: 7
minimum_uptime: 0
minimum_downtime: 2
maximum_startups: 13
maximum_shutdowns: 15
initial_status: 1

Pypsa

Minimum Working Example

Constraints Types

Following sections provide overview and examples on how tessif transform different constraint types, so pypsa can interprete them.

The code examples illustrated makes use of tessif components as well as the corresponding oemof transformation module:

>>> # prepare a list of oemof and tessif busses, to be used later on
>>> import tessif.model.components as tessif_components
>>> import tessif.transform.es2es.ppsa as tsf2pypsa
>>> tessif_bus = tessif_components.Bus(
...     name='my_bus',
...     inputs=('my_source.power', 'my_transformer.power'),
...     outputs=('my_sink.power', 'my_transformer.fuel'))

Optimization Parameters

pypsa energy systems components formulate flow rate specific cost parameters subject to global minimization bound to the individual component’s marginal_cost attribute.

Example

Parameterize and transform a tessif source object:

>>> tessif_source = tessif_components.Source(
...     name='my_source', outputs=('power',),
...     flow_costs={'power': 42})
>>> pypsa_source_dict = tsf2pypsa.create_pypsa_generators_from_sources(
...     sources=[tessif_source],
...     tessif_busses=[tessif_bus],
... )[0]

To see optimization parameter conversion:

>>> from pypsa import Network
>>> pypsa_es = Network()
>>> pypsa_es.add(**pypsa_source_dict)
>>> print(pypsa_es.generators.marginal_cost['my_source'])
42.0

Global Constraints

In the context of Tessif some linear parameters are subject to global constraints but bound to the individual component’s flow_rates. These are:

These are handled by tessif’s transformation utilities In particular by:

And are accessible via the energy system. Pypsa however uses a completely different approach. It binds a certain CO2 generation to the usage of a primary energy carrier.

Transforming between tessif’s and pypsa’s approach is done by:

  • tessif.transform.es2mapping.ppsa.IntegratedGlobalResultier

  • tessif.transform.es2es.ppsa.transform_emissions()

Linear Constraints

pypsa energy systems components possibly formulate a number of linear constraints. These include:

  • p_nom, q_nom

  • p_min_pu

  • p_max_pu

Note

Note that pypsa does not include a accumulated_amounts (minimum and maximum) constraint.

Further more, the attributes: ramp_limit_up and ramp_limit_down are considered milp constraints, where as in tessif and oemof they are considered as linear constraints.

Example

Parameterise and transform a tessif source object:

>>> tessif_source = tessif_components.Source(
...     name='my_source', outputs=('power',),
...     accumulated_amounts={'power': (42, 84)},
...     flow_rates={'power': (0, 42)},
...     flow_gradients={'power': (0, 100)},
...     gradient_costs={'power': (1, 1)},
...     )
>>> pypsa_source_dict = tsf2pypsa.create_pypsa_generators_from_sources(
...     sources=[tessif_source],
...     tessif_busses=[tessif_bus],
... )[0]

Add the new component to a pypsa energy system:

>>> from pypsa import Network
>>> pypsa_es = Network()
>>> pypsa_es.add(**pypsa_source_dict)

To see expansion problem parameter conversions:

>>> print(pypsa_es.generators[
...     ["p_nom", "p_min_pu", "p_max_pu", "ramp_limit_up", "ramp_limit_down"]])
attribute  p_nom  p_min_pu  p_max_pu  ramp_limit_up  ramp_limit_down
Generator
my_source   42.0       0.0       1.0            NaN              NaN

Expansion Constraints

pypsa energy systems components can formulate a number of expansion problem constraints. These expansion problem parameters include:

  • p_nom_extendable

  • p_nom_min

  • p_nom_max

  • capital_cost

Note

In contrast to oemof where an existing parameter exists (see oemof.solph.options.Investment), the already existing amount of installed capacity is defined by using the linear constraint p_nom.

The same approach is used by tessif, where the maximum flow rate is used for the amount of capacity already installed.

Example

Parameterise and transform a tessif source object:

>>> tessif_source = tessif_components.Source(
...     name='my_source', outputs=('power',),
...     expandable={'power': True},
...     expansion_costs={'power': 5},
...     expansion_limits={'power': (0, 100)}
...     )
>>> pypsa_source_dict = tsf2pypsa.create_pypsa_generators_from_sources(
...     sources=[tessif_source],
...     tessif_busses=[tessif_bus],
... )[0]

Add the new component to a pypsa energy system:

>>> from pypsa import Network
>>> pypsa_es = Network()
>>> pypsa_es.add(**pypsa_source_dict)

To see expansion problem parameter conversions:

>>> print(pypsa_es.generators[
...     ["p_nom_extendable", "p_nom_min", "p_nom_max", "capital_cost", ]])
attribute  p_nom_extendable  p_nom_min  p_nom_max  capital_cost
Generator
my_source              True        0.0      100.0           5.0

Mixed Integer Linear Constraints

pypsa energy systems components possibly formulate a number of mixed integer linear problem (milp) constraints. These milp parameters include:

  • committable

  • start_up_cost

  • shut_down_cost

  • ramp_limit_up

  • ramp_limit_down

  • min_up_time

  • min_down_time

  • up_time_before

In constrast to oemof and tessif, pypsa does not constrain the number of start ups and shutdowns. It also does not allow to formulate an activity cost parameter.

Pypsa however fomulates a number of milp constraints, other models don’t:

  • ramp_limit_start_up

  • ramp_limit_shut_down

  • down_time_before

Example

Parameterise and transform a tessif transformer object:

>>> tessif_transformer = tessif_components.Transformer(
...     name='my_transformer',
...     inputs=('fuel',),
...     outputs=('power',),
...     conversions={('fuel', 'power'): 0.42},
...     flow_rates={'power': (0, 100), 'fuel': (0, float('+inf'))},
...     flow_gradients={
...         'power': (100, 50),
...         'fuel': (float('+inf'), float('+inf'))},
...     milp={'power': True, 'fuel': False},
...     number_of_status_changes=(13, 15),
...     costs_for_being_active=7,
...     status_changing_costs=(9, 1),
...     status_inertia=(0, 2),
...     initial_status=(False),
...     )
>>> pypsa_generator_dict = tsf2pypsa.create_pypsa_generators_from_transformers(
...     transformers=[tessif_transformer],
...     tessif_busses=[tessif_bus],
... )[0]

Add the new component to a pypsa energy system:

>>> from pypsa import Network
>>> pypsa_es = Network()
>>> pypsa_es.add(**pypsa_generator_dict)

To see the milp parameter conversions:

>>> print(pypsa_es.generators[
...     ["committable", "start_up_cost", "shut_down_cost"]])
attribute       committable  start_up_cost  shut_down_cost
Generator
my_transformer         True            9.0             1.0
>>> print(pypsa_es.generators[
...     ["ramp_limit_up", "ramp_limit_down"]])
attribute       ramp_limit_up  ramp_limit_down
Generator
my_transformer            1.0              0.5
>>> print(pypsa_es.generators[
...     ["min_up_time", "min_down_time", "up_time_before"]])
attribute       min_up_time  min_down_time  up_time_before
Generator
my_transformer            0              2               0

Tessif does not formulate any of the following constraints, hence they are set to their default values:

>>> print(pypsa_es.generators[
...     ["ramp_limit_start_up", "ramp_limit_shut_down", "down_time_before" ]])
attribute       ramp_limit_start_up  ramp_limit_shut_down  down_time_before
Generator
my_transformer                  1.0                   1.0                 0