Osier Technology Tutorial#
An osier.Technology is the fundamental object in osier, representing power producing technologies such as power plants or energy storage. Other modules use and modify osier.Technology objects. This object contains all of the information one needs to run an energy system model.
Objectives:
Learn how to import a pre-defined technology from the
osiertechnology library.Modify the attributes of a technology object in the current instance.
Create your own
osiertechnology!
[1]:
# recommended imports
from unyt import MW, GW, hr, year
from unyt import unyt_array
import numpy as np
Importing from the Technology Library#
Osier comes with some generic, pre-defined technology classes that you can use immediately in your simulations. The cost data comes from the National Renewable Energy Laboratory’s (NREL) Annual Technology Baseline (ATB).
[2]:
# import the catalog to see what technologies are available
from osier.tech_library import catalog
display(catalog())
| Import Name | Technology Name | |
|---|---|---|
| 0 | battery | Battery |
| 1 | biomass | Biomass |
| 2 | coal | Coal_Conv |
| 3 | coal_adv | Coal_Adv |
| 4 | natural_gas | NaturalGas_Conv |
| 5 | natural_gas_adv | NaturalGas_Adv |
| 6 | nuclear | Nuclear |
| 7 | nuclear_adv | Nuclear_Adv |
| 8 | solar | SolarPanel |
| 9 | wind | WindTurbine |
[3]:
# import some specific technologies
from osier.tech_library import battery, nuclear, solar
# printing the technology will show the currently specified capacity.
print(nuclear)
Nuclear: 18609.404000000002 MW
All of the associated technology data can be viewed in a dataframe format using osier.Technology.to_dataframe().
[4]:
nuclear.to_dataframe()
[4]:
| technology_category | technology_type | dispatchable | renewable | fuel_type | ramp_up_rate (1/hr) | ramp_down_rate (1/hr) | lifetime | capacity (MW) | capacity_factor | capacity_credit | efficiency | capital_cost (1/kW) | om_cost_fixed (1/kW) | om_cost_variable (1/(MW*hr)) | fuel_cost (1/(MW*hr)) | co2_rate (megatonnes/(MW*hr)) | lifecycle_co2_rate (megatonnes/(GW*hr)) | land_intensity (km/MW**2) | heat_rate | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| technology_name | ||||||||||||||||||||
| Nuclear | thermal | production | True | False | None | 0 | 0 | 25 | 1.86e+04 | 1 | 1 | 1 | 5e-05 | 0.000178 | 0 | 5.81e-06 | 0 | 5.1e-06 | 0 | None |
[5]:
# one can also import a list of all technologies
from osier.tech_library import all_technologies
display(all_technologies())
[Battery: 815.3412599999999 MW,
Biomass: 0.0 MW,
Coal_Conv: 0.0 MW,
Coal_Adv: 0.0 MW,
NaturalGas_Conv: 8375.1331 MW,
NaturalGas_Adv: 0.0 MW,
Nuclear: 18609.404000000002 MW,
Nuclear_Adv: 0.0 MW,
SolarPanel: 2810.3015 MW,
WindTurbine: 0.0 MW]
Lastly, if you want to view all of the technology data in a single dataframe, you can simply import technology_dataframe from osier.utils.
[6]:
from osier.utils import technology_dataframe
technology_dataframe(all_technologies())
[6]:
| technology_category | technology_type | dispatchable | renewable | fuel_type | lifetime | capacity (MW) | capacity_factor | capacity_credit | efficiency | ... | fuel_cost (1/(MW*hr)) | co2_rate (megatonnes/(MW*hr)) | lifecycle_co2_rate (megatonnes/(GW*hr)) | land_intensity (km/MW**2) | storage_duration (hr) | initial_storage (MW*hr) | ramp_up_rate (1/hr) | ramp_down_rate (1/hr) | heat_rate | lifecycle_co2_rate (megatonnes/(MW*hr)) | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| technology_name | |||||||||||||||||||||
| Battery | base | storage | True | False | None | 25 | 815 | 1 | 0.5 | 0.85 | ... | 0 | 0 | 3.3e-05 | 0 | 4 | 0 | NaN | NaN | NaN | NaN |
| Biomass | thermal | production | True | True | None | 25 | 0 | 1 | 1 | 1 | ... | 4.7e-05 | 0 | 0.00023 | 0 | NaN | NaN | 1 | 1 | None | NaN |
| Coal_Conv | thermal | production | True | False | None | 25 | 0 | 1 | 1 | 1 | ... | 2.14e-05 | 0 | 0.001 | 0 | NaN | NaN | 0.5 | 0.5 | None | NaN |
| Coal_Adv | thermal | production | True | False | None | 25 | 0 | 1 | 1 | 1 | ... | 3.66e-05 | 0 | 0.00037 | 0 | NaN | NaN | 0.5 | 0.5 | None | NaN |
| NaturalGas_Conv | thermal | production | True | False | None | 25 | 8.38e+03 | 1 | 1 | 1 | ... | 2.24e-05 | 0 | 0.00049 | 0 | NaN | NaN | 1 | 1 | None | NaN |
| NaturalGas_Adv | thermal | production | True | False | None | 25 | 0 | 1 | 1 | 1 | ... | 2.75e-05 | 0 | 0.00013 | 0 | NaN | NaN | 1 | 1 | None | NaN |
| Nuclear | thermal | production | True | False | None | 25 | 1.86e+04 | 1 | 1 | 1 | ... | 5.81e-06 | 0 | 5.1e-06 | 0 | NaN | NaN | 0 | 0 | None | NaN |
| Nuclear_Adv | thermal | production | True | False | None | 25 | 0 | 1 | 1 | 1 | ... | 9.16e-06 | 0 | NaN | 0 | NaN | NaN | 0.25 | 0.25 | None | 5.1e-09 |
| SolarPanel | base | production | False | True | solar | 25 | 2.81e+03 | 1 | 0.19 | 1 | ... | 0 | 0 | 3.7e-05 | 0 | NaN | NaN | NaN | NaN | NaN | NaN |
| WindTurbine | base | production | False | True | wind | 25 | 0 | 1 | 0.35 | 1 | ... | 0 | 0 | 1.2e-05 | 0 | NaN | NaN | NaN | NaN | NaN | NaN |
10 rows × 23 columns
Entries with NaN values means that technology does not have that attribute.
Entries with None values means that technology possesses that attribute, but it has not been assigned a value!
Modifying Standard Technologies#
If you want to test different cost assumptions or optimize over an attribute that is not currently present in the technology, you can add or adjust at will. However, unless you modify the source code, these changes will not be saved if you restart the Python instance.
When modifying a capacity or cost attribute, specifying the units with the unyt library is recommended!
[7]:
from osier.tech_library import wind
# modify the capacity
display(wind)
wind.capacity = 5*GW
display(wind)
WindTurbine: 0.0 MW
WindTurbine: 5000.0 MW
[8]:
# add a new attribute
print(f"Is `readiness` present in the `wind` technology dataframe? {'readiness' in wind.to_dataframe().columns}")
wind.readiness = 9
print(f"The `wind` technology now has a `readiness` level of {wind.readiness}!")
print(f"Is `readiness` present in the `wind` technology dataframe? {'readiness' in wind.to_dataframe().columns}")
Is `readiness` present in the `wind` technology dataframe? False
The `wind` technology now has a `readiness` level of 9!
Is `readiness` present in the `wind` technology dataframe? True
Creating your own osier.Technology object#
Why would you want to create your own technology?
The technologies in
osier.tech_libraryare too generic or you want to model a specific technology version.There is a technology that is not represented.
The data in the osier.tech_library primarily comes from NREL’s Annual Technology Baseline. This representation is generic, though, and you may be interested in creating a vendor specific technology. Or perhaps an “idealized” technology that doesn’t exist, yet (e.g., fusion?).
osier offers several sub-classes of the osier.Technology class as well:
osier.RampingTechnology: A general class for technologies that typically have ramping constraints (e.g., nuclear, hydroelectric dams)osier.ThermalTechnology: A class that inherits fromosier.RampingTechnologyfor technologies that have heat rates.osier’s current implementation makes this redundant, but may be useful in the future.osier.StorageTechnology: A general class for technologies that primarily store energy rather than produce it.
Only the name is a required input.
[9]:
# import base class
from osier import Technology
[10]:
alien_technology = Technology(technology_name="AlienTechnology")
But that’s too simple. So let’s add some more values.
Note: When applying units from unyt, \(\frac{\$}{GW}\) is best expressed as \(\$*GW^{-1}\). unyt currently does not handle currencies due to exchange rate issues and unit conversion is one of unyt’s primary functionalities.
[11]:
alien_technology = Technology(technology_name="AlienTechnology",
technology_type="production",
dispatchable=True,
renewable=True,
capital_cost=5e4*GW**-1,
fuel_cost=1e2*(MW*hr)**-1,
capacity_factor=1.0,
lifecycle_co2_rate=0.0,
lifetime=1000,
om_cost_fixed=90*(GW)**-1)
If you’re ever unsure of what parameters a function or class take, you can always call the Python help() function!
If you’re using a jupyter notebook or ipykernel, you can use the magic command ? after the function or class, as shown below.
[12]:
# help(Technology)
Technology?
Init signature:
Technology(
technology_name,
technology_type='production',
technology_category='base',
dispatchable=True,
renewable=False,
capital_cost=0.0,
om_cost_fixed=0.0,
om_cost_variable=0.0,
fuel_cost=0.0,
fuel_type=None,
capacity=0.0,
capacity_factor=1.0,
capacity_credit=1.0,
co2_rate=0.0,
lifecycle_co2_rate=0.0,
land_intensity=0.0,
efficiency=1.0,
lifetime=25.0,
default_power_units=MW,
default_time_units=hr,
default_energy_units=None,
default_length_units=km,
default_volume_units=m**3,
default_mass_units=megatonnes,
) -> None
Docstring:
The :class:`Technology` base class contains the minimum required
data to solve an energy systems problem. Many optional data are
included here as well. All other technologies in
:mod:`osier` inherit from this class.
Parameters
----------
technology_name : str
The name identifier of the technology.
technology_type : str
The string identifier for the type of technology.
Two common types are: ["production", "storage"].
technology_category : str
The string identifier the the technology category.
For example: "renewable," "fossil," or "nuclear."
dispatchable : bool
Indicates whether the technology can be dispatched by a
grid operator, or if it produces variable electricity
that must be used or stored the moment it is produced.
For example, solar panels and wind turbines are not
dispatchable, but nuclear and biopower are dispatchable.
Default value is true.
renewable : bool
Indicates whether the technology is considered "renewable."
Useful for determining if a technology will contribute to
a renewable portfolio standard (RPS).
capital_cost : float or :class:`unyt.array.unyt_quantity`
Specifies the capital cost. If float,
the default unit is $/MW.
om_cost_fixed : float or :class:`unyt.array.unyt_quantity`
Specifies the fixed operating costs.
If float, the default unit is $/MW.
om_cost_variable : float, :class:`unyt.array.unyt_quantity`, or array-like
Specifies the variable operating costs. Users may pass timeseries data.
However, :class:`pandas.DataFrame` is not supported by this feature.
If float, the default unit is $/MWh.
fuel_cost : float, :class:`unyt.array.unyt_quantity`, or array-like
Specifies the fuel costs. Users may pass timeseries data.
However, :class:`pandas.DataFrame` is not supported by this feature.
If float, the default unit is $/MWh.
fuel_type : str
Specifies the type of fuel consumed by the technology.
capacity : float or :class:`unyt.array.unyt_quantity`
Specifies the technology capacity.
If float, the default unit is MW
capacity_factor : Optional, float
Specifies the 'usable' fraction of a technology's capacity.
Default is 1.0, i.e. all of the technology's capacity is
usable all of the time.
capacity_credit : Optional, float
Specifies the fraction of a technology's capacity that counts
towards reliability requirements. Most frequently used for
renewable technologies. For example, a solar farm might have
a capacity credit of 0.2. This means that in order to meet a
capacity requirement of 1 GW, 1.25 GW of solar would need to
be installed.
Default is 1.0, i.e. all of the technology's capacity contributes
to capacity requirements.
co2_rate : float or :class:`unyt.array.unyt_quantity`
Specifies the rate at which carbon dioxide is emitted during operation.
Generally only applicable for fossil fueled plants.
If float, the default units are megatonnes per MWh
lifecycle_co2_rate : float or :class:`unyt.array.unyt_quantity`
Specifies the rate at which of CO2eq emissions over a typical lifetime.
Unless you are reading this in a future where the economy is fully
decarbonized, all technologies should have a non-zero value for this
attribute.
If float, the default units are megatonnes per MWh
land_intensity : float or :class:`unyt.array.unyt_quantity`
The amount of land required per unit capacity. May be either lifecycle
land use or from direct use. However, consistency between
technologies is incumbent on the user.
efficiency : float
The technology's energy conversion efficiency expressed as
a fraction. Default is 1.0.
lifetime : float
The technology's operational lifetime in years. Default is 25 years.
default_power_units : str or :class:`unyt.unit_object.Unit`
An optional parameter, specifies the units
for power. Default is megawatts [MW].
default_time_units : str or :class:`unyt.unit_object.Unit`
An optional parameter, specifies the units
for time. Default is hours [hr].
default_mass_units : str or :class:`unyt.unit_object.Unit`
An optional parameter, specifies the units
for mass. Default is hours [kg].
default_energy_units : str or :class:`unyt.unit_object.Unit`
An optional parameter, specifies the units
for energy. Default is megawatt-hours [MWh]
Currently, `default_energy_units` is derived from the
time and power units.
Notes
-----
Cost values are listed in the docs as [$ / physical unit]. However,
:class:`osier` does not currently have a currency handler, therefore the
units are technically [1 / physical unit].
The :class:`unyt` library may not be able to interpret strings for
inverse units. For example:
>>> my_unit = "10 / MW"
>>> my_unit = unyt_quantity.from_string(my_unit)
ValueError: Received invalid quantity expression '10/MW'.
Instead, try the more explicit approach:
>>> my_unit = "10 MW**-1"
>>> my_unit = unyt_quantity.from_string(my_unit)
unyt_quantity(10., '1/MW')
However, inverse MWh cannot be converted from a string.
File: c:\users\sdotson\research\osier\osier\technology.py
Type: type
Subclasses: RampingTechnology, StorageTechnology