# tessif/simulate.py
"""
:mod:`~tessif.simulate` is a :mod:`tessif` module to wrap energy system
simulation calls into a convenient and ready to use interface.
energy systems using oemof.
Create powerfull, indepth, hassle-low wrappers that allow simulating the most
common use cases needing only an input location and some remarks on wich parser
and transformers to use.
"""
import numbers
from oemof import solph
# from FINE import energySystemModel as fn_esM
import tessif.transform.mapping2es.omf as tomf
import tessif.transform.mapping2es.tsf as ttsf
import tessif.transform.es2es.omf as tessif_to_oemof
import tessif.write.tools as write_tools
from tessif.frused.paths import write_dir
import logging
logger = logging.getLogger(__name__)
[docs]def omf(path, parser, solver='cbc', is_tessif=False, **kwargs):
""" Optimize an energy system using the `oemof
<https://oemof.readthedocs.io/en/v0.0.4/index.html>`_ library as well
data stored somewhere.
Parameters
----------
path: str
String representing of the energy system data path.
Passed to :paramref:`~simulate.parser`.
parser: :class:`~collections.abc.Callable`
Functional used to read in and parse the energy system data.
Usually one found in :mod:`tessif.parse`
Use :func:`functools.partial` for supplying parameters. See also the
Examples section.
solver: str, default='cbc'
String specifying the solver to be used. For `FOSS
<https://en.wikipedia.org/wiki/Free_and_open-source_software>`_
application, this is usually either ``cbc`` or ``glpk``.
But since `pyomo` is used for interfacing the solver. Any of it's
`supported solvers
<https://pyomo.readthedocs.io/en/stable/solving_pyomo_models.html#supported-solvers>`_
can be used.
Note
----
In case the link above is servered, use the pyomo command::
pyomo help --solvers
is_tessif: bool, default=False,
Boolean indicating if the read in data source is to be interpreted as
a :class:`tessif energy system
<tessif.model.energy_system.AbstractEnergySystem>`.
If ``True`` the energy system data is interpreted as
:class:`tessif energy system
<tessif.model.energy_system.AbstractEnergySystem>` and automatically
:meth:`transformed <tessif.transform.es2es.omf.transform>` into an
:class:`oemof energy system <oemof.energy_system.EnergySystem>`.
If ``False`` the energy system data is interpreted as
:class:`oemof energy system <oemof.energy_system.EnergySystem>`
kwargs:
Keywords parameterizing the solver used as well as the energy system
transformation process.
Use one of :meth:`solve's <oemof.solph.models.BaseModel.solve>`
parameters for tweaking the solver. All others will be passed to
:func:`tessif.transform.mapping2es.omf.transform` (Effectively passing
it to :meth:`oemof.core.energy_system.EnergySystem.add`)
Return
------
optimized_es : :class:`~oemof.energy_system.EnergySystem`
Energy system carrying the optimization results.
Examples
--------
Monkey patch logging level to silence warnings:
>>> from tessif.frused import configurations
>>> configurations.spellings_logging_level = 'debug'
Read in the oemof standard enregy system and simulate it:
>>> from tessif.simulate import omf
>>> from tessif.parse import xl_like
>>> from tessif.frused.paths import example_dir
>>> import os
>>> es = omf(
... path=os.path.join(example_dir, 'data', 'omf',
... 'xlsx', 'energy_system.xlsx'),
... parser=xl_like)
>>> print(type(es.results))
<class 'pyomo.opt.results.results_.SolverResults'>
>>> print(len(es.nodes) is len(es.results['main']))
True
Use functools.partial to tweak the parser and use glpk solver:
>>> import os
>>> import functools
>>> from tessif.frused.paths import example_dir
>>> import tessif.parse as parse
>>> import tessif.simulate as simulate
>>> es = simulate.omf(
... path=os.path.join(example_dir, 'data', 'omf',
... 'xlsx', 'energy_system.ods'),
... parser=functools.partial(
... parse.xl_like, sheet_name=None, engine='odf'),
... solver='glpk')
>>> print(type(es.results))
<class 'pyomo.opt.results.results_.SolverResults'>
>>> print(len(es.nodes) is len(es.results['main']))
True
Use tessif's energy system data interface:
>>> import os
>>> from tessif.frused.paths import example_dir
>>> import tessif.parse as parse
>>> import tessif.simulate as simulate
>>> es = simulate.omf(
... path=os.path.join(example_dir, 'data', 'tsf',
... 'cfg', 'flat', 'basic'),
... parser=parse.flat_config_folder,
... solver='cbc',
... is_tessif=True)
Show some results:
>>> import tessif.transform.es2mapping.omf as oemof_results
>>> resultier = oemof_results.LoadResultier(es)
>>> print(resultier.node_load['Power Line'])
Power Line Battery Generator Solar Panel Battery Demand
2015-01-01 00:00:00 -0.0 -0.0 -12.0 1.0 11.0
2015-01-01 01:00:00 -8.0 -0.0 -3.0 0.0 11.0
2015-01-01 02:00:00 -0.9 -3.1 -7.0 0.0 11.0
>>> stoRes = oemof_results.StorageResultier(es)
>>> print(stoRes.node_soc['Battery'])
2015-01-01 00:00:00 10.0
2015-01-01 01:00:00 1.0
2015-01-01 02:00:00 0.0
Freq: H, Name: Battery, dtype: float64
"""
# Default solver kwargs
skwargs = {
'solver_io': 'lp',
'solve_kwargs': {},
'cmdline_options': {},
}
# Seperate the kwargs:
for key in skwargs.keys():
if key in kwargs.keys():
skwargs.update({key: kwargs.pop(key)})
energy_system_mapping = parser(path)
if is_tessif:
es = ttsf.transform(energy_system_mapping, **kwargs)
es = tessif_to_oemof.transform(es)
else:
es = tomf.transform(energy_system_mapping, **kwargs)
# Return the optimized oemof energy system
return omf_from_es(es, solver=solver, **kwargs, **skwargs)
[docs]def omf_from_es(energy_system, solver='cbc', **kwargs):
""" Optimize an energy system using the `oemof
<https://oemof.readthedocs.io/en/v0.0.4/index.html>`_ library.
Parameters
----------
energy_system: ~oemof.energy_system.EnergySystem
Oemof energy system to be simulated.
solver: str, default='cbc'
String specifying the solver to be used. For `FOSS
<https://en.wikipedia.org/wiki/Free_and_open-source_software>`_
application, this is usually either ``cbc`` or ``glpk``.
But since :mod:`pyomo` is used for interfacing the solver. Any of it's
`supported solvers
<https://pyomo.readthedocs.io/en/stable/solving_pyomo_models.html#supported-solvers>`_
can be used.
Note
----
In case the link above is servered, use the pyomo cli command::
pyomo help --solvers
kwargs:
Keywords parameterizing the solver used as well as the energy system
transformation process.
Use one of :meth:`solve's <oemof.solph.models.BaseModel.solve>`
parameters for tweaking the solver.
Return
------
optimized_es : :class:`~oemof.energy_system.EnergySystem`
Energy system carrying the optimization results.
Examples
--------
Use the :mod:`oemof example hub <tessif.examples.data.omf>` to generate an
energy system that can be optimized:
Setting spellings.get_from's logging level to debug for decluttering
doctest output:
>>> from tessif.frused import configurations
>>> configurations.spellings_logging_level = 'debug'
Import and parse the data:
>>> import os
>>> from tessif.frused.paths import example_dir
>>> from tessif.parse import xl_like
>>> p = os.path.join(example_dir,
... 'data', 'omf', 'xlsx', 'energy_system.xls')
>>> energy_system_mapping = xl_like(p, engine='xlrd')
Transform the parsed data into an energy system:
>>> from tessif.transform.mapping2es.omf import transform
>>> energy_system = transform(energy_system_mapping)
Simulate the energy system:
>>> optimized_es = omf_from_es(energy_system)
Roughly verify that everything went as expected:
>>> print(type(optimized_es.results))
<class 'pyomo.opt.results.results_.SolverResults'>
>>> print(len(optimized_es.nodes) is len(optimized_es.results['main']))
True
"""
# Default solver kwargs
skwargs = {
'solver_io': 'lp',
'solve_kwargs': {},
'cmdline_options': {},
}
# Seperate the kwargs:
for key in skwargs.keys():
if key in kwargs.keys():
skwargs.update({key: kwargs.pop(key)})
# enforce solver from argument:
skwargs['solver'] = solver
# Prepare the optimization problem
om = solph.Model(energy_system)
# Parse global constraints (potentially added by a tessif transformation)
if hasattr(energy_system, 'global_constraints'):
for constraint, value in energy_system.global_constraints.items():
if isinstance(value, numbers.Number):
om = solph.constraints.generic_integral_limit(
om=om,
keyword=constraint,
limit=value)
om.solve(**skwargs)
# Pump results into the model:
energy_system.results['main'] = solph.processing.results(om)
energy_system.results['meta'] = solph.processing.meta_results(om)
# parse global results
energy_system.results['global'] = dict()
# Parse global constraint results (potentially added by a tessif
# transformation)
if hasattr(energy_system, 'global_constraints'):
for constraint, value in energy_system.global_constraints.items():
if isinstance(value, numbers.Number):
energy_system.results['global'][constraint] = getattr(
om, 'integral_limit_{}'.format(constraint))()
# # Parse global (constraint) results not added by tessif:
# # 1. get all attributes of this instance as a list of (name, value):
# attribute_names = dir(om)
# # get all attribute names starting with integral_limit
# global_constraint_names = list(
# a[0] for a in attribute_names
# if 'integral_limit' in a[0] and '_constraint' not in a[0])
# # compile dict entries for the global results:
# for attr_name in global_constraint_names:
# # only overwrite if not a duplicate
# if attr_name not in energy_system.results['global'].keys():
# energy_system.results['global'][
# attr_name] = getattr(om, attr_name)
energy_system.results['global']['costs'] = energy_system.results[
'meta']['objective']
# Return the optimized oemof energy system
return energy_system
[docs]def tsf(path, parser, **kwargs):
""" Optimize an energy system using the :mod:`Tessif
<tessif.model>` library.
Warning
-------
Not yet implemented!
"""
raise NotImplementedError("Someday it'll be there")
[docs]def ppsa_from_es(energy_system, solver='cbc', **kwargs):
""" Optimize an energy system using `Pypsa
<https://pypsa.readthedocs.io/en/latest/index.html>`_ .
Parameters
----------
energy_system: pypsa.Network
Pypsa energy system to be simulated
solver: str, default='cbc'
String specifying the solver to be used. For `FOSS
<https://en.wikipedia.org/wiki/Free_and_open-source_software>`_
application, this is usually either ``cbc`` or ``glpk``.
But since :mod:`pyomo` is used for interfacing the solver. Any of it's
`supported solvers
<https://pyomo.readthedocs.io/en/stable/solving_pyomo_models.html#supported-solvers>`_
can be used.
Pypsa also allows using its own solver. Archieved by passing ``pypsa``.
Note
----
In case the link above is servered, use the pyomo command::
pyomo help --solvers
kwargs:
Keywords parameterizing the solver used as well as the energy system
transformation process.
Use one of :meth:`lopf's <pypsa.Network.lopf>`
parameters for tweaking the solver.
Return
------
optimized_es : :class:`~oemof.energy_system.EnergySystem`
Energy system carrying the optimization results.
Examples
--------
Setting spellings.get_from's logging level to debug for decluttering
doctest output:
>>> from tessif.frused import configurations
>>> configurations.spellings_logging_level = 'debug'
Use the :mod:`pypsa example hub <tessif.examples.data.pypsa>` to
create an energy system that can be optimized:
>>> import tessif.examples.data.pypsa.py_hard as pypsa_hardcoded_examples
>>> pypsa_energy_system = pypsa_hardcoded_examples.create_mwe()
Simulate the energy system:
>>> optimized_pypsa_es = ppsa_from_es(pypsa_energy_system)
Roughly verify that everything went as expected:
>>> print(optimized_pypsa_es.objective)
190.0
"""
if solver == "pypsa":
pyomo = False
else:
pyomo = True
kwargs["solver_name"] = solver
# 5.) Optimize model:
# supress solver results getting printed to stdout
with write_tools.HideStdoutPrinting():
energy_system.lopf(pyomo=pyomo, **kwargs)
return energy_system
[docs]def fine_from_es(energy_system, solver='cbc', tsa=False, **kwargs):
""" Optimize an energy system using `fine
<https://vsa-fine.readthedocs.io/en/latest/index.html>`_ .
Parameters
----------
energy_system: fine energy system model
fine energy system to be simulated
solver: str, default='cbc'
String specifying the solver to be used. For `FOSS
<https://en.wikipedia.org/wiki/Free_and_open-source_software>`_
application, this is usually either ``cbc`` or ``glpk``.
But since :mod:`pyomo` is used for interfacing the solver. Any of it's
`supported solvers
<https://pyomo.readthedocs.io/en/stable/solving_pyomo_models.html#supported-solvers>`_
can be used.
Note
----
In case the link above is servered, use the pyomo command::
pyomo help --solvers
kwargs:
Keywords parameterizing the solver used as well as the energy system
transformation process.
Return
------
optimized_es : energy system model
Energy system carrying the optimization results.
Examples
--------
Setting spellings.get_from's logging level to debug for decluttering
doctest output:
>>> from tessif.frused import configurations
>>> configurations.spellings_logging_level = 'debug'
Use the :mod:`fine example hub <tessif.examples.data.fine>` to
create an energy system that can be optimized:
>>> import tessif.examples.data.fine.py_hard as fine_hardcoded_examples
>>> fine_energy_system = fine_hardcoded_examples.create_mwe()
Simulate the energy system:
>>> optimized_fine_es = fine_from_es(fine_energy_system)
Roughly verify that everything went as expected:
>>> print(round(optimized_fine_es.objectiveValue * optimized_fine_es.numberOfYears,0))
78.0
"""
kwargs['solver'] = solver
# states if the optimization of the energy system model should be done with (a) the full time series (False)
# or (b) clustered time series data (True).
if tsa is True:
kwargs['timeSeriesAggregation'] = True
energy_system.cluster()
else:
kwargs['timeSeriesAggregation'] = False
# supress solver results getting printed to stdout
with write_tools.HideStdoutPrinting():
energy_system.optimize(**kwargs)
return energy_system
[docs]def cllp_from_es(energy_system, solver='cbc', save=False, **kwargs):
""" Optimize an energy system using `Calliope
<https://calliope.readthedocs.io/en/stable/>`_ .
Parameters
----------
energy_system: calliope.core.model.Model
Calliope energy system to be simulated
solver: str, default='cbc'
String specifying the solver to be used. For `FOSS
<https://en.wikipedia.org/wiki/Free_and_open-source_software>`_
application, this is usually either ``cbc`` or ``glpk``.
But since :mod:`pyomo` is used for interfacing the solver. Any of it's
`supported solvers
<https://pyomo.readthedocs.io/en/stable/solving_pyomo_models.html#supported-solvers>`_
can be used.
Note
----
In case the link above is servered, use the pyomo command::
pyomo help --solvers
save: boolean, default=False
Boolean deciding whether the optimized energy system (energy system data
as well as results) is gonna be saved in csv-files or not.
Note
----
Earlier saves do not get overwritten.
kwargs:
Keywords parameterizing the solver used as well as the energy system
transformation process.
Return
------
optimized_es : :class:`~calliope.core.model.Model`
Energy system carrying the optimization results.
Examples
--------
Setting spellings.get_from's logging level to debug for decluttering
doctest output:
>>> from tessif.frused import configurations
>>> configurations.spellings_logging_level = 'debug'
Use the :mod:`calliope example hub <tessif.examples.data.calliope>` to
create an energy system that can be optimized:
>>> from tessif.frused.paths import example_dir
>>> import calliope
>>> calliope_es = calliope.Model(f'{example_dir}/data/calliope/fpwe/model.yaml')
Simulate the energy system:
>>> optimized_es = cllp_from_es(calliope_es)
Roughly verify that everything went as expected:
>>> print(round(optimized_es.results.objective_function_value), 0)
105 0
"""
energy_system.run_config.update({'solver': solver})
# assuming that the energy system should be run again when the simulate function is called,
# no matter if it already has been run before or not
energy_system.run(force_rerun=True, **kwargs)
# cannot overwrite saves. So this only works the first time the model is run.
if save:
energy_system.to_csv(f'{write_dir}/Calliope/{energy_system.model_config["name"]}_csv')
return energy_system