Source code for osier.equations
from osier.utils import (get_tech_names,
get_dispatchable_techs,
get_nondispatchable_techs, get_dispatchable_names)
from pyentrp.entropy import weighted_permutation_entropy
from functools import partial
import numpy as np
from unyt import unyt_array
[docs]
def objective_from_capacity(technology_list,
attribute,
solved_dispatch_model=None):
"""
This function calculates a general objective for a given
set of technologies and their corresponding dispatch on a
per-unit-capacity basis.
The general objective function is
.. math::
\\mathcal{K} = \\sum_g^G \\textbf{CAP}_g \\kappa_g,
.. math::
\\textbf{CAP}_g = \\text{the capacity of the g-th technology}
\\quad \\left[MW\\right].
.. math::
\\kappa_g = \\text{the power density of the g-th technology}
\\quad \\left[\\frac{-}{MW}\\right].
.. warning::
User-defined attributes are not validated by :class:`osier`.
Verify the units are accurate and uniform across all
technologies before running a simulation with this function.
Parameters
----------
technology_list : list of :class:`osier.Technology` objects
The list of technologies.
solved_dispatch_model : :class:`osier.DispatchModel`
A _solved_ dispatch model (i.e. with model results and objective
attributes).
attribute : string
The technology attribute to measure.
Returns
-------
objective_value : float
The objective value for a particular energy mix.
Examples
--------
The simplest way to employ this function is with `functools.partial`.
>>> import functools
>>> from osier import per_unit_capacity
>>> objectives_list = [functools.partial(objective_from_capacity, attribute='land_use'),
... functools.partial(objective_from_capacity, attribute='employment')]
"""
objective_value = np.array([getattr(t, attribute) * t.capacity
for t in technology_list
if hasattr(t, attribute)]).sum()
return objective_value
[docs]
def objective_from_energy(technology_list, attribute, solved_dispatch_model):
"""
This function calculates a general objective for a given
set of technologies and their corresponding dispatch on a
per-unit-energy basis.
The general objective function is
.. math::
\\mathcal{X} = \\sum_g^G \\xi_g \\sum_t^T x_{g,t},
.. math::
x_{g,t} = \\text{the energy produced by the g-th technology
at time t}\\quad \\left[MWh\\right]
.. math::
\\xi_g = \\text{the energy density of the g-th technology}\\quad
\\left[\\frac{-}{MWh}\\right].
.. warning::
User-defined attributes are not validated by :class:`osier`.
Verify the units are accurate and uniform across all
technologies before running a simulation with this function.
Parameters
----------
technology_list : list of :class:`osier.Technology` objects
The list of technologies.
solved_dispatch_model : :class:`osier.DispatchModel`
A _solved_ dispatch model (i.e. with model results and objective
attributes).
attribute : string
The technology attribute to measure.
Returns
-------
objective_value : float
The objective value for a particular energy mix.
Examples
--------
The simplest way to employ this function is with `functools.partial`.
>>> import functools
>>> from osier import per_unit_capacity
>>> objectives_list = [functools.partial(objective_from_energy, attribute='water_use'),
... functools.partial(objective_from_energy, attribute='death_rate')]
"""
valid_techs = [t for t in technology_list if hasattr(t, attribute)]
column_names = get_tech_names(valid_techs)
dispatch_results = solved_dispatch_model.results
power_units = solved_dispatch_model.power_units
time_delta = solved_dispatch_model.time_delta
dispatch_values = dispatch_results[column_names].values.T*power_units*time_delta
mass_u = valid_techs[0].unit_mass
attributes = unyt_array([getattr(t, attribute)
for t in valid_techs])
attributes = attributes.to(mass_u*(power_units*time_delta.units)**-1)*time_delta.to_value()
objective_value = np.dot(
attributes,
dispatch_values).sum()
return objective_value.to_value()
[docs]
def annualized_capital_cost(technology_list, solved_dispatch_model=None):
"""
This function calculates the annual capital cost for a given
set of technologies and their corresponding dispatch.
Parameters
----------
technology_list : list of :class:`osier.Technology` objects
The list of technologies.
solved_dispatch_model : :class:`osier.DispatchModel`
A _solved_ dispatch model (i.e. with model results and objective
attributes).
Returns
-------
capital_cost : float
The annual capital cost of the technology set.
"""
capital_cost = np.array([t.total_capital_cost / t.lifetime
for t in technology_list]).sum()
return capital_cost
[docs]
def annualized_fixed_cost(technology_list, solved_dispatch_model=None):
"""
This function calculates the annual fixed cost for a given
set of technologies.
Parameters
----------
technology_list : list of :class:`osier.Technology` objects
The list of technologies.
solved_dispatch_model : :class:`osier.DispatchModel`
A _solved_ dispatch model (i.e. with model results and objective
attributes).
Returns
-------
fixed_cost : float
The annual fixed cost of the technology set.
"""
fixed_cost = np.array([t.annual_fixed_cost
for t in technology_list]).sum()
return fixed_cost
[docs]
def annual_emission(
technology_list,
solved_dispatch_model,
emission='lifecycle_co2_rate'):
"""
This function calculates the total system co2 emissions for a given
set of technologies and their corresponding dispatch.
Parameters
----------
technology_list : list of :class:`osier.Technology` objects
The list of technologies.
solved_dispatch_model : :class:`osier.DispatchModel`
A _solved_ dispatch model (i.e. with model results and objective
attributes).
Returns
-------
emissions_total : float
The total emissions of the technology set.
"""
emissions_total = objective_from_energy(technology_list=technology_list,
solved_dispatch_model=solved_dispatch_model,
attribute=emission)
return emissions_total
annual_co2 = partial(annual_emission, attribute='co2') # for backwards compatibility
[docs]
def total_cost(technology_list, solved_dispatch_model):
"""
This function calculates the total system cost for a given
set of technologies and their corresponding dispatch.
Parameters
----------
technology_list : list of :class:`osier.Technology` objects
The list of technologies.
solved_dispatch_model : :class:`osier.DispatchModel`
A _solved_ dispatch model (i.e. with model results and objective
attributes).
Returns
-------
total_cost : float
The total system cost.
"""
capital_cost = annualized_capital_cost(technology_list)
fixed_cost = annualized_fixed_cost(technology_list)
variable_cost = solved_dispatch_model.objective
total_cost = capital_cost + fixed_cost + variable_cost
return total_cost
[docs]
def volatility(technology_list,
solved_dispatch_model,
attribute='demand',
m=3,
tau=60,
normalize=True):
"""
This function calculates the volatility of the electricity prices
or the electricity demand for a dispatch model with weighted
permutation entropy.
Parameters
----------
technology_list : list of :class:`osier.Technology` objects
The list of technologies.
solved_dispatch_model : :class:`osier.DispatchModel`
A _solved_ dispatch model (i.e. with model results and objective
attributes).
attribute : {'demand', 'volatility'}
Indicates whether to calculate the volatility of electricity demand
or electricity price.
m : int
The embedding dimension for the cost time series. Typically
determined using a false nearest neighbors algorithm.
The default value is 3.
tau : int
The time delay for the cost time series. Typically determined
by selecting the index of the first or second minimum of the
time series' delayed mutual information.
Returns
-------
wpe : float
The weighted permutation entropy of the cost time series.
Notes
-----
Users can modify the parameters for this function using :attr:`functools.partial`.
>>> import functools
>>> from osier import volatility
>>> objectives_list = [functools.partial(volatility, m=4, tau=100)]
"""
timeseries = {'price':solved_dispatch_model.results.Cost.values,
'demand':np.array(solved_dispatch_model.net_demand)}
wpe = weighted_permutation_entropy(timeseries[attribute],
order=m,
delay=tau,
normalize=normalize)
return wpe