PyPSA represents the power system using the following components:
This table is also available as a dictionary within each network
object as network.components
.
For each class of components, the data describing the components is
stored in a pandas.DataFrame
corresponding to the
list_name
. For example, all static data for buses is stored in
network.buses
. In this pandas.DataFrame
the index corresponds
to the unique string names of the components, while the columns
correspond to the component static attributes. For example,
network.buses.v_nom
gives the nominal voltages of each bus.
Time-varying series attributes are stored in a dictionary of
pandas.DataFrame
based on the list_name
followed by _t
,
e.g. network.buses_t
. For example, the set points for the per unit
voltage magnitude at each bus for each snapshot can be found in
network.buses_t.v_mag_pu_set
. Please also read :ref:`time-varying`.
For each component class their attributes, their types
(float/boolean/string/int/series), their defaults, their descriptions
and their statuses are stored in a pandas.DataFrame
in the
dictionary network.components
as
e.g. network.components["Bus"]["attrs"]
. This data is reproduced
as tables for each component below.
Their status is either "Input" for those which the user specifies or "Output" for those results which PyPSA calculates.
The inputs can be either "required", if the user must give the input, or "optional", if PyPSA will use a sensible default if the user gives no input.
For functions such as :doc:`power_flow` and :doc:`optimal_power_flow` the inputs used and outputs given are listed in their documentation.
The Network
is the overall container for all components. It also
has the major functions as methods, such as network.lopf()
and
network.pf()
.
Sub-networks are determined by PyPSA and should not be entered by the user.
Sub-networks are subsets of buses and passive branches (i.e. lines and transformers) that are connected.
They have a uniform energy``carrier`` inherited from the buses, such as "DC", "AC", "heat" or "gas". In the case of "AC" sub-networks, these correspond to synchronous areas. Only "AC" and "DC" sub-networks can contain passive branches; all other sub-networks must contain a single isolated bus.
The power flow in sub-networks is determined by the passive flow through passive branches due to the impedances of the passive branches.
Sub-Network are determined by calling
network.determine_network_topology()
.
The bus is the fundamental node of the network, to which components like loads, generators and transmission lines attach. It enforces energy conservation for all elements feeding in and out of it (i.e. like Kirchhoff's Current Law).
The carrier describes energy carriers and defaults to AC
for
alternating current electricity networks. DC
can be set for direct
current electricity networks. It can also take arbitrary values for
arbitrary energy carriers, e.g. wind
, heat
, hydrogen
or
natural gas
.
Attributes relevant for global constraints can also be stored in this table, the canonical example being CO2 emissions of the carrier relevant for limits on CO2 emissions.
(NB: In versions of PyPSA < 0.6.0, this was called Source.)
Global constraints are added to OPF problems and apply to many components at once. Currently only constraints related to primary energy (i.e. before conversion with losses by generators) are supported, the canonical example being CO2 emissions for an optimisation period. Other primary-energy-related gas emissions also fall into this framework.
Other types of global constraints will be added in future, e.g. "final energy" (for limits on the share of renewable or nuclear electricity after conversion), "generation capacity" (for limits on total capacity expansion of given carriers) and "transmission capacity" (for limits on the total expansion of lines and links).
Global constraints were added in PyPSA 0.10.0 and replace the ad hoc
network.co2_limit
attribute.
Generators attach to a single bus and can feed in power. It converts
energy from its carrier
to the carrier-type of the bus to which it
is attached.
In the LOPF the limits which a generator can output are set by
p_nom*p_max_pu
and p_nom*p_min_pu
, i.e. by limits defined per
unit of the nominal power p_nom
.
Generators can either have static or time-varying p_max_pu
and
p_min_pu
.
Generators with static limits are like controllable conventional
generators which can dispatch anywhere between p_nom*p_min_pu
and
p_nom*p_max_pu
at all times. The static factor p_max_pu
,
stored at network.generator.loc[gen_name,"p_max_pu"]
essentially
acts like a de-rating factor. In the following example p_max_pu =
0.9
and p_min_pu = 0
. Since p_nom
is 12000 MW, the maximum
dispatchable active power is 0.9*12000 MW = 10800 MW.
Generators with time-varying limits are like variable
weather-dependent renewable generators. The time series p_max_pu
,
stored as a series in network.generators_t.p_max_pu[gen_name]
,
dictates the active power availability for each snapshot per unit of
the nominal power p_nom
and another time series p_min_pu
which
dictates the minimum dispatch. These time series can take values
between 0 and 1, e.g. network.generators_t.p_max_pu[gen_name]
could be
This time series is then multiplied by p_nom
to get the available
power dispatch, which is the maximum that may be dispatched. The
actual dispatch p
, stored in network.generators_t.p[gen_name]
,
may be below this value, e.g.
For the implementation of unit commitment, see :ref:`unit-commitment`.
For generators, if p>0 the generator is supplying active power to the bus and if q>0 it is supplying reactive power (i.e. behaving like a capacitor).
Storage units attach to a single bus and are used for inter-temporal
power shifting. Each storage unit has a time-varying state of charge
and various efficiencies. The nominal energy is given as a fixed ratio
max_hours
of the nominal power. If you want to optimise the
storage energy capacity independently from the storage power capacity,
you should use a fundamental Store
component (see below) attached
with two Link
components, one for charging and one for
discharging. See also the example that replaces generators and
storage units with fundamental links and stores.
For storage units, if p>0 the storage unit is supplying active power to the bus and if q>0 it is supplying reactive power (i.e. behaving like a capacitor).
The Store
connects to a single bus. It is a more fundamental
component for storing energy only (it cannot convert between energy
carriers). It inherits its energy carrier from the bus to which it is
attached.
The Store, Bus and Link are fundamental components with which one can build more complicated components (Generators, Storage Units, CHPs, etc.).
The Store has controls and optimisation on the size of its energy capacity, but not it's power output; to control the power output, you must put a link in front of it, see the example that replaces generators and storage units with fundamental links and stores.
The load attaches to a single bus and consumes power as a PQ load.
For loads, if p>0 the load is consuming active power from the bus and if q>0 it is consuming reactive power (i.e. behaving like an inductor).
Shunt impedances attach to a single bus and have a voltage-dependent admittance.
For shunt impedances the power consumption is given by s_i = |V_i|^2 y_i^* so that p_i + j q_i = |V_i|^2 (g_i -jb_i). However the p and q below are defined directly proportional to g and b p = |V|^2g and q = |V|^2b, thus if p>0 the shunt impedance is consuming active power from the bus and if q>0 it is supplying reactive power (i.e. behaving like an capacitor).
Lines represent transmission and distribution lines. They connect a
bus0
to a bus1
. They can connect either AC buses or DC
buses. Power flow through lines is not directly controllable, but is
determined passively by their impedances and the nodal power
imbalances. To see how the impedances are used in the power flow, see
:ref:`line-model`.
Standard line types with per length values for impedances.
If for a line the attribute "type" is non-empty, then these values are multiplied with the line length to get the line's electrical parameters.
The line type parameters in the following table and the implementation in PyPSA are based on pandapower's standard types, whose parameterisation is in turn loosely based on DIgSILENT PowerFactory.
If you do not import your own line types, then PyPSA will provide standard types using the following table. This table was initially based on pandapower's standard types and we thank the pandapower team for allowing us to include this data. We take no responsibility for the accuracy of the values.
Transformers represent 2-winding transformers that convert AC power
from one voltage level to another. They connect a bus0
(typically at higher voltage) to a
bus1
(typically at lower voltage). Power flow through transformers is not
directly controllable, but is determined passively by their impedances
and the nodal power imbalances. To see how the impedances are used in
the power flow, see :ref:`transformer-model`.
Standard 2-winding transformer types.
If for a transformer the attribute "type" is non-empty, then these values are used for the transformer's electrical parameters.
The transformer type parameters in the following table and the implementation in PyPSA are based on pandapower's standard types, whose parameterisation is in turn loosely based on DIgSILENT PowerFactory.
If you do not import your own transformer types, then PyPSA will provide standard types using the following table. This table was initially based on pandapower's standard types and we thank the pandapower team for allowing us to include this data. We take no responsibility for the accuracy of the values.
The Link
is a component introduced in PyPSA 0.5.0 for controllable
directed flows between two buses bus0
and bus1
with arbitrary
energy carriers. It can have an efficiency loss and a marginal cost;
for this reason its default settings allow only for power flow in one
direction, from bus0
to bus1
(i.e. p_min_pu = 0
). To build
a bidirectional lossless link, set efficiency = 1
, marginal_cost
= 0
and p_min_pu = -1
.
The Link
component can be used for any element with a controllable
power flow: a bidirectional point-to-point HVDC link, a unidirectional
lossy HVDC link, a converter between an AC and a DC network, a heat
pump or resistive heater from an AC/DC bus to a heat bus, etc.
NB: Link
has replaced the Converter
component for linking AC
with DC buses and the TransportLink
component for providing
controllable flows between AC buses. If you want to replace
Converter
and TransportLink
components in your old code, use
the Link
with efficiency = 1
, marginal_cost = 0
,
p_min_pu = -1
, p_max_pu = 1
and p_nom* = s_nom*
.
Links can also be defined with multiple outputs in fixed ratio to the
power in the single input by defining new columns bus2
, bus3
,
etc. (bus
followed by an integer) in network.links
along with
associated columns for the efficiencies efficiency2
,
efficiency3
, etc. The different outputs are then proportional to
the input according to the efficiency; see :ref:`opf-links` for how
these are used in the LOPF and the example of a CHP with a fixed
power-heat ratio.
To define the new columns bus2
, efficiency2
, bus3
,
efficiency3
, etc. in network.links
you need to override the
standard component attributes by passing pypsa.Network()
an
override_component_attrs
argument. See the section
:ref:`custom_components` and the example of a CHP with a fixed
power-heat ratio.
If the column bus2
exists, values in the column are not compulsory
for all links; if the link has no 2nd output, simply leave it empty
network.links.at["my_link","bus2"] = ""
.
For links with multiple inputs in fixed ratio to a single output,
simply reverse the flow in a link with one input and multiple outputs
by setting my_link.p_max_pu = 0
and my_link.p_min_pu = -1
.
For multiple inputs to multiple outputs, connect a multi-to-single link to a single-to-multi link with an auxiliary bus in the middle.
In the code components are grouped according to their properties in
sets such as network.one_port_components
and
network.branch_components
.
One-ports share the property that they all connect to a single bus,
i.e. generators, loads, storage units, etc.. They share the attributes
bus
, p_set
, q_set
, p
, q
.
Branches connect two buses. A copy of their attributes can be accessed
as a group by the function network.branches()
. They share the
attributes bus0
, bus1
.
Passive branches are branches whose power flow is not directly controllable, but is determined passively by their impedances and the nodal power imbalances, i.e. lines and transformers.
Controllable branches are branches whose power flow can be controlled by e.g. the LOPF optimisation, i.e. links.
If you want to define your own components and override the standard
functionality of PyPSA, you can easily override the standard
components by passing pypsa.Network() the arguments
override_components
and override_component_attrs
.
For this network, these will replace the standard definitions in
pypsa.components.components
and
pypsa.components.component_attrs
, which correspond to the
repository CSV files pypsa/components.csv
and
pypsa/component_attrs/*.csv
.
components
is a pandas.DataFrame with the component name
,
list_name
and description
. component_attrs
is a
pypsa.descriptors.Dict of pandas.DataFrame with the attribute
properties for each component. Just follow the formatting for the
standard components.
There are examples for defining new components in the git repository
in examples/new_components/
, including an example of
overriding e.g. network.lopf()
for functionality for
combined-heat-and-power (CHP) plants.