PVSystem#
The PVSystem
represents one inverter and the
PV modules that supply DC power to the inverter. A PV system may be on fixed
mounting or single axis trackers. The PVSystem
is supported by the Array
which represents the
PV modules in the PVSystem
. An instance of
PVSystem
has a single inverter, but can have
multiple instances of Array
. An instance of the
Array class represents a group of modules with the same orientation and
module type. Different instances of Array can have different tilt, orientation,
and number or type of modules, where the orientation is defined by the
Array’s mount (a FixedMount
,
SingleAxisTrackerMount
, or other).
The PVSystem
class methods wrap many of the
functions in the pvsystem
module. Similarly, the Mount classes
and Array
wrap several functions with their class
methods. Methods that wrap functions have similar names as the wrapped functions.
This practice simplifies the API for PVSystem
and Array
methods by eliminating the need to specify
arguments that are stored as attributes of these classes, such as
module and inverter properties. Using PVSystem
is not better or worse than using the functions it wraps – it is an
alternative way of organizing your data and calculations.
This guide aims to build understanding of the PVSystem class. It assumes basic familiarity with object-oriented code in Python, but most information should be understandable without a solid understanding of classes. Keep in mind that functions are independent of objects, while methods are attached to objects.
See ModelChain
for an application of
PVSystem to time series modeling.
Design philosophy#
The PVSystem class allows modelers to easily separate the data that represents a PV system (e.g. tilt angle or module parameters) from the data that influences the PV system (e.g. the weather).
The data that represents the PV system is intrinsic. The data that influences the PV system is extrinsic.
Intrinsic data is stored in object attributes. For example, the parameters that describe a PV system’s modules and inverter are stored in PVSystem.module_parameters and PVSystem.inverter_parameters.
In [1]: module_parameters = {'pdc0': 5000, 'gamma_pdc': -0.004}
In [2]: inverter_parameters = {'pdc0': 5000, 'eta_inv_nom': 0.96}
In [3]: system = pvsystem.PVSystem(inverter_parameters=inverter_parameters,
...: module_parameters=module_parameters)
...:
In [4]: print(system.inverter_parameters)
{'pdc0': 5000, 'eta_inv_nom': 0.96}
Extrinsic data is passed to the arguments of PVSystem methods. For example,
the pvwatts_dc()
method accepts extrinsic
data irradiance and temperature.
In [5]: pdc = system.pvwatts_dc(g_poa_effective=1000, temp_cell=30)
In [6]: print(pdc)
4900.0
Methods attached to a PVSystem object wrap the corresponding functions in
pvsystem
. The methods simplify the argument list by
using data stored in the PVSystem attributes. Compare the
pvwatts_dc()
method signature to the
pvwatts_dc()
function signature:
How does this work? The pvwatts_dc()
method looks in PVSystem.module_parameters for the pdc0, and
gamma_pdc arguments. Then the PVSystem.pvwatts_dc
method calls the
pvsystem.pvwatts_dc
function with
all of the arguments and returns the result to the user. Note that the
function includes a default value for the parameter temp_ref. This
default value may be overridden by specifying the temp_ref key in the
PVSystem.module_parameters dictionary.
In [7]: system.arrays[0].module_parameters['temp_ref'] = 0
# lower temp_ref should lead to lower DC power than calculated above
In [8]: pdc = system.pvwatts_dc(1000, 30)
In [9]: print(pdc)
4400.0
Multiple methods may pull data from the same attribute. For example, the PVSystem.module_parameters attribute is used by the DC model methods as well as the incidence angle modifier methods.
PVSystem and Arrays#
The PVSystem class can represent a PV system with a single array of modules, or with multiple arrays. For a PV system with a single array, the parameters that describe the array can be provided directly to the PVSystem instand. For example, the parameters that describe the array’s modules can be passed to PVSystem.module_parameters:
In [10]: module_parameters = {'pdc0': 5000, 'gamma_pdc': -0.004}
In [11]: inverter_parameters = {'pdc0': 5000, 'eta_inv_nom': 0.96}
In [12]: system = pvsystem.PVSystem(module_parameters=module_parameters,
....: inverter_parameters=inverter_parameters)
....:
In [13]: print(system.arrays[0].module_parameters)
{'pdc0': 5000, 'gamma_pdc': -0.004}
In [14]: print(system.inverter_parameters)
{'pdc0': 5000, 'eta_inv_nom': 0.96}
A system with multiple arrays is specified by passing a list of
Array
to the PVSystem
constructor. For a PV system with several arrays, the module parameters are
provided for each array, and the arrays are provided to
PVSystem
as a tuple or list of instances of
Array
:
In [15]: module_parameters = {'pdc0': 5000, 'gamma_pdc': -0.004}
In [16]: mount = pvsystem.FixedMount(surface_tilt=20, surface_azimuth=180)
In [17]: array_one = pvsystem.Array(mount=mount, module_parameters=module_parameters)
In [18]: array_two = pvsystem.Array(mount=mount, module_parameters=module_parameters)
In [19]: system_two_arrays = pvsystem.PVSystem(arrays=[array_one, array_two],
....: inverter_parameters=inverter_parameters)
....:
In [20]: print([array.module_parameters for array in system_two_arrays.arrays])
[{'pdc0': 5000, 'gamma_pdc': -0.004}, {'pdc0': 5000, 'gamma_pdc': -0.004}]
In [21]: print(system_two_arrays.inverter_parameters)
{'pdc0': 5000, 'eta_inv_nom': 0.96}
The Array
class includes those
PVSystem
attributes that may vary from array
to array. These attributes include
module_parameters, temperature_model_parameters, modules_per_string,
strings_per_inverter, albedo, surface_type, module_type, and
racking_model.
When instantiating a PVSystem
with a tuple or list
of Array
, each array parameter must be specified for
each instance of Array
. For example, if all arrays
are at the same tilt you must still specify the tilt value for
each array. When using Array
you shouldn’t
also pass any array attributes to the PVSystem attributes; when Array instances
are provided to PVSystem, the PVSystem attributes are ignored.
PVSystem attributes#
Here we review the most commonly used PVSystem and Array attributes.
Please see the PVSystem
and
Array
class documentation for a
comprehensive list of attributes.
Tilt and azimuth#
The first parameters which describe the DC part of a PV system are the tilt
and azimuth of the modules. In the case of a PV system with a single array,
these parameters can be specified using the PVSystem.surface_tilt and
PVSystem.surface_azimuth attributes. This will automatically create
an Array
with a FixedMount
at the specified tilt and azimuth:
# single south-facing array at 20 deg tilt
In [22]: system_one_array = pvsystem.PVSystem(surface_tilt=20, surface_azimuth=180)
In [23]: print(system_one_array.arrays[0].mount)
FixedMount(surface_tilt=20, surface_azimuth=180, racking_model=None, module_height=None)
In the case of a PV system with several arrays, the parameters are specified
for each array by passing a different FixedMount
(or another Mount class):
In [24]: array_one = pvsystem.Array(pvsystem.FixedMount(surface_tilt=30, surface_azimuth=90))
In [25]: print(array_one.mount.surface_tilt, array_one.mount.surface_azimuth)
30 90
In [26]: array_two = pvsystem.Array(pvsystem.FixedMount(surface_tilt=30, surface_azimuth=220))
In [27]: system = pvsystem.PVSystem(arrays=[array_one, array_two])
In [28]: system.num_arrays
Out[28]: 2
In [29]: for array in system.arrays:
....: print(array.mount)
....:
FixedMount(surface_tilt=30, surface_azimuth=90, racking_model=None, module_height=None)
FixedMount(surface_tilt=30, surface_azimuth=220, racking_model=None, module_height=None)
The surface_tilt and surface_azimuth attributes are used in PVSystem
(or Array) methods such as get_aoi()
or
get_aoi()
. The angle of incidence (AOI)
calculations require surface_tilt, surface_azimuth and the extrinsic
sun position. The PVSystem method get_aoi()
uses the surface_tilt and surface_azimuth attributes from the
pvlib.pvsystem.PVSystem
instance, and so requires only solar_zenith
and solar_azimuth as arguments.
# single south-facing array at 20 deg tilt
In [30]: system_one_array = pvsystem.PVSystem(surface_tilt=20, surface_azimuth=180)
In [31]: print(system_one_array.arrays[0].mount)
FixedMount(surface_tilt=20, surface_azimuth=180, racking_model=None, module_height=None)
# call get_aoi with solar_zenith, solar_azimuth
In [32]: aoi = system_one_array.get_aoi(solar_zenith=30, solar_azimuth=180)
In [33]: print(aoi)
9.999999999999975
The Array method get_aoi()
operates in a similar manner.
# two arrays each at 30 deg tilt with different facing
In [34]: array_one = pvsystem.Array(pvsystem.FixedMount(surface_tilt=30, surface_azimuth=90))
In [35]: array_one_aoi = array_one.get_aoi(solar_zenith=30, solar_azimuth=180)
In [36]: print(array_one_aoi)
41.40962210927085
The PVSystem method get_aoi()
operates on all Array instances in the PVSystem, whereas the the
Array method operates only on its Array instance.
In [37]: array_two = pvsystem.Array(pvsystem.FixedMount(surface_tilt=30, surface_azimuth=220))
In [38]: system_multiarray = pvsystem.PVSystem(arrays=[array_one, array_two])
In [39]: print(system_multiarray.num_arrays)
2
# call get_aoi with solar_zenith, solar_azimuth
In [40]: aoi = system_multiarray.get_aoi(solar_zenith=30, solar_azimuth=180)
In [41]: print(aoi)
(np.float64(41.40962210927085), np.float64(19.693103879668143))
As a reminder, when the PV system includes more than one array, the output of the
PVSystem method get_aoi()
is a tuple with
the order of the elements corresponding to the order of the arrays.
Other PVSystem and Array methods operate in a similar manner. When a PVSystem method needs input for each array, the input is provided in a tuple:
In [42]: aoi = system.get_aoi(solar_zenith=30, solar_azimuth=180)
In [43]: print(aoi)
(np.float64(41.40962210927085), np.float64(19.693103879668143))
In [44]: system_multiarray.get_iam(aoi)
Out[44]: (np.float64(0.9918285608183904), np.float64(0.9995270516807687))
Module and inverter parameters#
module_parameters and inverter_parameters contain the data
necessary for computing DC and AC power using one of the available
PVSystem methods. Values for these attributes can be obtained from databases
included with pvlib python by using the retrieve_sam()
function:
# Load the database of CEC module model parameters
In [45]: modules = pvsystem.retrieve_sam('cecmod')
# retrieve_sam returns a dict. the dict keys are module names,
# and the values are model parameters for that module
In [46]: module_parameters = modules['Canadian_Solar_Inc__CS5P_220M']
# Load the database of CEC inverter model parameters
In [47]: inverters = pvsystem.retrieve_sam('cecinverter')
In [48]: inverter_parameters = inverters['ABB__MICRO_0_25_I_OUTD_US_208__208V_']
In [49]: system_one_array = pvsystem.PVSystem(module_parameters=module_parameters,
....: inverter_parameters=inverter_parameters)
....:
The module and/or inverter parameters can also be specified manually. This is useful for modules or inverters that are not included in the supplied databases, or when using the PVWatts model, as demonstrated in Design philosophy.
Module strings#
The attributes modules_per_string and strings_per_inverter are used
in the scale_voltage_current_power()
method. Some DC power models in ModelChain
automatically call this method and make use of these attributes. As an
example, consider a system with a single array comprising 35 modules
arranged into 5 strings of 7 modules each.
In [50]: system = pvsystem.PVSystem(modules_per_string=7, strings_per_inverter=5)
# crude numbers from a single module
In [51]: data = pd.DataFrame({'v_mp': 8, 'v_oc': 10, 'i_mp': 5, 'i_x': 6,
....: 'i_xx': 4, 'i_sc': 7, 'p_mp': 40}, index=[0])
....:
In [52]: data_scaled = system.scale_voltage_current_power(data)
In [53]: print(data_scaled)
v_mp v_oc i_mp i_x i_xx i_sc p_mp
0 56 70 25 30 20 35 1400
Losses#
The losses_parameters attribute contains data that may be used with
methods that calculate system losses. At present, these methods include
only pvlib.pvsystem.PVSystem.pvwatts_losses()
and
pvlib.pvsystem.pvwatts_losses()
, but we hope to add more related functions
and methods in the future.