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)

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)

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)

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)

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)

# 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)
(41.40962210927087, 19.69310387966818)

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)
(41.40962210927087, 19.69310387966818)

In [44]: system_multiarray.get_iam(aoi)
Out[44]: (0.9918285608183904, 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


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.