ppsa

Api

transform

Transform a tessif energy system into a pypsa energy system.

Internal Functionalities

infer_pypsa_transformer_types

Utility to infer how tessif transformers should be transformed into pypsa components.

compute_unneeded_supply_chains

Utility to compute unneeded tessif components uids.

parse_flow_parameters

Utility to parse flow related parameters from Tessif components to Pypsa components

create_pypsa_busses

Creates pypsa buses out of tessif busses.

create_pypsa_sinks

Create pypsa generators out of tessif sinks.

create_pypsa_generators_from_sources

Create pypsa generators out of tessif sources.

create_pypsa_generators_from_transformers

Create pypsa generators out of tessif transformers.

create_pypsa_links_from_transformers

Create pypsa links out of tessif transformers.

create_pypsa_connectors

Create pypsa links out of tessif connectors.

create_pypsa_storages

Create pypsa generators out of tessif storages.

tessif.transform.es2es.ppsa is a tessif module aggregating all the functionality for automatically transforming a tessif energy system into a pypsa energy system.

tessif.transform.es2es.ppsa.infer_pypsa_transformer_types(tessif_transformers, forced_links=None)[source]

Utility to infer how tessif transformers should be transformed into pypsa components.

Tessif transformers can have singular or multiple outputs and therefor be both:

For a tessif transformer being transformed into a pypsa link, one of the following has to be true:

On the other hand, for a tessif transformer beeing transformed into a pypsa generator, one of the following has to be true:

Parameters:
Returns:

inferred_transformers – Mapping of Tessif transformer uids keyed either by links or by generators

Return type:

dict

Examples

Use the example hub’s py_hard data base for showcasing basic usage:

  1. Using the mwe:

    >>> from tessif.examples.data.tsf.py_hard import create_mwe
    
    >>> tessif_es = create_mwe()
    >>> iptts = infer_pypsa_transformer_types(tessif_es.transformers)
    >>> for i, (key, transformer_uids) in enumerate(iptts.items()):
    ...     if i > 0:
    ...         print()
    ...     print(f'{key}:')
    ...     print(10*'-')
    ...     for uid in transformer_uids:
    ...         print(uid)
    generators:
    ----------
    Generator
    
    links:
    ----------
    
tessif.transform.es2es.ppsa.compute_unneeded_supply_chains(tessif_es, pypsa_generator_uids)[source]

Utility to compute unneeded tessif components uids.

Due to transforming certain tessif transformers into pypsa generators corresponding supply chains (usually a tessif bus and a tessif source) are not needed and hence need to be removed, to allow succesfull optimization.

Parameters:
Returns:

components_to_remove – Dictionairy of componend uid lists keyed by pypsa component type of components which are to be removed.

Return type:

dict

Example

Use the example hub’s py_hard data base for showcasing basic usage:

  1. Using the mwe:

    >>> from tessif.examples.data.tsf.py_hard import create_mwe
    
    >>> tessif_es = create_mwe()
    >>> gens = infer_pypsa_transformer_types(
    ...     tessif_es.transformers)['generators']
    
    >>> ctrs = compute_unneeded_supply_chains(
    ...     tessif_es=tessif_es,
    ...     pypsa_generator_uids=gens,
    ... )
    >>> for comp_type, components_to_remove in ctrs.items():
    ...     if comp_type != 'supply_chains':
    ...         print(f'{comp_type}: {components_to_remove}')
    Bus: ['Pipeline']
    Generator: ['Gas Station']
    
tessif.transform.es2es.ppsa.parse_flow_parameters(component, interface)[source]

Utility to parse flow related parameters from Tessif components to Pypsa components

Parameters:
  • component (tessif.model.components.AbstractEsComponent) – Tessif component of which it’s flow related parameters are parsed into pypsa recognizable parameters

  • interface (str) – String representing the flow related interface from the components point of view. One of the components interfaces.

Returns:

parsed_flow_parameters – Dictionairy representing the parsed flow parameters ready to be used as key word arguments for creating pypsa components.

Return type:

dict

Raises:

ValueError: – A value error is raised in case a nominal_value (max flow_rate value) is requested to be infinit AND in addition to milp parameters. This is due to the fact that an infinit nominal value is parsed into an expansion problem because pypsa can not deal with infinity values. Which in turn causes conflicts in dealing with milp parameters.

Examples

Parsing an fpwe component:

>>> import tessif.examples.data.tsf.py_hard as coded_tsf_examples
>>> fpwe = coded_tsf_examples.create_fpwe()
>>> parsed_fps = parse_flow_parameters(
...     component=list(fpwe.transformers)[0],
...     interface='electricity',
... )
>>> for attr, value in parsed_fps.items():
...    print(f"{attr} = {value}")
p_nom = 15
p_min_pu = 0.0
p_max_pu = 1.0
marginal_cost = 10

Parsing an infinit value into an expansion problem:

>>> import tessif.model.components as tcomps
>>> tsf_infinity_source = tcomps.Source(
...     name='infinity',
...     outputs=('electricity',),
...     flow_rates={'electricity': (0, float('+inf'))},
...     flow_costs={'electricity': 0},
... )
>>> parsed_fps = parse_flow_parameters(
...     component=tsf_infinity_source,
...     interface='electricity',
... )
>>> for attr, value in parsed_fps.items():
...    print(f"{attr} = {value}")
marginal_cost = 0
p_nom = 0.0
p_nom_min = 0.0
p_nom_max = inf
p_nom_extendable = True
capital_cost = 0
p_min_pu = 0.0
p_max_pu = 1.0

Using an infinit nominal value while also requesting milp parameters:

>>> import tessif.model.components as tcomps
>>> tsf_infinity_source = tcomps.Source(
...     name='infinity',
...     outputs=('electricity',),
...     flow_rates={'electricity': (0, float('+inf'))},
...     milp={'electricity': True}
... )
>>> try:
...     parsed_fps = parse_flow_parameters(
...         component=tsf_infinity_source,
...         interface='electricity',
...     )
... except ValueError as e:
...     print(e)
Pypsa cannot handle infinity nominal value and milp
constraints at the same time for a singular flow.
tessif.transform.es2es.ppsa.create_pypsa_busses(busses)[source]

Creates pypsa buses out of tessif busses.

Parameters:

busses (Sequence) – Sequence of tessif.model.components.Bus objects that are to be transformed into bus objects.

Returns:

Sequence of pypsa dictionairies representing the bus components.

Return type:

Sequence

Example

>>> import tessif.examples.data.tsf.py_hard as tsf_examples
>>> tsf_es = tsf_examples.create_mwe()
>>> pypsa_busses = create_pypsa_busses(tsf_es.busses)
>>> for i, bus in enumerate(pypsa_busses):
...     for attr, value in bus.items():
...         print(f"{attr} = {value}")
...     if i < len(pypsa_busses) - 1:
...         print()
class_name = Bus
name = Pipeline
x = 0.0
y = 0.0
carrier = None

class_name = Bus
name = Powerline
x = 0.0
y = 0.0
carrier = None
tessif.transform.es2es.ppsa.create_pypsa_generators_from_sources(sources, tessif_busses)[source]

Create pypsa generators out of tessif sources.

Parameters:
Returns:

Sequence of pypsa dictionairies representing the generator components representing tessif sources.

Return type:

class:~collections.abc.Sequence

Note

Tessif sources can have multiple outputs, pypsa generators not. So in case a tessif source requests to have multiple outputs, a singular pypsa generator is created for each output.

Example

>>> import tessif.examples.data.tsf.py_hard as tsf_examples
>>> tsf_es = tsf_examples.create_fpwe()
>>> pypsa_sources = create_pypsa_generators_from_sources(
...     tsf_es.sources, tsf_es.busses)
>>> for i, source in enumerate(pypsa_sources):
...     for attr, value in source.items():
...         print(f"{attr} = {value}")
...     if i < len(pypsa_sources) - 1:
...         print()
class_name = Generator
name = Gas Station
bus = Pipeline
carrier = Gas Station.carrier
p_nom = 100
p_min_pu = 0.0
p_max_pu = 1.0
marginal_cost = 10
efficiency = 1.0
flow_emissions = 3

class_name = Generator
name = Solar Panel
bus = Powerline
carrier = electricity
p_nom = 20
p_min_pu = [0.6  0.15 0.35]
p_max_pu = [0.6  0.15 0.35]
marginal_cost = 0
efficiency = 1.0
flow_emissions = 0

class_name = Carrier
name = Gas Station.carrier
co2_emissions = 3.0

Note how an extra Carrier object gets parsed to accomodate for the emission constraints.

tessif.transform.es2es.ppsa.create_pypsa_sinks(sinks, tessif_busses, timeseries='max')[source]

Create pypsa generators out of tessif sinks.

Parameters:
Returns:

Sequence of pypsa dictionairies representing the load components representing tessif sinks.

Return type:

Sequence

Example

>>> import tessif.examples.data.tsf.py_hard as tsf_examples
>>> tsf_es = tsf_examples.create_fpwe()
>>> pypsa_sinks = create_pypsa_sinks(
...     tsf_es.sinks, tsf_es.busses)
>>> for i, sink in enumerate(pypsa_sinks):
...     for attr, value in sink.items():
...         print(f"{attr} = {value}")
...     if i < len(pypsa_sinks) - 1:
...         print()
class_name = Load
name = Demand
bus = Powerline
p_set = 11.0
tessif.transform.es2es.ppsa.create_pypsa_excess_sinks(sinks, tessif_busses)[source]

Create pypsa generators out of tessif sinks.

Parameters:
Returns:

Sequence of pypsa dictionairies representing the storage unit components representing tessif excess sinks.

Return type:

Sequence

Example

>>> import tessif.examples.data.tsf.py_hard as tsf_examples
>>> tsf_es = tsf_examples.create_simple_transformer_grid_es()
>>> tsf_excess_sinks = [sink for sink in tsf_es.sinks
...                     if str(sink.uid) in ("HV-XS", "MV-XS")]
>>> pypsa_excess_sinks = create_pypsa_excess_sinks(
...     tsf_excess_sinks, tsf_es.busses)
>>> for i, sink in enumerate(pypsa_excess_sinks):
...     for attr, value in sink.items():
...         print(f"{attr} = {value}")
...     if i < len(pypsa_excess_sinks) - 1:
...         print()
class_name = Bus
name = HV-XS-Bus
type = ignore

class_name = Link
name = HV-XS-Link
bus0 = HV-Bus
bus1 = HV-XS-Bus
type = ignore
efficiency = 1.0
p_min_pu = -1.0
marginal_cost = 10
p_nom_extendable = True
capital_cost = 0

class_name = StorageUnit
name = HV-XS
bus = HV-XS-Bus
type = excess_sink
marginal_cost = 10
p_nom = 0.0
p_nom_min = 0.0
p_nom_max = inf
p_nom_extendable = True
capital_cost = 0
p_min_pu = -1.0
p_max_pu = 0.0

class_name = Bus
name = MV-XS-Bus
type = ignore

class_name = Link
name = MV-XS-Link
bus0 = MV-Bus
bus1 = MV-XS-Bus
type = ignore
efficiency = 1.0
p_min_pu = -1.0
marginal_cost = 10
p_nom_extendable = True
capital_cost = 0

class_name = StorageUnit
name = MV-XS
bus = MV-XS-Bus
type = excess_sink
marginal_cost = 10
p_nom = 0.0
p_nom_min = 0.0
p_nom_max = inf
p_nom_extendable = True
capital_cost = 0
p_min_pu = -1.0
p_max_pu = 0.0
tessif.transform.es2es.ppsa.create_pypsa_generators_from_transformers(transformers, tessif_busses)[source]

Create pypsa generators out of tessif transformers.

Parameters:
Returns:

Sequence of pypsa dictionairies representing the generator components representing tessif transformers.

Return type:

class:~collections.abc.Sequence

Note

Tessif transformers can have multiple outputs, pypsa generators not. So in case a tessif transformer requests to have multiple outputs, a singular pypsa generator is created for each output.

Example

>>> import tessif.examples.data.tsf.py_hard as tsf_examples
>>> tsf_es = tsf_examples.create_fpwe()
>>> pypsa_transformers = create_pypsa_generators_from_transformers(
...     tsf_es.transformers, tsf_es.busses)
>>> for i, transformer in enumerate(pypsa_transformers):
...     for attr, value in transformer.items():
...         print(f"{attr} = {value}")
...     if i < len(pypsa_transformers) - 1:
...         print()
class_name = Generator
name = Generator
bus = Powerline
carrier = Generator.carrier
p_nom = 15
p_min_pu = 0.0
p_max_pu = 1.0
marginal_cost = 10
efficiency = 0.42
flow_emissions = 10

class_name = Carrier
name = Generator.carrier
co2_emissions = 4.2

Note how an extra Carrier object gets parsed to accomodate for the Generator allocated emission constraints.

Create pypsa links out of tessif transformers.

Parameters:
Returns:

Sequence of pypsa dictionairies representing the link components representing tessif transformers.

Return type:

class:~collections.abc.Sequence

Warning

In case of chp transformation pypsa uses the highest installed capacity as reference capacity to calculate outgoing flows using the respective conversion factors.

Make sure the conversion factors reflect the expected installed capacities. If target capacaties are 100 and 50, make sure conversion factors are of ration 2/1 (i.e 0.6 and 0.3).

Raises:

NotImplementedError – Raised if more than 1 input is used, because tessif can’t handle this case yet.

Example

>>> import tessif.examples.data.tsf.py_hard as tsf_examples

Singular output Link:

>>> tsf_es = tsf_examples.create_fpwe()
>>> pypsa_links = create_pypsa_links_from_transformers(
...     tsf_es.transformers, tsf_es.busses)
>>> for i, link in enumerate(pypsa_links):
...     for attr, value in link.items():
...         print(f"{attr} = {value}")
...     if i < len(pypsa_links) - 1:
...         print()
class_name = Link
name = Generator
efficiency = 0.42
bus0 = Pipeline
bus1 = Powerline
siso_transformer = True
flow_costs = 10
expansion_costs = 0
flow_emissions = 10
p_nom = 35.714285714285715
p_min_pu = 0.0
p_max_pu = 1.0
marginal_cost = 4.2
capital_cost = 0
carrier = Generator.carrier

class_name = Carrier
name = Generator.carrier
co2_emissions = 4.2

Note how an extra Carrier object gets parsed to accomodate for the Link allocated emission constraints.

Multiple output Links:

>>> tsf_es = tsf_examples.create_hhes()
>>> tessif_transformers = [
...     t for t in tsf_es.transformers
...     if ('biomass chp' in str(t.uid) or 'chp3' in str(t.uid))]
>>> pypsa_links = create_pypsa_links_from_transformers(
...     tessif_transformers, tsf_es.busses)
>>> for i, link in enumerate(pypsa_links):
...     for attr, value in link.items():
...         print(f"{attr} = {value}")
...     if i < len(pypsa_links) - 1:
...         print()
class_name = Link
name = chp3
efficiency2 = 0.4075
efficiency = 0.4
bus0 = coal supply line
bus1 = district heating pipeline
bus2 = powerline
multiple_outputs = True
flow_costs2 = 82.0
flow_costs = 19.68
expansion_costs2 = 0
expansion_costs = 0
flow_emissions2 = 0
flow_emissions = 0
p_nom = 732.5
p_min_pu = 0.0
p_max_pu = 1.0
marginal_cost = 41.287
capital_cost = 0
p_nom2 = 461.3496932515338

class_name = Link
name = biomass chp
efficiency2 = 0.3841269841269841
efficiency = 1.0
bus0 = biomass logistics
bus1 = district heating pipeline
bus2 = powerline
multiple_outputs = True
flow_costs2 = 61
flow_costs = 20
expansion_costs2 = 0
expansion_costs = 0
flow_emissions2 = 0
flow_emissions = 0
p_nom = 126.0
p_min_pu = 0.0
p_max_pu = 1.0
marginal_cost = 43.43174603174603
capital_cost = 0
p_nom2 = 126.0
tessif.transform.es2es.ppsa.create_pypsa_connectors(connectors, tessif_busses)[source]

Create pypsa links out of tessif connectors.

Parameters:
Returns:

Sequence of pypsa dictionairies representing the link components representing tessif connectors.

Return type:

class:~collections.abc.Sequence

Note

Transforming tessif connectors to pypsa links following compatibility issues are faced:

  • Pypsa links are one-directional according to their efficiency calculations. For bidirectional tessif links to make sense, bidirectional pypsa links are set to an efficiency of 1.0.

  • Tessif connectors are not designed to constrain their flows, where as pypsa links are. Therefor pypsa links created from tessif connectors are parameterized as follows:

    1. nominal capacity is extendable having no extension costs

    2. specific min/max power flow is set to -1.0/1.0 respectively

    3. flow specific costs are set to 0.0

Example

>>> import tessif.examples.data.tsf.py_hard as tsf_examples
>>> tsf_es = tsf_examples.create_connected_es()
>>> pypsa_links = create_pypsa_connectors(
...     tsf_es.connectors, tsf_es.busses)
>>> for i, link in enumerate(pypsa_links):
...     for attr, value in link.items():
...         print(f"{attr} = {value}")
...     if i < len(pypsa_links) - 1:
...         print()
class_name = Link
name = connector
bus0 = bus-01
bus1 = bus-02
efficiency = 1.0
p_min_pu = -1.0
marginal_cost = 0
p_nom_extendable = True
capital_cost = 0
tessif.transform.es2es.ppsa.create_pypsa_storages(storages, tessif_busses)[source]

Create pypsa generators out of tessif storages.

Parameters:
Returns:

Sequence of pypsa dictionairies representing the storage components representing tessif storages.

Return type:

Sequence

Note

Pypsa forces the ratio of capacity and output flow to be constant, even during expansion. Doing that it restricts possible capacty expansion in relation to out_flow expansion. Meaning, outflow expansion is subject to parameterization while capacity expansion is just a subsequent byproduct.

Since this seems quite unintuitive, tessif parameterizes it the other way round, meaning the values for capacity expansion are enforced on the flow rate expansion repsecting the stated ration of capacity to flow rate.

Example

>>> import tessif.examples.data.tsf.py_hard as tsf_examples
>>> tsf_es = tsf_examples.create_fpwe()
>>> pypsa_storages = create_pypsa_storages(
...     tsf_es.storages, tsf_es.busses)
>>> for i, storage in enumerate(pypsa_storages):
...     for attr, value in storage.items():
...         print(f"{attr} = {value}")
...     if i < len(pypsa_storages) - 1:
...         print()
class_name = StorageUnit
name = Battery
bus = Powerline
max_hours = 0.3333333333333333
efficiency_store = 1
efficiency_dispatch = 1
state_of_charge_initial = 10
cyclic_state_of_charge = False
standing_loss = 0.1
inflow = 0
flow_emissions = 0
p_nom = 30
p_min_pu = -1.0
p_max_pu = 1.0
marginal_cost = 0
tessif.transform.es2es.ppsa.transform(tessif_es, transformer_style='infer', forced_links=None, excess_sinks=None)[source]

Transform a tessif energy system into a pypsa energy system.

Parameters:
Returns:

pypsa_es – The pypsa energy system that was transformed out of the tessif energy system.

Return type:

pypsa.Network

Examples

Use the example hub’s create_mwe() utility for showcasing basic usage:

  1. Create the mwe:

    >>> from tessif.examples.data.tsf.py_hard import create_mwe
    >>> tessif_es = create_mwe()
    >>> for node_uid in sorted([n.uid for n in tessif_es.nodes]):
    ...     print(node_uid)
    Battery
    Demand
    Gas Station
    Generator
    Pipeline
    Powerline
    
  2. Transform the tessif energy system:

    >>> import tessif.transform.es2es.ppsa as tsf2pypsa
    >>> pypsa_es = tsf2pypsa.transform(tessif_es)
    
  3. Simulate the pypsa energy system using tessif's simulate wrapper:

    >>> import tessif.simulate
    >>> optimized_pypsa_es = tessif.simulate.ppsa_from_es(pypsa_es)
    
  4. Show the simulation objective using pypsa’s native interface:

    >>> print(optimized_pypsa_es.objective)
    61.0