prompt: 
  We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  PVSystem with single Array generates an error
  **Is your feature request related to a problem? Please describe.**
  
  When a PVSystem has a single Array, you can't assign just the Array instance when constructing the PVSystem.
  
  ```
  mount = pvlib.pvsystem.FixedMount(surface_tilt=35, surface_azimuth=180)
  array = pvlib.pvsystem.Array(mount=mount)
  pv = pvlib.pvsystem.PVSystem(arrays=array)
  
  ---------------------------------------------------------------------------
  TypeError                                 Traceback (most recent call last)
  <ipython-input-13-f5424e3db16a> in <module>
        3 mount = pvlib.pvsystem.FixedMount(surface_tilt=35, surface_azimuth=180)
        4 array = pvlib.pvsystem.Array(mount=mount)
  ----> 5 pv = pvlib.pvsystem.PVSystem(arrays=array)
  
  ~\anaconda3\lib\site-packages\pvlib\pvsystem.py in __init__(self, arrays, surface_tilt, surface_azimuth, albedo, surface_type, module, module_type, module_parameters, temperature_model_parameters, modules_per_string, strings_per_inverter, inverter, inverter_parameters, racking_model, losses_parameters, name)
      251                 array_losses_parameters,
      252             ),)
  --> 253         elif len(arrays) == 0:
      254             raise ValueError("PVSystem must have at least one Array. "
      255                              "If you want to create a PVSystem instance "
  
  TypeError: object of type 'Array' has no len()
  
  ```
  
  Not a bug per se, since the PVSystem docstring requests that `arrays` be iterable. Still, a bit inconvenient to have to do this
  
  ```
  mount = pvlib.pvsystem.FixedMount(surface_tilt=35, surface_azimuth=180)
  array = pvlib.pvsystem.Array(mount=mount)
  pv = pvlib.pvsystem.PVSystem(arrays=[array])
  ```
  
  **Describe the solution you'd like**
  Handle `arrays=array` where `array` is an instance of `Array`
  
  **Describe alternatives you've considered**
  Status quo - either make the single Array into a list, or use the PVSystem kwargs.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  
  --- BEGIN FILE ---
  ```
  ### pvlib/pvsystem.py
  """
  The ``pvsystem`` module contains functions for modeling the output and
  performance of PV modules and inverters.
  """
  
  from collections import OrderedDict
  import functools
  import io
  import itertools
  import os
  import inspect
  from urllib.request import urlopen
  import numpy as np
  from scipy import constants
  import pandas as pd
  from dataclasses import dataclass
  from abc import ABC, abstractmethod
  from typing import Optional
  
  from pvlib._deprecation import deprecated, warn_deprecated
  
  from pvlib import (atmosphere, iam, inverter, irradiance,
                     singlediode as _singlediode, spectrum, temperature)
  from pvlib.tools import _build_kwargs, _build_args
  import pvlib.tools as tools
  
  
  # a dict of required parameter names for each DC power model
  _DC_MODEL_PARAMS = {
      'sapm': {
          'A0', 'A1', 'A2', 'A3', 'A4', 'B0', 'B1', 'B2', 'B3',
          'B4', 'B5', 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6',
          'C7', 'Isco', 'Impo', 'Voco', 'Vmpo', 'Aisc', 'Aimp', 'Bvoco',
          'Mbvoc', 'Bvmpo', 'Mbvmp', 'N', 'Cells_in_Series',
          'IXO', 'IXXO', 'FD'},
      'desoto': {
          'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',
          'R_sh_ref', 'R_s'},
      'cec': {
          'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',
          'R_sh_ref', 'R_s', 'Adjust'},
      'pvsyst': {
          'gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref',
          'R_sh_ref', 'R_sh_0', 'R_s', 'alpha_sc', 'EgRef',
          'cells_in_series'},
      'singlediode': {
          'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',
          'R_sh_ref', 'R_s'},
      'pvwatts': {'pdc0', 'gamma_pdc'}
  }
  
  
  def _unwrap_single_value(func):
      """Decorator for functions that return iterables.
  
      If the length of the iterable returned by `func` is 1, then
      the single member of the iterable is returned. If the length is
      greater than 1, then entire iterable is returned.
  
      Adds 'unwrap' as a keyword argument that can be set to False
      to force the return value to be a tuple, regardless of its length.
      """
      @functools.wraps(func)
      def f(*args, **kwargs):
          unwrap = kwargs.pop('unwrap', True)
          x = func(*args, **kwargs)
          if unwrap and len(x) == 1:
              return x[0]
          return x
      return f
  
  
  # not sure if this belongs in the pvsystem module.
  # maybe something more like core.py? It may eventually grow to
  # import a lot more functionality from other modules.
  class PVSystem:
      """
      The PVSystem class defines a standard set of PV system attributes
      and modeling functions. This class describes the collection and
      interactions of PV system components rather than an installed system
      on the ground. It is typically used in combination with
      :py:class:`~pvlib.location.Location` and
      :py:class:`~pvlib.modelchain.ModelChain`
      objects.
  
      The class supports basic system topologies consisting of:
  
          * `N` total modules arranged in series
            (`modules_per_string=N`, `strings_per_inverter=1`).
          * `M` total modules arranged in parallel
            (`modules_per_string=1`, `strings_per_inverter=M`).
          * `NxM` total modules arranged in `M` strings of `N` modules each
            (`modules_per_string=N`, `strings_per_inverter=M`).
  
      The class is complementary to the module-level functions.
  
      The attributes should generally be things that don't change about
      the system, such the type of module and the inverter. The instance
      methods accept arguments for things that do change, such as
      irradiance and temperature.
  
      Parameters
      ----------
      arrays : iterable of Array, optional
          List of arrays that are part of the system. If not specified
          a single array is created from the other parameters (e.g.
          `surface_tilt`, `surface_azimuth`). Must contain at least one Array,
          if length of arrays is 0 a ValueError is raised. If `arrays` is
          specified the following PVSystem parameters are ignored:
  
          - `surface_tilt`
          - `surface_azimuth`
          - `albedo`
          - `surface_type`
          - `module`
          - `module_type`
          - `module_parameters`
          - `temperature_model_parameters`
          - `modules_per_string`
          - `strings_per_inverter`
  
      surface_tilt: float or array-like, default 0
          Surface tilt angles in decimal degrees.
          The tilt angle is defined as degrees from horizontal
          (e.g. surface facing up = 0, surface facing horizon = 90)
  
      surface_azimuth: float or array-like, default 180
          Azimuth angle of the module surface.
          North=0, East=90, South=180, West=270.
  
      albedo : None or float, default None
          Ground surface albedo. If ``None``, then ``surface_type`` is used
          to look up a value in ``irradiance.SURFACE_ALBEDOS``.
          If ``surface_type`` is also None then a ground surface albedo
          of 0.25 is used.
  
      surface_type : None or string, default None
          The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for
          valid values.
  
      module : None or string, default None
          The model name of the modules.
          May be used to look up the module_parameters dictionary
          via some other method.
  
      module_type : None or string, default 'glass_polymer'
           Describes the module's construction. Valid strings are 'glass_polymer'
           and 'glass_glass'. Used for cell and module temperature calculations.
  
      module_parameters : None, dict or Series, default None
          Module parameters as defined by the SAPM, CEC, or other.
  
      temperature_model_parameters : None, dict or Series, default None.
          Temperature model parameters as required by one of the models in
          pvlib.temperature (excluding poa_global, temp_air and wind_speed).
  
      modules_per_string: int or float, default 1
          See system topology discussion above.
  
      strings_per_inverter: int or float, default 1
          See system topology discussion above.
  
      inverter : None or string, default None
          The model name of the inverters.
          May be used to look up the inverter_parameters dictionary
          via some other method.
  
      inverter_parameters : None, dict or Series, default None
          Inverter parameters as defined by the SAPM, CEC, or other.
  
      racking_model : None or string, default 'open_rack'
          Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.
          Used to identify a parameter set for the SAPM cell temperature model.
  
      losses_parameters : None, dict or Series, default None
          Losses parameters as defined by PVWatts or other.
  
      name : None or string, default None
  
      **kwargs
          Arbitrary keyword arguments.
          Included for compatibility, but not used.
  
      Raises
      ------
      ValueError
          If `arrays` is not None and has length 0.
  
      See also
      --------
      pvlib.location.Location
      """
  
      def __init__(self,
                   arrays=None,
                   surface_tilt=0, surface_azimuth=180,
                   albedo=None, surface_type=None,
                   module=None, module_type=None,
                   module_parameters=None,
                   temperature_model_parameters=None,
                   modules_per_string=1, strings_per_inverter=1,
                   inverter=None, inverter_parameters=None,
                   racking_model=None, losses_parameters=None, name=None):
  
          if arrays is None:
              if losses_parameters is None:
                  array_losses_parameters = {}
              else:
                  array_losses_parameters = _build_kwargs(['dc_ohmic_percent'],
                                                          losses_parameters)
              self.arrays = (Array(
                  FixedMount(surface_tilt, surface_azimuth, racking_model),
                  albedo,
                  surface_type,
                  module,
                  module_type,
                  module_parameters,
                  temperature_model_parameters,
                  modules_per_string,
                  strings_per_inverter,
                  array_losses_parameters,
              ),)
          elif len(arrays) == 0:
              raise ValueError("PVSystem must have at least one Array. "
                               "If you want to create a PVSystem instance "
                               "with a single Array pass `arrays=None` and pass "
                               "values directly to PVSystem attributes, e.g., "
                               "`surface_tilt=30`")
          else:
              self.arrays = tuple(arrays)
  
          self.inverter = inverter
          if inverter_parameters is None:
              self.inverter_parameters = {}
          else:
              self.inverter_parameters = inverter_parameters
  
          if losses_parameters is None:
              self.losses_parameters = {}
          else:
              self.losses_parameters = losses_parameters
  
          self.name = name
  
      def __repr__(self):
          repr = f'PVSystem:\n  name: {self.name}\n  '
          for array in self.arrays:
              repr += '\n  '.join(array.__repr__().split('\n'))
              repr += '\n  '
          repr += f'inverter: {self.inverter}'
          return repr
  
      def _validate_per_array(self, values, system_wide=False):
          """Check that `values` is a tuple of the same length as
          `self.arrays`.
  
          If `values` is not a tuple it is packed in to a length-1 tuple before
          the check. If the lengths are not the same a ValueError is raised,
          otherwise the tuple `values` is returned.
  
          When `system_wide` is True and `values` is not a tuple, `values`
          is replicated to a tuple of the same length as `self.arrays` and that
          tuple is returned.
          """
          if system_wide and not isinstance(values, tuple):
              return (values,) * self.num_arrays
          if not isinstance(values, tuple):
              values = (values,)
          if len(values) != len(self.arrays):
              raise ValueError("Length mismatch for per-array parameter")
          return values
  
      @_unwrap_single_value
      def _infer_cell_type(self):
          """
          Examines module_parameters and maps the Technology key for the CEC
          database and the Material key for the Sandia database to a common
          list of strings for cell type.
  
          Returns
          -------
          cell_type: str
          """
          return tuple(array._infer_cell_type() for array in self.arrays)
  
      @_unwrap_single_value
      def get_aoi(self, solar_zenith, solar_azimuth):
          """Get the angle of incidence on the Array(s) in the system.
  
          Parameters
          ----------
          solar_zenith : float or Series.
              Solar zenith angle.
          solar_azimuth : float or Series.
              Solar azimuth angle.
  
          Returns
          -------
          aoi : Series or tuple of Series
              The angle of incidence
          """
  
          return tuple(array.get_aoi(solar_zenith, solar_azimuth)
                       for array in self.arrays)
  
      @_unwrap_single_value
      def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
                         dni_extra=None, airmass=None, albedo=None,
                         model='haydavies', **kwargs):
          """
          Uses the :py:func:`irradiance.get_total_irradiance` function to
          calculate the plane of array irradiance components on the tilted
          surfaces defined by each array's ``surface_tilt`` and
          ``surface_azimuth``.
  
          Parameters
          ----------
          solar_zenith : float or Series
              Solar zenith angle.
          solar_azimuth : float or Series
              Solar azimuth angle.
          dni : float or Series or tuple of float or Series
              Direct Normal Irradiance. [W/m2]
          ghi : float or Series or tuple of float or Series
              Global horizontal irradiance. [W/m2]
          dhi : float or Series or tuple of float or Series
              Diffuse horizontal irradiance. [W/m2]
          dni_extra : None, float, Series or tuple of float or Series,\
              default None
              Extraterrestrial direct normal irradiance. [W/m2]
          airmass : None, float or Series, default None
              Airmass. [unitless]
          albedo : None, float or Series, default None
              Ground surface albedo. [unitless]
          model : String, default 'haydavies'
              Irradiance model.
  
          kwargs
              Extra parameters passed to :func:`irradiance.get_total_irradiance`.
  
          Notes
          -----
          Each of `dni`, `ghi`, and `dni` parameters may be passed as a tuple
          to provide different irradiance for each array in the system. If not
          passed as a tuple then the same value is used for input to each Array.
          If passed as a tuple the length must be the same as the number of
          Arrays.
  
          Returns
          -------
          poa_irradiance : DataFrame or tuple of DataFrame
              Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse',
              'poa_sky_diffuse', 'poa_ground_diffuse'``.
  
          See also
          --------
          pvlib.irradiance.get_total_irradiance
          """
          dni = self._validate_per_array(dni, system_wide=True)
          ghi = self._validate_per_array(ghi, system_wide=True)
          dhi = self._validate_per_array(dhi, system_wide=True)
  
          albedo = self._validate_per_array(albedo, system_wide=True)
  
          return tuple(
              array.get_irradiance(solar_zenith, solar_azimuth,
                                   dni, ghi, dhi,
                                   dni_extra=dni_extra, airmass=airmass,
                                   albedo=albedo, model=model, **kwargs)
              for array, dni, ghi, dhi, albedo in zip(
                  self.arrays, dni, ghi, dhi, albedo
              )
          )
  
      @_unwrap_single_value
      def get_iam(self, aoi, iam_model='physical'):
          """
          Determine the incidence angle modifier using the method specified by
          ``iam_model``.
  
          Parameters for the selected IAM model are expected to be in
          ``PVSystem.module_parameters``. Default parameters are available for
          the 'physical', 'ashrae' and 'martin_ruiz' models.
  
          Parameters
          ----------
          aoi : numeric or tuple of numeric
              The angle of incidence in degrees.
  
          aoi_model : string, default 'physical'
              The IAM model to be used. Valid strings are 'physical', 'ashrae',
              'martin_ruiz', 'sapm' and 'interp'.
          Returns
          -------
          iam : numeric or tuple of numeric
              The AOI modifier.
  
          Raises
          ------
          ValueError
              if `iam_model` is not a valid model name.
          """
          aoi = self._validate_per_array(aoi)
          return tuple(array.get_iam(aoi, iam_model)
                       for array, aoi in zip(self.arrays, aoi))
  
      @_unwrap_single_value
      def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,
                               effective_irradiance=None):
          """
          Determine cell temperature using the method specified by ``model``.
  
          Parameters
          ----------
          poa_global : numeric or tuple of numeric
              Total incident irradiance in W/m^2.
  
          temp_air : numeric or tuple of numeric
              Ambient dry bulb temperature in degrees C.
  
          wind_speed : numeric or tuple of numeric
              Wind speed in m/s.
  
          model : str
              Supported models include ``'sapm'``, ``'pvsyst'``,
              ``'faiman'``, ``'fuentes'``, and ``'noct_sam'``
  
          effective_irradiance : numeric or tuple of numeric, optional
              The irradiance that is converted to photocurrent in W/m^2.
              Only used for some models.
  
          Returns
          -------
          numeric or tuple of numeric
              Values in degrees C.
  
          See Also
          --------
          Array.get_cell_temperature
  
          Notes
          -----
          The `temp_air` and `wind_speed` parameters may be passed as tuples
          to provide different values for each Array in the system. If passed as
          a tuple the length must be the same as the number of Arrays. If not
          passed as a tuple then the same value is used for each Array.
          """
          poa_global = self._validate_per_array(poa_global)
          temp_air = self._validate_per_array(temp_air, system_wide=True)
          wind_speed = self._validate_per_array(wind_speed, system_wide=True)
          # Not used for all models, but Array.get_cell_temperature handles it
          effective_irradiance = self._validate_per_array(effective_irradiance,
                                                          system_wide=True)
  
          return tuple(
              array.get_cell_temperature(poa_global, temp_air, wind_speed,
                                         model, effective_irradiance)
              for array, poa_global, temp_air, wind_speed, effective_irradiance
              in zip(
                  self.arrays, poa_global, temp_air, wind_speed,
                  effective_irradiance
              )
          )
  
      @_unwrap_single_value
      def calcparams_desoto(self, effective_irradiance, temp_cell):
          """
          Use the :py:func:`calcparams_desoto` function, the input
          parameters and ``self.module_parameters`` to calculate the
          module currents and resistances.
  
          Parameters
          ----------
          effective_irradiance : numeric or tuple of numeric
              The irradiance (W/m2) that is converted to photocurrent.
  
          temp_cell : float or Series or tuple of float or Series
              The average cell temperature of cells within a module in C.
  
          Returns
          -------
          See pvsystem.calcparams_desoto for details
          """
          effective_irradiance = self._validate_per_array(effective_irradiance)
          temp_cell = self._validate_per_array(temp_cell)
  
          build_kwargs = functools.partial(
              _build_kwargs,
              ['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',
               'R_s', 'alpha_sc', 'EgRef', 'dEgdT',
               'irrad_ref', 'temp_ref']
          )
  
          return tuple(
              calcparams_desoto(
                  effective_irradiance, temp_cell,
                  **build_kwargs(array.module_parameters)
              )
              for array, effective_irradiance, temp_cell
              in zip(self.arrays, effective_irradiance, temp_cell)
          )
  
      @_unwrap_single_value
      def calcparams_cec(self, effective_irradiance, temp_cell):
          """
          Use the :py:func:`calcparams_cec` function, the input
          parameters and ``self.module_parameters`` to calculate the
          module currents and resistances.
  
          Parameters
          ----------
          effective_irradiance : numeric or tuple of numeric
              The irradiance (W/m2) that is converted to photocurrent.
  
          temp_cell : float or Series or tuple of float or Series
              The average cell temperature of cells within a module in C.
  
          Returns
          -------
          See pvsystem.calcparams_cec for details
          """
          effective_irradiance = self._validate_per_array(effective_irradiance)
          temp_cell = self._validate_per_array(temp_cell)
  
          build_kwargs = functools.partial(
              _build_kwargs,
              ['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',
               'R_s', 'alpha_sc', 'Adjust', 'EgRef', 'dEgdT',
               'irrad_ref', 'temp_ref']
          )
  
          return tuple(
              calcparams_cec(
                  effective_irradiance, temp_cell,
                  **build_kwargs(array.module_parameters)
              )
              for array, effective_irradiance, temp_cell
              in zip(self.arrays, effective_irradiance, temp_cell)
          )
  
      @_unwrap_single_value
      def calcparams_pvsyst(self, effective_irradiance, temp_cell):
          """
          Use the :py:func:`calcparams_pvsyst` function, the input
          parameters and ``self.module_parameters`` to calculate the
          module currents and resistances.
  
          Parameters
          ----------
          effective_irradiance : numeric or tuple of numeric
              The irradiance (W/m2) that is converted to photocurrent.
  
          temp_cell : float or Series or tuple of float or Series
              The average cell temperature of cells within a module in C.
  
          Returns
          -------
          See pvsystem.calcparams_pvsyst for details
          """
          effective_irradiance = self._validate_per_array(effective_irradiance)
          temp_cell = self._validate_per_array(temp_cell)
  
          build_kwargs = functools.partial(
              _build_kwargs,
              ['gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref',
               'R_sh_ref', 'R_sh_0', 'R_sh_exp',
               'R_s', 'alpha_sc', 'EgRef',
               'irrad_ref', 'temp_ref',
               'cells_in_series']
          )
  
          return tuple(
              calcparams_pvsyst(
                  effective_irradiance, temp_cell,
                  **build_kwargs(array.module_parameters)
              )
              for array, effective_irradiance, temp_cell
              in zip(self.arrays, effective_irradiance, temp_cell)
          )
  
      @_unwrap_single_value
      def sapm(self, effective_irradiance, temp_cell):
          """
          Use the :py:func:`sapm` function, the input parameters,
          and ``self.module_parameters`` to calculate
          Voc, Isc, Ix, Ixx, Vmp, and Imp.
  
          Parameters
          ----------
          effective_irradiance : numeric or tuple of numeric
              The irradiance (W/m2) that is converted to photocurrent.
  
          temp_cell : float or Series or tuple of float or Series
              The average cell temperature of cells within a module in C.
  
          Returns
          -------
          See pvsystem.sapm for details
          """
          effective_irradiance = self._validate_per_array(effective_irradiance)
          temp_cell = self._validate_per_array(temp_cell)
  
          return tuple(
              sapm(effective_irradiance, temp_cell, array.module_parameters)
              for array, effective_irradiance, temp_cell
              in zip(self.arrays, effective_irradiance, temp_cell)
          )
  
      @_unwrap_single_value
      def sapm_spectral_loss(self, airmass_absolute):
          """
          Use the :py:func:`pvlib.spectrum.spectral_factor_sapm` function,
          the input parameters, and ``self.module_parameters`` to calculate F1.
  
          Parameters
          ----------
          airmass_absolute : numeric
              Absolute airmass.
  
          Returns
          -------
          F1 : numeric or tuple of numeric
              The SAPM spectral loss coefficient.
          """
          return tuple(
              spectrum.spectral_factor_sapm(airmass_absolute,
                                            array.module_parameters)
              for array in self.arrays
          )
  
      @_unwrap_single_value
      def sapm_effective_irradiance(self, poa_direct, poa_diffuse,
                                    airmass_absolute, aoi,
                                    reference_irradiance=1000):
          """
          Use the :py:func:`sapm_effective_irradiance` function, the input
          parameters, and ``self.module_parameters`` to calculate
          effective irradiance.
  
          Parameters
          ----------
          poa_direct : numeric or tuple of numeric
              The direct irradiance incident upon the module.  [W/m2]
  
          poa_diffuse : numeric or tuple of numeric
              The diffuse irradiance incident on module.  [W/m2]
  
          airmass_absolute : numeric
              Absolute airmass. [unitless]
  
          aoi : numeric or tuple of numeric
              Angle of incidence. [degrees]
  
          Returns
          -------
          effective_irradiance : numeric or tuple of numeric
              The SAPM effective irradiance. [W/m2]
          """
          poa_direct = self._validate_per_array(poa_direct)
          poa_diffuse = self._validate_per_array(poa_diffuse)
          aoi = self._validate_per_array(aoi)
          return tuple(
              sapm_effective_irradiance(
                  poa_direct, poa_diffuse, airmass_absolute, aoi,
                  array.module_parameters)
              for array, poa_direct, poa_diffuse, aoi
              in zip(self.arrays, poa_direct, poa_diffuse, aoi)
          )
  
      @_unwrap_single_value
      def first_solar_spectral_loss(self, pw, airmass_absolute):
          """
          Use :py:func:`pvlib.spectrum.spectral_factor_firstsolar` to
          calculate the spectral loss modifier. The model coefficients are
          specific to the module's cell type, and are determined by searching
          for one of the following keys in self.module_parameters (in order):
  
          - 'first_solar_spectral_coefficients' (user-supplied coefficients)
          - 'Technology' - a string describing the cell type, can be read from
            the CEC module parameter database
          - 'Material' - a string describing the cell type, can be read from
            the Sandia module database.
  
          Parameters
          ----------
          pw : array-like
              atmospheric precipitable water (cm).
  
          airmass_absolute : array-like
              absolute (pressure corrected) airmass.
  
          Returns
          -------
          modifier: array-like or tuple of array-like
              spectral mismatch factor (unitless) which can be multiplied
              with broadband irradiance reaching a module's cells to estimate
              effective irradiance, i.e., the irradiance that is converted to
              electrical current.
          """
          pw = self._validate_per_array(pw, system_wide=True)
  
          def _spectral_correction(array, pw):
              if 'first_solar_spectral_coefficients' in \
                      array.module_parameters.keys():
                  coefficients = \
                      array.module_parameters[
                          'first_solar_spectral_coefficients'
                      ]
                  module_type = None
              else:
                  module_type = array._infer_cell_type()
                  coefficients = None
  
              return spectrum.spectral_factor_firstsolar(
                  pw, airmass_absolute, module_type, coefficients
              )
          return tuple(
              itertools.starmap(_spectral_correction, zip(self.arrays, pw))
          )
  
      def singlediode(self, photocurrent, saturation_current,
                      resistance_series, resistance_shunt, nNsVth,
                      ivcurve_pnts=None):
          """Wrapper around the :py:func:`pvlib.pvsystem.singlediode` function.
  
          See :py:func:`pvsystem.singlediode` for details
          """
          return singlediode(photocurrent, saturation_current,
                             resistance_series, resistance_shunt, nNsVth,
                             ivcurve_pnts=ivcurve_pnts)
  
      def i_from_v(self, voltage, photocurrent, saturation_current,
                   resistance_series, resistance_shunt, nNsVth):
          """Wrapper around the :py:func:`pvlib.pvsystem.i_from_v` function.
  
          See :py:func:`pvlib.pvsystem.i_from_v` for details.
  
          .. versionchanged:: 0.10.0
             The function's arguments have been reordered.
          """
          return i_from_v(voltage, photocurrent, saturation_current,
                          resistance_series, resistance_shunt, nNsVth)
  
      def get_ac(self, model, p_dc, v_dc=None):
          r"""Calculates AC power from p_dc using the inverter model indicated
          by model and self.inverter_parameters.
  
          Parameters
          ----------
          model : str
              Must be one of 'sandia', 'adr', or 'pvwatts'.
          p_dc : numeric, or tuple, list or array of numeric
              DC power on each MPPT input of the inverter. Use tuple, list or
              array for inverters with multiple MPPT inputs. If type is array,
              p_dc must be 2d with axis 0 being the MPPT inputs. [W]
          v_dc : numeric, or tuple, list or array of numeric
              DC voltage on each MPPT input of the inverter. Required when
              model='sandia' or model='adr'. Use tuple, list or
              array for inverters with multiple MPPT inputs. If type is array,
              v_dc must be 2d with axis 0 being the MPPT inputs. [V]
  
          Returns
          -------
          power_ac : numeric
              AC power output for the inverter. [W]
  
          Raises
          ------
          ValueError
              If model is not one of 'sandia', 'adr' or 'pvwatts'.
          ValueError
              If model='adr' and the PVSystem has more than one array.
  
          See also
          --------
          pvlib.inverter.sandia
          pvlib.inverter.sandia_multi
          pvlib.inverter.adr
          pvlib.inverter.pvwatts
          pvlib.inverter.pvwatts_multi
          """
          model = model.lower()
          multiple_arrays = self.num_arrays > 1
          if model == 'sandia':
              p_dc = self._validate_per_array(p_dc)
              v_dc = self._validate_per_array(v_dc)
              if multiple_arrays:
                  return inverter.sandia_multi(
                      v_dc, p_dc, self.inverter_parameters)
              return inverter.sandia(v_dc[0], p_dc[0], self.inverter_parameters)
          elif model == 'pvwatts':
              kwargs = _build_kwargs(['eta_inv_nom', 'eta_inv_ref'],
                                     self.inverter_parameters)
              p_dc = self._validate_per_array(p_dc)
              if multiple_arrays:
                  return inverter.pvwatts_multi(
                      p_dc, self.inverter_parameters['pdc0'], **kwargs)
              return inverter.pvwatts(
                  p_dc[0], self.inverter_parameters['pdc0'], **kwargs)
          elif model == 'adr':
              if multiple_arrays:
                  raise ValueError(
                      'The adr inverter function cannot be used for an inverter',
                      ' with multiple MPPT inputs')
              # While this is only used for single-array systems, calling
              # _validate_per_arry lets us pass in singleton tuples.
              p_dc = self._validate_per_array(p_dc)
              v_dc = self._validate_per_array(v_dc)
              return inverter.adr(v_dc[0], p_dc[0], self.inverter_parameters)
          else:
              raise ValueError(
                  model + ' is not a valid AC power model.',
                  ' model must be one of "sandia", "adr" or "pvwatts"')
  
      @_unwrap_single_value
      def scale_voltage_current_power(self, data):
          """
          Scales the voltage, current, and power of the `data` DataFrame
          by `self.modules_per_string` and `self.strings_per_inverter`.
  
          Parameters
          ----------
          data: DataFrame or tuple of DataFrame
              May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',
              'i_sc', 'p_mp'`.
  
          Returns
          -------
          scaled_data: DataFrame or tuple of DataFrame
              A scaled copy of the input data.
          """
          data = self._validate_per_array(data)
          return tuple(
              scale_voltage_current_power(data,
                                          voltage=array.modules_per_string,
                                          current=array.strings)
              for array, data in zip(self.arrays, data)
          )
  
      @_unwrap_single_value
      def pvwatts_dc(self, g_poa_effective, temp_cell):
          """
          Calcuates DC power according to the PVWatts model using
          :py:func:`pvlib.pvsystem.pvwatts_dc`, `self.module_parameters['pdc0']`,
          and `self.module_parameters['gamma_pdc']`.
  
          See :py:func:`pvlib.pvsystem.pvwatts_dc` for details.
          """
          g_poa_effective = self._validate_per_array(g_poa_effective)
          temp_cell = self._validate_per_array(temp_cell)
          return tuple(
              pvwatts_dc(g_poa_effective, temp_cell,
                         array.module_parameters['pdc0'],
                         array.module_parameters['gamma_pdc'],
                         **_build_kwargs(['temp_ref'], array.module_parameters))
              for array, g_poa_effective, temp_cell
              in zip(self.arrays, g_poa_effective, temp_cell)
          )
  
      def pvwatts_losses(self):
          """
          Calculates DC power losses according the PVwatts model using
          :py:func:`pvlib.pvsystem.pvwatts_losses` and
          ``self.losses_parameters``.
  
          See :py:func:`pvlib.pvsystem.pvwatts_losses` for details.
          """
          kwargs = _build_kwargs(['soiling', 'shading', 'snow', 'mismatch',
                                  'wiring', 'connections', 'lid',
                                  'nameplate_rating', 'age', 'availability'],
                                 self.losses_parameters)
          return pvwatts_losses(**kwargs)
  
      @_unwrap_single_value
      def dc_ohms_from_percent(self):
          """
          Calculates the equivalent resistance of the wires for each array using
          :py:func:`pvlib.pvsystem.dc_ohms_from_percent`
  
          See :py:func:`pvlib.pvsystem.dc_ohms_from_percent` for details.
          """
  
          return tuple(array.dc_ohms_from_percent() for array in self.arrays)
  
      @property
      def num_arrays(self):
          """The number of Arrays in the system."""
          return len(self.arrays)
  
  
  class Array:
      """
      An Array is a set of of modules at the same orientation.
  
      Specifically, an array is defined by its mount, the
      module parameters, the number of parallel strings of modules
      and the number of modules on each string.
  
      Parameters
      ----------
      mount: FixedMount, SingleAxisTrackerMount, or other
          Mounting for the array, either on fixed-tilt racking or horizontal
          single axis tracker. Mounting is used to determine module orientation.
          If not provided, a FixedMount with zero tilt is used.
  
      albedo : None or float, default None
          Ground surface albedo. If ``None``, then ``surface_type`` is used
          to look up a value in ``irradiance.SURFACE_ALBEDOS``.
          If ``surface_type`` is also None then a ground surface albedo
          of 0.25 is used.
  
      surface_type : None or string, default None
          The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for valid
          values.
  
      module : None or string, default None
          The model name of the modules.
          May be used to look up the module_parameters dictionary
          via some other method.
  
      module_type : None or string, default None
           Describes the module's construction. Valid strings are 'glass_polymer'
           and 'glass_glass'. Used for cell and module temperature calculations.
  
      module_parameters : None, dict or Series, default None
          Parameters for the module model, e.g., SAPM, CEC, or other.
  
      temperature_model_parameters : None, dict or Series, default None.
          Parameters for the module temperature model, e.g., SAPM, Pvsyst, or
          other.
  
      modules_per_string: int, default 1
          Number of modules per string in the array.
  
      strings: int, default 1
          Number of parallel strings in the array.
  
      array_losses_parameters: None, dict or Series, default None.
          Supported keys are 'dc_ohmic_percent'.
  
      name: None or str, default None
          Name of Array instance.
      """
  
      def __init__(self, mount,
                   albedo=None, surface_type=None,
                   module=None, module_type=None,
                   module_parameters=None,
                   temperature_model_parameters=None,
                   modules_per_string=1, strings=1,
                   array_losses_parameters=None,
                   name=None):
          self.mount = mount
  
          self.surface_type = surface_type
          if albedo is None:
              self.albedo = irradiance.SURFACE_ALBEDOS.get(surface_type, 0.25)
          else:
              self.albedo = albedo
  
          self.module = module
          if module_parameters is None:
              self.module_parameters = {}
          else:
              self.module_parameters = module_parameters
  
          self.module_type = module_type
  
          self.strings = strings
          self.modules_per_string = modules_per_string
  
          if temperature_model_parameters is None:
              self.temperature_model_parameters = \
                  self._infer_temperature_model_params()
          else:
              self.temperature_model_parameters = temperature_model_parameters
  
          if array_losses_parameters is None:
              self.array_losses_parameters = {}
          else:
              self.array_losses_parameters = array_losses_parameters
  
          self.name = name
  
      def __repr__(self):
          attrs = ['name', 'mount', 'module',
                   'albedo', 'module_type',
                   'temperature_model_parameters',
                   'strings', 'modules_per_string']
  
          return 'Array:\n  ' + '\n  '.join(
              f'{attr}: {getattr(self, attr)}' for attr in attrs
          )
  
      def _infer_temperature_model_params(self):
          # try to infer temperature model parameters from from racking_model
          # and module_type
          param_set = f'{self.mount.racking_model}_{self.module_type}'
          if param_set in temperature.TEMPERATURE_MODEL_PARAMETERS['sapm']:
              return temperature._temperature_model_params('sapm', param_set)
          elif 'freestanding' in param_set:
              return temperature._temperature_model_params('pvsyst',
                                                           'freestanding')
          elif 'insulated' in param_set:  # after SAPM to avoid confusing keys
              return temperature._temperature_model_params('pvsyst',
                                                           'insulated')
          else:
              return {}
  
      def _infer_cell_type(self):
          """
          Examines module_parameters and maps the Technology key for the CEC
          database and the Material key for the Sandia database to a common
          list of strings for cell type.
  
          Returns
          -------
          cell_type: str
  
          """
  
          _cell_type_dict = {'Multi-c-Si': 'multisi',
                             'Mono-c-Si': 'monosi',
                             'Thin Film': 'cigs',
                             'a-Si/nc': 'asi',
                             'CIS': 'cigs',
                             'CIGS': 'cigs',
                             '1-a-Si': 'asi',
                             'CdTe': 'cdte',
                             'a-Si': 'asi',
                             '2-a-Si': None,
                             '3-a-Si': None,
                             'HIT-Si': 'monosi',
                             'mc-Si': 'multisi',
                             'c-Si': 'multisi',
                             'Si-Film': 'asi',
                             'EFG mc-Si': 'multisi',
                             'GaAs': None,
                             'a-Si / mono-Si': 'monosi'}
  
          if 'Technology' in self.module_parameters.keys():
              # CEC module parameter set
              cell_type = _cell_type_dict[self.module_parameters['Technology']]
          elif 'Material' in self.module_parameters.keys():
              # Sandia module parameter set
              cell_type = _cell_type_dict[self.module_parameters['Material']]
          else:
              cell_type = None
  
          return cell_type
  
      def get_aoi(self, solar_zenith, solar_azimuth):
          """
          Get the angle of incidence on the array.
  
          Parameters
          ----------
          solar_zenith : float or Series
              Solar zenith angle.
          solar_azimuth : float or Series
              Solar azimuth angle
  
          Returns
          -------
          aoi : Series
              Then angle of incidence.
          """
          orientation = self.mount.get_orientation(solar_zenith, solar_azimuth)
          return irradiance.aoi(orientation['surface_tilt'],
                                orientation['surface_azimuth'],
                                solar_zenith, solar_azimuth)
  
      def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
                         dni_extra=None, airmass=None, albedo=None,
                         model='haydavies', **kwargs):
          """
          Get plane of array irradiance components.
  
          Uses the :py:func:`pvlib.irradiance.get_total_irradiance` function to
          calculate the plane of array irradiance components for a surface
          defined by ``self.surface_tilt`` and ``self.surface_azimuth``.
  
          Parameters
          ----------
          solar_zenith : float or Series.
              Solar zenith angle.
          solar_azimuth : float or Series.
              Solar azimuth angle.
          dni : float or Series
              Direct normal irradiance. [W/m2]
          ghi : float or Series. [W/m2]
              Global horizontal irradiance
          dhi : float or Series
              Diffuse horizontal irradiance. [W/m2]
          dni_extra : None, float or Series, default None
              Extraterrestrial direct normal irradiance. [W/m2]
          airmass : None, float or Series, default None
              Airmass. [unitless]
          albedo : None, float or Series, default None
              Ground surface albedo. [unitless]
          model : String, default 'haydavies'
              Irradiance model.
  
          kwargs
              Extra parameters passed to
              :py:func:`pvlib.irradiance.get_total_irradiance`.
  
          Returns
          -------
          poa_irradiance : DataFrame
              Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse',
              'poa_sky_diffuse', 'poa_ground_diffuse'``.
  
          See also
          --------
          :py:func:`pvlib.irradiance.get_total_irradiance`
          """
          if albedo is None:
              albedo = self.albedo
  
          # not needed for all models, but this is easier
          if dni_extra is None:
              dni_extra = irradiance.get_extra_radiation(solar_zenith.index)
  
          if airmass is None:
              airmass = atmosphere.get_relative_airmass(solar_zenith)
  
          orientation = self.mount.get_orientation(solar_zenith, solar_azimuth)
          return irradiance.get_total_irradiance(orientation['surface_tilt'],
                                                 orientation['surface_azimuth'],
                                                 solar_zenith, solar_azimuth,
                                                 dni, ghi, dhi,
                                                 dni_extra=dni_extra,
                                                 airmass=airmass,
                                                 albedo=albedo,
                                                 model=model,
                                                 **kwargs)
  
      def get_iam(self, aoi, iam_model='physical'):
          """
          Determine the incidence angle modifier using the method specified by
          ``iam_model``.
  
          Parameters for the selected IAM model are expected to be in
          ``Array.module_parameters``. Default parameters are available for
          the 'physical', 'ashrae' and 'martin_ruiz' models.
  
          Parameters
          ----------
          aoi : numeric
              The angle of incidence in degrees.
  
          aoi_model : string, default 'physical'
              The IAM model to be used. Valid strings are 'physical', 'ashrae',
              'martin_ruiz', 'sapm' and 'interp'.
  
          Returns
          -------
          iam : numeric
              The AOI modifier.
  
          Raises
          ------
          ValueError
              if `iam_model` is not a valid model name.
          """
          model = iam_model.lower()
          if model in ['ashrae', 'physical', 'martin_ruiz', 'interp']:
              func = getattr(iam, model)  # get function at pvlib.iam
              # get all parameters from function signature to retrieve them from
              # module_parameters if present
              params = set(inspect.signature(func).parameters.keys())
              params.discard('aoi')  # exclude aoi so it can't be repeated
              kwargs = _build_kwargs(params, self.module_parameters)
              return func(aoi, **kwargs)
          elif model == 'sapm':
              return iam.sapm(aoi, self.module_parameters)
          else:
              raise ValueError(model + ' is not a valid IAM model')
  
      def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,
                               effective_irradiance=None):
          """
          Determine cell temperature using the method specified by ``model``.
  
          Parameters
          ----------
          poa_global : numeric
              Total incident irradiance [W/m^2]
  
          temp_air : numeric
              Ambient dry bulb temperature [C]
  
          wind_speed : numeric
              Wind speed [m/s]
  
          model : str
              Supported models include ``'sapm'``, ``'pvsyst'``,
              ``'faiman'``, ``'fuentes'``, and ``'noct_sam'``
  
          effective_irradiance : numeric, optional
              The irradiance that is converted to photocurrent in W/m^2.
              Only used for some models.
  
          Returns
          -------
          numeric
              Values in degrees C.
  
          See Also
          --------
          pvlib.temperature.sapm_cell, pvlib.temperature.pvsyst_cell,
          pvlib.temperature.faiman, pvlib.temperature.fuentes,
          pvlib.temperature.noct_sam
  
          Notes
          -----
          Some temperature models have requirements for the input types;
          see the documentation of the underlying model function for details.
          """
          # convenience wrapper to avoid passing args 2 and 3 every call
          _build_tcell_args = functools.partial(
              _build_args, input_dict=self.temperature_model_parameters,
              dict_name='temperature_model_parameters')
  
          if model == 'sapm':
              func = temperature.sapm_cell
              required = _build_tcell_args(['a', 'b', 'deltaT'])
              optional = _build_kwargs(['irrad_ref'],
                                       self.temperature_model_parameters)
          elif model == 'pvsyst':
              func = temperature.pvsyst_cell
              required = tuple()
              optional = {
                  **_build_kwargs(['module_efficiency', 'alpha_absorption'],
                                  self.module_parameters),
                  **_build_kwargs(['u_c', 'u_v'],
                                  self.temperature_model_parameters)
              }
          elif model == 'faiman':
              func = temperature.faiman
              required = tuple()
              optional = _build_kwargs(['u0', 'u1'],
                                       self.temperature_model_parameters)
          elif model == 'fuentes':
              func = temperature.fuentes
              required = _build_tcell_args(['noct_installed'])
              optional = _build_kwargs([
                  'wind_height', 'emissivity', 'absorption',
                  'surface_tilt', 'module_width', 'module_length'],
                  self.temperature_model_parameters)
              if self.mount.module_height is not None:
                  optional['module_height'] = self.mount.module_height
          elif model == 'noct_sam':
              func = functools.partial(temperature.noct_sam,
                                       effective_irradiance=effective_irradiance)
              required = _build_tcell_args(['noct', 'module_efficiency'])
              optional = _build_kwargs(['transmittance_absorptance',
                                        'array_height', 'mount_standoff'],
                                       self.temperature_model_parameters)
          else:
              raise ValueError(f'{model} is not a valid cell temperature model')
  
          temperature_cell = func(poa_global, temp_air, wind_speed,
                                  *required, **optional)
          return temperature_cell
  
      def dc_ohms_from_percent(self):
          """
          Calculates the equivalent resistance of the wires using
          :py:func:`pvlib.pvsystem.dc_ohms_from_percent`
  
          Makes use of array module parameters according to the
          following DC models:
  
          CEC:
  
              * `self.module_parameters["V_mp_ref"]`
              * `self.module_parameters["I_mp_ref"]`
  
          SAPM:
  
              * `self.module_parameters["Vmpo"]`
              * `self.module_parameters["Impo"]`
  
          PVsyst-like or other:
  
              * `self.module_parameters["Vmpp"]`
              * `self.module_parameters["Impp"]`
  
          Other array parameters that are used are:
          `self.losses_parameters["dc_ohmic_percent"]`,
          `self.modules_per_string`, and
          `self.strings`.
  
          See :py:func:`pvlib.pvsystem.dc_ohms_from_percent` for more details.
          """
  
          # get relevent Vmp and Imp parameters from CEC parameters
          if all(elem in self.module_parameters
                 for elem in ['V_mp_ref', 'I_mp_ref']):
              vmp_ref = self.module_parameters['V_mp_ref']
              imp_ref = self.module_parameters['I_mp_ref']
  
          # get relevant Vmp and Imp parameters from SAPM parameters
          elif all(elem in self.module_parameters for elem in ['Vmpo', 'Impo']):
              vmp_ref = self.module_parameters['Vmpo']
              imp_ref = self.module_parameters['Impo']
  
          # get relevant Vmp and Imp parameters if they are PVsyst-like
          elif all(elem in self.module_parameters for elem in ['Vmpp', 'Impp']):
              vmp_ref = self.module_parameters['Vmpp']
              imp_ref = self.module_parameters['Impp']
  
          # raise error if relevant Vmp and Imp parameters are not found
          else:
              raise ValueError('Parameters for Vmp and Imp could not be found '
                               'in the array module parameters. Module '
                               'parameters must include one set of '
                               '{"V_mp_ref", "I_mp_Ref"}, '
                               '{"Vmpo", "Impo"}, or '
                               '{"Vmpp", "Impp"}.'
                               )
  
          return dc_ohms_from_percent(
              vmp_ref,
              imp_ref,
              self.array_losses_parameters['dc_ohmic_percent'],
              self.modules_per_string,
              self.strings)
  
  
  @dataclass
  class AbstractMount(ABC):
      """
      A base class for Mount classes to extend. It is not intended to be
      instantiated directly.
      """
  
      @abstractmethod
      def get_orientation(self, solar_zenith, solar_azimuth):
          """
          Determine module orientation.
  
          Parameters
          ----------
          solar_zenith : numeric
              Solar apparent zenith angle [degrees]
          solar_azimuth : numeric
              Solar azimuth angle [degrees]
  
          Returns
          -------
          orientation : dict-like
              A dict-like object with keys `'surface_tilt', 'surface_azimuth'`
              (typically a dict or pandas.DataFrame)
          """
  
  
  @dataclass
  class FixedMount(AbstractMount):
      """
      Racking at fixed (static) orientation.
  
      Parameters
      ----------
      surface_tilt : float, default 0
          Surface tilt angle. The tilt angle is defined as angle from horizontal
          (e.g. surface facing up = 0, surface facing horizon = 90) [degrees]
  
      surface_azimuth : float, default 180
          Azimuth angle of the module surface. North=0, East=90, South=180,
          West=270. [degrees]
  
      racking_model : str, optional
          Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.
          Used to identify a parameter set for the SAPM cell temperature model.
  
      module_height : float, optional
         The height above ground of the center of the module [m]. Used for
         the Fuentes cell temperature model.
      """
  
      surface_tilt: float = 0.0
      surface_azimuth: float = 180.0
      racking_model: Optional[str] = None
      module_height: Optional[float] = None
  
      def get_orientation(self, solar_zenith, solar_azimuth):
          # note -- docstring is automatically inherited from AbstractMount
          return {
              'surface_tilt': self.surface_tilt,
              'surface_azimuth': self.surface_azimuth,
          }
  
  
  @dataclass
  class SingleAxisTrackerMount(AbstractMount):
      """
      Single-axis tracker racking for dynamic solar tracking.
  
      Parameters
      ----------
      axis_tilt : float, default 0
          The tilt of the axis of rotation (i.e, the y-axis defined by
          axis_azimuth) with respect to horizontal. [degrees]
  
      axis_azimuth : float, default 180
          A value denoting the compass direction along which the axis of
          rotation lies, measured east of north. [degrees]
  
      max_angle : float, default 90
          A value denoting the maximum rotation angle
          of the one-axis tracker from its horizontal position (horizontal
          if axis_tilt = 0). A max_angle of 90 degrees allows the tracker
          to rotate to a vertical position to point the panel towards a
          horizon. max_angle of 180 degrees allows for full rotation. [degrees]
  
      backtrack : bool, default True
          Controls whether the tracker has the capability to "backtrack"
          to avoid row-to-row shading. False denotes no backtrack
          capability. True denotes backtrack capability.
  
      gcr : float, default 2.0/7.0
          A value denoting the ground coverage ratio of a tracker system
          which utilizes backtracking; i.e. the ratio between the PV array
          surface area to total ground area. A tracker system with modules
          2 meters wide, centered on the tracking axis, with 6 meters
          between the tracking axes has a gcr of 2/6=0.333. If gcr is not
          provided, a gcr of 2/7 is default. gcr must be <=1. [unitless]
  
      cross_axis_tilt : float, default 0.0
          The angle, relative to horizontal, of the line formed by the
          intersection between the slope containing the tracker axes and a plane
          perpendicular to the tracker axes. Cross-axis tilt should be specified
          using a right-handed convention. For example, trackers with axis
          azimuth of 180 degrees (heading south) will have a negative cross-axis
          tilt if the tracker axes plane slopes down to the east and positive
          cross-axis tilt if the tracker axes plane slopes up to the east. Use
          :func:`~pvlib.tracking.calc_cross_axis_tilt` to calculate
          `cross_axis_tilt`. [degrees]
  
      racking_model : str, optional
          Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.
          Used to identify a parameter set for the SAPM cell temperature model.
  
      module_height : float, optional
         The height above ground of the center of the module [m]. Used for
         the Fuentes cell temperature model.
      """
      axis_tilt: float = 0.0
      axis_azimuth: float = 0.0
      max_angle: float = 90.0
      backtrack: bool = True
      gcr: float = 2.0/7.0
      cross_axis_tilt: float = 0.0
      racking_model: Optional[str] = None
      module_height: Optional[float] = None
  
      def get_orientation(self, solar_zenith, solar_azimuth):
          # note -- docstring is automatically inherited from AbstractMount
          from pvlib import tracking  # avoid circular import issue
          tracking_data = tracking.singleaxis(
              solar_zenith, solar_azimuth,
              self.axis_tilt, self.axis_azimuth,
              self.max_angle, self.backtrack,
              self.gcr, self.cross_axis_tilt
          )
          return tracking_data
  
  
  def calcparams_desoto(effective_irradiance, temp_cell,
                        alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s,
                        EgRef=1.121, dEgdT=-0.0002677,
                        irrad_ref=1000, temp_ref=25):
      '''
      Calculates five parameter values for the single diode equation at
      effective irradiance and cell temperature using the De Soto et al.
      model described in [1]_. The five values returned by calcparams_desoto
      can be used by singlediode to calculate an IV curve.
  
      Parameters
      ----------
      effective_irradiance : numeric
          The irradiance (W/m2) that is converted to photocurrent.
  
      temp_cell : numeric
          The average cell temperature of cells within a module in C.
  
      alpha_sc : float
          The short-circuit current temperature coefficient of the
          module in units of A/C.
  
      a_ref : float
          The product of the usual diode ideality factor (n, unitless),
          number of cells in series (Ns), and cell thermal voltage at reference
          conditions, in units of V.
  
      I_L_ref : float
          The light-generated current (or photocurrent) at reference conditions,
          in amperes.
  
      I_o_ref : float
          The dark or diode reverse saturation current at reference conditions,
          in amperes.
  
      R_sh_ref : float
          The shunt resistance at reference conditions, in ohms.
  
      R_s : float
          The series resistance at reference conditions, in ohms.
  
      EgRef : float
          The energy bandgap at reference temperature in units of eV.
          1.121 eV for crystalline silicon. EgRef must be >0.  For parameters
          from the SAM CEC module database, EgRef=1.121 is implicit for all
          cell types in the parameter estimation algorithm used by NREL.
  
      dEgdT : float
          The temperature dependence of the energy bandgap at reference
          conditions in units of 1/K. May be either a scalar value
          (e.g. -0.0002677 as in [1]_) or a DataFrame (this may be useful if
          dEgdT is a modeled as a function of temperature). For parameters from
          the SAM CEC module database, dEgdT=-0.0002677 is implicit for all cell
          types in the parameter estimation algorithm used by NREL.
  
      irrad_ref : float (optional, default=1000)
          Reference irradiance in W/m^2.
  
      temp_ref : float (optional, default=25)
          Reference cell temperature in C.
  
      Returns
      -------
      Tuple of the following results:
  
      photocurrent : numeric
          Light-generated current in amperes
  
      saturation_current : numeric
          Diode saturation curent in amperes
  
      resistance_series : numeric
          Series resistance in ohms
  
      resistance_shunt : numeric
          Shunt resistance in ohms
  
      nNsVth : numeric
          The product of the usual diode ideality factor (n, unitless),
          number of cells in series (Ns), and cell thermal voltage at
          specified effective irradiance and cell temperature.
  
      References
      ----------
      .. [1] W. De Soto et al., "Improvement and validation of a model for
         photovoltaic array performance", Solar Energy, vol 80, pp. 78-88,
         2006.
  
      .. [2] System Advisor Model web page. https://sam.nrel.gov.
  
      .. [3] A. Dobos, "An Improved Coefficient Calculator for the California
         Energy Commission 6 Parameter Photovoltaic Module Model", Journal of
         Solar Energy Engineering, vol 134, 2012.
  
      .. [4] O. Madelung, "Semiconductors: Data Handbook, 3rd ed." ISBN
         3-540-40488-0
  
      See Also
      --------
      singlediode
      retrieve_sam
  
      Notes
      -----
      If the reference parameters in the ModuleParameters struct are read
      from a database or library of parameters (e.g. System Advisor
      Model), it is important to use the same EgRef and dEgdT values that
      were used to generate the reference parameters, regardless of the
      actual bandgap characteristics of the semiconductor. For example, in
      the case of the System Advisor Model library, created as described
      in [3], EgRef and dEgdT for all modules were 1.121 and -0.0002677,
      respectively.
  
      This table of reference bandgap energies (EgRef), bandgap energy
      temperature dependence (dEgdT), and "typical" airmass response (M)
      is provided purely as reference to those who may generate their own
      reference module parameters (a_ref, IL_ref, I0_ref, etc.) based upon
      the various PV semiconductors. Again, we stress the importance of
      using identical EgRef and dEgdT when generation reference parameters
      and modifying the reference parameters (for irradiance, temperature,
      and airmass) per DeSoto's equations.
  
       Crystalline Silicon (Si):
           * EgRef = 1.121
           * dEgdT = -0.0002677
  
           >>> M = np.polyval([-1.26E-4, 2.816E-3, -0.024459, 0.086257, 0.9181],
           ...                AMa) # doctest: +SKIP
  
           Source: [1]
  
       Cadmium Telluride (CdTe):
           * EgRef = 1.475
           * dEgdT = -0.0003
  
           >>> M = np.polyval([-2.46E-5, 9.607E-4, -0.0134, 0.0716, 0.9196],
           ...                AMa) # doctest: +SKIP
  
           Source: [4]
  
       Copper Indium diSelenide (CIS):
           * EgRef = 1.010
           * dEgdT = -0.00011
  
           >>> M = np.polyval([-3.74E-5, 0.00125, -0.01462, 0.0718, 0.9210],
           ...                AMa) # doctest: +SKIP
  
           Source: [4]
  
       Copper Indium Gallium diSelenide (CIGS):
           * EgRef = 1.15
           * dEgdT = ????
  
           >>> M = np.polyval([-9.07E-5, 0.0022, -0.0202, 0.0652, 0.9417],
           ...                AMa) # doctest: +SKIP
  
           Source: Wikipedia
  
       Gallium Arsenide (GaAs):
           * EgRef = 1.424
           * dEgdT = -0.000433
           * M = unknown
  
           Source: [4]
      '''
  
      # Boltzmann constant in eV/K, 8.617332478e-05
      k = constants.value('Boltzmann constant in eV/K')
  
      # reference temperature
      Tref_K = temp_ref + 273.15
      Tcell_K = temp_cell + 273.15
  
      E_g = EgRef * (1 + dEgdT*(Tcell_K - Tref_K))
  
      nNsVth = a_ref * (Tcell_K / Tref_K)
  
      # In the equation for IL, the single factor effective_irradiance is
      # used, in place of the product S*M in [1]. effective_irradiance is
      # equivalent to the product of S (irradiance reaching a module's cells) *
      # M (spectral adjustment factor) as described in [1].
      IL = effective_irradiance / irrad_ref * \
          (I_L_ref + alpha_sc * (Tcell_K - Tref_K))
      I0 = (I_o_ref * ((Tcell_K / Tref_K) ** 3) *
            (np.exp(EgRef / (k*(Tref_K)) - (E_g / (k*(Tcell_K))))))
      # Note that the equation for Rsh differs from [1]. In [1] Rsh is given as
      # Rsh = Rsh_ref * (S_ref / S) where S is broadband irradiance reaching
      # the module's cells. If desired this model behavior can be duplicated
      # by applying reflection and soiling losses to broadband plane of array
      # irradiance and not applying a spectral loss modifier, i.e.,
      # spectral_modifier = 1.0.
      # use errstate to silence divide by warning
      with np.errstate(divide='ignore'):
          Rsh = R_sh_ref * (irrad_ref / effective_irradiance)
  
      Rs = R_s
  
      numeric_args = (effective_irradiance, temp_cell)
      out = (IL, I0, Rs, Rsh, nNsVth)
  
      if all(map(np.isscalar, numeric_args)):
          return out
  
      index = tools.get_pandas_index(*numeric_args)
  
      if index is None:
          return np.broadcast_arrays(*out)
  
      return tuple(pd.Series(a, index=index).rename(None) for a in out)
  
  
  def calcparams_cec(effective_irradiance, temp_cell,
                     alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s,
                     Adjust, EgRef=1.121, dEgdT=-0.0002677,
                     irrad_ref=1000, temp_ref=25):
      '''
      Calculates five parameter values for the single diode equation at
      effective irradiance and cell temperature using the CEC
      model. The CEC model [1]_ differs from the De soto et al.
      model [3]_ by the parameter Adjust. The five values returned by
      calcparams_cec can be used by singlediode to calculate an IV curve.
  
      Parameters
      ----------
      effective_irradiance : numeric
          The irradiance (W/m2) that is converted to photocurrent.
  
      temp_cell : numeric
          The average cell temperature of cells within a module in C.
  
      alpha_sc : float
          The short-circuit current temperature coefficient of the
          module in units of A/C.
  
      a_ref : float
          The product of the usual diode ideality factor (n, unitless),
          number of cells in series (Ns), and cell thermal voltage at reference
          conditions, in units of V.
  
      I_L_ref : float
          The light-generated current (or photocurrent) at reference conditions,
          in amperes.
  
      I_o_ref : float
          The dark or diode reverse saturation current at reference conditions,
          in amperes.
  
      R_sh_ref : float
          The shunt resistance at reference conditions, in ohms.
  
      R_s : float
          The series resistance at reference conditions, in ohms.
  
      Adjust : float
          The adjustment to the temperature coefficient for short circuit
          current, in percent
  
      EgRef : float
          The energy bandgap at reference temperature in units of eV.
          1.121 eV for crystalline silicon. EgRef must be >0.  For parameters
          from the SAM CEC module database, EgRef=1.121 is implicit for all
          cell types in the parameter estimation algorithm used by NREL.
  
      dEgdT : float
          The temperature dependence of the energy bandgap at reference
          conditions in units of 1/K. May be either a scalar value
          (e.g. -0.0002677 as in [3]) or a DataFrame (this may be useful if
          dEgdT is a modeled as a function of temperature). For parameters from
          the SAM CEC module database, dEgdT=-0.0002677 is implicit for all cell
          types in the parameter estimation algorithm used by NREL.
  
      irrad_ref : float (optional, default=1000)
          Reference irradiance in W/m^2.
  
      temp_ref : float (optional, default=25)
          Reference cell temperature in C.
  
      Returns
      -------
      Tuple of the following results:
  
      photocurrent : numeric
          Light-generated current in amperes
  
      saturation_current : numeric
          Diode saturation curent in amperes
  
      resistance_series : numeric
          Series resistance in ohms
  
      resistance_shunt : numeric
          Shunt resistance in ohms
  
      nNsVth : numeric
          The product of the usual diode ideality factor (n, unitless),
          number of cells in series (Ns), and cell thermal voltage at
          specified effective irradiance and cell temperature.
  
      References
      ----------
      .. [1] A. Dobos, "An Improved Coefficient Calculator for the California
         Energy Commission 6 Parameter Photovoltaic Module Model", Journal of
         Solar Energy Engineering, vol 134, 2012.
  
      .. [2] System Advisor Model web page. https://sam.nrel.gov.
  
      .. [3] W. De Soto et al., "Improvement and validation of a model for
         photovoltaic array performance", Solar Energy, vol 80, pp. 78-88,
         2006.
  
      See Also
      --------
      calcparams_desoto
      singlediode
      retrieve_sam
  
      '''
  
      # pass adjusted temperature coefficient to desoto
      return calcparams_desoto(effective_irradiance, temp_cell,
                               alpha_sc*(1.0 - Adjust/100),
                               a_ref, I_L_ref, I_o_ref,
                               R_sh_ref, R_s,
                               EgRef=EgRef, dEgdT=dEgdT,
                               irrad_ref=irrad_ref, temp_ref=temp_ref)
  
  
  def calcparams_pvsyst(effective_irradiance, temp_cell,
                        alpha_sc, gamma_ref, mu_gamma,
                        I_L_ref, I_o_ref,
                        R_sh_ref, R_sh_0, R_s,
                        cells_in_series,
                        R_sh_exp=5.5,
                        EgRef=1.121,
                        irrad_ref=1000, temp_ref=25):
      '''
      Calculates five parameter values for the single diode equation at
      effective irradiance and cell temperature using the PVsyst v6
      model.  The PVsyst v6 model is described in [1]_, [2]_, [3]_.
      The five values returned by calcparams_pvsyst can be used by singlediode
      to calculate an IV curve.
  
      Parameters
      ----------
      effective_irradiance : numeric
          The irradiance (W/m2) that is converted to photocurrent.
  
      temp_cell : numeric
          The average cell temperature of cells within a module in C.
  
      alpha_sc : float
          The short-circuit current temperature coefficient of the
          module in units of A/C.
  
      gamma_ref : float
          The diode ideality factor
  
      mu_gamma : float
          The temperature coefficient for the diode ideality factor, 1/K
  
      I_L_ref : float
          The light-generated current (or photocurrent) at reference conditions,
          in amperes.
  
      I_o_ref : float
          The dark or diode reverse saturation current at reference conditions,
          in amperes.
  
      R_sh_ref : float
          The shunt resistance at reference conditions, in ohms.
  
      R_sh_0 : float
          The shunt resistance at zero irradiance conditions, in ohms.
  
      R_s : float
          The series resistance at reference conditions, in ohms.
  
      cells_in_series : integer
          The number of cells connected in series.
  
      R_sh_exp : float
          The exponent in the equation for shunt resistance, unitless. Defaults
          to 5.5.
  
      EgRef : float
          The energy bandgap at reference temperature in units of eV.
          1.121 eV for crystalline silicon. EgRef must be >0.
  
      irrad_ref : float (optional, default=1000)
          Reference irradiance in W/m^2.
  
      temp_ref : float (optional, default=25)
          Reference cell temperature in C.
  
      Returns
      -------
      Tuple of the following results:
  
      photocurrent : numeric
          Light-generated current in amperes
  
      saturation_current : numeric
          Diode saturation current in amperes
  
      resistance_series : numeric
          Series resistance in ohms
  
      resistance_shunt : numeric
          Shunt resistance in ohms
  
      nNsVth : numeric
          The product of the usual diode ideality factor (n, unitless),
          number of cells in series (Ns), and cell thermal voltage at
          specified effective irradiance and cell temperature.
  
      References
      ----------
      .. [1] K. Sauer, T. Roessler, C. W. Hansen, Modeling the Irradiance and
         Temperature Dependence of Photovoltaic Modules in PVsyst,
         IEEE Journal of Photovoltaics v5(1), January 2015.
  
      .. [2] A. Mermoud, PV modules modelling, Presentation at the 2nd PV
         Performance Modeling Workshop, Santa Clara, CA, May 2013
  
      .. [3] A. Mermoud, T. Lejeune, Performance Assessment of a Simulation Model
         for PV modules of any available technology, 25th European Photovoltaic
         Solar Energy Conference, Valencia, Spain, Sept. 2010
  
      See Also
      --------
      calcparams_desoto
      singlediode
  
      '''
  
      # Boltzmann constant in J/K
      k = constants.k
  
      # elementary charge in coulomb
      q = constants.e
  
      # reference temperature
      Tref_K = temp_ref + 273.15
      Tcell_K = temp_cell + 273.15
  
      gamma = gamma_ref + mu_gamma * (Tcell_K - Tref_K)
      nNsVth = gamma * k / q * cells_in_series * Tcell_K
  
      IL = effective_irradiance / irrad_ref * \
          (I_L_ref + alpha_sc * (Tcell_K - Tref_K))
  
      I0 = I_o_ref * ((Tcell_K / Tref_K) ** 3) * \
          (np.exp((q * EgRef) / (k * gamma) * (1 / Tref_K - 1 / Tcell_K)))
  
      Rsh_tmp = \
          (R_sh_ref - R_sh_0 * np.exp(-R_sh_exp)) / (1.0 - np.exp(-R_sh_exp))
      Rsh_base = np.maximum(0.0, Rsh_tmp)
  
      Rsh = Rsh_base + (R_sh_0 - Rsh_base) * \
          np.exp(-R_sh_exp * effective_irradiance / irrad_ref)
  
      Rs = R_s
  
      numeric_args = (effective_irradiance, temp_cell)
      out = (IL, I0, Rs, Rsh, nNsVth)
  
      if all(map(np.isscalar, numeric_args)):
          return out
  
      index = tools.get_pandas_index(*numeric_args)
  
      if index is None:
          return np.broadcast_arrays(*out)
  
      return tuple(pd.Series(a, index=index).rename(None) for a in out)
  
  
  def retrieve_sam(name=None, path=None):
      '''
      Retrieve latest module and inverter info from a local file or the
      SAM website.
  
      This function will retrieve either:
  
          * CEC module database
          * Sandia Module database
          * CEC Inverter database
          * Anton Driesse Inverter database
  
      and return it as a pandas DataFrame.
  
      Parameters
      ----------
      name : None or string, default None
          Name can be one of:
  
          * 'CECMod' - returns the CEC module database
          * 'CECInverter' - returns the CEC Inverter database
          * 'SandiaInverter' - returns the CEC Inverter database
            (CEC is only current inverter db available; tag kept for
            backwards compatibility)
          * 'SandiaMod' - returns the Sandia Module database
          * 'ADRInverter' - returns the ADR Inverter database
  
      path : None or string, default None
          Path to the SAM file. May also be a URL.
  
      Returns
      -------
      samfile : DataFrame
          A DataFrame containing all the elements of the desired database.
          Each column represents a module or inverter, and a specific
          dataset can be retrieved by the command
  
      Raises
      ------
      ValueError
          If no name or path is provided.
  
      Notes
      -----
      Files available at
          https://github.com/NREL/SAM/tree/develop/deploy/libraries
      Documentation for module and inverter data sets:
          https://sam.nrel.gov/photovoltaic/pv-sub-page-2.html
  
      Examples
      --------
  
      >>> from pvlib import pvsystem
      >>> invdb = pvsystem.retrieve_sam('CECInverter')
      >>> inverter = invdb.AE_Solar_Energy__AE6_0__277V_
      >>> inverter
      Vac                          277
      Pso                    36.197575
      Paco                      6000.0
      Pdco                 6158.746094
      Vdco                       360.0
      C0                     -0.000002
      C1                     -0.000026
      C2                     -0.001253
      C3                       0.00021
      Pnt                          1.8
      Vdcmax                     450.0
      Idcmax                 17.107628
      Mppt_low                   100.0
      Mppt_high                  450.0
      CEC_Date                     NaN
      CEC_Type     Utility Interactive
      Name: AE_Solar_Energy__AE6_0__277V_, dtype: object
      '''
  
      if name is not None:
          name = name.lower()
          data_path = os.path.join(
              os.path.dirname(os.path.abspath(__file__)), 'data')
          if name == 'cecmod':
              csvdata = os.path.join(
                  data_path, 'sam-library-cec-modules-2019-03-05.csv')
          elif name == 'sandiamod':
              csvdata = os.path.join(
                  data_path, 'sam-library-sandia-modules-2015-6-30.csv')
          elif name == 'adrinverter':
              csvdata = os.path.join(
                  data_path, 'adr-library-cec-inverters-2019-03-05.csv')
          elif name in ['cecinverter', 'sandiainverter']:
              # Allowing either, to provide for old code,
              # while aligning with current expectations
              csvdata = os.path.join(
                  data_path, 'sam-library-cec-inverters-2019-03-05.csv')
          else:
              raise ValueError(f'invalid name {name}')
      elif path is not None:
          if path.startswith('http'):
              response = urlopen(path)
              csvdata = io.StringIO(response.read().decode(errors='ignore'))
          else:
              csvdata = path
      elif name is None and path is None:
          raise ValueError("A name or path must be provided!")
  
      return _parse_raw_sam_df(csvdata)
  
  
  def _normalize_sam_product_names(names):
      '''
      Replace special characters within the product names to make them more
      suitable for use as Dataframe column names.
      '''
      # Contributed by Anton Driesse (@adriesse), PV Performance Labs. July, 2019
  
      import warnings
  
      BAD_CHARS = ' -.()[]:+/",'
      GOOD_CHARS = '____________'
  
      mapping = str.maketrans(BAD_CHARS, GOOD_CHARS)
      names = pd.Series(data=names)
      norm_names = names.str.translate(mapping)
  
      n_duplicates = names.duplicated().sum()
      if n_duplicates > 0:
          warnings.warn('Original names contain %d duplicate(s).' % n_duplicates)
  
      n_duplicates = norm_names.duplicated().sum()
      if n_duplicates > 0:
          warnings.warn(
              'Normalized names contain %d duplicate(s).' % n_duplicates)
  
      return norm_names.values
  
  
  def _parse_raw_sam_df(csvdata):
  
      df = pd.read_csv(csvdata, index_col=0, skiprows=[1, 2])
  
      df.columns = df.columns.str.replace(' ', '_')
      df.index = _normalize_sam_product_names(df.index)
      df = df.transpose()
  
      if 'ADRCoefficients' in df.index:
          ad_ce = 'ADRCoefficients'
          # for each inverter, parses a string of coefficients like
          # ' 1.33, 2.11, 3.12' into a list containing floats:
          # [1.33, 2.11, 3.12]
          df.loc[ad_ce] = df.loc[ad_ce].map(lambda x: list(
              map(float, x.strip(' []').split())))
  
      return df
  
  
  def sapm(effective_irradiance, temp_cell, module):
      '''
      The Sandia PV Array Performance Model (SAPM) generates 5 points on a
      PV module's I-V curve (Voc, Isc, Ix, Ixx, Vmp/Imp) according to
      SAND2004-3535. Assumes a reference cell temperature of 25 C.
  
      Parameters
      ----------
      effective_irradiance : numeric
          Irradiance reaching the module's cells, after reflections and
          adjustment for spectrum. [W/m2]
  
      temp_cell : numeric
          Cell temperature [C].
  
      module : dict-like
          A dict or Series defining the SAPM parameters. See the notes section
          for more details.
  
      Returns
      -------
      A DataFrame with the columns:
  
          * i_sc : Short-circuit current (A)
          * i_mp : Current at the maximum-power point (A)
          * v_oc : Open-circuit voltage (V)
          * v_mp : Voltage at maximum-power point (V)
          * p_mp : Power at maximum-power point (W)
          * i_x : Current at module V = 0.5Voc, defines 4th point on I-V
            curve for modeling curve shape
          * i_xx : Current at module V = 0.5(Voc+Vmp), defines 5th point on
            I-V curve for modeling curve shape
  
      Notes
      -----
      The SAPM parameters which are required in ``module`` are
      listed in the following table.
  
      The Sandia module database contains parameter values for a limited set
      of modules. The CEC module database does not contain these parameters.
      Both databases can be accessed using :py:func:`retrieve_sam`.
  
      ================   ========================================================
      Key                Description
      ================   ========================================================
      A0-A4              The airmass coefficients used in calculating
                         effective irradiance
      B0-B5              The angle of incidence coefficients used in calculating
                         effective irradiance
      C0-C7              The empirically determined coefficients relating
                         Imp, Vmp, Ix, and Ixx to effective irradiance
      Isco               Short circuit current at reference condition (amps)
      Impo               Maximum power current at reference condition (amps)
      Voco               Open circuit voltage at reference condition (amps)
      Vmpo               Maximum power voltage at reference condition (amps)
      Aisc               Short circuit current temperature coefficient at
                         reference condition (1/C)
      Aimp               Maximum power current temperature coefficient at
                         reference condition (1/C)
      Bvoco              Open circuit voltage temperature coefficient at
                         reference condition (V/C)
      Mbvoc              Coefficient providing the irradiance dependence for the
                         BetaVoc temperature coefficient at reference irradiance
                         (V/C)
      Bvmpo              Maximum power voltage temperature coefficient at
                         reference condition
      Mbvmp              Coefficient providing the irradiance dependence for the
                         BetaVmp temperature coefficient at reference irradiance
                         (V/C)
      N                  Empirically determined "diode factor" (dimensionless)
      Cells_in_Series    Number of cells in series in a module's cell string(s)
      IXO                Ix at reference conditions
      IXXO               Ixx at reference conditions
      FD                 Fraction of diffuse irradiance used by module
      ================   ========================================================
  
      References
      ----------
      .. [1] King, D. et al, 2004, "Sandia Photovoltaic Array Performance
         Model", SAND Report 3535, Sandia National Laboratories, Albuquerque,
         NM.
  
      See Also
      --------
      retrieve_sam
      pvlib.temperature.sapm_cell
      pvlib.temperature.sapm_module
      '''
  
      # TODO: someday, change temp_ref and irrad_ref to reference_temperature and
      # reference_irradiance and expose
      temp_ref = 25
      irrad_ref = 1000
  
      q = constants.e  # Elementary charge in units of coulombs
      kb = constants.k  # Boltzmann's constant in units of J/K
  
      # avoid problem with integer input
      Ee = np.array(effective_irradiance, dtype='float64') / irrad_ref
  
      # set up masking for 0, positive, and nan inputs
      Ee_gt_0 = np.full_like(Ee, False, dtype='bool')
      Ee_eq_0 = np.full_like(Ee, False, dtype='bool')
      notnan = ~np.isnan(Ee)
      np.greater(Ee, 0, where=notnan, out=Ee_gt_0)
      np.equal(Ee, 0, where=notnan, out=Ee_eq_0)
  
      Bvmpo = module['Bvmpo'] + module['Mbvmp']*(1 - Ee)
      Bvoco = module['Bvoco'] + module['Mbvoc']*(1 - Ee)
      delta = module['N'] * kb * (temp_cell + 273.15) / q
  
      # avoid repeated computation
      logEe = np.full_like(Ee, np.nan)
      np.log(Ee, where=Ee_gt_0, out=logEe)
      logEe = np.where(Ee_eq_0, -np.inf, logEe)
      # avoid repeated __getitem__
      cells_in_series = module['Cells_in_Series']
  
      out = OrderedDict()
  
      out['i_sc'] = (
          module['Isco'] * Ee * (1 + module['Aisc']*(temp_cell - temp_ref)))
  
      out['i_mp'] = (
          module['Impo'] * (module['C0']*Ee + module['C1']*(Ee**2)) *
          (1 + module['Aimp']*(temp_cell - temp_ref)))
  
      out['v_oc'] = np.maximum(0, (
          module['Voco'] + cells_in_series * delta * logEe +
          Bvoco*(temp_cell - temp_ref)))
  
      out['v_mp'] = np.maximum(0, (
          module['Vmpo'] +
          module['C2'] * cells_in_series * delta * logEe +
          module['C3'] * cells_in_series * ((delta * logEe) ** 2) +
          Bvmpo*(temp_cell - temp_ref)))
  
      out['p_mp'] = out['i_mp'] * out['v_mp']
  
      out['i_x'] = (
          module['IXO'] * (module['C4']*Ee + module['C5']*(Ee**2)) *
          (1 + module['Aisc']*(temp_cell - temp_ref)))
  
      # the Ixx calculation in King 2004 has a typo (mixes up Aisc and Aimp)
      out['i_xx'] = (
          module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) *
          (1 + module['Aisc']*(temp_cell - temp_ref)))
  
      if isinstance(out['i_sc'], pd.Series):
          out = pd.DataFrame(out)
  
      return out
  
  
  sapm_spectral_loss = deprecated(
      since='0.10.0',
      alternative='pvlib.spectrum.spectral_factor_sapm'
  )(spectrum.spectral_factor_sapm)
  
  
  def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,
                                module):
      r"""
      Calculates the SAPM effective irradiance using the SAPM spectral
      loss and SAPM angle of incidence loss functions.
  
      Parameters
      ----------
      poa_direct : numeric
          The direct irradiance incident upon the module. [W/m2]
  
      poa_diffuse : numeric
          The diffuse irradiance incident on module.  [W/m2]
  
      airmass_absolute : numeric
          Absolute airmass. [unitless]
  
      aoi : numeric
          Angle of incidence. [degrees]
  
      module : dict-like
          A dict, Series, or DataFrame defining the SAPM performance
          parameters. See the :py:func:`sapm` notes section for more
          details.
  
      Returns
      -------
      effective_irradiance : numeric
          Effective irradiance accounting for reflections and spectral content.
          [W/m2]
  
      Notes
      -----
      The SAPM model for effective irradiance [1]_ translates broadband direct
      and diffuse irradiance on the plane of array to the irradiance absorbed by
      a module's cells.
  
      The model is
      .. math::
  
          `Ee = f_1(AM_a) (E_b f_2(AOI) + f_d E_d)`
  
      where :math:`Ee` is effective irradiance (W/m2), :math:`f_1` is a fourth
      degree polynomial in air mass :math:`AM_a`, :math:`E_b` is beam (direct)
      irradiance on the plane of array, :math:`E_d` is diffuse irradiance on the
      plane of array, :math:`f_2` is a fifth degree polynomial in the angle of
      incidence :math:`AOI`, and :math:`f_d` is the fraction of diffuse
      irradiance on the plane of array that is not reflected away.
  
      References
      ----------
      .. [1] D. King et al, "Sandia Photovoltaic Array Performance Model",
         SAND2004-3535, Sandia National Laboratories, Albuquerque, NM
  
      See also
      --------
      pvlib.iam.sapm
      pvlib.spectrum.spectral_factor_sapm
      pvlib.pvsystem.sapm
      """
  
      F1 = spectrum.spectral_factor_sapm(airmass_absolute, module)
      F2 = iam.sapm(aoi, module)
  
      Ee = F1 * (poa_direct * F2 + module['FD'] * poa_diffuse)
  
      return Ee
  
  
  def singlediode(photocurrent, saturation_current, resistance_series,
                  resistance_shunt, nNsVth, ivcurve_pnts=None,
                  method='lambertw'):
      r"""
      Solve the single diode equation to obtain a photovoltaic IV curve.
  
      Solves the single diode equation [1]_
  
      .. math::
  
          I = I_L -
              I_0 \left[
                  \exp \left(\frac{V+I R_s}{n N_s V_{th}} \right)-1
              \right] -
              \frac{V + I R_s}{R_{sh}}
  
      for :math:`I` and :math:`V` when given :math:`I_L, I_0, R_s, R_{sh},` and
      :math:`n N_s V_{th}` which are described later. The five points on the I-V
      curve specified in [3]_ are returned. If :math:`I_L, I_0, R_s, R_{sh},` and
      :math:`n N_s V_{th}` are all scalars, a single curve is returned. If any
      are array-like (of the same length), multiple IV curves are calculated.
  
      The input parameters can be calculated from meteorological data using a
      function for a single diode model, e.g.,
      :py:func:`~pvlib.pvsystem.calcparams_desoto`.
  
      Parameters
      ----------
      photocurrent : numeric
          Light-generated current :math:`I_L` (photocurrent)
          ``0 <= photocurrent``. [A]
  
      saturation_current : numeric
          Diode saturation :math:`I_0` current under desired IV curve
          conditions. ``0 < saturation_current``. [A]
  
      resistance_series : numeric
          Series resistance :math:`R_s` under desired IV curve conditions.
          ``0 <= resistance_series < numpy.inf``.  [ohm]
  
      resistance_shunt : numeric
          Shunt resistance :math:`R_{sh}` under desired IV curve conditions.
          ``0 < resistance_shunt <= numpy.inf``.  [ohm]
  
      nNsVth : numeric
          The product of three components: 1) the usual diode ideality factor
          :math:`n`, 2) the number of cells in series :math:`N_s`, and 3)
          the cell thermal voltage
          :math:`V_{th}`. The thermal voltage of the cell (in volts) may be
          calculated as :math:`k_B T_c / q`, where :math:`k_B` is
          Boltzmann's constant (J/K), :math:`T_c` is the temperature of the p-n
          junction in Kelvin, and :math:`q` is the charge of an electron
          (coulombs). ``0 < nNsVth``.  [V]
  
      ivcurve_pnts : None or int, default None
          Number of points in the desired IV curve. If None or 0, no points on
          the IV curves will be produced.
  
          .. deprecated:: 0.10.0
             Use :py:func:`pvlib.pvsystem.v_from_i` and
             :py:func:`pvlib.pvsystem.i_from_v` instead.
  
      method : str, default 'lambertw'
          Determines the method used to calculate points on the IV curve. The
          options are ``'lambertw'``, ``'newton'``, or ``'brentq'``.
  
      Returns
      -------
      dict or pandas.DataFrame
          The returned dict-like object always contains the keys/columns:
  
              * i_sc - short circuit current in amperes.
              * v_oc - open circuit voltage in volts.
              * i_mp - current at maximum power point in amperes.
              * v_mp - voltage at maximum power point in volts.
              * p_mp - power at maximum power point in watts.
              * i_x - current, in amperes, at ``v = 0.5*v_oc``.
              * i_xx - current, in amperes, at ``v = 0.5*(v_oc+v_mp)``.
  
          A dict is returned when the input parameters are scalars or
          ``ivcurve_pnts > 0``. If ``ivcurve_pnts > 0``, the output dictionary
          will also include the keys:
  
              * i - IV curve current in amperes.
              * v - IV curve voltage in volts.
  
      See also
      --------
      calcparams_desoto
      calcparams_cec
      calcparams_pvsyst
      sapm
      pvlib.singlediode.bishop88
  
      Notes
      -----
      If the method is ``'lambertw'`` then the solution employed to solve the
      implicit diode equation utilizes the Lambert W function to obtain an
      explicit function of :math:`V=f(I)` and :math:`I=f(V)` as shown in [2]_.
  
      If the method is ``'newton'`` then the root-finding Newton-Raphson method
      is used. It should be safe for well behaved IV-curves, but the ``'brentq'``
      method is recommended for reliability.
  
      If the method is ``'brentq'`` then Brent's bisection search method is used
      that guarantees convergence by bounding the voltage between zero and
      open-circuit.
  
      If the method is either ``'newton'`` or ``'brentq'`` and ``ivcurve_pnts``
      are indicated, then :func:`pvlib.singlediode.bishop88` [4]_ is used to
      calculate the points on the IV curve points at diode voltages from zero to
      open-circuit voltage with a log spacing that gets closer as voltage
      increases. If the method is ``'lambertw'`` then the calculated points on
      the IV curve are linearly spaced.
  
      References
      ----------
      .. [1] S.R. Wenham, M.A. Green, M.E. Watt, "Applied Photovoltaics" ISBN
         0 86758 909 4
  
      .. [2] A. Jain, A. Kapoor, "Exact analytical solutions of the
         parameters of real solar cells using Lambert W-function", Solar
         Energy Materials and Solar Cells, 81 (2004) 269-277.
  
      .. [3] D. King et al, "Sandia Photovoltaic Array Performance Model",
         SAND2004-3535, Sandia National Laboratories, Albuquerque, NM
  
      .. [4] "Computer simulation of the effects of electrical mismatches in
         photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988)
         https://doi.org/10.1016/0379-6787(88)90059-2
      """
      if ivcurve_pnts:
          warn_deprecated('0.10.0', name='pvlib.pvsystem.singlediode',
                          alternative=('pvlib.pvsystem.v_from_i and '
                                       'pvlib.pvsystem.i_from_v'),
                          obj_type='parameter ivcurve_pnts',
                          removal='0.11.0')
      args = (photocurrent, saturation_current, resistance_series,
              resistance_shunt, nNsVth)  # collect args
      # Calculate points on the IV curve using the LambertW solution to the
      # single diode equation
      if method.lower() == 'lambertw':
          out = _singlediode._lambertw(*args, ivcurve_pnts)
          points = out[:7]
          if ivcurve_pnts:
              ivcurve_i, ivcurve_v = out[7:]
      else:
          # Calculate points on the IV curve using either 'newton' or 'brentq'
          # methods. Voltages are determined by first solving the single diode
          # equation for the diode voltage V_d then backing out voltage
          v_oc = _singlediode.bishop88_v_from_i(
              0.0, *args, method=method.lower()
          )
          i_mp, v_mp, p_mp = _singlediode.bishop88_mpp(
              *args, method=method.lower()
          )
          i_sc = _singlediode.bishop88_i_from_v(
              0.0, *args, method=method.lower()
          )
          i_x = _singlediode.bishop88_i_from_v(
              v_oc / 2.0, *args, method=method.lower()
          )
          i_xx = _singlediode.bishop88_i_from_v(
              (v_oc + v_mp) / 2.0, *args, method=method.lower()
          )
          points = i_sc, v_oc, i_mp, v_mp, p_mp, i_x, i_xx
  
          # calculate the IV curve if requested using bishop88
          if ivcurve_pnts:
              vd = v_oc * (
                  (11.0 - np.logspace(np.log10(11.0), 0.0, ivcurve_pnts)) / 10.0
              )
              ivcurve_i, ivcurve_v, _ = _singlediode.bishop88(vd, *args)
  
      columns = ('i_sc', 'v_oc', 'i_mp', 'v_mp', 'p_mp', 'i_x', 'i_xx')
  
      if all(map(np.isscalar, args)) or ivcurve_pnts:
          out = {c: p for c, p in zip(columns, points)}
  
          if ivcurve_pnts:
              out.update(i=ivcurve_i, v=ivcurve_v)
  
          return out
  
      points = np.atleast_1d(*points)  # convert scalars to 1d-arrays
      points = np.vstack(points).T  # collect rows into DataFrame columns
  
      # save the first available pd.Series index, otherwise set to None
      index = next((a.index for a in args if isinstance(a, pd.Series)), None)
  
      out = pd.DataFrame(points, columns=columns, index=index)
  
      return out
  
  
  def max_power_point(photocurrent, saturation_current, resistance_series,
                      resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.Inf,
                      method='brentq'):
      """
      Given the single diode equation coefficients, calculates the maximum power
      point (MPP).
  
      Parameters
      ----------
      photocurrent : numeric
          photo-generated current [A]
      saturation_current : numeric
          diode reverse saturation current [A]
      resistance_series : numeric
          series resitance [ohms]
      resistance_shunt : numeric
          shunt resitance [ohms]
      nNsVth : numeric
          product of thermal voltage ``Vth`` [V], diode ideality factor ``n``,
          and number of serices cells ``Ns``
      d2mutau : numeric, default 0
          PVsyst parameter for cadmium-telluride (CdTe) and amorphous-silicon
          (a-Si) modules that accounts for recombination current in the
          intrinsic layer. The value is the ratio of intrinsic layer thickness
          squared :math:`d^2` to the diffusion length of charge carriers
          :math:`\\mu \\tau`. [V]
      NsVbi : numeric, default np.inf
          PVsyst parameter for cadmium-telluride (CdTe) and amorphous-silicon
          (a-Si) modules that is the product of the PV module number of series
          cells ``Ns`` and the builtin voltage ``Vbi`` of the intrinsic layer.
          [V].
      method : str
          either ``'newton'`` or ``'brentq'``
  
      Returns
      -------
      OrderedDict or pandas.DataFrame
          ``(i_mp, v_mp, p_mp)``
  
      Notes
      -----
      Use this function when you only want to find the maximum power point. Use
      :func:`singlediode` when you need to find additional points on the IV
      curve. This function uses Brent's method by default because it is
      guaranteed to converge.
      """
      i_mp, v_mp, p_mp = _singlediode.bishop88_mpp(
          photocurrent, saturation_current, resistance_series,
          resistance_shunt, nNsVth, d2mutau, NsVbi, method=method.lower()
      )
      if isinstance(photocurrent, pd.Series):
          ivp = {'i_mp': i_mp, 'v_mp': v_mp, 'p_mp': p_mp}
          out = pd.DataFrame(ivp, index=photocurrent.index)
      else:
          out = OrderedDict()
          out['i_mp'] = i_mp
          out['v_mp'] = v_mp
          out['p_mp'] = p_mp
      return out
  
  
  def v_from_i(current, photocurrent, saturation_current, resistance_series,
               resistance_shunt, nNsVth, method='lambertw'):
      '''
      Device voltage at the given device current for the single diode model.
  
      Uses the single diode model (SDM) as described in, e.g.,
      Jain and Kapoor 2004 [1]_.
      The solution is per Eq 3 of [1]_ except when resistance_shunt=numpy.inf,
      in which case the explict solution for voltage is used.
      Ideal device parameters are specified by resistance_shunt=np.inf and
      resistance_series=0.
      Inputs to this function can include scalars and pandas.Series, but it is
      the caller's responsibility to ensure that the arguments are all float64
      and within the proper ranges.
  
      .. versionchanged:: 0.10.0
         The function's arguments have been reordered.
  
      Parameters
      ----------
      current : numeric
          The current in amperes under desired IV curve conditions.
  
      photocurrent : numeric
          Light-generated current (photocurrent) in amperes under desired
          IV curve conditions. Often abbreviated ``I_L``.
          0 <= photocurrent
  
      saturation_current : numeric
          Diode saturation current in amperes under desired IV curve
          conditions. Often abbreviated ``I_0``.
          0 < saturation_current
  
      resistance_series : numeric
          Series resistance in ohms under desired IV curve conditions.
          Often abbreviated ``Rs``.
          0 <= resistance_series < numpy.inf
  
      resistance_shunt : numeric
          Shunt resistance in ohms under desired IV curve conditions.
          Often abbreviated ``Rsh``.
          0 < resistance_shunt <= numpy.inf
  
      nNsVth : numeric
          The product of three components. 1) The usual diode ideal factor
          (n), 2) the number of cells in series (Ns), and 3) the cell
          thermal voltage under the desired IV curve conditions (Vth). The
          thermal voltage of the cell (in volts) may be calculated as
          ``k*temp_cell/q``, where k is Boltzmann's constant (J/K),
          temp_cell is the temperature of the p-n junction in Kelvin, and
          q is the charge of an electron (coulombs).
          0 < nNsVth
  
      method : str
          Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*:
          ``'brentq'`` is limited to 1st quadrant only.
  
      Returns
      -------
      current : np.ndarray or scalar
  
      References
      ----------
      .. [1] A. Jain, A. Kapoor, "Exact analytical solutions of the
         parameters of real solar cells using Lambert W-function", Solar
         Energy Materials and Solar Cells, 81 (2004) 269-277.
      '''
      args = (current, photocurrent, saturation_current,
              resistance_series, resistance_shunt, nNsVth)
      if method.lower() == 'lambertw':
          return _singlediode._lambertw_v_from_i(*args)
      else:
          # Calculate points on the IV curve using either 'newton' or 'brentq'
          # methods. Voltages are determined by first solving the single diode
          # equation for the diode voltage V_d then backing out voltage
          V = _singlediode.bishop88_v_from_i(*args, method=method.lower())
          if all(map(np.isscalar, args)):
              return V
          shape = _singlediode._shape_of_max_size(*args)
          return np.broadcast_to(V, shape)
  
  
  def i_from_v(voltage, photocurrent, saturation_current, resistance_series,
               resistance_shunt, nNsVth, method='lambertw'):
      '''
      Device current at the given device voltage for the single diode model.
  
      Uses the single diode model (SDM) as described in, e.g.,
      Jain and Kapoor 2004 [1]_.
      The solution is per Eq 2 of [1] except when resistance_series=0,
      in which case the explict solution for current is used.
      Ideal device parameters are specified by resistance_shunt=np.inf and
      resistance_series=0.
      Inputs to this function can include scalars and pandas.Series, but it is
      the caller's responsibility to ensure that the arguments are all float64
      and within the proper ranges.
  
      .. versionchanged:: 0.10.0
         The function's arguments have been reordered.
  
      Parameters
      ----------
      voltage : numeric
          The voltage in Volts under desired IV curve conditions.
  
      photocurrent : numeric
          Light-generated current (photocurrent) in amperes under desired
          IV curve conditions. Often abbreviated ``I_L``.
          0 <= photocurrent
  
      saturation_current : numeric
          Diode saturation current in amperes under desired IV curve
          conditions. Often abbreviated ``I_0``.
          0 < saturation_current
  
      resistance_series : numeric
          Series resistance in ohms under desired IV curve conditions.
          Often abbreviated ``Rs``.
          0 <= resistance_series < numpy.inf
  
      resistance_shunt : numeric
          Shunt resistance in ohms under desired IV curve conditions.
          Often abbreviated ``Rsh``.
          0 < resistance_shunt <= numpy.inf
  
      nNsVth : numeric
          The product of three components. 1) The usual diode ideal factor
          (n), 2) the number of cells in series (Ns), and 3) the cell
          thermal voltage under the desired IV curve conditions (Vth). The
          thermal voltage of the cell (in volts) may be calculated as
          ``k*temp_cell/q``, where k is Boltzmann's constant (J/K),
          temp_cell is the temperature of the p-n junction in Kelvin, and
          q is the charge of an electron (coulombs).
          0 < nNsVth
  
      method : str
          Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*:
          ``'brentq'`` is limited to 1st quadrant only.
  
      Returns
      -------
      current : np.ndarray or scalar
  
      References
      ----------
      .. [1] A. Jain, A. Kapoor, "Exact analytical solutions of the
         parameters of real solar cells using Lambert W-function", Solar
         Energy Materials and Solar Cells, 81 (2004) 269-277.
      '''
      args = (voltage, photocurrent, saturation_current,
              resistance_series, resistance_shunt, nNsVth)
      if method.lower() == 'lambertw':
          return _singlediode._lambertw_i_from_v(*args)
      else:
          # Calculate points on the IV curve using either 'newton' or 'brentq'
          # methods. Voltages are determined by first solving the single diode
          # equation for the diode voltage V_d then backing out voltage
          current = _singlediode.bishop88_i_from_v(*args, method=method.lower())
          if all(map(np.isscalar, args)):
              return current
          shape = _singlediode._shape_of_max_size(*args)
          return np.broadcast_to(current, shape)
  
  
  def scale_voltage_current_power(data, voltage=1, current=1):
      """
      Scales the voltage, current, and power in data by the voltage
      and current factors.
  
      Parameters
      ----------
      data: DataFrame
          May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',
          'i_sc', 'p_mp'`.
      voltage: numeric, default 1
          The amount by which to multiply the voltages.
      current: numeric, default 1
          The amount by which to multiply the currents.
  
      Returns
      -------
      scaled_data: DataFrame
          A scaled copy of the input data.
          `'p_mp'` is scaled by `voltage * current`.
      """
  
      # as written, only works with a DataFrame
      # could make it work with a dict, but it would be more verbose
      voltage_keys = ['v_mp', 'v_oc']
      current_keys = ['i_mp', 'i_x', 'i_xx', 'i_sc']
      power_keys = ['p_mp']
      voltage_df = data.filter(voltage_keys, axis=1) * voltage
      current_df = data.filter(current_keys, axis=1) * current
      power_df = data.filter(power_keys, axis=1) * voltage * current
      df = pd.concat([voltage_df, current_df, power_df], axis=1)
      df_sorted = df[data.columns]  # retain original column order
      return df_sorted
  
  
  def pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.):
      r"""
      Implements NREL's PVWatts DC power model. The PVWatts DC model [1]_ is:
  
      .. math::
  
          P_{dc} = \frac{G_{poa eff}}{1000} P_{dc0} ( 1 + \gamma_{pdc} (T_{cell} - T_{ref}))
  
      Note that ``pdc0`` is also used as a symbol in
      :py:func:`pvlib.inverter.pvwatts`. ``pdc0`` in this function refers to the DC
      power of the modules at reference conditions. ``pdc0`` in
      :py:func:`pvlib.inverter.pvwatts` refers to the DC power input limit of
      the inverter.
  
      Parameters
      ----------
      g_poa_effective: numeric
          Irradiance transmitted to the PV cells. To be
          fully consistent with PVWatts, the user must have already
          applied angle of incidence losses, but not soiling, spectral,
          etc. [W/m^2]
      temp_cell: numeric
          Cell temperature [C].
      pdc0: numeric
          Power of the modules at 1000 W/m^2 and cell reference temperature. [W]
      gamma_pdc: numeric
          The temperature coefficient of power. Typically -0.002 to
          -0.005 per degree C. [1/C]
      temp_ref: numeric, default 25.0
          Cell reference temperature. PVWatts defines it to be 25 C and
          is included here for flexibility. [C]
  
      Returns
      -------
      pdc: numeric
          DC power. [W]
  
      References
      ----------
      .. [1] A. P. Dobos, "PVWatts Version 5 Manual"
             http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf
             (2014).
      """  # noqa: E501
  
      pdc = (g_poa_effective * 0.001 * pdc0 *
             (1 + gamma_pdc * (temp_cell - temp_ref)))
  
      return pdc
  
  
  def pvwatts_losses(soiling=2, shading=3, snow=0, mismatch=2, wiring=2,
                     connections=0.5, lid=1.5, nameplate_rating=1, age=0,
                     availability=3):
      r"""
      Implements NREL's PVWatts system loss model.
      The PVWatts loss model [1]_ is:
  
      .. math::
  
          L_{total}(\%) = 100 [ 1 - \Pi_i ( 1 - \frac{L_i}{100} ) ]
  
      All parameters must be in units of %. Parameters may be
      array-like, though all array sizes must match.
  
      Parameters
      ----------
      soiling: numeric, default 2
      shading: numeric, default 3
      snow: numeric, default 0
      mismatch: numeric, default 2
      wiring: numeric, default 2
      connections: numeric, default 0.5
      lid: numeric, default 1.5
          Light induced degradation
      nameplate_rating: numeric, default 1
      age: numeric, default 0
      availability: numeric, default 3
  
      Returns
      -------
      losses: numeric
          System losses in units of %.
  
      References
      ----------
      .. [1] A. P. Dobos, "PVWatts Version 5 Manual"
             http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf
             (2014).
      """
  
      params = [soiling, shading, snow, mismatch, wiring, connections, lid,
                nameplate_rating, age, availability]
  
      # manually looping over params allows for numpy/pandas to handle any
      # array-like broadcasting that might be necessary.
      perf = 1
      for param in params:
          perf *= 1 - param/100
  
      losses = (1 - perf) * 100.
  
      return losses
  
  
  def dc_ohms_from_percent(vmp_ref, imp_ref, dc_ohmic_percent,
                           modules_per_string=1,
                           strings=1):
      """
      Calculates the equivalent resistance of the wires from a percent
      ohmic loss at STC.
  
      Equivalent resistance is calculated with the function:
  
      .. math::
          Rw = (L_{stc} / 100) * (Varray / Iarray)
  
      :math:`Rw` is the equivalent resistance in ohms
      :math:`Varray` is the Vmp of the modules times modules per string
      :math:`Iarray` is the Imp of the modules times strings per array
      :math:`L_{stc}` is the input dc loss percent
  
      Parameters
      ----------
      vmp_ref: numeric
          Voltage at maximum power in reference conditions [V]
      imp_ref: numeric
          Current at maximum power in reference conditions [V]
      dc_ohmic_percent: numeric, default 0
          input dc loss as a percent, e.g. 1.5% loss is input as 1.5
      modules_per_string: int, default 1
          Number of modules per string in the array.
      strings: int, default 1
          Number of parallel strings in the array.
  
      Returns
      ----------
      Rw: numeric
          Equivalent resistance [ohm]
  
      See Also
      --------
      pvlib.pvsystem.dc_ohmic_losses
  
      References
      ----------
      .. [1] PVsyst 7 Help. "Array ohmic wiring loss".
         https://www.pvsyst.com/help/ohmic_loss.htm
      """
      vmp = modules_per_string * vmp_ref
  
      imp = strings * imp_ref
  
      Rw = (dc_ohmic_percent / 100) * (vmp / imp)
  
      return Rw
  
  
  def dc_ohmic_losses(resistance, current):
      """
      Returns ohmic losses in units of power from the equivalent
      resistance of the wires and the operating current.
  
      Parameters
      ----------
      resistance: numeric
          Equivalent resistance of wires [ohm]
      current: numeric, float or array-like
          Operating current [A]
  
      Returns
      ----------
      loss: numeric
          Power Loss [W]
  
      See Also
      --------
      pvlib.pvsystem.dc_ohms_from_percent
  
      References
      ----------
      .. [1] PVsyst 7 Help. "Array ohmic wiring loss".
         https://www.pvsyst.com/help/ohmic_loss.htm
      """
      return resistance * current * current
  
  
  def combine_loss_factors(index, *losses, fill_method='ffill'):
      r"""
      Combines Series loss fractions while setting a common index.
  
      The separate losses are compounded using the following equation:
  
      .. math::
  
          L_{total} = 1 - [ 1 - \Pi_i ( 1 - L_i ) ]
  
      :math:`L_{total}` is the total loss returned
      :math:`L_i` is each individual loss factor input
  
      Note the losses must each be a series with a DatetimeIndex.
      All losses will be resampled to match the index parameter using
      the fill method specified (defaults to "fill forward").
  
      Parameters
      ----------
      index : DatetimeIndex
          The index of the returned loss factors
  
      *losses : Series
          One or more Series of fractions to be compounded
  
      fill_method : {'ffill', 'bfill', 'nearest'}, default 'ffill'
          Method to use for filling holes in reindexed DataFrame
  
      Returns
      -------
      Series
          Fractions resulting from the combination of each loss factor
      """
      combined_factor = 1
  
      for loss in losses:
          loss = loss.reindex(index, method=fill_method)
          combined_factor *= (1 - loss)
  
      return 1 - combined_factor
  
  ```
  --- END FILE ---
  
  Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.
  
  Every *SEARCH/REPLACE* edit must use this format:
  1. The file path
  2. The start of search block: <<<<<<< SEARCH
  3. A contiguous chunk of lines to search for in the existing source code
  4. The dividing line: =======
  5. The lines to replace into the source code
  6. The end of the replace block: >>>>>>> REPLACE
  
  Here is an example:
  
  ```python
  ### mathweb/flask/app.py
  <<<<<<< SEARCH
  from flask import Flask
  =======
  import math
  from flask import Flask
  >>>>>>> REPLACE
  ```
  
  Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
  Wrap the *SEARCH/REPLACE* edit in blocks ```python...```.
  
instance: {"FAIL_TO_PASS": "[\"pvlib/tests/test_pvsystem.py::test_PVSystem_single_array\"]", "PASS_TO_PASS": "[\"pvlib/tests/test_pvsystem.py::test_PVSystem_get_iam[ashrae-model_params0]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_iam[physical-model_params1]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_iam[martin_ruiz-model_params2]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_get_iam\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_iam_sapm\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_iam_interp\", \"pvlib/tests/test_pvsystem.py::test__normalize_sam_product_names\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_iam_invalid\", \"pvlib/tests/test_pvsystem.py::test_retrieve_sam_raise_no_parameters\", \"pvlib/tests/test_pvsystem.py::test_retrieve_sam_cecmod\", \"pvlib/tests/test_pvsystem.py::test_retrieve_sam_cecinverter\", \"pvlib/tests/test_pvsystem.py::test_sapm\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_sapm\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_sapm\", \"pvlib/tests/test_pvsystem.py::test_sapm_spectral_loss_deprecated\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_sapm_spectral_loss\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_sapm_spectral_loss\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_first_solar_spectral_loss[module_parameters0-multisi-None]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_first_solar_spectral_loss[module_parameters1-multisi-None]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_first_solar_spectral_loss[module_parameters2-None-coefficients2]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_first_solar_spectral_loss\", \"pvlib/tests/test_pvsystem.py::test_sapm_effective_irradiance[test_input0-1140.0510967821876]\", \"pvlib/tests/test_pvsystem.py::test_sapm_effective_irradiance[test_input1-expected1]\", \"pvlib/tests/test_pvsystem.py::test_sapm_effective_irradiance[test_input2-expected2]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_sapm_effective_irradiance\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_sapm_effective_irradiance\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_sapm_effective_irradiance_value_error[20-poa_diffuse0-aoi0]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_sapm_effective_irradiance_value_error[poa_direct1-poa_diffuse1-aoi1]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_sapm_effective_irradiance_value_error[poa_direct2-poa_diffuse2-20]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_sapm_celltemp\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_sapm_celltemp_kwargs\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_sapm_celltemp_different_arrays\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_pvsyst_celltemp\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_faiman_celltemp\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_noct_celltemp\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_noct_celltemp_error\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_functions[faiman]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_functions[pvsyst]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_functions[sapm]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_functions[fuentes]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_functions[noct_sam]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_multi_temp[faiman]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_multi_temp[pvsyst]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_multi_temp[sapm]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_multi_temp[fuentes]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_multi_temp[noct_sam]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_multi_wind[faiman]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_multi_wind[pvsyst]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_multi_wind[sapm]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_multi_wind[fuentes]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_multi_wind[noct_sam]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_cell_temperature_invalid\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_temp_too_short[faiman]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_temp_too_short[pvsyst]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_temp_too_short[sapm]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_temp_too_short[fuentes]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_temp_too_short[noct_sam]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_temp_too_long[faiman]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_temp_too_long[pvsyst]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_temp_too_long[sapm]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_temp_too_long[fuentes]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_temp_too_long[noct_sam]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_wind_too_short[faiman]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_wind_too_short[pvsyst]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_wind_too_short[sapm]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_wind_too_short[fuentes]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_wind_too_short[noct_sam]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_wind_too_long[faiman]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_wind_too_long[pvsyst]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_wind_too_long[sapm]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_wind_too_long[fuentes]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_wind_too_long[noct_sam]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_poa_length_mismatch[faiman]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_poa_length_mismatch[pvsyst]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_poa_length_mismatch[sapm]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_poa_length_mismatch[fuentes]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_celltemp_poa_length_mismatch[noct_sam]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_fuentes_celltemp\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_fuentes_module_height\", \"pvlib/tests/test_pvsystem.py::test_Array__infer_temperature_model_params\", \"pvlib/tests/test_pvsystem.py::test_Array__infer_cell_type\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs0]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs1]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs2]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs3]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs4]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs5]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs6]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs7]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs8]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs9]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs10]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs11]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs12]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs13]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs14]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_returns_correct_Python_type[numeric_type_funcs15]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs0]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs1]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs2]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs3]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs4]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs5]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs6]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs7]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs8]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs9]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs10]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs11]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs12]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs13]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs14]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_returns_correct_Python_type[numeric_type_funcs15]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs0]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs1]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs2]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs3]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs4]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs5]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs6]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs7]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs8]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs9]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs10]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs11]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs12]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs13]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs14]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_returns_correct_Python_type[numeric_type_funcs15]\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto_all_scalars\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_all_scalars\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst_all_scalars\", \"pvlib/tests/test_pvsystem.py::test_calcparams_desoto\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec\", \"pvlib/tests/test_pvsystem.py::test_calcparams_cec_extra_params_propagation\", \"pvlib/tests/test_pvsystem.py::test_calcparams_pvsyst\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_calcparams_desoto\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_calcparams_pvsyst\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_calcparams[calcparams_pvsyst]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_calcparams[calcparams_desoto]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_calcparams[calcparams_cec]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_calcparams_value_error[calcparams_desoto-1-celltemp0]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_calcparams_value_error[calcparams_desoto-irrad1-1]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_calcparams_value_error[calcparams_cec-1-celltemp2]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_calcparams_value_error[calcparams_cec-irrad3-1]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_calcparams_value_error[calcparams_pvsyst-1-celltemp4]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_calcparams_value_error[calcparams_pvsyst-irrad5-1]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i0-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i0-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i0-newton-1e-08]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i1-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i1-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i1-newton-1e-08]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i2-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i2-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i2-newton-1e-08]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i3-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i3-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i3-newton-1e-08]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i4-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i4-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i4-newton-1e-08]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i5-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i5-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i5-newton-1e-08]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i6-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i6-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i6-newton-1e-08]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i7-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i7-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i7-newton-1e-08]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i8-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i8-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i8-newton-1e-08]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i9-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i9-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i9-newton-1e-08]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i10-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i10-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_v_from_i[fixture_v_from_i10-newton-1e-08]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v_from_i[fixture_v_from_i0]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v_from_i[fixture_v_from_i1]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v_from_i[fixture_v_from_i2]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v_from_i[fixture_v_from_i3]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v_from_i[fixture_v_from_i4]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v_from_i[fixture_v_from_i5]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v_from_i[fixture_v_from_i6]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v_from_i[fixture_v_from_i7]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v_from_i[fixture_v_from_i8]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v_from_i[fixture_v_from_i9]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v_from_i[fixture_v_from_i10]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v0-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v0-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v0-newton-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v1-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v1-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v1-newton-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v2-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v2-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v2-newton-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v3-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v3-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v3-newton-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v4-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v4-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v4-newton-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v5-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v5-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v5-newton-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v6-lambertw-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v6-brentq-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_i_from_v[fixture_i_from_v6-newton-1e-11]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_i_from_v\", \"pvlib/tests/test_pvsystem.py::test_i_from_v_size\", \"pvlib/tests/test_pvsystem.py::test_v_from_i_size\", \"pvlib/tests/test_pvsystem.py::test_mpp_floats\", \"pvlib/tests/test_pvsystem.py::test_mpp_recombination\", \"pvlib/tests/test_pvsystem.py::test_mpp_array\", \"pvlib/tests/test_pvsystem.py::test_mpp_series\", \"pvlib/tests/test_pvsystem.py::test_singlediode_series\", \"pvlib/tests/test_pvsystem.py::test_singlediode_array\", \"pvlib/tests/test_pvsystem.py::test_singlediode_floats\", \"pvlib/tests/test_pvsystem.py::test_singlediode_floats_ivcurve\", \"pvlib/tests/test_pvsystem.py::test_singlediode_series_ivcurve\", \"pvlib/tests/test_pvsystem.py::test_singlediode_ivcurvepnts_deprecation_warning[lambertw]\", \"pvlib/tests/test_pvsystem.py::test_singlediode_ivcurvepnts_deprecation_warning[brentq]\", \"pvlib/tests/test_pvsystem.py::test_singlediode_ivcurvepnts_deprecation_warning[newton]\", \"pvlib/tests/test_pvsystem.py::test_scale_voltage_current_power\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_scale_voltage_current_power\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_scale_voltage_current_power\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_ac_sandia\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_ac_sandia_multi\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_ac_pvwatts\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_ac_pvwatts_kwargs\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_ac_pvwatts_multi\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_ac_single_array_tuple_input[sandia]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_ac_single_array_tuple_input[adr]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_ac_single_array_tuple_input[pvwatts]\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_ac_adr\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_ac_adr_multi\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_ac_invalid\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_creation\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multiple_array_creation\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_aoi\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multiple_array_get_aoi\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_irradiance\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_irradiance_albedo\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_get_irradiance_model\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_get_irradiance\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array_get_irradiance_multi_irrad\", \"pvlib/tests/test_pvsystem.py::test_Array_get_irradiance\", \"pvlib/tests/test_pvsystem.py::test_PVSystem___repr__\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multi_array___repr__\", \"pvlib/tests/test_pvsystem.py::test_Array___repr__\", \"pvlib/tests/test_pvsystem.py::test_pvwatts_dc_scalars\", \"pvlib/tests/test_pvsystem.py::test_pvwatts_dc_arrays\", \"pvlib/tests/test_pvsystem.py::test_pvwatts_dc_series\", \"pvlib/tests/test_pvsystem.py::test_pvwatts_losses_default\", \"pvlib/tests/test_pvsystem.py::test_pvwatts_losses_arrays\", \"pvlib/tests/test_pvsystem.py::test_pvwatts_losses_series\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_pvwatts_dc\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_pvwatts_dc_kwargs\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multiple_array_pvwatts_dc\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_multiple_array_pvwatts_dc_value_error\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_pvwatts_losses\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_num_arrays\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_at_least_one_array\", \"pvlib/tests/test_pvsystem.py::test_combine_loss_factors\", \"pvlib/tests/test_pvsystem.py::test_no_extra_kwargs\", \"pvlib/tests/test_pvsystem.py::test_AbstractMount_constructor\", \"pvlib/tests/test_pvsystem.py::test_FixedMount_constructor\", \"pvlib/tests/test_pvsystem.py::test_FixedMount_get_orientation\", \"pvlib/tests/test_pvsystem.py::test_SingleAxisTrackerMount_constructor\", \"pvlib/tests/test_pvsystem.py::test_SingleAxisTrackerMount_get_orientation\", \"pvlib/tests/test_pvsystem.py::test_dc_ohms_from_percent\", \"pvlib/tests/test_pvsystem.py::test_PVSystem_dc_ohms_from_percent\", \"pvlib/tests/test_pvsystem.py::test_dc_ohmic_losses\", \"pvlib/tests/test_pvsystem.py::test_Array_dc_ohms_from_percent\", \"pvlib/tests/test_pvsystem.py::test_Array_temperature_missing_parameters[sapm-keys0]\", \"pvlib/tests/test_pvsystem.py::test_Array_temperature_missing_parameters[fuentes-keys1]\", \"pvlib/tests/test_pvsystem.py::test_Array_temperature_missing_parameters[noct_sam-keys2]\"]", "base_commit": "27a3a07ebc84b11014d3753e4923902adf9a38c0", "created_at": "2023-09-13T17:25:47Z", "environment_setup_commit": "6072e0982c3c0236f532ddfa48fbf461180d834e", "hints_text": "", "instance_id": "pvlib__pvlib-python-1854", "patch": "diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py\n--- a/pvlib/pvsystem.py\n+++ b/pvlib/pvsystem.py\n@@ -101,10 +101,11 @@ class PVSystem:\n \n     Parameters\n     ----------\n-    arrays : iterable of Array, optional\n-        List of arrays that are part of the system. If not specified\n-        a single array is created from the other parameters (e.g.\n-        `surface_tilt`, `surface_azimuth`). Must contain at least one Array,\n+    arrays : Array or iterable of Array, optional\n+        An Array or list of arrays that are part of the system. If not\n+        specified a single array is created from the other parameters (e.g.\n+        `surface_tilt`, `surface_azimuth`). If specified as a list, the list\n+        must contain at least one Array;\n         if length of arrays is 0 a ValueError is raised. If `arrays` is\n         specified the following PVSystem parameters are ignored:\n \n@@ -220,6 +221,8 @@ def __init__(self,\n                 strings_per_inverter,\n                 array_losses_parameters,\n             ),)\n+        elif isinstance(arrays, Array):\n+            self.arrays = (arrays,)\n         elif len(arrays) == 0:\n             raise ValueError(\"PVSystem must have at least one Array. \"\n                              \"If you want to create a PVSystem instance \"\n", "problem_statement": "PVSystem with single Array generates an error\n**Is your feature request related to a problem? Please describe.**\r\n\r\nWhen a PVSystem has a single Array, you can't assign just the Array instance when constructing the PVSystem.\r\n\r\n```\r\nmount = pvlib.pvsystem.FixedMount(surface_tilt=35, surface_azimuth=180)\r\narray = pvlib.pvsystem.Array(mount=mount)\r\npv = pvlib.pvsystem.PVSystem(arrays=array)\r\n\r\n---------------------------------------------------------------------------\r\nTypeError                                 Traceback (most recent call last)\r\n<ipython-input-13-f5424e3db16a> in <module>\r\n      3 mount = pvlib.pvsystem.FixedMount(surface_tilt=35, surface_azimuth=180)\r\n      4 array = pvlib.pvsystem.Array(mount=mount)\r\n----> 5 pv = pvlib.pvsystem.PVSystem(arrays=array)\r\n\r\n~\\anaconda3\\lib\\site-packages\\pvlib\\pvsystem.py in __init__(self, arrays, surface_tilt, surface_azimuth, albedo, surface_type, module, module_type, module_parameters, temperature_model_parameters, modules_per_string, strings_per_inverter, inverter, inverter_parameters, racking_model, losses_parameters, name)\r\n    251                 array_losses_parameters,\r\n    252             ),)\r\n--> 253         elif len(arrays) == 0:\r\n    254             raise ValueError(\"PVSystem must have at least one Array. \"\r\n    255                              \"If you want to create a PVSystem instance \"\r\n\r\nTypeError: object of type 'Array' has no len()\r\n\r\n```\r\n\r\nNot a bug per se, since the PVSystem docstring requests that `arrays` be iterable. Still, a bit inconvenient to have to do this\r\n\r\n```\r\nmount = pvlib.pvsystem.FixedMount(surface_tilt=35, surface_azimuth=180)\r\narray = pvlib.pvsystem.Array(mount=mount)\r\npv = pvlib.pvsystem.PVSystem(arrays=[array])\r\n```\r\n\r\n**Describe the solution you'd like**\r\nHandle `arrays=array` where `array` is an instance of `Array`\r\n\r\n**Describe alternatives you've considered**\r\nStatus quo - either make the single Array into a list, or use the PVSystem kwargs.\r\n\n", "repo": "pvlib/pvlib-python", "test_patch": "diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py\n--- a/pvlib/tests/test_pvsystem.py\n+++ b/pvlib/tests/test_pvsystem.py\n@@ -1887,8 +1887,6 @@ def test_PVSystem_multiple_array_creation():\n     assert pv_system.arrays[0].module_parameters == {}\n     assert pv_system.arrays[1].module_parameters == {'pdc0': 1}\n     assert pv_system.arrays == (array_one, array_two)\n-    with pytest.raises(TypeError):\n-        pvsystem.PVSystem(arrays=array_one)\n \n \n def test_PVSystem_get_aoi():\n@@ -2362,6 +2360,14 @@ def test_PVSystem_at_least_one_array():\n         pvsystem.PVSystem(arrays=[])\n \n \n+def test_PVSystem_single_array():\n+    # GH 1831\n+    single_array = pvsystem.Array(pvsystem.FixedMount())\n+    system = pvsystem.PVSystem(arrays=single_array)\n+    assert isinstance(system.arrays, tuple)\n+    assert system.arrays[0] is single_array\n+\n+\n def test_combine_loss_factors():\n     test_index = pd.date_range(start='1990/01/01T12:00', periods=365, freq='D')\n     loss_1 = pd.Series(.10, index=test_index)\n", "version": "0.9"}
parsed_commit_content: {"file_diffs":[{"old_file_content":"\"\"\"\nThe ``pvsystem`` module contains functions for modeling the output and\nperformance of PV modules and inverters.\n\"\"\"\n\nfrom collections import OrderedDict\nimport functools\nimport io\nimport itertools\nimport os\nimport inspect\nfrom urllib.request import urlopen\nimport numpy as np\nfrom scipy import constants\nimport pandas as pd\nfrom dataclasses import dataclass\nfrom abc import ABC, abstractmethod\nfrom typing import Optional\n\nfrom pvlib._deprecation import deprecated, warn_deprecated\n\nfrom pvlib import (atmosphere, iam, inverter, irradiance,\n                   singlediode as _singlediode, spectrum, temperature)\nfrom pvlib.tools import _build_kwargs, _build_args\nimport pvlib.tools as tools\n\n\n# a dict of required parameter names for each DC power model\n_DC_MODEL_PARAMS = {\n    'sapm': {\n        'A0', 'A1', 'A2', 'A3', 'A4', 'B0', 'B1', 'B2', 'B3',\n        'B4', 'B5', 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6',\n        'C7', 'Isco', 'Impo', 'Voco', 'Vmpo', 'Aisc', 'Aimp', 'Bvoco',\n        'Mbvoc', 'Bvmpo', 'Mbvmp', 'N', 'Cells_in_Series',\n        'IXO', 'IXXO', 'FD'},\n    'desoto': {\n        'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_s'},\n    'cec': {\n        'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_s', 'Adjust'},\n    'pvsyst': {\n        'gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_sh_0', 'R_s', 'alpha_sc', 'EgRef',\n        'cells_in_series'},\n    'singlediode': {\n        'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_s'},\n    'pvwatts': {'pdc0', 'gamma_pdc'}\n}\n\n\ndef _unwrap_single_value(func):\n    \"\"\"Decorator for functions that return iterables.\n\n    If the length of the iterable returned by `func` is 1, then\n    the single member of the iterable is returned. If the length is\n    greater than 1, then entire iterable is returned.\n\n    Adds 'unwrap' as a keyword argument that can be set to False\n    to force the return value to be a tuple, regardless of its length.\n    \"\"\"\n    @functools.wraps(func)\n    def f(*args, **kwargs):\n        unwrap = kwargs.pop('unwrap', True)\n        x = func(*args, **kwargs)\n        if unwrap and len(x) == 1:\n            return x[0]\n        return x\n    return f\n\n\n# not sure if this belongs in the pvsystem module.\n# maybe something more like core.py? It may eventually grow to\n# import a lot more functionality from other modules.\nclass PVSystem:\n    \"\"\"\n    The PVSystem class defines a standard set of PV system attributes\n    and modeling functions. This class describes the collection and\n    interactions of PV system components rather than an installed system\n    on the ground. It is typically used in combination with\n    :py:class:`~pvlib.location.Location` and\n    :py:class:`~pvlib.modelchain.ModelChain`\n    objects.\n\n    The class supports basic system topologies consisting of:\n\n        * `N` total modules arranged in series\n          (`modules_per_string=N`, `strings_per_inverter=1`).\n        * `M` total modules arranged in parallel\n          (`modules_per_string=1`, `strings_per_inverter=M`).\n        * `NxM` total modules arranged in `M` strings of `N` modules each\n          (`modules_per_string=N`, `strings_per_inverter=M`).\n\n    The class is complementary to the module-level functions.\n\n    The attributes should generally be things that don't change about\n    the system, such the type of module and the inverter. The instance\n    methods accept arguments for things that do change, such as\n    irradiance and temperature.\n\n    Parameters\n    ----------\n    arrays : iterable of Array, optional\n        List of arrays that are part of the system. If not specified\n        a single array is created from the other parameters (e.g.\n        `surface_tilt`, `surface_azimuth`). Must contain at least one Array,\n        if length of arrays is 0 a ValueError is raised. If `arrays` is\n        specified the following PVSystem parameters are ignored:\n\n        - `surface_tilt`\n        - `surface_azimuth`\n        - `albedo`\n        - `surface_type`\n        - `module`\n        - `module_type`\n        - `module_parameters`\n        - `temperature_model_parameters`\n        - `modules_per_string`\n        - `strings_per_inverter`\n\n    surface_tilt: float or array-like, default 0\n        Surface tilt angles in decimal degrees.\n        The tilt angle is defined as degrees from horizontal\n        (e.g. surface facing up = 0, surface facing horizon = 90)\n\n    surface_azimuth: float or array-like, default 180\n        Azimuth angle of the module surface.\n        North=0, East=90, South=180, West=270.\n\n    albedo : None or float, default None\n        Ground surface albedo. If ``None``, then ``surface_type`` is used\n        to look up a value in ``irradiance.SURFACE_ALBEDOS``.\n        If ``surface_type`` is also None then a ground surface albedo\n        of 0.25 is used.\n\n    surface_type : None or string, default None\n        The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for\n        valid values.\n\n    module : None or string, default None\n        The model name of the modules.\n        May be used to look up the module_parameters dictionary\n        via some other method.\n\n    module_type : None or string, default 'glass_polymer'\n         Describes the module's construction. Valid strings are 'glass_polymer'\n         and 'glass_glass'. Used for cell and module temperature calculations.\n\n    module_parameters : None, dict or Series, default None\n        Module parameters as defined by the SAPM, CEC, or other.\n\n    temperature_model_parameters : None, dict or Series, default None.\n        Temperature model parameters as required by one of the models in\n        pvlib.temperature (excluding poa_global, temp_air and wind_speed).\n\n    modules_per_string: int or float, default 1\n        See system topology discussion above.\n\n    strings_per_inverter: int or float, default 1\n        See system topology discussion above.\n\n    inverter : None or string, default None\n        The model name of the inverters.\n        May be used to look up the inverter_parameters dictionary\n        via some other method.\n\n    inverter_parameters : None, dict or Series, default None\n        Inverter parameters as defined by the SAPM, CEC, or other.\n\n    racking_model : None or string, default 'open_rack'\n        Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.\n        Used to identify a parameter set for the SAPM cell temperature model.\n\n    losses_parameters : None, dict or Series, default None\n        Losses parameters as defined by PVWatts or other.\n\n    name : None or string, default None\n\n    **kwargs\n        Arbitrary keyword arguments.\n        Included for compatibility, but not used.\n\n    Raises\n    ------\n    ValueError\n        If `arrays` is not None and has length 0.\n\n    See also\n    --------\n    pvlib.location.Location\n    \"\"\"\n\n    def __init__(self,\n                 arrays=None,\n                 surface_tilt=0, surface_azimuth=180,\n                 albedo=None, surface_type=None,\n                 module=None, module_type=None,\n                 module_parameters=None,\n                 temperature_model_parameters=None,\n                 modules_per_string=1, strings_per_inverter=1,\n                 inverter=None, inverter_parameters=None,\n                 racking_model=None, losses_parameters=None, name=None):\n\n        if arrays is None:\n            if losses_parameters is None:\n                array_losses_parameters = {}\n            else:\n                array_losses_parameters = _build_kwargs(['dc_ohmic_percent'],\n                                                        losses_parameters)\n            self.arrays = (Array(\n                FixedMount(surface_tilt, surface_azimuth, racking_model),\n                albedo,\n                surface_type,\n                module,\n                module_type,\n                module_parameters,\n                temperature_model_parameters,\n                modules_per_string,\n                strings_per_inverter,\n                array_losses_parameters,\n            ),)\n        elif len(arrays) == 0:\n            raise ValueError(\"PVSystem must have at least one Array. \"\n                             \"If you want to create a PVSystem instance \"\n                             \"with a single Array pass `arrays=None` and pass \"\n                             \"values directly to PVSystem attributes, e.g., \"\n                             \"`surface_tilt=30`\")\n        else:\n            self.arrays = tuple(arrays)\n\n        self.inverter = inverter\n        if inverter_parameters is None:\n            self.inverter_parameters = {}\n        else:\n            self.inverter_parameters = inverter_parameters\n\n        if losses_parameters is None:\n            self.losses_parameters = {}\n        else:\n            self.losses_parameters = losses_parameters\n\n        self.name = name\n\n    def __repr__(self):\n        repr = f'PVSystem:\\n  name: {self.name}\\n  '\n        for array in self.arrays:\n            repr += '\\n  '.join(array.__repr__().split('\\n'))\n            repr += '\\n  '\n        repr += f'inverter: {self.inverter}'\n        return repr\n\n    def _validate_per_array(self, values, system_wide=False):\n        \"\"\"Check that `values` is a tuple of the same length as\n        `self.arrays`.\n\n        If `values` is not a tuple it is packed in to a length-1 tuple before\n        the check. If the lengths are not the same a ValueError is raised,\n        otherwise the tuple `values` is returned.\n\n        When `system_wide` is True and `values` is not a tuple, `values`\n        is replicated to a tuple of the same length as `self.arrays` and that\n        tuple is returned.\n        \"\"\"\n        if system_wide and not isinstance(values, tuple):\n            return (values,) * self.num_arrays\n        if not isinstance(values, tuple):\n            values = (values,)\n        if len(values) != len(self.arrays):\n            raise ValueError(\"Length mismatch for per-array parameter\")\n        return values\n\n    @_unwrap_single_value\n    def _infer_cell_type(self):\n        \"\"\"\n        Examines module_parameters and maps the Technology key for the CEC\n        database and the Material key for the Sandia database to a common\n        list of strings for cell type.\n\n        Returns\n        -------\n        cell_type: str\n        \"\"\"\n        return tuple(array._infer_cell_type() for array in self.arrays)\n\n    @_unwrap_single_value\n    def get_aoi(self, solar_zenith, solar_azimuth):\n        \"\"\"Get the angle of incidence on the Array(s) in the system.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series.\n            Solar zenith angle.\n        solar_azimuth : float or Series.\n            Solar azimuth angle.\n\n        Returns\n        -------\n        aoi : Series or tuple of Series\n            The angle of incidence\n        \"\"\"\n\n        return tuple(array.get_aoi(solar_zenith, solar_azimuth)\n                     for array in self.arrays)\n\n    @_unwrap_single_value\n    def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,\n                       dni_extra=None, airmass=None, albedo=None,\n                       model='haydavies', **kwargs):\n        \"\"\"\n        Uses the :py:func:`irradiance.get_total_irradiance` function to\n        calculate the plane of array irradiance components on the tilted\n        surfaces defined by each array's ``surface_tilt`` and\n        ``surface_azimuth``.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series\n            Solar zenith angle.\n        solar_azimuth : float or Series\n            Solar azimuth angle.\n        dni : float or Series or tuple of float or Series\n            Direct Normal Irradiance. [W/m2]\n        ghi : float or Series or tuple of float or Series\n            Global horizontal irradiance. [W/m2]\n        dhi : float or Series or tuple of float or Series\n            Diffuse horizontal irradiance. [W/m2]\n        dni_extra : None, float, Series or tuple of float or Series,\\\n            default None\n            Extraterrestrial direct normal irradiance. [W/m2]\n        airmass : None, float or Series, default None\n            Airmass. [unitless]\n        albedo : None, float or Series, default None\n            Ground surface albedo. [unitless]\n        model : String, default 'haydavies'\n            Irradiance model.\n\n        kwargs\n            Extra parameters passed to :func:`irradiance.get_total_irradiance`.\n\n        Notes\n        -----\n        Each of `dni`, `ghi`, and `dni` parameters may be passed as a tuple\n        to provide different irradiance for each array in the system. If not\n        passed as a tuple then the same value is used for input to each Array.\n        If passed as a tuple the length must be the same as the number of\n        Arrays.\n\n        Returns\n        -------\n        poa_irradiance : DataFrame or tuple of DataFrame\n            Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse',\n            'poa_sky_diffuse', 'poa_ground_diffuse'``.\n\n        See also\n        --------\n        pvlib.irradiance.get_total_irradiance\n        \"\"\"\n        dni = self._validate_per_array(dni, system_wide=True)\n        ghi = self._validate_per_array(ghi, system_wide=True)\n        dhi = self._validate_per_array(dhi, system_wide=True)\n\n        albedo = self._validate_per_array(albedo, system_wide=True)\n\n        return tuple(\n            array.get_irradiance(solar_zenith, solar_azimuth,\n                                 dni, ghi, dhi,\n                                 dni_extra=dni_extra, airmass=airmass,\n                                 albedo=albedo, model=model, **kwargs)\n            for array, dni, ghi, dhi, albedo in zip(\n                self.arrays, dni, ghi, dhi, albedo\n            )\n        )\n\n    @_unwrap_single_value\n    def get_iam(self, aoi, iam_model='physical'):\n        \"\"\"\n        Determine the incidence angle modifier using the method specified by\n        ``iam_model``.\n\n        Parameters for the selected IAM model are expected to be in\n        ``PVSystem.module_parameters``. Default parameters are available for\n        the 'physical', 'ashrae' and 'martin_ruiz' models.\n\n        Parameters\n        ----------\n        aoi : numeric or tuple of numeric\n            The angle of incidence in degrees.\n\n        aoi_model : string, default 'physical'\n            The IAM model to be used. Valid strings are 'physical', 'ashrae',\n            'martin_ruiz', 'sapm' and 'interp'.\n        Returns\n        -------\n        iam : numeric or tuple of numeric\n            The AOI modifier.\n\n        Raises\n        ------\n        ValueError\n            if `iam_model` is not a valid model name.\n        \"\"\"\n        aoi = self._validate_per_array(aoi)\n        return tuple(array.get_iam(aoi, iam_model)\n                     for array, aoi in zip(self.arrays, aoi))\n\n    @_unwrap_single_value\n    def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,\n                             effective_irradiance=None):\n        \"\"\"\n        Determine cell temperature using the method specified by ``model``.\n\n        Parameters\n        ----------\n        poa_global : numeric or tuple of numeric\n            Total incident irradiance in W/m^2.\n\n        temp_air : numeric or tuple of numeric\n            Ambient dry bulb temperature in degrees C.\n\n        wind_speed : numeric or tuple of numeric\n            Wind speed in m/s.\n\n        model : str\n            Supported models include ``'sapm'``, ``'pvsyst'``,\n            ``'faiman'``, ``'fuentes'``, and ``'noct_sam'``\n\n        effective_irradiance : numeric or tuple of numeric, optional\n            The irradiance that is converted to photocurrent in W/m^2.\n            Only used for some models.\n\n        Returns\n        -------\n        numeric or tuple of numeric\n            Values in degrees C.\n\n        See Also\n        --------\n        Array.get_cell_temperature\n\n        Notes\n        -----\n        The `temp_air` and `wind_speed` parameters may be passed as tuples\n        to provide different values for each Array in the system. If passed as\n        a tuple the length must be the same as the number of Arrays. If not\n        passed as a tuple then the same value is used for each Array.\n        \"\"\"\n        poa_global = self._validate_per_array(poa_global)\n        temp_air = self._validate_per_array(temp_air, system_wide=True)\n        wind_speed = self._validate_per_array(wind_speed, system_wide=True)\n        # Not used for all models, but Array.get_cell_temperature handles it\n        effective_irradiance = self._validate_per_array(effective_irradiance,\n                                                        system_wide=True)\n\n        return tuple(\n            array.get_cell_temperature(poa_global, temp_air, wind_speed,\n                                       model, effective_irradiance)\n            for array, poa_global, temp_air, wind_speed, effective_irradiance\n            in zip(\n                self.arrays, poa_global, temp_air, wind_speed,\n                effective_irradiance\n            )\n        )\n\n    @_unwrap_single_value\n    def calcparams_desoto(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`calcparams_desoto` function, the input\n        parameters and ``self.module_parameters`` to calculate the\n        module currents and resistances.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.calcparams_desoto for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        build_kwargs = functools.partial(\n            _build_kwargs,\n            ['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',\n             'R_s', 'alpha_sc', 'EgRef', 'dEgdT',\n             'irrad_ref', 'temp_ref']\n        )\n\n        return tuple(\n            calcparams_desoto(\n                effective_irradiance, temp_cell,\n                **build_kwargs(array.module_parameters)\n            )\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def calcparams_cec(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`calcparams_cec` function, the input\n        parameters and ``self.module_parameters`` to calculate the\n        module currents and resistances.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.calcparams_cec for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        build_kwargs = functools.partial(\n            _build_kwargs,\n            ['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',\n             'R_s', 'alpha_sc', 'Adjust', 'EgRef', 'dEgdT',\n             'irrad_ref', 'temp_ref']\n        )\n\n        return tuple(\n            calcparams_cec(\n                effective_irradiance, temp_cell,\n                **build_kwargs(array.module_parameters)\n            )\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def calcparams_pvsyst(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`calcparams_pvsyst` function, the input\n        parameters and ``self.module_parameters`` to calculate the\n        module currents and resistances.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.calcparams_pvsyst for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        build_kwargs = functools.partial(\n            _build_kwargs,\n            ['gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref',\n             'R_sh_ref', 'R_sh_0', 'R_sh_exp',\n             'R_s', 'alpha_sc', 'EgRef',\n             'irrad_ref', 'temp_ref',\n             'cells_in_series']\n        )\n\n        return tuple(\n            calcparams_pvsyst(\n                effective_irradiance, temp_cell,\n                **build_kwargs(array.module_parameters)\n            )\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def sapm(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`sapm` function, the input parameters,\n        and ``self.module_parameters`` to calculate\n        Voc, Isc, Ix, Ixx, Vmp, and Imp.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.sapm for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        return tuple(\n            sapm(effective_irradiance, temp_cell, array.module_parameters)\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def sapm_spectral_loss(self, airmass_absolute):\n        \"\"\"\n        Use the :py:func:`pvlib.spectrum.spectral_factor_sapm` function,\n        the input parameters, and ``self.module_parameters`` to calculate F1.\n\n        Parameters\n        ----------\n        airmass_absolute : numeric\n            Absolute airmass.\n\n        Returns\n        -------\n        F1 : numeric or tuple of numeric\n            The SAPM spectral loss coefficient.\n        \"\"\"\n        return tuple(\n            spectrum.spectral_factor_sapm(airmass_absolute,\n                                          array.module_parameters)\n            for array in self.arrays\n        )\n\n    @_unwrap_single_value\n    def sapm_effective_irradiance(self, poa_direct, poa_diffuse,\n                                  airmass_absolute, aoi,\n                                  reference_irradiance=1000):\n        \"\"\"\n        Use the :py:func:`sapm_effective_irradiance` function, the input\n        parameters, and ``self.module_parameters`` to calculate\n        effective irradiance.\n\n        Parameters\n        ----------\n        poa_direct : numeric or tuple of numeric\n            The direct irradiance incident upon the module.  [W/m2]\n\n        poa_diffuse : numeric or tuple of numeric\n            The diffuse irradiance incident on module.  [W/m2]\n\n        airmass_absolute : numeric\n            Absolute airmass. [unitless]\n\n        aoi : numeric or tuple of numeric\n            Angle of incidence. [degrees]\n\n        Returns\n        -------\n        effective_irradiance : numeric or tuple of numeric\n            The SAPM effective irradiance. [W/m2]\n        \"\"\"\n        poa_direct = self._validate_per_array(poa_direct)\n        poa_diffuse = self._validate_per_array(poa_diffuse)\n        aoi = self._validate_per_array(aoi)\n        return tuple(\n            sapm_effective_irradiance(\n                poa_direct, poa_diffuse, airmass_absolute, aoi,\n                array.module_parameters)\n            for array, poa_direct, poa_diffuse, aoi\n            in zip(self.arrays, poa_direct, poa_diffuse, aoi)\n        )\n\n    @_unwrap_single_value\n    def first_solar_spectral_loss(self, pw, airmass_absolute):\n        \"\"\"\n        Use :py:func:`pvlib.spectrum.spectral_factor_firstsolar` to\n        calculate the spectral loss modifier. The model coefficients are\n        specific to the module's cell type, and are determined by searching\n        for one of the following keys in self.module_parameters (in order):\n\n        - 'first_solar_spectral_coefficients' (user-supplied coefficients)\n        - 'Technology' - a string describing the cell type, can be read from\n          the CEC module parameter database\n        - 'Material' - a string describing the cell type, can be read from\n          the Sandia module database.\n\n        Parameters\n        ----------\n        pw : array-like\n            atmospheric precipitable water (cm).\n\n        airmass_absolute : array-like\n            absolute (pressure corrected) airmass.\n\n        Returns\n        -------\n        modifier: array-like or tuple of array-like\n            spectral mismatch factor (unitless) which can be multiplied\n            with broadband irradiance reaching a module's cells to estimate\n            effective irradiance, i.e., the irradiance that is converted to\n            electrical current.\n        \"\"\"\n        pw = self._validate_per_array(pw, system_wide=True)\n\n        def _spectral_correction(array, pw):\n            if 'first_solar_spectral_coefficients' in \\\n                    array.module_parameters.keys():\n                coefficients = \\\n                    array.module_parameters[\n                        'first_solar_spectral_coefficients'\n                    ]\n                module_type = None\n            else:\n                module_type = array._infer_cell_type()\n                coefficients = None\n\n            return spectrum.spectral_factor_firstsolar(\n                pw, airmass_absolute, module_type, coefficients\n            )\n        return tuple(\n            itertools.starmap(_spectral_correction, zip(self.arrays, pw))\n        )\n\n    def singlediode(self, photocurrent, saturation_current,\n                    resistance_series, resistance_shunt, nNsVth,\n                    ivcurve_pnts=None):\n        \"\"\"Wrapper around the :py:func:`pvlib.pvsystem.singlediode` function.\n\n        See :py:func:`pvsystem.singlediode` for details\n        \"\"\"\n        return singlediode(photocurrent, saturation_current,\n                           resistance_series, resistance_shunt, nNsVth,\n                           ivcurve_pnts=ivcurve_pnts)\n\n    def i_from_v(self, voltage, photocurrent, saturation_current,\n                 resistance_series, resistance_shunt, nNsVth):\n        \"\"\"Wrapper around the :py:func:`pvlib.pvsystem.i_from_v` function.\n\n        See :py:func:`pvlib.pvsystem.i_from_v` for details.\n\n        .. versionchanged:: 0.10.0\n           The function's arguments have been reordered.\n        \"\"\"\n        return i_from_v(voltage, photocurrent, saturation_current,\n                        resistance_series, resistance_shunt, nNsVth)\n\n    def get_ac(self, model, p_dc, v_dc=None):\n        r\"\"\"Calculates AC power from p_dc using the inverter model indicated\n        by model and self.inverter_parameters.\n\n        Parameters\n        ----------\n        model : str\n            Must be one of 'sandia', 'adr', or 'pvwatts'.\n        p_dc : numeric, or tuple, list or array of numeric\n            DC power on each MPPT input of the inverter. Use tuple, list or\n            array for inverters with multiple MPPT inputs. If type is array,\n            p_dc must be 2d with axis 0 being the MPPT inputs. [W]\n        v_dc : numeric, or tuple, list or array of numeric\n            DC voltage on each MPPT input of the inverter. Required when\n            model='sandia' or model='adr'. Use tuple, list or\n            array for inverters with multiple MPPT inputs. If type is array,\n            v_dc must be 2d with axis 0 being the MPPT inputs. [V]\n\n        Returns\n        -------\n        power_ac : numeric\n            AC power output for the inverter. [W]\n\n        Raises\n        ------\n        ValueError\n            If model is not one of 'sandia', 'adr' or 'pvwatts'.\n        ValueError\n            If model='adr' and the PVSystem has more than one array.\n\n        See also\n        --------\n        pvlib.inverter.sandia\n        pvlib.inverter.sandia_multi\n        pvlib.inverter.adr\n        pvlib.inverter.pvwatts\n        pvlib.inverter.pvwatts_multi\n        \"\"\"\n        model = model.lower()\n        multiple_arrays = self.num_arrays > 1\n        if model == 'sandia':\n            p_dc = self._validate_per_array(p_dc)\n            v_dc = self._validate_per_array(v_dc)\n            if multiple_arrays:\n                return inverter.sandia_multi(\n                    v_dc, p_dc, self.inverter_parameters)\n            return inverter.sandia(v_dc[0], p_dc[0], self.inverter_parameters)\n        elif model == 'pvwatts':\n            kwargs = _build_kwargs(['eta_inv_nom', 'eta_inv_ref'],\n                                   self.inverter_parameters)\n            p_dc = self._validate_per_array(p_dc)\n            if multiple_arrays:\n                return inverter.pvwatts_multi(\n                    p_dc, self.inverter_parameters['pdc0'], **kwargs)\n            return inverter.pvwatts(\n                p_dc[0], self.inverter_parameters['pdc0'], **kwargs)\n        elif model == 'adr':\n            if multiple_arrays:\n                raise ValueError(\n                    'The adr inverter function cannot be used for an inverter',\n                    ' with multiple MPPT inputs')\n            # While this is only used for single-array systems, calling\n            # _validate_per_arry lets us pass in singleton tuples.\n            p_dc = self._validate_per_array(p_dc)\n            v_dc = self._validate_per_array(v_dc)\n            return inverter.adr(v_dc[0], p_dc[0], self.inverter_parameters)\n        else:\n            raise ValueError(\n                model + ' is not a valid AC power model.',\n                ' model must be one of \"sandia\", \"adr\" or \"pvwatts\"')\n\n    @_unwrap_single_value\n    def scale_voltage_current_power(self, data):\n        \"\"\"\n        Scales the voltage, current, and power of the `data` DataFrame\n        by `self.modules_per_string` and `self.strings_per_inverter`.\n\n        Parameters\n        ----------\n        data: DataFrame or tuple of DataFrame\n            May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',\n            'i_sc', 'p_mp'`.\n\n        Returns\n        -------\n        scaled_data: DataFrame or tuple of DataFrame\n            A scaled copy of the input data.\n        \"\"\"\n        data = self._validate_per_array(data)\n        return tuple(\n            scale_voltage_current_power(data,\n                                        voltage=array.modules_per_string,\n                                        current=array.strings)\n            for array, data in zip(self.arrays, data)\n        )\n\n    @_unwrap_single_value\n    def pvwatts_dc(self, g_poa_effective, temp_cell):\n        \"\"\"\n        Calcuates DC power according to the PVWatts model using\n        :py:func:`pvlib.pvsystem.pvwatts_dc`, `self.module_parameters['pdc0']`,\n        and `self.module_parameters['gamma_pdc']`.\n\n        See :py:func:`pvlib.pvsystem.pvwatts_dc` for details.\n        \"\"\"\n        g_poa_effective = self._validate_per_array(g_poa_effective)\n        temp_cell = self._validate_per_array(temp_cell)\n        return tuple(\n            pvwatts_dc(g_poa_effective, temp_cell,\n                       array.module_parameters['pdc0'],\n                       array.module_parameters['gamma_pdc'],\n                       **_build_kwargs(['temp_ref'], array.module_parameters))\n            for array, g_poa_effective, temp_cell\n            in zip(self.arrays, g_poa_effective, temp_cell)\n        )\n\n    def pvwatts_losses(self):\n        \"\"\"\n        Calculates DC power losses according the PVwatts model using\n        :py:func:`pvlib.pvsystem.pvwatts_losses` and\n        ``self.losses_parameters``.\n\n        See :py:func:`pvlib.pvsystem.pvwatts_losses` for details.\n        \"\"\"\n        kwargs = _build_kwargs(['soiling', 'shading', 'snow', 'mismatch',\n                                'wiring', 'connections', 'lid',\n                                'nameplate_rating', 'age', 'availability'],\n                               self.losses_parameters)\n        return pvwatts_losses(**kwargs)\n\n    @_unwrap_single_value\n    def dc_ohms_from_percent(self):\n        \"\"\"\n        Calculates the equivalent resistance of the wires for each array using\n        :py:func:`pvlib.pvsystem.dc_ohms_from_percent`\n\n        See :py:func:`pvlib.pvsystem.dc_ohms_from_percent` for details.\n        \"\"\"\n\n        return tuple(array.dc_ohms_from_percent() for array in self.arrays)\n\n    @property\n    def num_arrays(self):\n        \"\"\"The number of Arrays in the system.\"\"\"\n        return len(self.arrays)\n\n\nclass Array:\n    \"\"\"\n    An Array is a set of of modules at the same orientation.\n\n    Specifically, an array is defined by its mount, the\n    module parameters, the number of parallel strings of modules\n    and the number of modules on each string.\n\n    Parameters\n    ----------\n    mount: FixedMount, SingleAxisTrackerMount, or other\n        Mounting for the array, either on fixed-tilt racking or horizontal\n        single axis tracker. Mounting is used to determine module orientation.\n        If not provided, a FixedMount with zero tilt is used.\n\n    albedo : None or float, default None\n        Ground surface albedo. If ``None``, then ``surface_type`` is used\n        to look up a value in ``irradiance.SURFACE_ALBEDOS``.\n        If ``surface_type`` is also None then a ground surface albedo\n        of 0.25 is used.\n\n    surface_type : None or string, default None\n        The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for valid\n        values.\n\n    module : None or string, default None\n        The model name of the modules.\n        May be used to look up the module_parameters dictionary\n        via some other method.\n\n    module_type : None or string, default None\n         Describes the module's construction. Valid strings are 'glass_polymer'\n         and 'glass_glass'. Used for cell and module temperature calculations.\n\n    module_parameters : None, dict or Series, default None\n        Parameters for the module model, e.g., SAPM, CEC, or other.\n\n    temperature_model_parameters : None, dict or Series, default None.\n        Parameters for the module temperature model, e.g., SAPM, Pvsyst, or\n        other.\n\n    modules_per_string: int, default 1\n        Number of modules per string in the array.\n\n    strings: int, default 1\n        Number of parallel strings in the array.\n\n    array_losses_parameters: None, dict or Series, default None.\n        Supported keys are 'dc_ohmic_percent'.\n\n    name: None or str, default None\n        Name of Array instance.\n    \"\"\"\n\n    def __init__(self, mount,\n                 albedo=None, surface_type=None,\n                 module=None, module_type=None,\n                 module_parameters=None,\n                 temperature_model_parameters=None,\n                 modules_per_string=1, strings=1,\n                 array_losses_parameters=None,\n                 name=None):\n        self.mount = mount\n\n        self.surface_type = surface_type\n        if albedo is None:\n            self.albedo = irradiance.SURFACE_ALBEDOS.get(surface_type, 0.25)\n        else:\n            self.albedo = albedo\n\n        self.module = module\n        if module_parameters is None:\n            self.module_parameters = {}\n        else:\n            self.module_parameters = module_parameters\n\n        self.module_type = module_type\n\n        self.strings = strings\n        self.modules_per_string = modules_per_string\n\n        if temperature_model_parameters is None:\n            self.temperature_model_parameters = \\\n                self._infer_temperature_model_params()\n        else:\n            self.temperature_model_parameters = temperature_model_parameters\n\n        if array_losses_parameters is None:\n            self.array_losses_parameters = {}\n        else:\n            self.array_losses_parameters = array_losses_parameters\n\n        self.name = name\n\n    def __repr__(self):\n        attrs = ['name', 'mount', 'module',\n                 'albedo', 'module_type',\n                 'temperature_model_parameters',\n                 'strings', 'modules_per_string']\n\n        return 'Array:\\n  ' + '\\n  '.join(\n            f'{attr}: {getattr(self, attr)}' for attr in attrs\n        )\n\n    def _infer_temperature_model_params(self):\n        # try to infer temperature model parameters from from racking_model\n        # and module_type\n        param_set = f'{self.mount.racking_model}_{self.module_type}'\n        if param_set in temperature.TEMPERATURE_MODEL_PARAMETERS['sapm']:\n            return temperature._temperature_model_params('sapm', param_set)\n        elif 'freestanding' in param_set:\n            return temperature._temperature_model_params('pvsyst',\n                                                         'freestanding')\n        elif 'insulated' in param_set:  # after SAPM to avoid confusing keys\n            return temperature._temperature_model_params('pvsyst',\n                                                         'insulated')\n        else:\n            return {}\n\n    def _infer_cell_type(self):\n        \"\"\"\n        Examines module_parameters and maps the Technology key for the CEC\n        database and the Material key for the Sandia database to a common\n        list of strings for cell type.\n\n        Returns\n        -------\n        cell_type: str\n\n        \"\"\"\n\n        _cell_type_dict = {'Multi-c-Si': 'multisi',\n                           'Mono-c-Si': 'monosi',\n                           'Thin Film': 'cigs',\n                           'a-Si/nc': 'asi',\n                           'CIS': 'cigs',\n                           'CIGS': 'cigs',\n                           '1-a-Si': 'asi',\n                           'CdTe': 'cdte',\n                           'a-Si': 'asi',\n                           '2-a-Si': None,\n                           '3-a-Si': None,\n                           'HIT-Si': 'monosi',\n                           'mc-Si': 'multisi',\n                           'c-Si': 'multisi',\n                           'Si-Film': 'asi',\n                           'EFG mc-Si': 'multisi',\n                           'GaAs': None,\n                           'a-Si / mono-Si': 'monosi'}\n\n        if 'Technology' in self.module_parameters.keys():\n            # CEC module parameter set\n            cell_type = _cell_type_dict[self.module_parameters['Technology']]\n        elif 'Material' in self.module_parameters.keys():\n            # Sandia module parameter set\n            cell_type = _cell_type_dict[self.module_parameters['Material']]\n        else:\n            cell_type = None\n\n        return cell_type\n\n    def get_aoi(self, solar_zenith, solar_azimuth):\n        \"\"\"\n        Get the angle of incidence on the array.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series\n            Solar zenith angle.\n        solar_azimuth : float or Series\n            Solar azimuth angle\n\n        Returns\n        -------\n        aoi : Series\n            Then angle of incidence.\n        \"\"\"\n        orientation = self.mount.get_orientation(solar_zenith, solar_azimuth)\n        return irradiance.aoi(orientation['surface_tilt'],\n                              orientation['surface_azimuth'],\n                              solar_zenith, solar_azimuth)\n\n    def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,\n                       dni_extra=None, airmass=None, albedo=None,\n                       model='haydavies', **kwargs):\n        \"\"\"\n        Get plane of array irradiance components.\n\n        Uses the :py:func:`pvlib.irradiance.get_total_irradiance` function to\n        calculate the plane of array irradiance components for a surface\n        defined by ``self.surface_tilt`` and ``self.surface_azimuth``.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series.\n            Solar zenith angle.\n        solar_azimuth : float or Series.\n            Solar azimuth angle.\n        dni : float or Series\n            Direct normal irradiance. [W/m2]\n        ghi : float or Series. [W/m2]\n            Global horizontal irradiance\n        dhi : float or Series\n            Diffuse horizontal irradiance. [W/m2]\n        dni_extra : None, float or Series, default None\n            Extraterrestrial direct normal irradiance. [W/m2]\n        airmass : None, float or Series, default None\n            Airmass. [unitless]\n        albedo : None, float or Series, default None\n            Ground surface albedo. [unitless]\n        model : String, default 'haydavies'\n            Irradiance model.\n\n        kwargs\n            Extra parameters passed to\n            :py:func:`pvlib.irradiance.get_total_irradiance`.\n\n        Returns\n        -------\n        poa_irradiance : DataFrame\n            Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse',\n            'poa_sky_diffuse', 'poa_ground_diffuse'``.\n\n        See also\n        --------\n        :py:func:`pvlib.irradiance.get_total_irradiance`\n        \"\"\"\n        if albedo is None:\n            albedo = self.albedo\n\n        # not needed for all models, but this is easier\n        if dni_extra is None:\n            dni_extra = irradiance.get_extra_radiation(solar_zenith.index)\n\n        if airmass is None:\n            airmass = atmosphere.get_relative_airmass(solar_zenith)\n\n        orientation = self.mount.get_orientation(solar_zenith, solar_azimuth)\n        return irradiance.get_total_irradiance(orientation['surface_tilt'],\n                                               orientation['surface_azimuth'],\n                                               solar_zenith, solar_azimuth,\n                                               dni, ghi, dhi,\n                                               dni_extra=dni_extra,\n                                               airmass=airmass,\n                                               albedo=albedo,\n                                               model=model,\n                                               **kwargs)\n\n    def get_iam(self, aoi, iam_model='physical'):\n        \"\"\"\n        Determine the incidence angle modifier using the method specified by\n        ``iam_model``.\n\n        Parameters for the selected IAM model are expected to be in\n        ``Array.module_parameters``. Default parameters are available for\n        the 'physical', 'ashrae' and 'martin_ruiz' models.\n\n        Parameters\n        ----------\n        aoi : numeric\n            The angle of incidence in degrees.\n\n        aoi_model : string, default 'physical'\n            The IAM model to be used. Valid strings are 'physical', 'ashrae',\n            'martin_ruiz', 'sapm' and 'interp'.\n\n        Returns\n        -------\n        iam : numeric\n            The AOI modifier.\n\n        Raises\n        ------\n        ValueError\n            if `iam_model` is not a valid model name.\n        \"\"\"\n        model = iam_model.lower()\n        if model in ['ashrae', 'physical', 'martin_ruiz', 'interp']:\n            func = getattr(iam, model)  # get function at pvlib.iam\n            # get all parameters from function signature to retrieve them from\n            # module_parameters if present\n            params = set(inspect.signature(func).parameters.keys())\n            params.discard('aoi')  # exclude aoi so it can't be repeated\n            kwargs = _build_kwargs(params, self.module_parameters)\n            return func(aoi, **kwargs)\n        elif model == 'sapm':\n            return iam.sapm(aoi, self.module_parameters)\n        else:\n            raise ValueError(model + ' is not a valid IAM model')\n\n    def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,\n                             effective_irradiance=None):\n        \"\"\"\n        Determine cell temperature using the method specified by ``model``.\n\n        Parameters\n        ----------\n        poa_global : numeric\n            Total incident irradiance [W/m^2]\n\n        temp_air : numeric\n            Ambient dry bulb temperature [C]\n\n        wind_speed : numeric\n            Wind speed [m/s]\n\n        model : str\n            Supported models include ``'sapm'``, ``'pvsyst'``,\n            ``'faiman'``, ``'fuentes'``, and ``'noct_sam'``\n\n        effective_irradiance : numeric, optional\n            The irradiance that is converted to photocurrent in W/m^2.\n            Only used for some models.\n\n        Returns\n        -------\n        numeric\n            Values in degrees C.\n\n        See Also\n        --------\n        pvlib.temperature.sapm_cell, pvlib.temperature.pvsyst_cell,\n        pvlib.temperature.faiman, pvlib.temperature.fuentes,\n        pvlib.temperature.noct_sam\n\n        Notes\n        -----\n        Some temperature models have requirements for the input types;\n        see the documentation of the underlying model function for details.\n        \"\"\"\n        # convenience wrapper to avoid passing args 2 and 3 every call\n        _build_tcell_args = functools.partial(\n            _build_args, input_dict=self.temperature_model_parameters,\n            dict_name='temperature_model_parameters')\n\n        if model == 'sapm':\n            func = temperature.sapm_cell\n            required = _build_tcell_args(['a', 'b', 'deltaT'])\n            optional = _build_kwargs(['irrad_ref'],\n                                     self.temperature_model_parameters)\n        elif model == 'pvsyst':\n            func = temperature.pvsyst_cell\n            required = tuple()\n            optional = {\n                **_build_kwargs(['module_efficiency', 'alpha_absorption'],\n                                self.module_parameters),\n                **_build_kwargs(['u_c', 'u_v'],\n                                self.temperature_model_parameters)\n            }\n        elif model == 'faiman':\n            func = temperature.faiman\n            required = tuple()\n            optional = _build_kwargs(['u0', 'u1'],\n                                     self.temperature_model_parameters)\n        elif model == 'fuentes':\n            func = temperature.fuentes\n            required = _build_tcell_args(['noct_installed'])\n            optional = _build_kwargs([\n                'wind_height', 'emissivity', 'absorption',\n                'surface_tilt', 'module_width', 'module_length'],\n                self.temperature_model_parameters)\n            if self.mount.module_height is not None:\n                optional['module_height'] = self.mount.module_height\n        elif model == 'noct_sam':\n            func = functools.partial(temperature.noct_sam,\n                                     effective_irradiance=effective_irradiance)\n            required = _build_tcell_args(['noct', 'module_efficiency'])\n            optional = _build_kwargs(['transmittance_absorptance',\n                                      'array_height', 'mount_standoff'],\n                                     self.temperature_model_parameters)\n        else:\n            raise ValueError(f'{model} is not a valid cell temperature model')\n\n        temperature_cell = func(poa_global, temp_air, wind_speed,\n                                *required, **optional)\n        return temperature_cell\n\n    def dc_ohms_from_percent(self):\n        \"\"\"\n        Calculates the equivalent resistance of the wires using\n        :py:func:`pvlib.pvsystem.dc_ohms_from_percent`\n\n        Makes use of array module parameters according to the\n        following DC models:\n\n        CEC:\n\n            * `self.module_parameters[\"V_mp_ref\"]`\n            * `self.module_parameters[\"I_mp_ref\"]`\n\n        SAPM:\n\n            * `self.module_parameters[\"Vmpo\"]`\n            * `self.module_parameters[\"Impo\"]`\n\n        PVsyst-like or other:\n\n            * `self.module_parameters[\"Vmpp\"]`\n            * `self.module_parameters[\"Impp\"]`\n\n        Other array parameters that are used are:\n        `self.losses_parameters[\"dc_ohmic_percent\"]`,\n        `self.modules_per_string`, and\n        `self.strings`.\n\n        See :py:func:`pvlib.pvsystem.dc_ohms_from_percent` for more details.\n        \"\"\"\n\n        # get relevent Vmp and Imp parameters from CEC parameters\n        if all(elem in self.module_parameters\n               for elem in ['V_mp_ref', 'I_mp_ref']):\n            vmp_ref = self.module_parameters['V_mp_ref']\n            imp_ref = self.module_parameters['I_mp_ref']\n\n        # get relevant Vmp and Imp parameters from SAPM parameters\n        elif all(elem in self.module_parameters for elem in ['Vmpo', 'Impo']):\n            vmp_ref = self.module_parameters['Vmpo']\n            imp_ref = self.module_parameters['Impo']\n\n        # get relevant Vmp and Imp parameters if they are PVsyst-like\n        elif all(elem in self.module_parameters for elem in ['Vmpp', 'Impp']):\n            vmp_ref = self.module_parameters['Vmpp']\n            imp_ref = self.module_parameters['Impp']\n\n        # raise error if relevant Vmp and Imp parameters are not found\n        else:\n            raise ValueError('Parameters for Vmp and Imp could not be found '\n                             'in the array module parameters. Module '\n                             'parameters must include one set of '\n                             '{\"V_mp_ref\", \"I_mp_Ref\"}, '\n                             '{\"Vmpo\", \"Impo\"}, or '\n                             '{\"Vmpp\", \"Impp\"}.'\n                             )\n\n        return dc_ohms_from_percent(\n            vmp_ref,\n            imp_ref,\n            self.array_losses_parameters['dc_ohmic_percent'],\n            self.modules_per_string,\n            self.strings)\n\n\n@dataclass\nclass AbstractMount(ABC):\n    \"\"\"\n    A base class for Mount classes to extend. It is not intended to be\n    instantiated directly.\n    \"\"\"\n\n    @abstractmethod\n    def get_orientation(self, solar_zenith, solar_azimuth):\n        \"\"\"\n        Determine module orientation.\n\n        Parameters\n        ----------\n        solar_zenith : numeric\n            Solar apparent zenith angle [degrees]\n        solar_azimuth : numeric\n            Solar azimuth angle [degrees]\n\n        Returns\n        -------\n        orientation : dict-like\n            A dict-like object with keys `'surface_tilt', 'surface_azimuth'`\n            (typically a dict or pandas.DataFrame)\n        \"\"\"\n\n\n@dataclass\nclass FixedMount(AbstractMount):\n    \"\"\"\n    Racking at fixed (static) orientation.\n\n    Parameters\n    ----------\n    surface_tilt : float, default 0\n        Surface tilt angle. The tilt angle is defined as angle from horizontal\n        (e.g. surface facing up = 0, surface facing horizon = 90) [degrees]\n\n    surface_azimuth : float, default 180\n        Azimuth angle of the module surface. North=0, East=90, South=180,\n        West=270. [degrees]\n\n    racking_model : str, optional\n        Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.\n        Used to identify a parameter set for the SAPM cell temperature model.\n\n    module_height : float, optional\n       The height above ground of the center of the module [m]. Used for\n       the Fuentes cell temperature model.\n    \"\"\"\n\n    surface_tilt: float = 0.0\n    surface_azimuth: float = 180.0\n    racking_model: Optional[str] = None\n    module_height: Optional[float] = None\n\n    def get_orientation(self, solar_zenith, solar_azimuth):\n        # note -- docstring is automatically inherited from AbstractMount\n        return {\n            'surface_tilt': self.surface_tilt,\n            'surface_azimuth': self.surface_azimuth,\n        }\n\n\n@dataclass\nclass SingleAxisTrackerMount(AbstractMount):\n    \"\"\"\n    Single-axis tracker racking for dynamic solar tracking.\n\n    Parameters\n    ----------\n    axis_tilt : float, default 0\n        The tilt of the axis of rotation (i.e, the y-axis defined by\n        axis_azimuth) with respect to horizontal. [degrees]\n\n    axis_azimuth : float, default 180\n        A value denoting the compass direction along which the axis of\n        rotation lies, measured east of north. [degrees]\n\n    max_angle : float, default 90\n        A value denoting the maximum rotation angle\n        of the one-axis tracker from its horizontal position (horizontal\n        if axis_tilt = 0). A max_angle of 90 degrees allows the tracker\n        to rotate to a vertical position to point the panel towards a\n        horizon. max_angle of 180 degrees allows for full rotation. [degrees]\n\n    backtrack : bool, default True\n        Controls whether the tracker has the capability to \"backtrack\"\n        to avoid row-to-row shading. False denotes no backtrack\n        capability. True denotes backtrack capability.\n\n    gcr : float, default 2.0/7.0\n        A value denoting the ground coverage ratio of a tracker system\n        which utilizes backtracking; i.e. the ratio between the PV array\n        surface area to total ground area. A tracker system with modules\n        2 meters wide, centered on the tracking axis, with 6 meters\n        between the tracking axes has a gcr of 2/6=0.333. If gcr is not\n        provided, a gcr of 2/7 is default. gcr must be <=1. [unitless]\n\n    cross_axis_tilt : float, default 0.0\n        The angle, relative to horizontal, of the line formed by the\n        intersection between the slope containing the tracker axes and a plane\n        perpendicular to the tracker axes. Cross-axis tilt should be specified\n        using a right-handed convention. For example, trackers with axis\n        azimuth of 180 degrees (heading south) will have a negative cross-axis\n        tilt if the tracker axes plane slopes down to the east and positive\n        cross-axis tilt if the tracker axes plane slopes up to the east. Use\n        :func:`~pvlib.tracking.calc_cross_axis_tilt` to calculate\n        `cross_axis_tilt`. [degrees]\n\n    racking_model : str, optional\n        Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.\n        Used to identify a parameter set for the SAPM cell temperature model.\n\n    module_height : float, optional\n       The height above ground of the center of the module [m]. Used for\n       the Fuentes cell temperature model.\n    \"\"\"\n    axis_tilt: float = 0.0\n    axis_azimuth: float = 0.0\n    max_angle: float = 90.0\n    backtrack: bool = True\n    gcr: float = 2.0/7.0\n    cross_axis_tilt: float = 0.0\n    racking_model: Optional[str] = None\n    module_height: Optional[float] = None\n\n    def get_orientation(self, solar_zenith, solar_azimuth):\n        # note -- docstring is automatically inherited from AbstractMount\n        from pvlib import tracking  # avoid circular import issue\n        tracking_data = tracking.singleaxis(\n            solar_zenith, solar_azimuth,\n            self.axis_tilt, self.axis_azimuth,\n            self.max_angle, self.backtrack,\n            self.gcr, self.cross_axis_tilt\n        )\n        return tracking_data\n\n\ndef calcparams_desoto(effective_irradiance, temp_cell,\n                      alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s,\n                      EgRef=1.121, dEgdT=-0.0002677,\n                      irrad_ref=1000, temp_ref=25):\n    '''\n    Calculates five parameter values for the single diode equation at\n    effective irradiance and cell temperature using the De Soto et al.\n    model described in [1]_. The five values returned by calcparams_desoto\n    can be used by singlediode to calculate an IV curve.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        The irradiance (W/m2) that is converted to photocurrent.\n\n    temp_cell : numeric\n        The average cell temperature of cells within a module in C.\n\n    alpha_sc : float\n        The short-circuit current temperature coefficient of the\n        module in units of A/C.\n\n    a_ref : float\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at reference\n        conditions, in units of V.\n\n    I_L_ref : float\n        The light-generated current (or photocurrent) at reference conditions,\n        in amperes.\n\n    I_o_ref : float\n        The dark or diode reverse saturation current at reference conditions,\n        in amperes.\n\n    R_sh_ref : float\n        The shunt resistance at reference conditions, in ohms.\n\n    R_s : float\n        The series resistance at reference conditions, in ohms.\n\n    EgRef : float\n        The energy bandgap at reference temperature in units of eV.\n        1.121 eV for crystalline silicon. EgRef must be >0.  For parameters\n        from the SAM CEC module database, EgRef=1.121 is implicit for all\n        cell types in the parameter estimation algorithm used by NREL.\n\n    dEgdT : float\n        The temperature dependence of the energy bandgap at reference\n        conditions in units of 1/K. May be either a scalar value\n        (e.g. -0.0002677 as in [1]_) or a DataFrame (this may be useful if\n        dEgdT is a modeled as a function of temperature). For parameters from\n        the SAM CEC module database, dEgdT=-0.0002677 is implicit for all cell\n        types in the parameter estimation algorithm used by NREL.\n\n    irrad_ref : float (optional, default=1000)\n        Reference irradiance in W/m^2.\n\n    temp_ref : float (optional, default=25)\n        Reference cell temperature in C.\n\n    Returns\n    -------\n    Tuple of the following results:\n\n    photocurrent : numeric\n        Light-generated current in amperes\n\n    saturation_current : numeric\n        Diode saturation curent in amperes\n\n    resistance_series : numeric\n        Series resistance in ohms\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms\n\n    nNsVth : numeric\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at\n        specified effective irradiance and cell temperature.\n\n    References\n    ----------\n    .. [1] W. De Soto et al., \"Improvement and validation of a model for\n       photovoltaic array performance\", Solar Energy, vol 80, pp. 78-88,\n       2006.\n\n    .. [2] System Advisor Model web page. https://sam.nrel.gov.\n\n    .. [3] A. Dobos, \"An Improved Coefficient Calculator for the California\n       Energy Commission 6 Parameter Photovoltaic Module Model\", Journal of\n       Solar Energy Engineering, vol 134, 2012.\n\n    .. [4] O. Madelung, \"Semiconductors: Data Handbook, 3rd ed.\" ISBN\n       3-540-40488-0\n\n    See Also\n    --------\n    singlediode\n    retrieve_sam\n\n    Notes\n    -----\n    If the reference parameters in the ModuleParameters struct are read\n    from a database or library of parameters (e.g. System Advisor\n    Model), it is important to use the same EgRef and dEgdT values that\n    were used to generate the reference parameters, regardless of the\n    actual bandgap characteristics of the semiconductor. For example, in\n    the case of the System Advisor Model library, created as described\n    in [3], EgRef and dEgdT for all modules were 1.121 and -0.0002677,\n    respectively.\n\n    This table of reference bandgap energies (EgRef), bandgap energy\n    temperature dependence (dEgdT), and \"typical\" airmass response (M)\n    is provided purely as reference to those who may generate their own\n    reference module parameters (a_ref, IL_ref, I0_ref, etc.) based upon\n    the various PV semiconductors. Again, we stress the importance of\n    using identical EgRef and dEgdT when generation reference parameters\n    and modifying the reference parameters (for irradiance, temperature,\n    and airmass) per DeSoto's equations.\n\n     Crystalline Silicon (Si):\n         * EgRef = 1.121\n         * dEgdT = -0.0002677\n\n         >>> M = np.polyval([-1.26E-4, 2.816E-3, -0.024459, 0.086257, 0.9181],\n         ...                AMa) # doctest: +SKIP\n\n         Source: [1]\n\n     Cadmium Telluride (CdTe):\n         * EgRef = 1.475\n         * dEgdT = -0.0003\n\n         >>> M = np.polyval([-2.46E-5, 9.607E-4, -0.0134, 0.0716, 0.9196],\n         ...                AMa) # doctest: +SKIP\n\n         Source: [4]\n\n     Copper Indium diSelenide (CIS):\n         * EgRef = 1.010\n         * dEgdT = -0.00011\n\n         >>> M = np.polyval([-3.74E-5, 0.00125, -0.01462, 0.0718, 0.9210],\n         ...                AMa) # doctest: +SKIP\n\n         Source: [4]\n\n     Copper Indium Gallium diSelenide (CIGS):\n         * EgRef = 1.15\n         * dEgdT = ????\n\n         >>> M = np.polyval([-9.07E-5, 0.0022, -0.0202, 0.0652, 0.9417],\n         ...                AMa) # doctest: +SKIP\n\n         Source: Wikipedia\n\n     Gallium Arsenide (GaAs):\n         * EgRef = 1.424\n         * dEgdT = -0.000433\n         * M = unknown\n\n         Source: [4]\n    '''\n\n    # Boltzmann constant in eV/K, 8.617332478e-05\n    k = constants.value('Boltzmann constant in eV/K')\n\n    # reference temperature\n    Tref_K = temp_ref + 273.15\n    Tcell_K = temp_cell + 273.15\n\n    E_g = EgRef * (1 + dEgdT*(Tcell_K - Tref_K))\n\n    nNsVth = a_ref * (Tcell_K / Tref_K)\n\n    # In the equation for IL, the single factor effective_irradiance is\n    # used, in place of the product S*M in [1]. effective_irradiance is\n    # equivalent to the product of S (irradiance reaching a module's cells) *\n    # M (spectral adjustment factor) as described in [1].\n    IL = effective_irradiance / irrad_ref * \\\n        (I_L_ref + alpha_sc * (Tcell_K - Tref_K))\n    I0 = (I_o_ref * ((Tcell_K / Tref_K) ** 3) *\n          (np.exp(EgRef / (k*(Tref_K)) - (E_g / (k*(Tcell_K))))))\n    # Note that the equation for Rsh differs from [1]. In [1] Rsh is given as\n    # Rsh = Rsh_ref * (S_ref / S) where S is broadband irradiance reaching\n    # the module's cells. If desired this model behavior can be duplicated\n    # by applying reflection and soiling losses to broadband plane of array\n    # irradiance and not applying a spectral loss modifier, i.e.,\n    # spectral_modifier = 1.0.\n    # use errstate to silence divide by warning\n    with np.errstate(divide='ignore'):\n        Rsh = R_sh_ref * (irrad_ref / effective_irradiance)\n\n    Rs = R_s\n\n    numeric_args = (effective_irradiance, temp_cell)\n    out = (IL, I0, Rs, Rsh, nNsVth)\n\n    if all(map(np.isscalar, numeric_args)):\n        return out\n\n    index = tools.get_pandas_index(*numeric_args)\n\n    if index is None:\n        return np.broadcast_arrays(*out)\n\n    return tuple(pd.Series(a, index=index).rename(None) for a in out)\n\n\ndef calcparams_cec(effective_irradiance, temp_cell,\n                   alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s,\n                   Adjust, EgRef=1.121, dEgdT=-0.0002677,\n                   irrad_ref=1000, temp_ref=25):\n    '''\n    Calculates five parameter values for the single diode equation at\n    effective irradiance and cell temperature using the CEC\n    model. The CEC model [1]_ differs from the De soto et al.\n    model [3]_ by the parameter Adjust. The five values returned by\n    calcparams_cec can be used by singlediode to calculate an IV curve.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        The irradiance (W/m2) that is converted to photocurrent.\n\n    temp_cell : numeric\n        The average cell temperature of cells within a module in C.\n\n    alpha_sc : float\n        The short-circuit current temperature coefficient of the\n        module in units of A/C.\n\n    a_ref : float\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at reference\n        conditions, in units of V.\n\n    I_L_ref : float\n        The light-generated current (or photocurrent) at reference conditions,\n        in amperes.\n\n    I_o_ref : float\n        The dark or diode reverse saturation current at reference conditions,\n        in amperes.\n\n    R_sh_ref : float\n        The shunt resistance at reference conditions, in ohms.\n\n    R_s : float\n        The series resistance at reference conditions, in ohms.\n\n    Adjust : float\n        The adjustment to the temperature coefficient for short circuit\n        current, in percent\n\n    EgRef : float\n        The energy bandgap at reference temperature in units of eV.\n        1.121 eV for crystalline silicon. EgRef must be >0.  For parameters\n        from the SAM CEC module database, EgRef=1.121 is implicit for all\n        cell types in the parameter estimation algorithm used by NREL.\n\n    dEgdT : float\n        The temperature dependence of the energy bandgap at reference\n        conditions in units of 1/K. May be either a scalar value\n        (e.g. -0.0002677 as in [3]) or a DataFrame (this may be useful if\n        dEgdT is a modeled as a function of temperature). For parameters from\n        the SAM CEC module database, dEgdT=-0.0002677 is implicit for all cell\n        types in the parameter estimation algorithm used by NREL.\n\n    irrad_ref : float (optional, default=1000)\n        Reference irradiance in W/m^2.\n\n    temp_ref : float (optional, default=25)\n        Reference cell temperature in C.\n\n    Returns\n    -------\n    Tuple of the following results:\n\n    photocurrent : numeric\n        Light-generated current in amperes\n\n    saturation_current : numeric\n        Diode saturation curent in amperes\n\n    resistance_series : numeric\n        Series resistance in ohms\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms\n\n    nNsVth : numeric\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at\n        specified effective irradiance and cell temperature.\n\n    References\n    ----------\n    .. [1] A. Dobos, \"An Improved Coefficient Calculator for the California\n       Energy Commission 6 Parameter Photovoltaic Module Model\", Journal of\n       Solar Energy Engineering, vol 134, 2012.\n\n    .. [2] System Advisor Model web page. https://sam.nrel.gov.\n\n    .. [3] W. De Soto et al., \"Improvement and validation of a model for\n       photovoltaic array performance\", Solar Energy, vol 80, pp. 78-88,\n       2006.\n\n    See Also\n    --------\n    calcparams_desoto\n    singlediode\n    retrieve_sam\n\n    '''\n\n    # pass adjusted temperature coefficient to desoto\n    return calcparams_desoto(effective_irradiance, temp_cell,\n                             alpha_sc*(1.0 - Adjust/100),\n                             a_ref, I_L_ref, I_o_ref,\n                             R_sh_ref, R_s,\n                             EgRef=EgRef, dEgdT=dEgdT,\n                             irrad_ref=irrad_ref, temp_ref=temp_ref)\n\n\ndef calcparams_pvsyst(effective_irradiance, temp_cell,\n                      alpha_sc, gamma_ref, mu_gamma,\n                      I_L_ref, I_o_ref,\n                      R_sh_ref, R_sh_0, R_s,\n                      cells_in_series,\n                      R_sh_exp=5.5,\n                      EgRef=1.121,\n                      irrad_ref=1000, temp_ref=25):\n    '''\n    Calculates five parameter values for the single diode equation at\n    effective irradiance and cell temperature using the PVsyst v6\n    model.  The PVsyst v6 model is described in [1]_, [2]_, [3]_.\n    The five values returned by calcparams_pvsyst can be used by singlediode\n    to calculate an IV curve.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        The irradiance (W/m2) that is converted to photocurrent.\n\n    temp_cell : numeric\n        The average cell temperature of cells within a module in C.\n\n    alpha_sc : float\n        The short-circuit current temperature coefficient of the\n        module in units of A/C.\n\n    gamma_ref : float\n        The diode ideality factor\n\n    mu_gamma : float\n        The temperature coefficient for the diode ideality factor, 1/K\n\n    I_L_ref : float\n        The light-generated current (or photocurrent) at reference conditions,\n        in amperes.\n\n    I_o_ref : float\n        The dark or diode reverse saturation current at reference conditions,\n        in amperes.\n\n    R_sh_ref : float\n        The shunt resistance at reference conditions, in ohms.\n\n    R_sh_0 : float\n        The shunt resistance at zero irradiance conditions, in ohms.\n\n    R_s : float\n        The series resistance at reference conditions, in ohms.\n\n    cells_in_series : integer\n        The number of cells connected in series.\n\n    R_sh_exp : float\n        The exponent in the equation for shunt resistance, unitless. Defaults\n        to 5.5.\n\n    EgRef : float\n        The energy bandgap at reference temperature in units of eV.\n        1.121 eV for crystalline silicon. EgRef must be >0.\n\n    irrad_ref : float (optional, default=1000)\n        Reference irradiance in W/m^2.\n\n    temp_ref : float (optional, default=25)\n        Reference cell temperature in C.\n\n    Returns\n    -------\n    Tuple of the following results:\n\n    photocurrent : numeric\n        Light-generated current in amperes\n\n    saturation_current : numeric\n        Diode saturation current in amperes\n\n    resistance_series : numeric\n        Series resistance in ohms\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms\n\n    nNsVth : numeric\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at\n        specified effective irradiance and cell temperature.\n\n    References\n    ----------\n    .. [1] K. Sauer, T. Roessler, C. W. Hansen, Modeling the Irradiance and\n       Temperature Dependence of Photovoltaic Modules in PVsyst,\n       IEEE Journal of Photovoltaics v5(1), January 2015.\n\n    .. [2] A. Mermoud, PV modules modelling, Presentation at the 2nd PV\n       Performance Modeling Workshop, Santa Clara, CA, May 2013\n\n    .. [3] A. Mermoud, T. Lejeune, Performance Assessment of a Simulation Model\n       for PV modules of any available technology, 25th European Photovoltaic\n       Solar Energy Conference, Valencia, Spain, Sept. 2010\n\n    See Also\n    --------\n    calcparams_desoto\n    singlediode\n\n    '''\n\n    # Boltzmann constant in J/K\n    k = constants.k\n\n    # elementary charge in coulomb\n    q = constants.e\n\n    # reference temperature\n    Tref_K = temp_ref + 273.15\n    Tcell_K = temp_cell + 273.15\n\n    gamma = gamma_ref + mu_gamma * (Tcell_K - Tref_K)\n    nNsVth = gamma * k / q * cells_in_series * Tcell_K\n\n    IL = effective_irradiance / irrad_ref * \\\n        (I_L_ref + alpha_sc * (Tcell_K - Tref_K))\n\n    I0 = I_o_ref * ((Tcell_K / Tref_K) ** 3) * \\\n        (np.exp((q * EgRef) / (k * gamma) * (1 / Tref_K - 1 / Tcell_K)))\n\n    Rsh_tmp = \\\n        (R_sh_ref - R_sh_0 * np.exp(-R_sh_exp)) / (1.0 - np.exp(-R_sh_exp))\n    Rsh_base = np.maximum(0.0, Rsh_tmp)\n\n    Rsh = Rsh_base + (R_sh_0 - Rsh_base) * \\\n        np.exp(-R_sh_exp * effective_irradiance / irrad_ref)\n\n    Rs = R_s\n\n    numeric_args = (effective_irradiance, temp_cell)\n    out = (IL, I0, Rs, Rsh, nNsVth)\n\n    if all(map(np.isscalar, numeric_args)):\n        return out\n\n    index = tools.get_pandas_index(*numeric_args)\n\n    if index is None:\n        return np.broadcast_arrays(*out)\n\n    return tuple(pd.Series(a, index=index).rename(None) for a in out)\n\n\ndef retrieve_sam(name=None, path=None):\n    '''\n    Retrieve latest module and inverter info from a local file or the\n    SAM website.\n\n    This function will retrieve either:\n\n        * CEC module database\n        * Sandia Module database\n        * CEC Inverter database\n        * Anton Driesse Inverter database\n\n    and return it as a pandas DataFrame.\n\n    Parameters\n    ----------\n    name : None or string, default None\n        Name can be one of:\n\n        * 'CECMod' - returns the CEC module database\n        * 'CECInverter' - returns the CEC Inverter database\n        * 'SandiaInverter' - returns the CEC Inverter database\n          (CEC is only current inverter db available; tag kept for\n          backwards compatibility)\n        * 'SandiaMod' - returns the Sandia Module database\n        * 'ADRInverter' - returns the ADR Inverter database\n\n    path : None or string, default None\n        Path to the SAM file. May also be a URL.\n\n    Returns\n    -------\n    samfile : DataFrame\n        A DataFrame containing all the elements of the desired database.\n        Each column represents a module or inverter, and a specific\n        dataset can be retrieved by the command\n\n    Raises\n    ------\n    ValueError\n        If no name or path is provided.\n\n    Notes\n    -----\n    Files available at\n        https://github.com/NREL/SAM/tree/develop/deploy/libraries\n    Documentation for module and inverter data sets:\n        https://sam.nrel.gov/photovoltaic/pv-sub-page-2.html\n\n    Examples\n    --------\n\n    >>> from pvlib import pvsystem\n    >>> invdb = pvsystem.retrieve_sam('CECInverter')\n    >>> inverter = invdb.AE_Solar_Energy__AE6_0__277V_\n    >>> inverter\n    Vac                          277\n    Pso                    36.197575\n    Paco                      6000.0\n    Pdco                 6158.746094\n    Vdco                       360.0\n    C0                     -0.000002\n    C1                     -0.000026\n    C2                     -0.001253\n    C3                       0.00021\n    Pnt                          1.8\n    Vdcmax                     450.0\n    Idcmax                 17.107628\n    Mppt_low                   100.0\n    Mppt_high                  450.0\n    CEC_Date                     NaN\n    CEC_Type     Utility Interactive\n    Name: AE_Solar_Energy__AE6_0__277V_, dtype: object\n    '''\n\n    if name is not None:\n        name = name.lower()\n        data_path = os.path.join(\n            os.path.dirname(os.path.abspath(__file__)), 'data')\n        if name == 'cecmod':\n            csvdata = os.path.join(\n                data_path, 'sam-library-cec-modules-2019-03-05.csv')\n        elif name == 'sandiamod':\n            csvdata = os.path.join(\n                data_path, 'sam-library-sandia-modules-2015-6-30.csv')\n        elif name == 'adrinverter':\n            csvdata = os.path.join(\n                data_path, 'adr-library-cec-inverters-2019-03-05.csv')\n        elif name in ['cecinverter', 'sandiainverter']:\n            # Allowing either, to provide for old code,\n            # while aligning with current expectations\n            csvdata = os.path.join(\n                data_path, 'sam-library-cec-inverters-2019-03-05.csv')\n        else:\n            raise ValueError(f'invalid name {name}')\n    elif path is not None:\n        if path.startswith('http'):\n            response = urlopen(path)\n            csvdata = io.StringIO(response.read().decode(errors='ignore'))\n        else:\n            csvdata = path\n    elif name is None and path is None:\n        raise ValueError(\"A name or path must be provided!\")\n\n    return _parse_raw_sam_df(csvdata)\n\n\ndef _normalize_sam_product_names(names):\n    '''\n    Replace special characters within the product names to make them more\n    suitable for use as Dataframe column names.\n    '''\n    # Contributed by Anton Driesse (@adriesse), PV Performance Labs. July, 2019\n\n    import warnings\n\n    BAD_CHARS = ' -.()[]:+/\",'\n    GOOD_CHARS = '____________'\n\n    mapping = str.maketrans(BAD_CHARS, GOOD_CHARS)\n    names = pd.Series(data=names)\n    norm_names = names.str.translate(mapping)\n\n    n_duplicates = names.duplicated().sum()\n    if n_duplicates > 0:\n        warnings.warn('Original names contain %d duplicate(s).' % n_duplicates)\n\n    n_duplicates = norm_names.duplicated().sum()\n    if n_duplicates > 0:\n        warnings.warn(\n            'Normalized names contain %d duplicate(s).' % n_duplicates)\n\n    return norm_names.values\n\n\ndef _parse_raw_sam_df(csvdata):\n\n    df = pd.read_csv(csvdata, index_col=0, skiprows=[1, 2])\n\n    df.columns = df.columns.str.replace(' ', '_')\n    df.index = _normalize_sam_product_names(df.index)\n    df = df.transpose()\n\n    if 'ADRCoefficients' in df.index:\n        ad_ce = 'ADRCoefficients'\n        # for each inverter, parses a string of coefficients like\n        # ' 1.33, 2.11, 3.12' into a list containing floats:\n        # [1.33, 2.11, 3.12]\n        df.loc[ad_ce] = df.loc[ad_ce].map(lambda x: list(\n            map(float, x.strip(' []').split())))\n\n    return df\n\n\ndef sapm(effective_irradiance, temp_cell, module):\n    '''\n    The Sandia PV Array Performance Model (SAPM) generates 5 points on a\n    PV module's I-V curve (Voc, Isc, Ix, Ixx, Vmp/Imp) according to\n    SAND2004-3535. Assumes a reference cell temperature of 25 C.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        Irradiance reaching the module's cells, after reflections and\n        adjustment for spectrum. [W/m2]\n\n    temp_cell : numeric\n        Cell temperature [C].\n\n    module : dict-like\n        A dict or Series defining the SAPM parameters. See the notes section\n        for more details.\n\n    Returns\n    -------\n    A DataFrame with the columns:\n\n        * i_sc : Short-circuit current (A)\n        * i_mp : Current at the maximum-power point (A)\n        * v_oc : Open-circuit voltage (V)\n        * v_mp : Voltage at maximum-power point (V)\n        * p_mp : Power at maximum-power point (W)\n        * i_x : Current at module V = 0.5Voc, defines 4th point on I-V\n          curve for modeling curve shape\n        * i_xx : Current at module V = 0.5(Voc+Vmp), defines 5th point on\n          I-V curve for modeling curve shape\n\n    Notes\n    -----\n    The SAPM parameters which are required in ``module`` are\n    listed in the following table.\n\n    The Sandia module database contains parameter values for a limited set\n    of modules. The CEC module database does not contain these parameters.\n    Both databases can be accessed using :py:func:`retrieve_sam`.\n\n    ================   ========================================================\n    Key                Description\n    ================   ========================================================\n    A0-A4              The airmass coefficients used in calculating\n                       effective irradiance\n    B0-B5              The angle of incidence coefficients used in calculating\n                       effective irradiance\n    C0-C7              The empirically determined coefficients relating\n                       Imp, Vmp, Ix, and Ixx to effective irradiance\n    Isco               Short circuit current at reference condition (amps)\n    Impo               Maximum power current at reference condition (amps)\n    Voco               Open circuit voltage at reference condition (amps)\n    Vmpo               Maximum power voltage at reference condition (amps)\n    Aisc               Short circuit current temperature coefficient at\n                       reference condition (1/C)\n    Aimp               Maximum power current temperature coefficient at\n                       reference condition (1/C)\n    Bvoco              Open circuit voltage temperature coefficient at\n                       reference condition (V/C)\n    Mbvoc              Coefficient providing the irradiance dependence for the\n                       BetaVoc temperature coefficient at reference irradiance\n                       (V/C)\n    Bvmpo              Maximum power voltage temperature coefficient at\n                       reference condition\n    Mbvmp              Coefficient providing the irradiance dependence for the\n                       BetaVmp temperature coefficient at reference irradiance\n                       (V/C)\n    N                  Empirically determined \"diode factor\" (dimensionless)\n    Cells_in_Series    Number of cells in series in a module's cell string(s)\n    IXO                Ix at reference conditions\n    IXXO               Ixx at reference conditions\n    FD                 Fraction of diffuse irradiance used by module\n    ================   ========================================================\n\n    References\n    ----------\n    .. [1] King, D. et al, 2004, \"Sandia Photovoltaic Array Performance\n       Model\", SAND Report 3535, Sandia National Laboratories, Albuquerque,\n       NM.\n\n    See Also\n    --------\n    retrieve_sam\n    pvlib.temperature.sapm_cell\n    pvlib.temperature.sapm_module\n    '''\n\n    # TODO: someday, change temp_ref and irrad_ref to reference_temperature and\n    # reference_irradiance and expose\n    temp_ref = 25\n    irrad_ref = 1000\n\n    q = constants.e  # Elementary charge in units of coulombs\n    kb = constants.k  # Boltzmann's constant in units of J/K\n\n    # avoid problem with integer input\n    Ee = np.array(effective_irradiance, dtype='float64') / irrad_ref\n\n    # set up masking for 0, positive, and nan inputs\n    Ee_gt_0 = np.full_like(Ee, False, dtype='bool')\n    Ee_eq_0 = np.full_like(Ee, False, dtype='bool')\n    notnan = ~np.isnan(Ee)\n    np.greater(Ee, 0, where=notnan, out=Ee_gt_0)\n    np.equal(Ee, 0, where=notnan, out=Ee_eq_0)\n\n    Bvmpo = module['Bvmpo'] + module['Mbvmp']*(1 - Ee)\n    Bvoco = module['Bvoco'] + module['Mbvoc']*(1 - Ee)\n    delta = module['N'] * kb * (temp_cell + 273.15) / q\n\n    # avoid repeated computation\n    logEe = np.full_like(Ee, np.nan)\n    np.log(Ee, where=Ee_gt_0, out=logEe)\n    logEe = np.where(Ee_eq_0, -np.inf, logEe)\n    # avoid repeated __getitem__\n    cells_in_series = module['Cells_in_Series']\n\n    out = OrderedDict()\n\n    out['i_sc'] = (\n        module['Isco'] * Ee * (1 + module['Aisc']*(temp_cell - temp_ref)))\n\n    out['i_mp'] = (\n        module['Impo'] * (module['C0']*Ee + module['C1']*(Ee**2)) *\n        (1 + module['Aimp']*(temp_cell - temp_ref)))\n\n    out['v_oc'] = np.maximum(0, (\n        module['Voco'] + cells_in_series * delta * logEe +\n        Bvoco*(temp_cell - temp_ref)))\n\n    out['v_mp'] = np.maximum(0, (\n        module['Vmpo'] +\n        module['C2'] * cells_in_series * delta * logEe +\n        module['C3'] * cells_in_series * ((delta * logEe) ** 2) +\n        Bvmpo*(temp_cell - temp_ref)))\n\n    out['p_mp'] = out['i_mp'] * out['v_mp']\n\n    out['i_x'] = (\n        module['IXO'] * (module['C4']*Ee + module['C5']*(Ee**2)) *\n        (1 + module['Aisc']*(temp_cell - temp_ref)))\n\n    # the Ixx calculation in King 2004 has a typo (mixes up Aisc and Aimp)\n    out['i_xx'] = (\n        module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) *\n        (1 + module['Aisc']*(temp_cell - temp_ref)))\n\n    if isinstance(out['i_sc'], pd.Series):\n        out = pd.DataFrame(out)\n\n    return out\n\n\nsapm_spectral_loss = deprecated(\n    since='0.10.0',\n    alternative='pvlib.spectrum.spectral_factor_sapm'\n)(spectrum.spectral_factor_sapm)\n\n\ndef sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,\n                              module):\n    r\"\"\"\n    Calculates the SAPM effective irradiance using the SAPM spectral\n    loss and SAPM angle of incidence loss functions.\n\n    Parameters\n    ----------\n    poa_direct : numeric\n        The direct irradiance incident upon the module. [W/m2]\n\n    poa_diffuse : numeric\n        The diffuse irradiance incident on module.  [W/m2]\n\n    airmass_absolute : numeric\n        Absolute airmass. [unitless]\n\n    aoi : numeric\n        Angle of incidence. [degrees]\n\n    module : dict-like\n        A dict, Series, or DataFrame defining the SAPM performance\n        parameters. See the :py:func:`sapm` notes section for more\n        details.\n\n    Returns\n    -------\n    effective_irradiance : numeric\n        Effective irradiance accounting for reflections and spectral content.\n        [W/m2]\n\n    Notes\n    -----\n    The SAPM model for effective irradiance [1]_ translates broadband direct\n    and diffuse irradiance on the plane of array to the irradiance absorbed by\n    a module's cells.\n\n    The model is\n    .. math::\n\n        `Ee = f_1(AM_a) (E_b f_2(AOI) + f_d E_d)`\n\n    where :math:`Ee` is effective irradiance (W/m2), :math:`f_1` is a fourth\n    degree polynomial in air mass :math:`AM_a`, :math:`E_b` is beam (direct)\n    irradiance on the plane of array, :math:`E_d` is diffuse irradiance on the\n    plane of array, :math:`f_2` is a fifth degree polynomial in the angle of\n    incidence :math:`AOI`, and :math:`f_d` is the fraction of diffuse\n    irradiance on the plane of array that is not reflected away.\n\n    References\n    ----------\n    .. [1] D. King et al, \"Sandia Photovoltaic Array Performance Model\",\n       SAND2004-3535, Sandia National Laboratories, Albuquerque, NM\n\n    See also\n    --------\n    pvlib.iam.sapm\n    pvlib.spectrum.spectral_factor_sapm\n    pvlib.pvsystem.sapm\n    \"\"\"\n\n    F1 = spectrum.spectral_factor_sapm(airmass_absolute, module)\n    F2 = iam.sapm(aoi, module)\n\n    Ee = F1 * (poa_direct * F2 + module['FD'] * poa_diffuse)\n\n    return Ee\n\n\ndef singlediode(photocurrent, saturation_current, resistance_series,\n                resistance_shunt, nNsVth, ivcurve_pnts=None,\n                method='lambertw'):\n    r\"\"\"\n    Solve the single diode equation to obtain a photovoltaic IV curve.\n\n    Solves the single diode equation [1]_\n\n    .. math::\n\n        I = I_L -\n            I_0 \\left[\n                \\exp \\left(\\frac{V+I R_s}{n N_s V_{th}} \\right)-1\n            \\right] -\n            \\frac{V + I R_s}{R_{sh}}\n\n    for :math:`I` and :math:`V` when given :math:`I_L, I_0, R_s, R_{sh},` and\n    :math:`n N_s V_{th}` which are described later. The five points on the I-V\n    curve specified in [3]_ are returned. If :math:`I_L, I_0, R_s, R_{sh},` and\n    :math:`n N_s V_{th}` are all scalars, a single curve is returned. If any\n    are array-like (of the same length), multiple IV curves are calculated.\n\n    The input parameters can be calculated from meteorological data using a\n    function for a single diode model, e.g.,\n    :py:func:`~pvlib.pvsystem.calcparams_desoto`.\n\n    Parameters\n    ----------\n    photocurrent : numeric\n        Light-generated current :math:`I_L` (photocurrent)\n        ``0 <= photocurrent``. [A]\n\n    saturation_current : numeric\n        Diode saturation :math:`I_0` current under desired IV curve\n        conditions. ``0 < saturation_current``. [A]\n\n    resistance_series : numeric\n        Series resistance :math:`R_s` under desired IV curve conditions.\n        ``0 <= resistance_series < numpy.inf``.  [ohm]\n\n    resistance_shunt : numeric\n        Shunt resistance :math:`R_{sh}` under desired IV curve conditions.\n        ``0 < resistance_shunt <= numpy.inf``.  [ohm]\n\n    nNsVth : numeric\n        The product of three components: 1) the usual diode ideality factor\n        :math:`n`, 2) the number of cells in series :math:`N_s`, and 3)\n        the cell thermal voltage\n        :math:`V_{th}`. The thermal voltage of the cell (in volts) may be\n        calculated as :math:`k_B T_c / q`, where :math:`k_B` is\n        Boltzmann's constant (J/K), :math:`T_c` is the temperature of the p-n\n        junction in Kelvin, and :math:`q` is the charge of an electron\n        (coulombs). ``0 < nNsVth``.  [V]\n\n    ivcurve_pnts : None or int, default None\n        Number of points in the desired IV curve. If None or 0, no points on\n        the IV curves will be produced.\n\n        .. deprecated:: 0.10.0\n           Use :py:func:`pvlib.pvsystem.v_from_i` and\n           :py:func:`pvlib.pvsystem.i_from_v` instead.\n\n    method : str, default 'lambertw'\n        Determines the method used to calculate points on the IV curve. The\n        options are ``'lambertw'``, ``'newton'``, or ``'brentq'``.\n\n    Returns\n    -------\n    dict or pandas.DataFrame\n        The returned dict-like object always contains the keys/columns:\n\n            * i_sc - short circuit current in amperes.\n            * v_oc - open circuit voltage in volts.\n            * i_mp - current at maximum power point in amperes.\n            * v_mp - voltage at maximum power point in volts.\n            * p_mp - power at maximum power point in watts.\n            * i_x - current, in amperes, at ``v = 0.5*v_oc``.\n            * i_xx - current, in amperes, at ``v = 0.5*(v_oc+v_mp)``.\n\n        A dict is returned when the input parameters are scalars or\n        ``ivcurve_pnts > 0``. If ``ivcurve_pnts > 0``, the output dictionary\n        will also include the keys:\n\n            * i - IV curve current in amperes.\n            * v - IV curve voltage in volts.\n\n    See also\n    --------\n    calcparams_desoto\n    calcparams_cec\n    calcparams_pvsyst\n    sapm\n    pvlib.singlediode.bishop88\n\n    Notes\n    -----\n    If the method is ``'lambertw'`` then the solution employed to solve the\n    implicit diode equation utilizes the Lambert W function to obtain an\n    explicit function of :math:`V=f(I)` and :math:`I=f(V)` as shown in [2]_.\n\n    If the method is ``'newton'`` then the root-finding Newton-Raphson method\n    is used. It should be safe for well behaved IV-curves, but the ``'brentq'``\n    method is recommended for reliability.\n\n    If the method is ``'brentq'`` then Brent's bisection search method is used\n    that guarantees convergence by bounding the voltage between zero and\n    open-circuit.\n\n    If the method is either ``'newton'`` or ``'brentq'`` and ``ivcurve_pnts``\n    are indicated, then :func:`pvlib.singlediode.bishop88` [4]_ is used to\n    calculate the points on the IV curve points at diode voltages from zero to\n    open-circuit voltage with a log spacing that gets closer as voltage\n    increases. If the method is ``'lambertw'`` then the calculated points on\n    the IV curve are linearly spaced.\n\n    References\n    ----------\n    .. [1] S.R. Wenham, M.A. Green, M.E. Watt, \"Applied Photovoltaics\" ISBN\n       0 86758 909 4\n\n    .. [2] A. Jain, A. Kapoor, \"Exact analytical solutions of the\n       parameters of real solar cells using Lambert W-function\", Solar\n       Energy Materials and Solar Cells, 81 (2004) 269-277.\n\n    .. [3] D. King et al, \"Sandia Photovoltaic Array Performance Model\",\n       SAND2004-3535, Sandia National Laboratories, Albuquerque, NM\n\n    .. [4] \"Computer simulation of the effects of electrical mismatches in\n       photovoltaic cell interconnection circuits\" JW Bishop, Solar Cell (1988)\n       https://doi.org/10.1016/0379-6787(88)90059-2\n    \"\"\"\n    if ivcurve_pnts:\n        warn_deprecated('0.10.0', name='pvlib.pvsystem.singlediode',\n                        alternative=('pvlib.pvsystem.v_from_i and '\n                                     'pvlib.pvsystem.i_from_v'),\n                        obj_type='parameter ivcurve_pnts',\n                        removal='0.11.0')\n    args = (photocurrent, saturation_current, resistance_series,\n            resistance_shunt, nNsVth)  # collect args\n    # Calculate points on the IV curve using the LambertW solution to the\n    # single diode equation\n    if method.lower() == 'lambertw':\n        out = _singlediode._lambertw(*args, ivcurve_pnts)\n        points = out[:7]\n        if ivcurve_pnts:\n            ivcurve_i, ivcurve_v = out[7:]\n    else:\n        # Calculate points on the IV curve using either 'newton' or 'brentq'\n        # methods. Voltages are determined by first solving the single diode\n        # equation for the diode voltage V_d then backing out voltage\n        v_oc = _singlediode.bishop88_v_from_i(\n            0.0, *args, method=method.lower()\n        )\n        i_mp, v_mp, p_mp = _singlediode.bishop88_mpp(\n            *args, method=method.lower()\n        )\n        i_sc = _singlediode.bishop88_i_from_v(\n            0.0, *args, method=method.lower()\n        )\n        i_x = _singlediode.bishop88_i_from_v(\n            v_oc / 2.0, *args, method=method.lower()\n        )\n        i_xx = _singlediode.bishop88_i_from_v(\n            (v_oc + v_mp) / 2.0, *args, method=method.lower()\n        )\n        points = i_sc, v_oc, i_mp, v_mp, p_mp, i_x, i_xx\n\n        # calculate the IV curve if requested using bishop88\n        if ivcurve_pnts:\n            vd = v_oc * (\n                (11.0 - np.logspace(np.log10(11.0), 0.0, ivcurve_pnts)) / 10.0\n            )\n            ivcurve_i, ivcurve_v, _ = _singlediode.bishop88(vd, *args)\n\n    columns = ('i_sc', 'v_oc', 'i_mp', 'v_mp', 'p_mp', 'i_x', 'i_xx')\n\n    if all(map(np.isscalar, args)) or ivcurve_pnts:\n        out = {c: p for c, p in zip(columns, points)}\n\n        if ivcurve_pnts:\n            out.update(i=ivcurve_i, v=ivcurve_v)\n\n        return out\n\n    points = np.atleast_1d(*points)  # convert scalars to 1d-arrays\n    points = np.vstack(points).T  # collect rows into DataFrame columns\n\n    # save the first available pd.Series index, otherwise set to None\n    index = next((a.index for a in args if isinstance(a, pd.Series)), None)\n\n    out = pd.DataFrame(points, columns=columns, index=index)\n\n    return out\n\n\ndef max_power_point(photocurrent, saturation_current, resistance_series,\n                    resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.Inf,\n                    method='brentq'):\n    \"\"\"\n    Given the single diode equation coefficients, calculates the maximum power\n    point (MPP).\n\n    Parameters\n    ----------\n    photocurrent : numeric\n        photo-generated current [A]\n    saturation_current : numeric\n        diode reverse saturation current [A]\n    resistance_series : numeric\n        series resitance [ohms]\n    resistance_shunt : numeric\n        shunt resitance [ohms]\n    nNsVth : numeric\n        product of thermal voltage ``Vth`` [V], diode ideality factor ``n``,\n        and number of serices cells ``Ns``\n    d2mutau : numeric, default 0\n        PVsyst parameter for cadmium-telluride (CdTe) and amorphous-silicon\n        (a-Si) modules that accounts for recombination current in the\n        intrinsic layer. The value is the ratio of intrinsic layer thickness\n        squared :math:`d^2` to the diffusion length of charge carriers\n        :math:`\\\\mu \\\\tau`. [V]\n    NsVbi : numeric, default np.inf\n        PVsyst parameter for cadmium-telluride (CdTe) and amorphous-silicon\n        (a-Si) modules that is the product of the PV module number of series\n        cells ``Ns`` and the builtin voltage ``Vbi`` of the intrinsic layer.\n        [V].\n    method : str\n        either ``'newton'`` or ``'brentq'``\n\n    Returns\n    -------\n    OrderedDict or pandas.DataFrame\n        ``(i_mp, v_mp, p_mp)``\n\n    Notes\n    -----\n    Use this function when you only want to find the maximum power point. Use\n    :func:`singlediode` when you need to find additional points on the IV\n    curve. This function uses Brent's method by default because it is\n    guaranteed to converge.\n    \"\"\"\n    i_mp, v_mp, p_mp = _singlediode.bishop88_mpp(\n        photocurrent, saturation_current, resistance_series,\n        resistance_shunt, nNsVth, d2mutau, NsVbi, method=method.lower()\n    )\n    if isinstance(photocurrent, pd.Series):\n        ivp = {'i_mp': i_mp, 'v_mp': v_mp, 'p_mp': p_mp}\n        out = pd.DataFrame(ivp, index=photocurrent.index)\n    else:\n        out = OrderedDict()\n        out['i_mp'] = i_mp\n        out['v_mp'] = v_mp\n        out['p_mp'] = p_mp\n    return out\n\n\ndef v_from_i(current, photocurrent, saturation_current, resistance_series,\n             resistance_shunt, nNsVth, method='lambertw'):\n    '''\n    Device voltage at the given device current for the single diode model.\n\n    Uses the single diode model (SDM) as described in, e.g.,\n    Jain and Kapoor 2004 [1]_.\n    The solution is per Eq 3 of [1]_ except when resistance_shunt=numpy.inf,\n    in which case the explict solution for voltage is used.\n    Ideal device parameters are specified by resistance_shunt=np.inf and\n    resistance_series=0.\n    Inputs to this function can include scalars and pandas.Series, but it is\n    the caller's responsibility to ensure that the arguments are all float64\n    and within the proper ranges.\n\n    .. versionchanged:: 0.10.0\n       The function's arguments have been reordered.\n\n    Parameters\n    ----------\n    current : numeric\n        The current in amperes under desired IV curve conditions.\n\n    photocurrent : numeric\n        Light-generated current (photocurrent) in amperes under desired\n        IV curve conditions. Often abbreviated ``I_L``.\n        0 <= photocurrent\n\n    saturation_current : numeric\n        Diode saturation current in amperes under desired IV curve\n        conditions. Often abbreviated ``I_0``.\n        0 < saturation_current\n\n    resistance_series : numeric\n        Series resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rs``.\n        0 <= resistance_series < numpy.inf\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rsh``.\n        0 < resistance_shunt <= numpy.inf\n\n    nNsVth : numeric\n        The product of three components. 1) The usual diode ideal factor\n        (n), 2) the number of cells in series (Ns), and 3) the cell\n        thermal voltage under the desired IV curve conditions (Vth). The\n        thermal voltage of the cell (in volts) may be calculated as\n        ``k*temp_cell/q``, where k is Boltzmann's constant (J/K),\n        temp_cell is the temperature of the p-n junction in Kelvin, and\n        q is the charge of an electron (coulombs).\n        0 < nNsVth\n\n    method : str\n        Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*:\n        ``'brentq'`` is limited to 1st quadrant only.\n\n    Returns\n    -------\n    current : np.ndarray or scalar\n\n    References\n    ----------\n    .. [1] A. Jain, A. Kapoor, \"Exact analytical solutions of the\n       parameters of real solar cells using Lambert W-function\", Solar\n       Energy Materials and Solar Cells, 81 (2004) 269-277.\n    '''\n    args = (current, photocurrent, saturation_current,\n            resistance_series, resistance_shunt, nNsVth)\n    if method.lower() == 'lambertw':\n        return _singlediode._lambertw_v_from_i(*args)\n    else:\n        # Calculate points on the IV curve using either 'newton' or 'brentq'\n        # methods. Voltages are determined by first solving the single diode\n        # equation for the diode voltage V_d then backing out voltage\n        V = _singlediode.bishop88_v_from_i(*args, method=method.lower())\n        if all(map(np.isscalar, args)):\n            return V\n        shape = _singlediode._shape_of_max_size(*args)\n        return np.broadcast_to(V, shape)\n\n\ndef i_from_v(voltage, photocurrent, saturation_current, resistance_series,\n             resistance_shunt, nNsVth, method='lambertw'):\n    '''\n    Device current at the given device voltage for the single diode model.\n\n    Uses the single diode model (SDM) as described in, e.g.,\n    Jain and Kapoor 2004 [1]_.\n    The solution is per Eq 2 of [1] except when resistance_series=0,\n    in which case the explict solution for current is used.\n    Ideal device parameters are specified by resistance_shunt=np.inf and\n    resistance_series=0.\n    Inputs to this function can include scalars and pandas.Series, but it is\n    the caller's responsibility to ensure that the arguments are all float64\n    and within the proper ranges.\n\n    .. versionchanged:: 0.10.0\n       The function's arguments have been reordered.\n\n    Parameters\n    ----------\n    voltage : numeric\n        The voltage in Volts under desired IV curve conditions.\n\n    photocurrent : numeric\n        Light-generated current (photocurrent) in amperes under desired\n        IV curve conditions. Often abbreviated ``I_L``.\n        0 <= photocurrent\n\n    saturation_current : numeric\n        Diode saturation current in amperes under desired IV curve\n        conditions. Often abbreviated ``I_0``.\n        0 < saturation_current\n\n    resistance_series : numeric\n        Series resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rs``.\n        0 <= resistance_series < numpy.inf\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rsh``.\n        0 < resistance_shunt <= numpy.inf\n\n    nNsVth : numeric\n        The product of three components. 1) The usual diode ideal factor\n        (n), 2) the number of cells in series (Ns), and 3) the cell\n        thermal voltage under the desired IV curve conditions (Vth). The\n        thermal voltage of the cell (in volts) may be calculated as\n        ``k*temp_cell/q``, where k is Boltzmann's constant (J/K),\n        temp_cell is the temperature of the p-n junction in Kelvin, and\n        q is the charge of an electron (coulombs).\n        0 < nNsVth\n\n    method : str\n        Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*:\n        ``'brentq'`` is limited to 1st quadrant only.\n\n    Returns\n    -------\n    current : np.ndarray or scalar\n\n    References\n    ----------\n    .. [1] A. Jain, A. Kapoor, \"Exact analytical solutions of the\n       parameters of real solar cells using Lambert W-function\", Solar\n       Energy Materials and Solar Cells, 81 (2004) 269-277.\n    '''\n    args = (voltage, photocurrent, saturation_current,\n            resistance_series, resistance_shunt, nNsVth)\n    if method.lower() == 'lambertw':\n        return _singlediode._lambertw_i_from_v(*args)\n    else:\n        # Calculate points on the IV curve using either 'newton' or 'brentq'\n        # methods. Voltages are determined by first solving the single diode\n        # equation for the diode voltage V_d then backing out voltage\n        current = _singlediode.bishop88_i_from_v(*args, method=method.lower())\n        if all(map(np.isscalar, args)):\n            return current\n        shape = _singlediode._shape_of_max_size(*args)\n        return np.broadcast_to(current, shape)\n\n\ndef scale_voltage_current_power(data, voltage=1, current=1):\n    \"\"\"\n    Scales the voltage, current, and power in data by the voltage\n    and current factors.\n\n    Parameters\n    ----------\n    data: DataFrame\n        May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',\n        'i_sc', 'p_mp'`.\n    voltage: numeric, default 1\n        The amount by which to multiply the voltages.\n    current: numeric, default 1\n        The amount by which to multiply the currents.\n\n    Returns\n    -------\n    scaled_data: DataFrame\n        A scaled copy of the input data.\n        `'p_mp'` is scaled by `voltage * current`.\n    \"\"\"\n\n    # as written, only works with a DataFrame\n    # could make it work with a dict, but it would be more verbose\n    voltage_keys = ['v_mp', 'v_oc']\n    current_keys = ['i_mp', 'i_x', 'i_xx', 'i_sc']\n    power_keys = ['p_mp']\n    voltage_df = data.filter(voltage_keys, axis=1) * voltage\n    current_df = data.filter(current_keys, axis=1) * current\n    power_df = data.filter(power_keys, axis=1) * voltage * current\n    df = pd.concat([voltage_df, current_df, power_df], axis=1)\n    df_sorted = df[data.columns]  # retain original column order\n    return df_sorted\n\n\ndef pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.):\n    r\"\"\"\n    Implements NREL's PVWatts DC power model. The PVWatts DC model [1]_ is:\n\n    .. math::\n\n        P_{dc} = \\frac{G_{poa eff}}{1000} P_{dc0} ( 1 + \\gamma_{pdc} (T_{cell} - T_{ref}))\n\n    Note that ``pdc0`` is also used as a symbol in\n    :py:func:`pvlib.inverter.pvwatts`. ``pdc0`` in this function refers to the DC\n    power of the modules at reference conditions. ``pdc0`` in\n    :py:func:`pvlib.inverter.pvwatts` refers to the DC power input limit of\n    the inverter.\n\n    Parameters\n    ----------\n    g_poa_effective: numeric\n        Irradiance transmitted to the PV cells. To be\n        fully consistent with PVWatts, the user must have already\n        applied angle of incidence losses, but not soiling, spectral,\n        etc. [W/m^2]\n    temp_cell: numeric\n        Cell temperature [C].\n    pdc0: numeric\n        Power of the modules at 1000 W/m^2 and cell reference temperature. [W]\n    gamma_pdc: numeric\n        The temperature coefficient of power. Typically -0.002 to\n        -0.005 per degree C. [1/C]\n    temp_ref: numeric, default 25.0\n        Cell reference temperature. PVWatts defines it to be 25 C and\n        is included here for flexibility. [C]\n\n    Returns\n    -------\n    pdc: numeric\n        DC power. [W]\n\n    References\n    ----------\n    .. [1] A. P. Dobos, \"PVWatts Version 5 Manual\"\n           http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf\n           (2014).\n    \"\"\"  # noqa: E501\n\n    pdc = (g_poa_effective * 0.001 * pdc0 *\n           (1 + gamma_pdc * (temp_cell - temp_ref)))\n\n    return pdc\n\n\ndef pvwatts_losses(soiling=2, shading=3, snow=0, mismatch=2, wiring=2,\n                   connections=0.5, lid=1.5, nameplate_rating=1, age=0,\n                   availability=3):\n    r\"\"\"\n    Implements NREL's PVWatts system loss model.\n    The PVWatts loss model [1]_ is:\n\n    .. math::\n\n        L_{total}(\\%) = 100 [ 1 - \\Pi_i ( 1 - \\frac{L_i}{100} ) ]\n\n    All parameters must be in units of %. Parameters may be\n    array-like, though all array sizes must match.\n\n    Parameters\n    ----------\n    soiling: numeric, default 2\n    shading: numeric, default 3\n    snow: numeric, default 0\n    mismatch: numeric, default 2\n    wiring: numeric, default 2\n    connections: numeric, default 0.5\n    lid: numeric, default 1.5\n        Light induced degradation\n    nameplate_rating: numeric, default 1\n    age: numeric, default 0\n    availability: numeric, default 3\n\n    Returns\n    -------\n    losses: numeric\n        System losses in units of %.\n\n    References\n    ----------\n    .. [1] A. P. Dobos, \"PVWatts Version 5 Manual\"\n           http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf\n           (2014).\n    \"\"\"\n\n    params = [soiling, shading, snow, mismatch, wiring, connections, lid,\n              nameplate_rating, age, availability]\n\n    # manually looping over params allows for numpy/pandas to handle any\n    # array-like broadcasting that might be necessary.\n    perf = 1\n    for param in params:\n        perf *= 1 - param/100\n\n    losses = (1 - perf) * 100.\n\n    return losses\n\n\ndef dc_ohms_from_percent(vmp_ref, imp_ref, dc_ohmic_percent,\n                         modules_per_string=1,\n                         strings=1):\n    \"\"\"\n    Calculates the equivalent resistance of the wires from a percent\n    ohmic loss at STC.\n\n    Equivalent resistance is calculated with the function:\n\n    .. math::\n        Rw = (L_{stc} / 100) * (Varray / Iarray)\n\n    :math:`Rw` is the equivalent resistance in ohms\n    :math:`Varray` is the Vmp of the modules times modules per string\n    :math:`Iarray` is the Imp of the modules times strings per array\n    :math:`L_{stc}` is the input dc loss percent\n\n    Parameters\n    ----------\n    vmp_ref: numeric\n        Voltage at maximum power in reference conditions [V]\n    imp_ref: numeric\n        Current at maximum power in reference conditions [V]\n    dc_ohmic_percent: numeric, default 0\n        input dc loss as a percent, e.g. 1.5% loss is input as 1.5\n    modules_per_string: int, default 1\n        Number of modules per string in the array.\n    strings: int, default 1\n        Number of parallel strings in the array.\n\n    Returns\n    ----------\n    Rw: numeric\n        Equivalent resistance [ohm]\n\n    See Also\n    --------\n    pvlib.pvsystem.dc_ohmic_losses\n\n    References\n    ----------\n    .. [1] PVsyst 7 Help. \"Array ohmic wiring loss\".\n       https://www.pvsyst.com/help/ohmic_loss.htm\n    \"\"\"\n    vmp = modules_per_string * vmp_ref\n\n    imp = strings * imp_ref\n\n    Rw = (dc_ohmic_percent / 100) * (vmp / imp)\n\n    return Rw\n\n\ndef dc_ohmic_losses(resistance, current):\n    \"\"\"\n    Returns ohmic losses in units of power from the equivalent\n    resistance of the wires and the operating current.\n\n    Parameters\n    ----------\n    resistance: numeric\n        Equivalent resistance of wires [ohm]\n    current: numeric, float or array-like\n        Operating current [A]\n\n    Returns\n    ----------\n    loss: numeric\n        Power Loss [W]\n\n    See Also\n    --------\n    pvlib.pvsystem.dc_ohms_from_percent\n\n    References\n    ----------\n    .. [1] PVsyst 7 Help. \"Array ohmic wiring loss\".\n       https://www.pvsyst.com/help/ohmic_loss.htm\n    \"\"\"\n    return resistance * current * current\n\n\ndef combine_loss_factors(index, *losses, fill_method='ffill'):\n    r\"\"\"\n    Combines Series loss fractions while setting a common index.\n\n    The separate losses are compounded using the following equation:\n\n    .. math::\n\n        L_{total} = 1 - [ 1 - \\Pi_i ( 1 - L_i ) ]\n\n    :math:`L_{total}` is the total loss returned\n    :math:`L_i` is each individual loss factor input\n\n    Note the losses must each be a series with a DatetimeIndex.\n    All losses will be resampled to match the index parameter using\n    the fill method specified (defaults to \"fill forward\").\n\n    Parameters\n    ----------\n    index : DatetimeIndex\n        The index of the returned loss factors\n\n    *losses : Series\n        One or more Series of fractions to be compounded\n\n    fill_method : {'ffill', 'bfill', 'nearest'}, default 'ffill'\n        Method to use for filling holes in reindexed DataFrame\n\n    Returns\n    -------\n    Series\n        Fractions resulting from the combination of each loss factor\n    \"\"\"\n    combined_factor = 1\n\n    for loss in losses:\n        loss = loss.reindex(index, method=fill_method)\n        combined_factor *= (1 - loss)\n\n    return 1 - combined_factor\n","new_file_content":"\"\"\"\nThe ``pvsystem`` module contains functions for modeling the output and\nperformance of PV modules and inverters.\n\"\"\"\n\nfrom collections import OrderedDict\nimport functools\nimport io\nimport itertools\nimport os\nimport inspect\nfrom urllib.request import urlopen\nimport numpy as np\nfrom scipy import constants\nimport pandas as pd\nfrom dataclasses import dataclass\nfrom abc import ABC, abstractmethod\nfrom typing import Optional\n\nfrom pvlib._deprecation import deprecated, warn_deprecated\n\nfrom pvlib import (atmosphere, iam, inverter, irradiance,\n                   singlediode as _singlediode, spectrum, temperature)\nfrom pvlib.tools import _build_kwargs, _build_args\nimport pvlib.tools as tools\n\n\n# a dict of required parameter names for each DC power model\n_DC_MODEL_PARAMS = {\n    'sapm': {\n        'A0', 'A1', 'A2', 'A3', 'A4', 'B0', 'B1', 'B2', 'B3',\n        'B4', 'B5', 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6',\n        'C7', 'Isco', 'Impo', 'Voco', 'Vmpo', 'Aisc', 'Aimp', 'Bvoco',\n        'Mbvoc', 'Bvmpo', 'Mbvmp', 'N', 'Cells_in_Series',\n        'IXO', 'IXXO', 'FD'},\n    'desoto': {\n        'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_s'},\n    'cec': {\n        'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_s', 'Adjust'},\n    'pvsyst': {\n        'gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_sh_0', 'R_s', 'alpha_sc', 'EgRef',\n        'cells_in_series'},\n    'singlediode': {\n        'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_s'},\n    'pvwatts': {'pdc0', 'gamma_pdc'}\n}\n\n\ndef _unwrap_single_value(func):\n    \"\"\"Decorator for functions that return iterables.\n\n    If the length of the iterable returned by `func` is 1, then\n    the single member of the iterable is returned. If the length is\n    greater than 1, then entire iterable is returned.\n\n    Adds 'unwrap' as a keyword argument that can be set to False\n    to force the return value to be a tuple, regardless of its length.\n    \"\"\"\n    @functools.wraps(func)\n    def f(*args, **kwargs):\n        unwrap = kwargs.pop('unwrap', True)\n        x = func(*args, **kwargs)\n        if unwrap and len(x) == 1:\n            return x[0]\n        return x\n    return f\n\n\n# not sure if this belongs in the pvsystem module.\n# maybe something more like core.py? It may eventually grow to\n# import a lot more functionality from other modules.\nclass PVSystem:\n    \"\"\"\n    The PVSystem class defines a standard set of PV system attributes\n    and modeling functions. This class describes the collection and\n    interactions of PV system components rather than an installed system\n    on the ground. It is typically used in combination with\n    :py:class:`~pvlib.location.Location` and\n    :py:class:`~pvlib.modelchain.ModelChain`\n    objects.\n\n    The class supports basic system topologies consisting of:\n\n        * `N` total modules arranged in series\n          (`modules_per_string=N`, `strings_per_inverter=1`).\n        * `M` total modules arranged in parallel\n          (`modules_per_string=1`, `strings_per_inverter=M`).\n        * `NxM` total modules arranged in `M` strings of `N` modules each\n          (`modules_per_string=N`, `strings_per_inverter=M`).\n\n    The class is complementary to the module-level functions.\n\n    The attributes should generally be things that don't change about\n    the system, such the type of module and the inverter. The instance\n    methods accept arguments for things that do change, such as\n    irradiance and temperature.\n\n    Parameters\n    ----------\n    arrays : Array or iterable of Array, optional\n        An Array or list of arrays that are part of the system. If not\n        specified a single array is created from the other parameters (e.g.\n        `surface_tilt`, `surface_azimuth`). If specified as a list, the list\n        must contain at least one Array;\n        if length of arrays is 0 a ValueError is raised. If `arrays` is\n        specified the following PVSystem parameters are ignored:\n\n        - `surface_tilt`\n        - `surface_azimuth`\n        - `albedo`\n        - `surface_type`\n        - `module`\n        - `module_type`\n        - `module_parameters`\n        - `temperature_model_parameters`\n        - `modules_per_string`\n        - `strings_per_inverter`\n\n    surface_tilt: float or array-like, default 0\n        Surface tilt angles in decimal degrees.\n        The tilt angle is defined as degrees from horizontal\n        (e.g. surface facing up = 0, surface facing horizon = 90)\n\n    surface_azimuth: float or array-like, default 180\n        Azimuth angle of the module surface.\n        North=0, East=90, South=180, West=270.\n\n    albedo : None or float, default None\n        Ground surface albedo. If ``None``, then ``surface_type`` is used\n        to look up a value in ``irradiance.SURFACE_ALBEDOS``.\n        If ``surface_type`` is also None then a ground surface albedo\n        of 0.25 is used.\n\n    surface_type : None or string, default None\n        The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for\n        valid values.\n\n    module : None or string, default None\n        The model name of the modules.\n        May be used to look up the module_parameters dictionary\n        via some other method.\n\n    module_type : None or string, default 'glass_polymer'\n         Describes the module's construction. Valid strings are 'glass_polymer'\n         and 'glass_glass'. Used for cell and module temperature calculations.\n\n    module_parameters : None, dict or Series, default None\n        Module parameters as defined by the SAPM, CEC, or other.\n\n    temperature_model_parameters : None, dict or Series, default None.\n        Temperature model parameters as required by one of the models in\n        pvlib.temperature (excluding poa_global, temp_air and wind_speed).\n\n    modules_per_string: int or float, default 1\n        See system topology discussion above.\n\n    strings_per_inverter: int or float, default 1\n        See system topology discussion above.\n\n    inverter : None or string, default None\n        The model name of the inverters.\n        May be used to look up the inverter_parameters dictionary\n        via some other method.\n\n    inverter_parameters : None, dict or Series, default None\n        Inverter parameters as defined by the SAPM, CEC, or other.\n\n    racking_model : None or string, default 'open_rack'\n        Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.\n        Used to identify a parameter set for the SAPM cell temperature model.\n\n    losses_parameters : None, dict or Series, default None\n        Losses parameters as defined by PVWatts or other.\n\n    name : None or string, default None\n\n    **kwargs\n        Arbitrary keyword arguments.\n        Included for compatibility, but not used.\n\n    Raises\n    ------\n    ValueError\n        If `arrays` is not None and has length 0.\n\n    See also\n    --------\n    pvlib.location.Location\n    \"\"\"\n\n    def __init__(self,\n                 arrays=None,\n                 surface_tilt=0, surface_azimuth=180,\n                 albedo=None, surface_type=None,\n                 module=None, module_type=None,\n                 module_parameters=None,\n                 temperature_model_parameters=None,\n                 modules_per_string=1, strings_per_inverter=1,\n                 inverter=None, inverter_parameters=None,\n                 racking_model=None, losses_parameters=None, name=None):\n\n        if arrays is None:\n            if losses_parameters is None:\n                array_losses_parameters = {}\n            else:\n                array_losses_parameters = _build_kwargs(['dc_ohmic_percent'],\n                                                        losses_parameters)\n            self.arrays = (Array(\n                FixedMount(surface_tilt, surface_azimuth, racking_model),\n                albedo,\n                surface_type,\n                module,\n                module_type,\n                module_parameters,\n                temperature_model_parameters,\n                modules_per_string,\n                strings_per_inverter,\n                array_losses_parameters,\n            ),)\n        elif isinstance(arrays, Array):\n            self.arrays = (arrays,)\n        elif len(arrays) == 0:\n            raise ValueError(\"PVSystem must have at least one Array. \"\n                             \"If you want to create a PVSystem instance \"\n                             \"with a single Array pass `arrays=None` and pass \"\n                             \"values directly to PVSystem attributes, e.g., \"\n                             \"`surface_tilt=30`\")\n        else:\n            self.arrays = tuple(arrays)\n\n        self.inverter = inverter\n        if inverter_parameters is None:\n            self.inverter_parameters = {}\n        else:\n            self.inverter_parameters = inverter_parameters\n\n        if losses_parameters is None:\n            self.losses_parameters = {}\n        else:\n            self.losses_parameters = losses_parameters\n\n        self.name = name\n\n    def __repr__(self):\n        repr = f'PVSystem:\\n  name: {self.name}\\n  '\n        for array in self.arrays:\n            repr += '\\n  '.join(array.__repr__().split('\\n'))\n            repr += '\\n  '\n        repr += f'inverter: {self.inverter}'\n        return repr\n\n    def _validate_per_array(self, values, system_wide=False):\n        \"\"\"Check that `values` is a tuple of the same length as\n        `self.arrays`.\n\n        If `values` is not a tuple it is packed in to a length-1 tuple before\n        the check. If the lengths are not the same a ValueError is raised,\n        otherwise the tuple `values` is returned.\n\n        When `system_wide` is True and `values` is not a tuple, `values`\n        is replicated to a tuple of the same length as `self.arrays` and that\n        tuple is returned.\n        \"\"\"\n        if system_wide and not isinstance(values, tuple):\n            return (values,) * self.num_arrays\n        if not isinstance(values, tuple):\n            values = (values,)\n        if len(values) != len(self.arrays):\n            raise ValueError(\"Length mismatch for per-array parameter\")\n        return values\n\n    @_unwrap_single_value\n    def _infer_cell_type(self):\n        \"\"\"\n        Examines module_parameters and maps the Technology key for the CEC\n        database and the Material key for the Sandia database to a common\n        list of strings for cell type.\n\n        Returns\n        -------\n        cell_type: str\n        \"\"\"\n        return tuple(array._infer_cell_type() for array in self.arrays)\n\n    @_unwrap_single_value\n    def get_aoi(self, solar_zenith, solar_azimuth):\n        \"\"\"Get the angle of incidence on the Array(s) in the system.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series.\n            Solar zenith angle.\n        solar_azimuth : float or Series.\n            Solar azimuth angle.\n\n        Returns\n        -------\n        aoi : Series or tuple of Series\n            The angle of incidence\n        \"\"\"\n\n        return tuple(array.get_aoi(solar_zenith, solar_azimuth)\n                     for array in self.arrays)\n\n    @_unwrap_single_value\n    def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,\n                       dni_extra=None, airmass=None, albedo=None,\n                       model='haydavies', **kwargs):\n        \"\"\"\n        Uses the :py:func:`irradiance.get_total_irradiance` function to\n        calculate the plane of array irradiance components on the tilted\n        surfaces defined by each array's ``surface_tilt`` and\n        ``surface_azimuth``.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series\n            Solar zenith angle.\n        solar_azimuth : float or Series\n            Solar azimuth angle.\n        dni : float or Series or tuple of float or Series\n            Direct Normal Irradiance. [W/m2]\n        ghi : float or Series or tuple of float or Series\n            Global horizontal irradiance. [W/m2]\n        dhi : float or Series or tuple of float or Series\n            Diffuse horizontal irradiance. [W/m2]\n        dni_extra : None, float, Series or tuple of float or Series,\\\n            default None\n            Extraterrestrial direct normal irradiance. [W/m2]\n        airmass : None, float or Series, default None\n            Airmass. [unitless]\n        albedo : None, float or Series, default None\n            Ground surface albedo. [unitless]\n        model : String, default 'haydavies'\n            Irradiance model.\n\n        kwargs\n            Extra parameters passed to :func:`irradiance.get_total_irradiance`.\n\n        Notes\n        -----\n        Each of `dni`, `ghi`, and `dni` parameters may be passed as a tuple\n        to provide different irradiance for each array in the system. If not\n        passed as a tuple then the same value is used for input to each Array.\n        If passed as a tuple the length must be the same as the number of\n        Arrays.\n\n        Returns\n        -------\n        poa_irradiance : DataFrame or tuple of DataFrame\n            Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse',\n            'poa_sky_diffuse', 'poa_ground_diffuse'``.\n\n        See also\n        --------\n        pvlib.irradiance.get_total_irradiance\n        \"\"\"\n        dni = self._validate_per_array(dni, system_wide=True)\n        ghi = self._validate_per_array(ghi, system_wide=True)\n        dhi = self._validate_per_array(dhi, system_wide=True)\n\n        albedo = self._validate_per_array(albedo, system_wide=True)\n\n        return tuple(\n            array.get_irradiance(solar_zenith, solar_azimuth,\n                                 dni, ghi, dhi,\n                                 dni_extra=dni_extra, airmass=airmass,\n                                 albedo=albedo, model=model, **kwargs)\n            for array, dni, ghi, dhi, albedo in zip(\n                self.arrays, dni, ghi, dhi, albedo\n            )\n        )\n\n    @_unwrap_single_value\n    def get_iam(self, aoi, iam_model='physical'):\n        \"\"\"\n        Determine the incidence angle modifier using the method specified by\n        ``iam_model``.\n\n        Parameters for the selected IAM model are expected to be in\n        ``PVSystem.module_parameters``. Default parameters are available for\n        the 'physical', 'ashrae' and 'martin_ruiz' models.\n\n        Parameters\n        ----------\n        aoi : numeric or tuple of numeric\n            The angle of incidence in degrees.\n\n        aoi_model : string, default 'physical'\n            The IAM model to be used. Valid strings are 'physical', 'ashrae',\n            'martin_ruiz', 'sapm' and 'interp'.\n        Returns\n        -------\n        iam : numeric or tuple of numeric\n            The AOI modifier.\n\n        Raises\n        ------\n        ValueError\n            if `iam_model` is not a valid model name.\n        \"\"\"\n        aoi = self._validate_per_array(aoi)\n        return tuple(array.get_iam(aoi, iam_model)\n                     for array, aoi in zip(self.arrays, aoi))\n\n    @_unwrap_single_value\n    def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,\n                             effective_irradiance=None):\n        \"\"\"\n        Determine cell temperature using the method specified by ``model``.\n\n        Parameters\n        ----------\n        poa_global : numeric or tuple of numeric\n            Total incident irradiance in W/m^2.\n\n        temp_air : numeric or tuple of numeric\n            Ambient dry bulb temperature in degrees C.\n\n        wind_speed : numeric or tuple of numeric\n            Wind speed in m/s.\n\n        model : str\n            Supported models include ``'sapm'``, ``'pvsyst'``,\n            ``'faiman'``, ``'fuentes'``, and ``'noct_sam'``\n\n        effective_irradiance : numeric or tuple of numeric, optional\n            The irradiance that is converted to photocurrent in W/m^2.\n            Only used for some models.\n\n        Returns\n        -------\n        numeric or tuple of numeric\n            Values in degrees C.\n\n        See Also\n        --------\n        Array.get_cell_temperature\n\n        Notes\n        -----\n        The `temp_air` and `wind_speed` parameters may be passed as tuples\n        to provide different values for each Array in the system. If passed as\n        a tuple the length must be the same as the number of Arrays. If not\n        passed as a tuple then the same value is used for each Array.\n        \"\"\"\n        poa_global = self._validate_per_array(poa_global)\n        temp_air = self._validate_per_array(temp_air, system_wide=True)\n        wind_speed = self._validate_per_array(wind_speed, system_wide=True)\n        # Not used for all models, but Array.get_cell_temperature handles it\n        effective_irradiance = self._validate_per_array(effective_irradiance,\n                                                        system_wide=True)\n\n        return tuple(\n            array.get_cell_temperature(poa_global, temp_air, wind_speed,\n                                       model, effective_irradiance)\n            for array, poa_global, temp_air, wind_speed, effective_irradiance\n            in zip(\n                self.arrays, poa_global, temp_air, wind_speed,\n                effective_irradiance\n            )\n        )\n\n    @_unwrap_single_value\n    def calcparams_desoto(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`calcparams_desoto` function, the input\n        parameters and ``self.module_parameters`` to calculate the\n        module currents and resistances.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.calcparams_desoto for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        build_kwargs = functools.partial(\n            _build_kwargs,\n            ['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',\n             'R_s', 'alpha_sc', 'EgRef', 'dEgdT',\n             'irrad_ref', 'temp_ref']\n        )\n\n        return tuple(\n            calcparams_desoto(\n                effective_irradiance, temp_cell,\n                **build_kwargs(array.module_parameters)\n            )\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def calcparams_cec(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`calcparams_cec` function, the input\n        parameters and ``self.module_parameters`` to calculate the\n        module currents and resistances.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.calcparams_cec for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        build_kwargs = functools.partial(\n            _build_kwargs,\n            ['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',\n             'R_s', 'alpha_sc', 'Adjust', 'EgRef', 'dEgdT',\n             'irrad_ref', 'temp_ref']\n        )\n\n        return tuple(\n            calcparams_cec(\n                effective_irradiance, temp_cell,\n                **build_kwargs(array.module_parameters)\n            )\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def calcparams_pvsyst(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`calcparams_pvsyst` function, the input\n        parameters and ``self.module_parameters`` to calculate the\n        module currents and resistances.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.calcparams_pvsyst for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        build_kwargs = functools.partial(\n            _build_kwargs,\n            ['gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref',\n             'R_sh_ref', 'R_sh_0', 'R_sh_exp',\n             'R_s', 'alpha_sc', 'EgRef',\n             'irrad_ref', 'temp_ref',\n             'cells_in_series']\n        )\n\n        return tuple(\n            calcparams_pvsyst(\n                effective_irradiance, temp_cell,\n                **build_kwargs(array.module_parameters)\n            )\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def sapm(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`sapm` function, the input parameters,\n        and ``self.module_parameters`` to calculate\n        Voc, Isc, Ix, Ixx, Vmp, and Imp.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.sapm for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        return tuple(\n            sapm(effective_irradiance, temp_cell, array.module_parameters)\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def sapm_spectral_loss(self, airmass_absolute):\n        \"\"\"\n        Use the :py:func:`pvlib.spectrum.spectral_factor_sapm` function,\n        the input parameters, and ``self.module_parameters`` to calculate F1.\n\n        Parameters\n        ----------\n        airmass_absolute : numeric\n            Absolute airmass.\n\n        Returns\n        -------\n        F1 : numeric or tuple of numeric\n            The SAPM spectral loss coefficient.\n        \"\"\"\n        return tuple(\n            spectrum.spectral_factor_sapm(airmass_absolute,\n                                          array.module_parameters)\n            for array in self.arrays\n        )\n\n    @_unwrap_single_value\n    def sapm_effective_irradiance(self, poa_direct, poa_diffuse,\n                                  airmass_absolute, aoi,\n                                  reference_irradiance=1000):\n        \"\"\"\n        Use the :py:func:`sapm_effective_irradiance` function, the input\n        parameters, and ``self.module_parameters`` to calculate\n        effective irradiance.\n\n        Parameters\n        ----------\n        poa_direct : numeric or tuple of numeric\n            The direct irradiance incident upon the module.  [W/m2]\n\n        poa_diffuse : numeric or tuple of numeric\n            The diffuse irradiance incident on module.  [W/m2]\n\n        airmass_absolute : numeric\n            Absolute airmass. [unitless]\n\n        aoi : numeric or tuple of numeric\n            Angle of incidence. [degrees]\n\n        Returns\n        -------\n        effective_irradiance : numeric or tuple of numeric\n            The SAPM effective irradiance. [W/m2]\n        \"\"\"\n        poa_direct = self._validate_per_array(poa_direct)\n        poa_diffuse = self._validate_per_array(poa_diffuse)\n        aoi = self._validate_per_array(aoi)\n        return tuple(\n            sapm_effective_irradiance(\n                poa_direct, poa_diffuse, airmass_absolute, aoi,\n                array.module_parameters)\n            for array, poa_direct, poa_diffuse, aoi\n            in zip(self.arrays, poa_direct, poa_diffuse, aoi)\n        )\n\n    @_unwrap_single_value\n    def first_solar_spectral_loss(self, pw, airmass_absolute):\n        \"\"\"\n        Use :py:func:`pvlib.spectrum.spectral_factor_firstsolar` to\n        calculate the spectral loss modifier. The model coefficients are\n        specific to the module's cell type, and are determined by searching\n        for one of the following keys in self.module_parameters (in order):\n\n        - 'first_solar_spectral_coefficients' (user-supplied coefficients)\n        - 'Technology' - a string describing the cell type, can be read from\n          the CEC module parameter database\n        - 'Material' - a string describing the cell type, can be read from\n          the Sandia module database.\n\n        Parameters\n        ----------\n        pw : array-like\n            atmospheric precipitable water (cm).\n\n        airmass_absolute : array-like\n            absolute (pressure corrected) airmass.\n\n        Returns\n        -------\n        modifier: array-like or tuple of array-like\n            spectral mismatch factor (unitless) which can be multiplied\n            with broadband irradiance reaching a module's cells to estimate\n            effective irradiance, i.e., the irradiance that is converted to\n            electrical current.\n        \"\"\"\n        pw = self._validate_per_array(pw, system_wide=True)\n\n        def _spectral_correction(array, pw):\n            if 'first_solar_spectral_coefficients' in \\\n                    array.module_parameters.keys():\n                coefficients = \\\n                    array.module_parameters[\n                        'first_solar_spectral_coefficients'\n                    ]\n                module_type = None\n            else:\n                module_type = array._infer_cell_type()\n                coefficients = None\n\n            return spectrum.spectral_factor_firstsolar(\n                pw, airmass_absolute, module_type, coefficients\n            )\n        return tuple(\n            itertools.starmap(_spectral_correction, zip(self.arrays, pw))\n        )\n\n    def singlediode(self, photocurrent, saturation_current,\n                    resistance_series, resistance_shunt, nNsVth,\n                    ivcurve_pnts=None):\n        \"\"\"Wrapper around the :py:func:`pvlib.pvsystem.singlediode` function.\n\n        See :py:func:`pvsystem.singlediode` for details\n        \"\"\"\n        return singlediode(photocurrent, saturation_current,\n                           resistance_series, resistance_shunt, nNsVth,\n                           ivcurve_pnts=ivcurve_pnts)\n\n    def i_from_v(self, voltage, photocurrent, saturation_current,\n                 resistance_series, resistance_shunt, nNsVth):\n        \"\"\"Wrapper around the :py:func:`pvlib.pvsystem.i_from_v` function.\n\n        See :py:func:`pvlib.pvsystem.i_from_v` for details.\n\n        .. versionchanged:: 0.10.0\n           The function's arguments have been reordered.\n        \"\"\"\n        return i_from_v(voltage, photocurrent, saturation_current,\n                        resistance_series, resistance_shunt, nNsVth)\n\n    def get_ac(self, model, p_dc, v_dc=None):\n        r\"\"\"Calculates AC power from p_dc using the inverter model indicated\n        by model and self.inverter_parameters.\n\n        Parameters\n        ----------\n        model : str\n            Must be one of 'sandia', 'adr', or 'pvwatts'.\n        p_dc : numeric, or tuple, list or array of numeric\n            DC power on each MPPT input of the inverter. Use tuple, list or\n            array for inverters with multiple MPPT inputs. If type is array,\n            p_dc must be 2d with axis 0 being the MPPT inputs. [W]\n        v_dc : numeric, or tuple, list or array of numeric\n            DC voltage on each MPPT input of the inverter. Required when\n            model='sandia' or model='adr'. Use tuple, list or\n            array for inverters with multiple MPPT inputs. If type is array,\n            v_dc must be 2d with axis 0 being the MPPT inputs. [V]\n\n        Returns\n        -------\n        power_ac : numeric\n            AC power output for the inverter. [W]\n\n        Raises\n        ------\n        ValueError\n            If model is not one of 'sandia', 'adr' or 'pvwatts'.\n        ValueError\n            If model='adr' and the PVSystem has more than one array.\n\n        See also\n        --------\n        pvlib.inverter.sandia\n        pvlib.inverter.sandia_multi\n        pvlib.inverter.adr\n        pvlib.inverter.pvwatts\n        pvlib.inverter.pvwatts_multi\n        \"\"\"\n        model = model.lower()\n        multiple_arrays = self.num_arrays > 1\n        if model == 'sandia':\n            p_dc = self._validate_per_array(p_dc)\n            v_dc = self._validate_per_array(v_dc)\n            if multiple_arrays:\n                return inverter.sandia_multi(\n                    v_dc, p_dc, self.inverter_parameters)\n            return inverter.sandia(v_dc[0], p_dc[0], self.inverter_parameters)\n        elif model == 'pvwatts':\n            kwargs = _build_kwargs(['eta_inv_nom', 'eta_inv_ref'],\n                                   self.inverter_parameters)\n            p_dc = self._validate_per_array(p_dc)\n            if multiple_arrays:\n                return inverter.pvwatts_multi(\n                    p_dc, self.inverter_parameters['pdc0'], **kwargs)\n            return inverter.pvwatts(\n                p_dc[0], self.inverter_parameters['pdc0'], **kwargs)\n        elif model == 'adr':\n            if multiple_arrays:\n                raise ValueError(\n                    'The adr inverter function cannot be used for an inverter',\n                    ' with multiple MPPT inputs')\n            # While this is only used for single-array systems, calling\n            # _validate_per_arry lets us pass in singleton tuples.\n            p_dc = self._validate_per_array(p_dc)\n            v_dc = self._validate_per_array(v_dc)\n            return inverter.adr(v_dc[0], p_dc[0], self.inverter_parameters)\n        else:\n            raise ValueError(\n                model + ' is not a valid AC power model.',\n                ' model must be one of \"sandia\", \"adr\" or \"pvwatts\"')\n\n    @_unwrap_single_value\n    def scale_voltage_current_power(self, data):\n        \"\"\"\n        Scales the voltage, current, and power of the `data` DataFrame\n        by `self.modules_per_string` and `self.strings_per_inverter`.\n\n        Parameters\n        ----------\n        data: DataFrame or tuple of DataFrame\n            May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',\n            'i_sc', 'p_mp'`.\n\n        Returns\n        -------\n        scaled_data: DataFrame or tuple of DataFrame\n            A scaled copy of the input data.\n        \"\"\"\n        data = self._validate_per_array(data)\n        return tuple(\n            scale_voltage_current_power(data,\n                                        voltage=array.modules_per_string,\n                                        current=array.strings)\n            for array, data in zip(self.arrays, data)\n        )\n\n    @_unwrap_single_value\n    def pvwatts_dc(self, g_poa_effective, temp_cell):\n        \"\"\"\n        Calcuates DC power according to the PVWatts model using\n        :py:func:`pvlib.pvsystem.pvwatts_dc`, `self.module_parameters['pdc0']`,\n        and `self.module_parameters['gamma_pdc']`.\n\n        See :py:func:`pvlib.pvsystem.pvwatts_dc` for details.\n        \"\"\"\n        g_poa_effective = self._validate_per_array(g_poa_effective)\n        temp_cell = self._validate_per_array(temp_cell)\n        return tuple(\n            pvwatts_dc(g_poa_effective, temp_cell,\n                       array.module_parameters['pdc0'],\n                       array.module_parameters['gamma_pdc'],\n                       **_build_kwargs(['temp_ref'], array.module_parameters))\n            for array, g_poa_effective, temp_cell\n            in zip(self.arrays, g_poa_effective, temp_cell)\n        )\n\n    def pvwatts_losses(self):\n        \"\"\"\n        Calculates DC power losses according the PVwatts model using\n        :py:func:`pvlib.pvsystem.pvwatts_losses` and\n        ``self.losses_parameters``.\n\n        See :py:func:`pvlib.pvsystem.pvwatts_losses` for details.\n        \"\"\"\n        kwargs = _build_kwargs(['soiling', 'shading', 'snow', 'mismatch',\n                                'wiring', 'connections', 'lid',\n                                'nameplate_rating', 'age', 'availability'],\n                               self.losses_parameters)\n        return pvwatts_losses(**kwargs)\n\n    @_unwrap_single_value\n    def dc_ohms_from_percent(self):\n        \"\"\"\n        Calculates the equivalent resistance of the wires for each array using\n        :py:func:`pvlib.pvsystem.dc_ohms_from_percent`\n\n        See :py:func:`pvlib.pvsystem.dc_ohms_from_percent` for details.\n        \"\"\"\n\n        return tuple(array.dc_ohms_from_percent() for array in self.arrays)\n\n    @property\n    def num_arrays(self):\n        \"\"\"The number of Arrays in the system.\"\"\"\n        return len(self.arrays)\n\n\nclass Array:\n    \"\"\"\n    An Array is a set of of modules at the same orientation.\n\n    Specifically, an array is defined by its mount, the\n    module parameters, the number of parallel strings of modules\n    and the number of modules on each string.\n\n    Parameters\n    ----------\n    mount: FixedMount, SingleAxisTrackerMount, or other\n        Mounting for the array, either on fixed-tilt racking or horizontal\n        single axis tracker. Mounting is used to determine module orientation.\n        If not provided, a FixedMount with zero tilt is used.\n\n    albedo : None or float, default None\n        Ground surface albedo. If ``None``, then ``surface_type`` is used\n        to look up a value in ``irradiance.SURFACE_ALBEDOS``.\n        If ``surface_type`` is also None then a ground surface albedo\n        of 0.25 is used.\n\n    surface_type : None or string, default None\n        The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for valid\n        values.\n\n    module : None or string, default None\n        The model name of the modules.\n        May be used to look up the module_parameters dictionary\n        via some other method.\n\n    module_type : None or string, default None\n         Describes the module's construction. Valid strings are 'glass_polymer'\n         and 'glass_glass'. Used for cell and module temperature calculations.\n\n    module_parameters : None, dict or Series, default None\n        Parameters for the module model, e.g., SAPM, CEC, or other.\n\n    temperature_model_parameters : None, dict or Series, default None.\n        Parameters for the module temperature model, e.g., SAPM, Pvsyst, or\n        other.\n\n    modules_per_string: int, default 1\n        Number of modules per string in the array.\n\n    strings: int, default 1\n        Number of parallel strings in the array.\n\n    array_losses_parameters: None, dict or Series, default None.\n        Supported keys are 'dc_ohmic_percent'.\n\n    name: None or str, default None\n        Name of Array instance.\n    \"\"\"\n\n    def __init__(self, mount,\n                 albedo=None, surface_type=None,\n                 module=None, module_type=None,\n                 module_parameters=None,\n                 temperature_model_parameters=None,\n                 modules_per_string=1, strings=1,\n                 array_losses_parameters=None,\n                 name=None):\n        self.mount = mount\n\n        self.surface_type = surface_type\n        if albedo is None:\n            self.albedo = irradiance.SURFACE_ALBEDOS.get(surface_type, 0.25)\n        else:\n            self.albedo = albedo\n\n        self.module = module\n        if module_parameters is None:\n            self.module_parameters = {}\n        else:\n            self.module_parameters = module_parameters\n\n        self.module_type = module_type\n\n        self.strings = strings\n        self.modules_per_string = modules_per_string\n\n        if temperature_model_parameters is None:\n            self.temperature_model_parameters = \\\n                self._infer_temperature_model_params()\n        else:\n            self.temperature_model_parameters = temperature_model_parameters\n\n        if array_losses_parameters is None:\n            self.array_losses_parameters = {}\n        else:\n            self.array_losses_parameters = array_losses_parameters\n\n        self.name = name\n\n    def __repr__(self):\n        attrs = ['name', 'mount', 'module',\n                 'albedo', 'module_type',\n                 'temperature_model_parameters',\n                 'strings', 'modules_per_string']\n\n        return 'Array:\\n  ' + '\\n  '.join(\n            f'{attr}: {getattr(self, attr)}' for attr in attrs\n        )\n\n    def _infer_temperature_model_params(self):\n        # try to infer temperature model parameters from from racking_model\n        # and module_type\n        param_set = f'{self.mount.racking_model}_{self.module_type}'\n        if param_set in temperature.TEMPERATURE_MODEL_PARAMETERS['sapm']:\n            return temperature._temperature_model_params('sapm', param_set)\n        elif 'freestanding' in param_set:\n            return temperature._temperature_model_params('pvsyst',\n                                                         'freestanding')\n        elif 'insulated' in param_set:  # after SAPM to avoid confusing keys\n            return temperature._temperature_model_params('pvsyst',\n                                                         'insulated')\n        else:\n            return {}\n\n    def _infer_cell_type(self):\n        \"\"\"\n        Examines module_parameters and maps the Technology key for the CEC\n        database and the Material key for the Sandia database to a common\n        list of strings for cell type.\n\n        Returns\n        -------\n        cell_type: str\n\n        \"\"\"\n\n        _cell_type_dict = {'Multi-c-Si': 'multisi',\n                           'Mono-c-Si': 'monosi',\n                           'Thin Film': 'cigs',\n                           'a-Si/nc': 'asi',\n                           'CIS': 'cigs',\n                           'CIGS': 'cigs',\n                           '1-a-Si': 'asi',\n                           'CdTe': 'cdte',\n                           'a-Si': 'asi',\n                           '2-a-Si': None,\n                           '3-a-Si': None,\n                           'HIT-Si': 'monosi',\n                           'mc-Si': 'multisi',\n                           'c-Si': 'multisi',\n                           'Si-Film': 'asi',\n                           'EFG mc-Si': 'multisi',\n                           'GaAs': None,\n                           'a-Si / mono-Si': 'monosi'}\n\n        if 'Technology' in self.module_parameters.keys():\n            # CEC module parameter set\n            cell_type = _cell_type_dict[self.module_parameters['Technology']]\n        elif 'Material' in self.module_parameters.keys():\n            # Sandia module parameter set\n            cell_type = _cell_type_dict[self.module_parameters['Material']]\n        else:\n            cell_type = None\n\n        return cell_type\n\n    def get_aoi(self, solar_zenith, solar_azimuth):\n        \"\"\"\n        Get the angle of incidence on the array.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series\n            Solar zenith angle.\n        solar_azimuth : float or Series\n            Solar azimuth angle\n\n        Returns\n        -------\n        aoi : Series\n            Then angle of incidence.\n        \"\"\"\n        orientation = self.mount.get_orientation(solar_zenith, solar_azimuth)\n        return irradiance.aoi(orientation['surface_tilt'],\n                              orientation['surface_azimuth'],\n                              solar_zenith, solar_azimuth)\n\n    def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,\n                       dni_extra=None, airmass=None, albedo=None,\n                       model='haydavies', **kwargs):\n        \"\"\"\n        Get plane of array irradiance components.\n\n        Uses the :py:func:`pvlib.irradiance.get_total_irradiance` function to\n        calculate the plane of array irradiance components for a surface\n        defined by ``self.surface_tilt`` and ``self.surface_azimuth``.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series.\n            Solar zenith angle.\n        solar_azimuth : float or Series.\n            Solar azimuth angle.\n        dni : float or Series\n            Direct normal irradiance. [W/m2]\n        ghi : float or Series. [W/m2]\n            Global horizontal irradiance\n        dhi : float or Series\n            Diffuse horizontal irradiance. [W/m2]\n        dni_extra : None, float or Series, default None\n            Extraterrestrial direct normal irradiance. [W/m2]\n        airmass : None, float or Series, default None\n            Airmass. [unitless]\n        albedo : None, float or Series, default None\n            Ground surface albedo. [unitless]\n        model : String, default 'haydavies'\n            Irradiance model.\n\n        kwargs\n            Extra parameters passed to\n            :py:func:`pvlib.irradiance.get_total_irradiance`.\n\n        Returns\n        -------\n        poa_irradiance : DataFrame\n            Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse',\n            'poa_sky_diffuse', 'poa_ground_diffuse'``.\n\n        See also\n        --------\n        :py:func:`pvlib.irradiance.get_total_irradiance`\n        \"\"\"\n        if albedo is None:\n            albedo = self.albedo\n\n        # not needed for all models, but this is easier\n        if dni_extra is None:\n            dni_extra = irradiance.get_extra_radiation(solar_zenith.index)\n\n        if airmass is None:\n            airmass = atmosphere.get_relative_airmass(solar_zenith)\n\n        orientation = self.mount.get_orientation(solar_zenith, solar_azimuth)\n        return irradiance.get_total_irradiance(orientation['surface_tilt'],\n                                               orientation['surface_azimuth'],\n                                               solar_zenith, solar_azimuth,\n                                               dni, ghi, dhi,\n                                               dni_extra=dni_extra,\n                                               airmass=airmass,\n                                               albedo=albedo,\n                                               model=model,\n                                               **kwargs)\n\n    def get_iam(self, aoi, iam_model='physical'):\n        \"\"\"\n        Determine the incidence angle modifier using the method specified by\n        ``iam_model``.\n\n        Parameters for the selected IAM model are expected to be in\n        ``Array.module_parameters``. Default parameters are available for\n        the 'physical', 'ashrae' and 'martin_ruiz' models.\n\n        Parameters\n        ----------\n        aoi : numeric\n            The angle of incidence in degrees.\n\n        aoi_model : string, default 'physical'\n            The IAM model to be used. Valid strings are 'physical', 'ashrae',\n            'martin_ruiz', 'sapm' and 'interp'.\n\n        Returns\n        -------\n        iam : numeric\n            The AOI modifier.\n\n        Raises\n        ------\n        ValueError\n            if `iam_model` is not a valid model name.\n        \"\"\"\n        model = iam_model.lower()\n        if model in ['ashrae', 'physical', 'martin_ruiz', 'interp']:\n            func = getattr(iam, model)  # get function at pvlib.iam\n            # get all parameters from function signature to retrieve them from\n            # module_parameters if present\n            params = set(inspect.signature(func).parameters.keys())\n            params.discard('aoi')  # exclude aoi so it can't be repeated\n            kwargs = _build_kwargs(params, self.module_parameters)\n            return func(aoi, **kwargs)\n        elif model == 'sapm':\n            return iam.sapm(aoi, self.module_parameters)\n        else:\n            raise ValueError(model + ' is not a valid IAM model')\n\n    def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,\n                             effective_irradiance=None):\n        \"\"\"\n        Determine cell temperature using the method specified by ``model``.\n\n        Parameters\n        ----------\n        poa_global : numeric\n            Total incident irradiance [W/m^2]\n\n        temp_air : numeric\n            Ambient dry bulb temperature [C]\n\n        wind_speed : numeric\n            Wind speed [m/s]\n\n        model : str\n            Supported models include ``'sapm'``, ``'pvsyst'``,\n            ``'faiman'``, ``'fuentes'``, and ``'noct_sam'``\n\n        effective_irradiance : numeric, optional\n            The irradiance that is converted to photocurrent in W/m^2.\n            Only used for some models.\n\n        Returns\n        -------\n        numeric\n            Values in degrees C.\n\n        See Also\n        --------\n        pvlib.temperature.sapm_cell, pvlib.temperature.pvsyst_cell,\n        pvlib.temperature.faiman, pvlib.temperature.fuentes,\n        pvlib.temperature.noct_sam\n\n        Notes\n        -----\n        Some temperature models have requirements for the input types;\n        see the documentation of the underlying model function for details.\n        \"\"\"\n        # convenience wrapper to avoid passing args 2 and 3 every call\n        _build_tcell_args = functools.partial(\n            _build_args, input_dict=self.temperature_model_parameters,\n            dict_name='temperature_model_parameters')\n\n        if model == 'sapm':\n            func = temperature.sapm_cell\n            required = _build_tcell_args(['a', 'b', 'deltaT'])\n            optional = _build_kwargs(['irrad_ref'],\n                                     self.temperature_model_parameters)\n        elif model == 'pvsyst':\n            func = temperature.pvsyst_cell\n            required = tuple()\n            optional = {\n                **_build_kwargs(['module_efficiency', 'alpha_absorption'],\n                                self.module_parameters),\n                **_build_kwargs(['u_c', 'u_v'],\n                                self.temperature_model_parameters)\n            }\n        elif model == 'faiman':\n            func = temperature.faiman\n            required = tuple()\n            optional = _build_kwargs(['u0', 'u1'],\n                                     self.temperature_model_parameters)\n        elif model == 'fuentes':\n            func = temperature.fuentes\n            required = _build_tcell_args(['noct_installed'])\n            optional = _build_kwargs([\n                'wind_height', 'emissivity', 'absorption',\n                'surface_tilt', 'module_width', 'module_length'],\n                self.temperature_model_parameters)\n            if self.mount.module_height is not None:\n                optional['module_height'] = self.mount.module_height\n        elif model == 'noct_sam':\n            func = functools.partial(temperature.noct_sam,\n                                     effective_irradiance=effective_irradiance)\n            required = _build_tcell_args(['noct', 'module_efficiency'])\n            optional = _build_kwargs(['transmittance_absorptance',\n                                      'array_height', 'mount_standoff'],\n                                     self.temperature_model_parameters)\n        else:\n            raise ValueError(f'{model} is not a valid cell temperature model')\n\n        temperature_cell = func(poa_global, temp_air, wind_speed,\n                                *required, **optional)\n        return temperature_cell\n\n    def dc_ohms_from_percent(self):\n        \"\"\"\n        Calculates the equivalent resistance of the wires using\n        :py:func:`pvlib.pvsystem.dc_ohms_from_percent`\n\n        Makes use of array module parameters according to the\n        following DC models:\n\n        CEC:\n\n            * `self.module_parameters[\"V_mp_ref\"]`\n            * `self.module_parameters[\"I_mp_ref\"]`\n\n        SAPM:\n\n            * `self.module_parameters[\"Vmpo\"]`\n            * `self.module_parameters[\"Impo\"]`\n\n        PVsyst-like or other:\n\n            * `self.module_parameters[\"Vmpp\"]`\n            * `self.module_parameters[\"Impp\"]`\n\n        Other array parameters that are used are:\n        `self.losses_parameters[\"dc_ohmic_percent\"]`,\n        `self.modules_per_string`, and\n        `self.strings`.\n\n        See :py:func:`pvlib.pvsystem.dc_ohms_from_percent` for more details.\n        \"\"\"\n\n        # get relevent Vmp and Imp parameters from CEC parameters\n        if all(elem in self.module_parameters\n               for elem in ['V_mp_ref', 'I_mp_ref']):\n            vmp_ref = self.module_parameters['V_mp_ref']\n            imp_ref = self.module_parameters['I_mp_ref']\n\n        # get relevant Vmp and Imp parameters from SAPM parameters\n        elif all(elem in self.module_parameters for elem in ['Vmpo', 'Impo']):\n            vmp_ref = self.module_parameters['Vmpo']\n            imp_ref = self.module_parameters['Impo']\n\n        # get relevant Vmp and Imp parameters if they are PVsyst-like\n        elif all(elem in self.module_parameters for elem in ['Vmpp', 'Impp']):\n            vmp_ref = self.module_parameters['Vmpp']\n            imp_ref = self.module_parameters['Impp']\n\n        # raise error if relevant Vmp and Imp parameters are not found\n        else:\n            raise ValueError('Parameters for Vmp and Imp could not be found '\n                             'in the array module parameters. Module '\n                             'parameters must include one set of '\n                             '{\"V_mp_ref\", \"I_mp_Ref\"}, '\n                             '{\"Vmpo\", \"Impo\"}, or '\n                             '{\"Vmpp\", \"Impp\"}.'\n                             )\n\n        return dc_ohms_from_percent(\n            vmp_ref,\n            imp_ref,\n            self.array_losses_parameters['dc_ohmic_percent'],\n            self.modules_per_string,\n            self.strings)\n\n\n@dataclass\nclass AbstractMount(ABC):\n    \"\"\"\n    A base class for Mount classes to extend. It is not intended to be\n    instantiated directly.\n    \"\"\"\n\n    @abstractmethod\n    def get_orientation(self, solar_zenith, solar_azimuth):\n        \"\"\"\n        Determine module orientation.\n\n        Parameters\n        ----------\n        solar_zenith : numeric\n            Solar apparent zenith angle [degrees]\n        solar_azimuth : numeric\n            Solar azimuth angle [degrees]\n\n        Returns\n        -------\n        orientation : dict-like\n            A dict-like object with keys `'surface_tilt', 'surface_azimuth'`\n            (typically a dict or pandas.DataFrame)\n        \"\"\"\n\n\n@dataclass\nclass FixedMount(AbstractMount):\n    \"\"\"\n    Racking at fixed (static) orientation.\n\n    Parameters\n    ----------\n    surface_tilt : float, default 0\n        Surface tilt angle. The tilt angle is defined as angle from horizontal\n        (e.g. surface facing up = 0, surface facing horizon = 90) [degrees]\n\n    surface_azimuth : float, default 180\n        Azimuth angle of the module surface. North=0, East=90, South=180,\n        West=270. [degrees]\n\n    racking_model : str, optional\n        Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.\n        Used to identify a parameter set for the SAPM cell temperature model.\n\n    module_height : float, optional\n       The height above ground of the center of the module [m]. Used for\n       the Fuentes cell temperature model.\n    \"\"\"\n\n    surface_tilt: float = 0.0\n    surface_azimuth: float = 180.0\n    racking_model: Optional[str] = None\n    module_height: Optional[float] = None\n\n    def get_orientation(self, solar_zenith, solar_azimuth):\n        # note -- docstring is automatically inherited from AbstractMount\n        return {\n            'surface_tilt': self.surface_tilt,\n            'surface_azimuth': self.surface_azimuth,\n        }\n\n\n@dataclass\nclass SingleAxisTrackerMount(AbstractMount):\n    \"\"\"\n    Single-axis tracker racking for dynamic solar tracking.\n\n    Parameters\n    ----------\n    axis_tilt : float, default 0\n        The tilt of the axis of rotation (i.e, the y-axis defined by\n        axis_azimuth) with respect to horizontal. [degrees]\n\n    axis_azimuth : float, default 180\n        A value denoting the compass direction along which the axis of\n        rotation lies, measured east of north. [degrees]\n\n    max_angle : float, default 90\n        A value denoting the maximum rotation angle\n        of the one-axis tracker from its horizontal position (horizontal\n        if axis_tilt = 0). A max_angle of 90 degrees allows the tracker\n        to rotate to a vertical position to point the panel towards a\n        horizon. max_angle of 180 degrees allows for full rotation. [degrees]\n\n    backtrack : bool, default True\n        Controls whether the tracker has the capability to \"backtrack\"\n        to avoid row-to-row shading. False denotes no backtrack\n        capability. True denotes backtrack capability.\n\n    gcr : float, default 2.0/7.0\n        A value denoting the ground coverage ratio of a tracker system\n        which utilizes backtracking; i.e. the ratio between the PV array\n        surface area to total ground area. A tracker system with modules\n        2 meters wide, centered on the tracking axis, with 6 meters\n        between the tracking axes has a gcr of 2/6=0.333. If gcr is not\n        provided, a gcr of 2/7 is default. gcr must be <=1. [unitless]\n\n    cross_axis_tilt : float, default 0.0\n        The angle, relative to horizontal, of the line formed by the\n        intersection between the slope containing the tracker axes and a plane\n        perpendicular to the tracker axes. Cross-axis tilt should be specified\n        using a right-handed convention. For example, trackers with axis\n        azimuth of 180 degrees (heading south) will have a negative cross-axis\n        tilt if the tracker axes plane slopes down to the east and positive\n        cross-axis tilt if the tracker axes plane slopes up to the east. Use\n        :func:`~pvlib.tracking.calc_cross_axis_tilt` to calculate\n        `cross_axis_tilt`. [degrees]\n\n    racking_model : str, optional\n        Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.\n        Used to identify a parameter set for the SAPM cell temperature model.\n\n    module_height : float, optional\n       The height above ground of the center of the module [m]. Used for\n       the Fuentes cell temperature model.\n    \"\"\"\n    axis_tilt: float = 0.0\n    axis_azimuth: float = 0.0\n    max_angle: float = 90.0\n    backtrack: bool = True\n    gcr: float = 2.0/7.0\n    cross_axis_tilt: float = 0.0\n    racking_model: Optional[str] = None\n    module_height: Optional[float] = None\n\n    def get_orientation(self, solar_zenith, solar_azimuth):\n        # note -- docstring is automatically inherited from AbstractMount\n        from pvlib import tracking  # avoid circular import issue\n        tracking_data = tracking.singleaxis(\n            solar_zenith, solar_azimuth,\n            self.axis_tilt, self.axis_azimuth,\n            self.max_angle, self.backtrack,\n            self.gcr, self.cross_axis_tilt\n        )\n        return tracking_data\n\n\ndef calcparams_desoto(effective_irradiance, temp_cell,\n                      alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s,\n                      EgRef=1.121, dEgdT=-0.0002677,\n                      irrad_ref=1000, temp_ref=25):\n    '''\n    Calculates five parameter values for the single diode equation at\n    effective irradiance and cell temperature using the De Soto et al.\n    model described in [1]_. The five values returned by calcparams_desoto\n    can be used by singlediode to calculate an IV curve.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        The irradiance (W/m2) that is converted to photocurrent.\n\n    temp_cell : numeric\n        The average cell temperature of cells within a module in C.\n\n    alpha_sc : float\n        The short-circuit current temperature coefficient of the\n        module in units of A/C.\n\n    a_ref : float\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at reference\n        conditions, in units of V.\n\n    I_L_ref : float\n        The light-generated current (or photocurrent) at reference conditions,\n        in amperes.\n\n    I_o_ref : float\n        The dark or diode reverse saturation current at reference conditions,\n        in amperes.\n\n    R_sh_ref : float\n        The shunt resistance at reference conditions, in ohms.\n\n    R_s : float\n        The series resistance at reference conditions, in ohms.\n\n    EgRef : float\n        The energy bandgap at reference temperature in units of eV.\n        1.121 eV for crystalline silicon. EgRef must be >0.  For parameters\n        from the SAM CEC module database, EgRef=1.121 is implicit for all\n        cell types in the parameter estimation algorithm used by NREL.\n\n    dEgdT : float\n        The temperature dependence of the energy bandgap at reference\n        conditions in units of 1/K. May be either a scalar value\n        (e.g. -0.0002677 as in [1]_) or a DataFrame (this may be useful if\n        dEgdT is a modeled as a function of temperature). For parameters from\n        the SAM CEC module database, dEgdT=-0.0002677 is implicit for all cell\n        types in the parameter estimation algorithm used by NREL.\n\n    irrad_ref : float (optional, default=1000)\n        Reference irradiance in W/m^2.\n\n    temp_ref : float (optional, default=25)\n        Reference cell temperature in C.\n\n    Returns\n    -------\n    Tuple of the following results:\n\n    photocurrent : numeric\n        Light-generated current in amperes\n\n    saturation_current : numeric\n        Diode saturation curent in amperes\n\n    resistance_series : numeric\n        Series resistance in ohms\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms\n\n    nNsVth : numeric\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at\n        specified effective irradiance and cell temperature.\n\n    References\n    ----------\n    .. [1] W. De Soto et al., \"Improvement and validation of a model for\n       photovoltaic array performance\", Solar Energy, vol 80, pp. 78-88,\n       2006.\n\n    .. [2] System Advisor Model web page. https://sam.nrel.gov.\n\n    .. [3] A. Dobos, \"An Improved Coefficient Calculator for the California\n       Energy Commission 6 Parameter Photovoltaic Module Model\", Journal of\n       Solar Energy Engineering, vol 134, 2012.\n\n    .. [4] O. Madelung, \"Semiconductors: Data Handbook, 3rd ed.\" ISBN\n       3-540-40488-0\n\n    See Also\n    --------\n    singlediode\n    retrieve_sam\n\n    Notes\n    -----\n    If the reference parameters in the ModuleParameters struct are read\n    from a database or library of parameters (e.g. System Advisor\n    Model), it is important to use the same EgRef and dEgdT values that\n    were used to generate the reference parameters, regardless of the\n    actual bandgap characteristics of the semiconductor. For example, in\n    the case of the System Advisor Model library, created as described\n    in [3], EgRef and dEgdT for all modules were 1.121 and -0.0002677,\n    respectively.\n\n    This table of reference bandgap energies (EgRef), bandgap energy\n    temperature dependence (dEgdT), and \"typical\" airmass response (M)\n    is provided purely as reference to those who may generate their own\n    reference module parameters (a_ref, IL_ref, I0_ref, etc.) based upon\n    the various PV semiconductors. Again, we stress the importance of\n    using identical EgRef and dEgdT when generation reference parameters\n    and modifying the reference parameters (for irradiance, temperature,\n    and airmass) per DeSoto's equations.\n\n     Crystalline Silicon (Si):\n         * EgRef = 1.121\n         * dEgdT = -0.0002677\n\n         >>> M = np.polyval([-1.26E-4, 2.816E-3, -0.024459, 0.086257, 0.9181],\n         ...                AMa) # doctest: +SKIP\n\n         Source: [1]\n\n     Cadmium Telluride (CdTe):\n         * EgRef = 1.475\n         * dEgdT = -0.0003\n\n         >>> M = np.polyval([-2.46E-5, 9.607E-4, -0.0134, 0.0716, 0.9196],\n         ...                AMa) # doctest: +SKIP\n\n         Source: [4]\n\n     Copper Indium diSelenide (CIS):\n         * EgRef = 1.010\n         * dEgdT = -0.00011\n\n         >>> M = np.polyval([-3.74E-5, 0.00125, -0.01462, 0.0718, 0.9210],\n         ...                AMa) # doctest: +SKIP\n\n         Source: [4]\n\n     Copper Indium Gallium diSelenide (CIGS):\n         * EgRef = 1.15\n         * dEgdT = ????\n\n         >>> M = np.polyval([-9.07E-5, 0.0022, -0.0202, 0.0652, 0.9417],\n         ...                AMa) # doctest: +SKIP\n\n         Source: Wikipedia\n\n     Gallium Arsenide (GaAs):\n         * EgRef = 1.424\n         * dEgdT = -0.000433\n         * M = unknown\n\n         Source: [4]\n    '''\n\n    # Boltzmann constant in eV/K, 8.617332478e-05\n    k = constants.value('Boltzmann constant in eV/K')\n\n    # reference temperature\n    Tref_K = temp_ref + 273.15\n    Tcell_K = temp_cell + 273.15\n\n    E_g = EgRef * (1 + dEgdT*(Tcell_K - Tref_K))\n\n    nNsVth = a_ref * (Tcell_K / Tref_K)\n\n    # In the equation for IL, the single factor effective_irradiance is\n    # used, in place of the product S*M in [1]. effective_irradiance is\n    # equivalent to the product of S (irradiance reaching a module's cells) *\n    # M (spectral adjustment factor) as described in [1].\n    IL = effective_irradiance / irrad_ref * \\\n        (I_L_ref + alpha_sc * (Tcell_K - Tref_K))\n    I0 = (I_o_ref * ((Tcell_K / Tref_K) ** 3) *\n          (np.exp(EgRef / (k*(Tref_K)) - (E_g / (k*(Tcell_K))))))\n    # Note that the equation for Rsh differs from [1]. In [1] Rsh is given as\n    # Rsh = Rsh_ref * (S_ref / S) where S is broadband irradiance reaching\n    # the module's cells. If desired this model behavior can be duplicated\n    # by applying reflection and soiling losses to broadband plane of array\n    # irradiance and not applying a spectral loss modifier, i.e.,\n    # spectral_modifier = 1.0.\n    # use errstate to silence divide by warning\n    with np.errstate(divide='ignore'):\n        Rsh = R_sh_ref * (irrad_ref / effective_irradiance)\n\n    Rs = R_s\n\n    numeric_args = (effective_irradiance, temp_cell)\n    out = (IL, I0, Rs, Rsh, nNsVth)\n\n    if all(map(np.isscalar, numeric_args)):\n        return out\n\n    index = tools.get_pandas_index(*numeric_args)\n\n    if index is None:\n        return np.broadcast_arrays(*out)\n\n    return tuple(pd.Series(a, index=index).rename(None) for a in out)\n\n\ndef calcparams_cec(effective_irradiance, temp_cell,\n                   alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s,\n                   Adjust, EgRef=1.121, dEgdT=-0.0002677,\n                   irrad_ref=1000, temp_ref=25):\n    '''\n    Calculates five parameter values for the single diode equation at\n    effective irradiance and cell temperature using the CEC\n    model. The CEC model [1]_ differs from the De soto et al.\n    model [3]_ by the parameter Adjust. The five values returned by\n    calcparams_cec can be used by singlediode to calculate an IV curve.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        The irradiance (W/m2) that is converted to photocurrent.\n\n    temp_cell : numeric\n        The average cell temperature of cells within a module in C.\n\n    alpha_sc : float\n        The short-circuit current temperature coefficient of the\n        module in units of A/C.\n\n    a_ref : float\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at reference\n        conditions, in units of V.\n\n    I_L_ref : float\n        The light-generated current (or photocurrent) at reference conditions,\n        in amperes.\n\n    I_o_ref : float\n        The dark or diode reverse saturation current at reference conditions,\n        in amperes.\n\n    R_sh_ref : float\n        The shunt resistance at reference conditions, in ohms.\n\n    R_s : float\n        The series resistance at reference conditions, in ohms.\n\n    Adjust : float\n        The adjustment to the temperature coefficient for short circuit\n        current, in percent\n\n    EgRef : float\n        The energy bandgap at reference temperature in units of eV.\n        1.121 eV for crystalline silicon. EgRef must be >0.  For parameters\n        from the SAM CEC module database, EgRef=1.121 is implicit for all\n        cell types in the parameter estimation algorithm used by NREL.\n\n    dEgdT : float\n        The temperature dependence of the energy bandgap at reference\n        conditions in units of 1/K. May be either a scalar value\n        (e.g. -0.0002677 as in [3]) or a DataFrame (this may be useful if\n        dEgdT is a modeled as a function of temperature). For parameters from\n        the SAM CEC module database, dEgdT=-0.0002677 is implicit for all cell\n        types in the parameter estimation algorithm used by NREL.\n\n    irrad_ref : float (optional, default=1000)\n        Reference irradiance in W/m^2.\n\n    temp_ref : float (optional, default=25)\n        Reference cell temperature in C.\n\n    Returns\n    -------\n    Tuple of the following results:\n\n    photocurrent : numeric\n        Light-generated current in amperes\n\n    saturation_current : numeric\n        Diode saturation curent in amperes\n\n    resistance_series : numeric\n        Series resistance in ohms\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms\n\n    nNsVth : numeric\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at\n        specified effective irradiance and cell temperature.\n\n    References\n    ----------\n    .. [1] A. Dobos, \"An Improved Coefficient Calculator for the California\n       Energy Commission 6 Parameter Photovoltaic Module Model\", Journal of\n       Solar Energy Engineering, vol 134, 2012.\n\n    .. [2] System Advisor Model web page. https://sam.nrel.gov.\n\n    .. [3] W. De Soto et al., \"Improvement and validation of a model for\n       photovoltaic array performance\", Solar Energy, vol 80, pp. 78-88,\n       2006.\n\n    See Also\n    --------\n    calcparams_desoto\n    singlediode\n    retrieve_sam\n\n    '''\n\n    # pass adjusted temperature coefficient to desoto\n    return calcparams_desoto(effective_irradiance, temp_cell,\n                             alpha_sc*(1.0 - Adjust/100),\n                             a_ref, I_L_ref, I_o_ref,\n                             R_sh_ref, R_s,\n                             EgRef=EgRef, dEgdT=dEgdT,\n                             irrad_ref=irrad_ref, temp_ref=temp_ref)\n\n\ndef calcparams_pvsyst(effective_irradiance, temp_cell,\n                      alpha_sc, gamma_ref, mu_gamma,\n                      I_L_ref, I_o_ref,\n                      R_sh_ref, R_sh_0, R_s,\n                      cells_in_series,\n                      R_sh_exp=5.5,\n                      EgRef=1.121,\n                      irrad_ref=1000, temp_ref=25):\n    '''\n    Calculates five parameter values for the single diode equation at\n    effective irradiance and cell temperature using the PVsyst v6\n    model.  The PVsyst v6 model is described in [1]_, [2]_, [3]_.\n    The five values returned by calcparams_pvsyst can be used by singlediode\n    to calculate an IV curve.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        The irradiance (W/m2) that is converted to photocurrent.\n\n    temp_cell : numeric\n        The average cell temperature of cells within a module in C.\n\n    alpha_sc : float\n        The short-circuit current temperature coefficient of the\n        module in units of A/C.\n\n    gamma_ref : float\n        The diode ideality factor\n\n    mu_gamma : float\n        The temperature coefficient for the diode ideality factor, 1/K\n\n    I_L_ref : float\n        The light-generated current (or photocurrent) at reference conditions,\n        in amperes.\n\n    I_o_ref : float\n        The dark or diode reverse saturation current at reference conditions,\n        in amperes.\n\n    R_sh_ref : float\n        The shunt resistance at reference conditions, in ohms.\n\n    R_sh_0 : float\n        The shunt resistance at zero irradiance conditions, in ohms.\n\n    R_s : float\n        The series resistance at reference conditions, in ohms.\n\n    cells_in_series : integer\n        The number of cells connected in series.\n\n    R_sh_exp : float\n        The exponent in the equation for shunt resistance, unitless. Defaults\n        to 5.5.\n\n    EgRef : float\n        The energy bandgap at reference temperature in units of eV.\n        1.121 eV for crystalline silicon. EgRef must be >0.\n\n    irrad_ref : float (optional, default=1000)\n        Reference irradiance in W/m^2.\n\n    temp_ref : float (optional, default=25)\n        Reference cell temperature in C.\n\n    Returns\n    -------\n    Tuple of the following results:\n\n    photocurrent : numeric\n        Light-generated current in amperes\n\n    saturation_current : numeric\n        Diode saturation current in amperes\n\n    resistance_series : numeric\n        Series resistance in ohms\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms\n\n    nNsVth : numeric\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at\n        specified effective irradiance and cell temperature.\n\n    References\n    ----------\n    .. [1] K. Sauer, T. Roessler, C. W. Hansen, Modeling the Irradiance and\n       Temperature Dependence of Photovoltaic Modules in PVsyst,\n       IEEE Journal of Photovoltaics v5(1), January 2015.\n\n    .. [2] A. Mermoud, PV modules modelling, Presentation at the 2nd PV\n       Performance Modeling Workshop, Santa Clara, CA, May 2013\n\n    .. [3] A. Mermoud, T. Lejeune, Performance Assessment of a Simulation Model\n       for PV modules of any available technology, 25th European Photovoltaic\n       Solar Energy Conference, Valencia, Spain, Sept. 2010\n\n    See Also\n    --------\n    calcparams_desoto\n    singlediode\n\n    '''\n\n    # Boltzmann constant in J/K\n    k = constants.k\n\n    # elementary charge in coulomb\n    q = constants.e\n\n    # reference temperature\n    Tref_K = temp_ref + 273.15\n    Tcell_K = temp_cell + 273.15\n\n    gamma = gamma_ref + mu_gamma * (Tcell_K - Tref_K)\n    nNsVth = gamma * k / q * cells_in_series * Tcell_K\n\n    IL = effective_irradiance / irrad_ref * \\\n        (I_L_ref + alpha_sc * (Tcell_K - Tref_K))\n\n    I0 = I_o_ref * ((Tcell_K / Tref_K) ** 3) * \\\n        (np.exp((q * EgRef) / (k * gamma) * (1 / Tref_K - 1 / Tcell_K)))\n\n    Rsh_tmp = \\\n        (R_sh_ref - R_sh_0 * np.exp(-R_sh_exp)) / (1.0 - np.exp(-R_sh_exp))\n    Rsh_base = np.maximum(0.0, Rsh_tmp)\n\n    Rsh = Rsh_base + (R_sh_0 - Rsh_base) * \\\n        np.exp(-R_sh_exp * effective_irradiance / irrad_ref)\n\n    Rs = R_s\n\n    numeric_args = (effective_irradiance, temp_cell)\n    out = (IL, I0, Rs, Rsh, nNsVth)\n\n    if all(map(np.isscalar, numeric_args)):\n        return out\n\n    index = tools.get_pandas_index(*numeric_args)\n\n    if index is None:\n        return np.broadcast_arrays(*out)\n\n    return tuple(pd.Series(a, index=index).rename(None) for a in out)\n\n\ndef retrieve_sam(name=None, path=None):\n    '''\n    Retrieve latest module and inverter info from a local file or the\n    SAM website.\n\n    This function will retrieve either:\n\n        * CEC module database\n        * Sandia Module database\n        * CEC Inverter database\n        * Anton Driesse Inverter database\n\n    and return it as a pandas DataFrame.\n\n    Parameters\n    ----------\n    name : None or string, default None\n        Name can be one of:\n\n        * 'CECMod' - returns the CEC module database\n        * 'CECInverter' - returns the CEC Inverter database\n        * 'SandiaInverter' - returns the CEC Inverter database\n          (CEC is only current inverter db available; tag kept for\n          backwards compatibility)\n        * 'SandiaMod' - returns the Sandia Module database\n        * 'ADRInverter' - returns the ADR Inverter database\n\n    path : None or string, default None\n        Path to the SAM file. May also be a URL.\n\n    Returns\n    -------\n    samfile : DataFrame\n        A DataFrame containing all the elements of the desired database.\n        Each column represents a module or inverter, and a specific\n        dataset can be retrieved by the command\n\n    Raises\n    ------\n    ValueError\n        If no name or path is provided.\n\n    Notes\n    -----\n    Files available at\n        https://github.com/NREL/SAM/tree/develop/deploy/libraries\n    Documentation for module and inverter data sets:\n        https://sam.nrel.gov/photovoltaic/pv-sub-page-2.html\n\n    Examples\n    --------\n\n    >>> from pvlib import pvsystem\n    >>> invdb = pvsystem.retrieve_sam('CECInverter')\n    >>> inverter = invdb.AE_Solar_Energy__AE6_0__277V_\n    >>> inverter\n    Vac                          277\n    Pso                    36.197575\n    Paco                      6000.0\n    Pdco                 6158.746094\n    Vdco                       360.0\n    C0                     -0.000002\n    C1                     -0.000026\n    C2                     -0.001253\n    C3                       0.00021\n    Pnt                          1.8\n    Vdcmax                     450.0\n    Idcmax                 17.107628\n    Mppt_low                   100.0\n    Mppt_high                  450.0\n    CEC_Date                     NaN\n    CEC_Type     Utility Interactive\n    Name: AE_Solar_Energy__AE6_0__277V_, dtype: object\n    '''\n\n    if name is not None:\n        name = name.lower()\n        data_path = os.path.join(\n            os.path.dirname(os.path.abspath(__file__)), 'data')\n        if name == 'cecmod':\n            csvdata = os.path.join(\n                data_path, 'sam-library-cec-modules-2019-03-05.csv')\n        elif name == 'sandiamod':\n            csvdata = os.path.join(\n                data_path, 'sam-library-sandia-modules-2015-6-30.csv')\n        elif name == 'adrinverter':\n            csvdata = os.path.join(\n                data_path, 'adr-library-cec-inverters-2019-03-05.csv')\n        elif name in ['cecinverter', 'sandiainverter']:\n            # Allowing either, to provide for old code,\n            # while aligning with current expectations\n            csvdata = os.path.join(\n                data_path, 'sam-library-cec-inverters-2019-03-05.csv')\n        else:\n            raise ValueError(f'invalid name {name}')\n    elif path is not None:\n        if path.startswith('http'):\n            response = urlopen(path)\n            csvdata = io.StringIO(response.read().decode(errors='ignore'))\n        else:\n            csvdata = path\n    elif name is None and path is None:\n        raise ValueError(\"A name or path must be provided!\")\n\n    return _parse_raw_sam_df(csvdata)\n\n\ndef _normalize_sam_product_names(names):\n    '''\n    Replace special characters within the product names to make them more\n    suitable for use as Dataframe column names.\n    '''\n    # Contributed by Anton Driesse (@adriesse), PV Performance Labs. July, 2019\n\n    import warnings\n\n    BAD_CHARS = ' -.()[]:+/\",'\n    GOOD_CHARS = '____________'\n\n    mapping = str.maketrans(BAD_CHARS, GOOD_CHARS)\n    names = pd.Series(data=names)\n    norm_names = names.str.translate(mapping)\n\n    n_duplicates = names.duplicated().sum()\n    if n_duplicates > 0:\n        warnings.warn('Original names contain %d duplicate(s).' % n_duplicates)\n\n    n_duplicates = norm_names.duplicated().sum()\n    if n_duplicates > 0:\n        warnings.warn(\n            'Normalized names contain %d duplicate(s).' % n_duplicates)\n\n    return norm_names.values\n\n\ndef _parse_raw_sam_df(csvdata):\n\n    df = pd.read_csv(csvdata, index_col=0, skiprows=[1, 2])\n\n    df.columns = df.columns.str.replace(' ', '_')\n    df.index = _normalize_sam_product_names(df.index)\n    df = df.transpose()\n\n    if 'ADRCoefficients' in df.index:\n        ad_ce = 'ADRCoefficients'\n        # for each inverter, parses a string of coefficients like\n        # ' 1.33, 2.11, 3.12' into a list containing floats:\n        # [1.33, 2.11, 3.12]\n        df.loc[ad_ce] = df.loc[ad_ce].map(lambda x: list(\n            map(float, x.strip(' []').split())))\n\n    return df\n\n\ndef sapm(effective_irradiance, temp_cell, module):\n    '''\n    The Sandia PV Array Performance Model (SAPM) generates 5 points on a\n    PV module's I-V curve (Voc, Isc, Ix, Ixx, Vmp/Imp) according to\n    SAND2004-3535. Assumes a reference cell temperature of 25 C.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        Irradiance reaching the module's cells, after reflections and\n        adjustment for spectrum. [W/m2]\n\n    temp_cell : numeric\n        Cell temperature [C].\n\n    module : dict-like\n        A dict or Series defining the SAPM parameters. See the notes section\n        for more details.\n\n    Returns\n    -------\n    A DataFrame with the columns:\n\n        * i_sc : Short-circuit current (A)\n        * i_mp : Current at the maximum-power point (A)\n        * v_oc : Open-circuit voltage (V)\n        * v_mp : Voltage at maximum-power point (V)\n        * p_mp : Power at maximum-power point (W)\n        * i_x : Current at module V = 0.5Voc, defines 4th point on I-V\n          curve for modeling curve shape\n        * i_xx : Current at module V = 0.5(Voc+Vmp), defines 5th point on\n          I-V curve for modeling curve shape\n\n    Notes\n    -----\n    The SAPM parameters which are required in ``module`` are\n    listed in the following table.\n\n    The Sandia module database contains parameter values for a limited set\n    of modules. The CEC module database does not contain these parameters.\n    Both databases can be accessed using :py:func:`retrieve_sam`.\n\n    ================   ========================================================\n    Key                Description\n    ================   ========================================================\n    A0-A4              The airmass coefficients used in calculating\n                       effective irradiance\n    B0-B5              The angle of incidence coefficients used in calculating\n                       effective irradiance\n    C0-C7              The empirically determined coefficients relating\n                       Imp, Vmp, Ix, and Ixx to effective irradiance\n    Isco               Short circuit current at reference condition (amps)\n    Impo               Maximum power current at reference condition (amps)\n    Voco               Open circuit voltage at reference condition (amps)\n    Vmpo               Maximum power voltage at reference condition (amps)\n    Aisc               Short circuit current temperature coefficient at\n                       reference condition (1/C)\n    Aimp               Maximum power current temperature coefficient at\n                       reference condition (1/C)\n    Bvoco              Open circuit voltage temperature coefficient at\n                       reference condition (V/C)\n    Mbvoc              Coefficient providing the irradiance dependence for the\n                       BetaVoc temperature coefficient at reference irradiance\n                       (V/C)\n    Bvmpo              Maximum power voltage temperature coefficient at\n                       reference condition\n    Mbvmp              Coefficient providing the irradiance dependence for the\n                       BetaVmp temperature coefficient at reference irradiance\n                       (V/C)\n    N                  Empirically determined \"diode factor\" (dimensionless)\n    Cells_in_Series    Number of cells in series in a module's cell string(s)\n    IXO                Ix at reference conditions\n    IXXO               Ixx at reference conditions\n    FD                 Fraction of diffuse irradiance used by module\n    ================   ========================================================\n\n    References\n    ----------\n    .. [1] King, D. et al, 2004, \"Sandia Photovoltaic Array Performance\n       Model\", SAND Report 3535, Sandia National Laboratories, Albuquerque,\n       NM.\n\n    See Also\n    --------\n    retrieve_sam\n    pvlib.temperature.sapm_cell\n    pvlib.temperature.sapm_module\n    '''\n\n    # TODO: someday, change temp_ref and irrad_ref to reference_temperature and\n    # reference_irradiance and expose\n    temp_ref = 25\n    irrad_ref = 1000\n\n    q = constants.e  # Elementary charge in units of coulombs\n    kb = constants.k  # Boltzmann's constant in units of J/K\n\n    # avoid problem with integer input\n    Ee = np.array(effective_irradiance, dtype='float64') / irrad_ref\n\n    # set up masking for 0, positive, and nan inputs\n    Ee_gt_0 = np.full_like(Ee, False, dtype='bool')\n    Ee_eq_0 = np.full_like(Ee, False, dtype='bool')\n    notnan = ~np.isnan(Ee)\n    np.greater(Ee, 0, where=notnan, out=Ee_gt_0)\n    np.equal(Ee, 0, where=notnan, out=Ee_eq_0)\n\n    Bvmpo = module['Bvmpo'] + module['Mbvmp']*(1 - Ee)\n    Bvoco = module['Bvoco'] + module['Mbvoc']*(1 - Ee)\n    delta = module['N'] * kb * (temp_cell + 273.15) / q\n\n    # avoid repeated computation\n    logEe = np.full_like(Ee, np.nan)\n    np.log(Ee, where=Ee_gt_0, out=logEe)\n    logEe = np.where(Ee_eq_0, -np.inf, logEe)\n    # avoid repeated __getitem__\n    cells_in_series = module['Cells_in_Series']\n\n    out = OrderedDict()\n\n    out['i_sc'] = (\n        module['Isco'] * Ee * (1 + module['Aisc']*(temp_cell - temp_ref)))\n\n    out['i_mp'] = (\n        module['Impo'] * (module['C0']*Ee + module['C1']*(Ee**2)) *\n        (1 + module['Aimp']*(temp_cell - temp_ref)))\n\n    out['v_oc'] = np.maximum(0, (\n        module['Voco'] + cells_in_series * delta * logEe +\n        Bvoco*(temp_cell - temp_ref)))\n\n    out['v_mp'] = np.maximum(0, (\n        module['Vmpo'] +\n        module['C2'] * cells_in_series * delta * logEe +\n        module['C3'] * cells_in_series * ((delta * logEe) ** 2) +\n        Bvmpo*(temp_cell - temp_ref)))\n\n    out['p_mp'] = out['i_mp'] * out['v_mp']\n\n    out['i_x'] = (\n        module['IXO'] * (module['C4']*Ee + module['C5']*(Ee**2)) *\n        (1 + module['Aisc']*(temp_cell - temp_ref)))\n\n    # the Ixx calculation in King 2004 has a typo (mixes up Aisc and Aimp)\n    out['i_xx'] = (\n        module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) *\n        (1 + module['Aisc']*(temp_cell - temp_ref)))\n\n    if isinstance(out['i_sc'], pd.Series):\n        out = pd.DataFrame(out)\n\n    return out\n\n\nsapm_spectral_loss = deprecated(\n    since='0.10.0',\n    alternative='pvlib.spectrum.spectral_factor_sapm'\n)(spectrum.spectral_factor_sapm)\n\n\ndef sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,\n                              module):\n    r\"\"\"\n    Calculates the SAPM effective irradiance using the SAPM spectral\n    loss and SAPM angle of incidence loss functions.\n\n    Parameters\n    ----------\n    poa_direct : numeric\n        The direct irradiance incident upon the module. [W/m2]\n\n    poa_diffuse : numeric\n        The diffuse irradiance incident on module.  [W/m2]\n\n    airmass_absolute : numeric\n        Absolute airmass. [unitless]\n\n    aoi : numeric\n        Angle of incidence. [degrees]\n\n    module : dict-like\n        A dict, Series, or DataFrame defining the SAPM performance\n        parameters. See the :py:func:`sapm` notes section for more\n        details.\n\n    Returns\n    -------\n    effective_irradiance : numeric\n        Effective irradiance accounting for reflections and spectral content.\n        [W/m2]\n\n    Notes\n    -----\n    The SAPM model for effective irradiance [1]_ translates broadband direct\n    and diffuse irradiance on the plane of array to the irradiance absorbed by\n    a module's cells.\n\n    The model is\n    .. math::\n\n        `Ee = f_1(AM_a) (E_b f_2(AOI) + f_d E_d)`\n\n    where :math:`Ee` is effective irradiance (W/m2), :math:`f_1` is a fourth\n    degree polynomial in air mass :math:`AM_a`, :math:`E_b` is beam (direct)\n    irradiance on the plane of array, :math:`E_d` is diffuse irradiance on the\n    plane of array, :math:`f_2` is a fifth degree polynomial in the angle of\n    incidence :math:`AOI`, and :math:`f_d` is the fraction of diffuse\n    irradiance on the plane of array that is not reflected away.\n\n    References\n    ----------\n    .. [1] D. King et al, \"Sandia Photovoltaic Array Performance Model\",\n       SAND2004-3535, Sandia National Laboratories, Albuquerque, NM\n\n    See also\n    --------\n    pvlib.iam.sapm\n    pvlib.spectrum.spectral_factor_sapm\n    pvlib.pvsystem.sapm\n    \"\"\"\n\n    F1 = spectrum.spectral_factor_sapm(airmass_absolute, module)\n    F2 = iam.sapm(aoi, module)\n\n    Ee = F1 * (poa_direct * F2 + module['FD'] * poa_diffuse)\n\n    return Ee\n\n\ndef singlediode(photocurrent, saturation_current, resistance_series,\n                resistance_shunt, nNsVth, ivcurve_pnts=None,\n                method='lambertw'):\n    r\"\"\"\n    Solve the single diode equation to obtain a photovoltaic IV curve.\n\n    Solves the single diode equation [1]_\n\n    .. math::\n\n        I = I_L -\n            I_0 \\left[\n                \\exp \\left(\\frac{V+I R_s}{n N_s V_{th}} \\right)-1\n            \\right] -\n            \\frac{V + I R_s}{R_{sh}}\n\n    for :math:`I` and :math:`V` when given :math:`I_L, I_0, R_s, R_{sh},` and\n    :math:`n N_s V_{th}` which are described later. The five points on the I-V\n    curve specified in [3]_ are returned. If :math:`I_L, I_0, R_s, R_{sh},` and\n    :math:`n N_s V_{th}` are all scalars, a single curve is returned. If any\n    are array-like (of the same length), multiple IV curves are calculated.\n\n    The input parameters can be calculated from meteorological data using a\n    function for a single diode model, e.g.,\n    :py:func:`~pvlib.pvsystem.calcparams_desoto`.\n\n    Parameters\n    ----------\n    photocurrent : numeric\n        Light-generated current :math:`I_L` (photocurrent)\n        ``0 <= photocurrent``. [A]\n\n    saturation_current : numeric\n        Diode saturation :math:`I_0` current under desired IV curve\n        conditions. ``0 < saturation_current``. [A]\n\n    resistance_series : numeric\n        Series resistance :math:`R_s` under desired IV curve conditions.\n        ``0 <= resistance_series < numpy.inf``.  [ohm]\n\n    resistance_shunt : numeric\n        Shunt resistance :math:`R_{sh}` under desired IV curve conditions.\n        ``0 < resistance_shunt <= numpy.inf``.  [ohm]\n\n    nNsVth : numeric\n        The product of three components: 1) the usual diode ideality factor\n        :math:`n`, 2) the number of cells in series :math:`N_s`, and 3)\n        the cell thermal voltage\n        :math:`V_{th}`. The thermal voltage of the cell (in volts) may be\n        calculated as :math:`k_B T_c / q`, where :math:`k_B` is\n        Boltzmann's constant (J/K), :math:`T_c` is the temperature of the p-n\n        junction in Kelvin, and :math:`q` is the charge of an electron\n        (coulombs). ``0 < nNsVth``.  [V]\n\n    ivcurve_pnts : None or int, default None\n        Number of points in the desired IV curve. If None or 0, no points on\n        the IV curves will be produced.\n\n        .. deprecated:: 0.10.0\n           Use :py:func:`pvlib.pvsystem.v_from_i` and\n           :py:func:`pvlib.pvsystem.i_from_v` instead.\n\n    method : str, default 'lambertw'\n        Determines the method used to calculate points on the IV curve. The\n        options are ``'lambertw'``, ``'newton'``, or ``'brentq'``.\n\n    Returns\n    -------\n    dict or pandas.DataFrame\n        The returned dict-like object always contains the keys/columns:\n\n            * i_sc - short circuit current in amperes.\n            * v_oc - open circuit voltage in volts.\n            * i_mp - current at maximum power point in amperes.\n            * v_mp - voltage at maximum power point in volts.\n            * p_mp - power at maximum power point in watts.\n            * i_x - current, in amperes, at ``v = 0.5*v_oc``.\n            * i_xx - current, in amperes, at ``v = 0.5*(v_oc+v_mp)``.\n\n        A dict is returned when the input parameters are scalars or\n        ``ivcurve_pnts > 0``. If ``ivcurve_pnts > 0``, the output dictionary\n        will also include the keys:\n\n            * i - IV curve current in amperes.\n            * v - IV curve voltage in volts.\n\n    See also\n    --------\n    calcparams_desoto\n    calcparams_cec\n    calcparams_pvsyst\n    sapm\n    pvlib.singlediode.bishop88\n\n    Notes\n    -----\n    If the method is ``'lambertw'`` then the solution employed to solve the\n    implicit diode equation utilizes the Lambert W function to obtain an\n    explicit function of :math:`V=f(I)` and :math:`I=f(V)` as shown in [2]_.\n\n    If the method is ``'newton'`` then the root-finding Newton-Raphson method\n    is used. It should be safe for well behaved IV-curves, but the ``'brentq'``\n    method is recommended for reliability.\n\n    If the method is ``'brentq'`` then Brent's bisection search method is used\n    that guarantees convergence by bounding the voltage between zero and\n    open-circuit.\n\n    If the method is either ``'newton'`` or ``'brentq'`` and ``ivcurve_pnts``\n    are indicated, then :func:`pvlib.singlediode.bishop88` [4]_ is used to\n    calculate the points on the IV curve points at diode voltages from zero to\n    open-circuit voltage with a log spacing that gets closer as voltage\n    increases. If the method is ``'lambertw'`` then the calculated points on\n    the IV curve are linearly spaced.\n\n    References\n    ----------\n    .. [1] S.R. Wenham, M.A. Green, M.E. Watt, \"Applied Photovoltaics\" ISBN\n       0 86758 909 4\n\n    .. [2] A. Jain, A. Kapoor, \"Exact analytical solutions of the\n       parameters of real solar cells using Lambert W-function\", Solar\n       Energy Materials and Solar Cells, 81 (2004) 269-277.\n\n    .. [3] D. King et al, \"Sandia Photovoltaic Array Performance Model\",\n       SAND2004-3535, Sandia National Laboratories, Albuquerque, NM\n\n    .. [4] \"Computer simulation of the effects of electrical mismatches in\n       photovoltaic cell interconnection circuits\" JW Bishop, Solar Cell (1988)\n       https://doi.org/10.1016/0379-6787(88)90059-2\n    \"\"\"\n    if ivcurve_pnts:\n        warn_deprecated('0.10.0', name='pvlib.pvsystem.singlediode',\n                        alternative=('pvlib.pvsystem.v_from_i and '\n                                     'pvlib.pvsystem.i_from_v'),\n                        obj_type='parameter ivcurve_pnts',\n                        removal='0.11.0')\n    args = (photocurrent, saturation_current, resistance_series,\n            resistance_shunt, nNsVth)  # collect args\n    # Calculate points on the IV curve using the LambertW solution to the\n    # single diode equation\n    if method.lower() == 'lambertw':\n        out = _singlediode._lambertw(*args, ivcurve_pnts)\n        points = out[:7]\n        if ivcurve_pnts:\n            ivcurve_i, ivcurve_v = out[7:]\n    else:\n        # Calculate points on the IV curve using either 'newton' or 'brentq'\n        # methods. Voltages are determined by first solving the single diode\n        # equation for the diode voltage V_d then backing out voltage\n        v_oc = _singlediode.bishop88_v_from_i(\n            0.0, *args, method=method.lower()\n        )\n        i_mp, v_mp, p_mp = _singlediode.bishop88_mpp(\n            *args, method=method.lower()\n        )\n        i_sc = _singlediode.bishop88_i_from_v(\n            0.0, *args, method=method.lower()\n        )\n        i_x = _singlediode.bishop88_i_from_v(\n            v_oc / 2.0, *args, method=method.lower()\n        )\n        i_xx = _singlediode.bishop88_i_from_v(\n            (v_oc + v_mp) / 2.0, *args, method=method.lower()\n        )\n        points = i_sc, v_oc, i_mp, v_mp, p_mp, i_x, i_xx\n\n        # calculate the IV curve if requested using bishop88\n        if ivcurve_pnts:\n            vd = v_oc * (\n                (11.0 - np.logspace(np.log10(11.0), 0.0, ivcurve_pnts)) / 10.0\n            )\n            ivcurve_i, ivcurve_v, _ = _singlediode.bishop88(vd, *args)\n\n    columns = ('i_sc', 'v_oc', 'i_mp', 'v_mp', 'p_mp', 'i_x', 'i_xx')\n\n    if all(map(np.isscalar, args)) or ivcurve_pnts:\n        out = {c: p for c, p in zip(columns, points)}\n\n        if ivcurve_pnts:\n            out.update(i=ivcurve_i, v=ivcurve_v)\n\n        return out\n\n    points = np.atleast_1d(*points)  # convert scalars to 1d-arrays\n    points = np.vstack(points).T  # collect rows into DataFrame columns\n\n    # save the first available pd.Series index, otherwise set to None\n    index = next((a.index for a in args if isinstance(a, pd.Series)), None)\n\n    out = pd.DataFrame(points, columns=columns, index=index)\n\n    return out\n\n\ndef max_power_point(photocurrent, saturation_current, resistance_series,\n                    resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.Inf,\n                    method='brentq'):\n    \"\"\"\n    Given the single diode equation coefficients, calculates the maximum power\n    point (MPP).\n\n    Parameters\n    ----------\n    photocurrent : numeric\n        photo-generated current [A]\n    saturation_current : numeric\n        diode reverse saturation current [A]\n    resistance_series : numeric\n        series resitance [ohms]\n    resistance_shunt : numeric\n        shunt resitance [ohms]\n    nNsVth : numeric\n        product of thermal voltage ``Vth`` [V], diode ideality factor ``n``,\n        and number of serices cells ``Ns``\n    d2mutau : numeric, default 0\n        PVsyst parameter for cadmium-telluride (CdTe) and amorphous-silicon\n        (a-Si) modules that accounts for recombination current in the\n        intrinsic layer. The value is the ratio of intrinsic layer thickness\n        squared :math:`d^2` to the diffusion length of charge carriers\n        :math:`\\\\mu \\\\tau`. [V]\n    NsVbi : numeric, default np.inf\n        PVsyst parameter for cadmium-telluride (CdTe) and amorphous-silicon\n        (a-Si) modules that is the product of the PV module number of series\n        cells ``Ns`` and the builtin voltage ``Vbi`` of the intrinsic layer.\n        [V].\n    method : str\n        either ``'newton'`` or ``'brentq'``\n\n    Returns\n    -------\n    OrderedDict or pandas.DataFrame\n        ``(i_mp, v_mp, p_mp)``\n\n    Notes\n    -----\n    Use this function when you only want to find the maximum power point. Use\n    :func:`singlediode` when you need to find additional points on the IV\n    curve. This function uses Brent's method by default because it is\n    guaranteed to converge.\n    \"\"\"\n    i_mp, v_mp, p_mp = _singlediode.bishop88_mpp(\n        photocurrent, saturation_current, resistance_series,\n        resistance_shunt, nNsVth, d2mutau, NsVbi, method=method.lower()\n    )\n    if isinstance(photocurrent, pd.Series):\n        ivp = {'i_mp': i_mp, 'v_mp': v_mp, 'p_mp': p_mp}\n        out = pd.DataFrame(ivp, index=photocurrent.index)\n    else:\n        out = OrderedDict()\n        out['i_mp'] = i_mp\n        out['v_mp'] = v_mp\n        out['p_mp'] = p_mp\n    return out\n\n\ndef v_from_i(current, photocurrent, saturation_current, resistance_series,\n             resistance_shunt, nNsVth, method='lambertw'):\n    '''\n    Device voltage at the given device current for the single diode model.\n\n    Uses the single diode model (SDM) as described in, e.g.,\n    Jain and Kapoor 2004 [1]_.\n    The solution is per Eq 3 of [1]_ except when resistance_shunt=numpy.inf,\n    in which case the explict solution for voltage is used.\n    Ideal device parameters are specified by resistance_shunt=np.inf and\n    resistance_series=0.\n    Inputs to this function can include scalars and pandas.Series, but it is\n    the caller's responsibility to ensure that the arguments are all float64\n    and within the proper ranges.\n\n    .. versionchanged:: 0.10.0\n       The function's arguments have been reordered.\n\n    Parameters\n    ----------\n    current : numeric\n        The current in amperes under desired IV curve conditions.\n\n    photocurrent : numeric\n        Light-generated current (photocurrent) in amperes under desired\n        IV curve conditions. Often abbreviated ``I_L``.\n        0 <= photocurrent\n\n    saturation_current : numeric\n        Diode saturation current in amperes under desired IV curve\n        conditions. Often abbreviated ``I_0``.\n        0 < saturation_current\n\n    resistance_series : numeric\n        Series resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rs``.\n        0 <= resistance_series < numpy.inf\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rsh``.\n        0 < resistance_shunt <= numpy.inf\n\n    nNsVth : numeric\n        The product of three components. 1) The usual diode ideal factor\n        (n), 2) the number of cells in series (Ns), and 3) the cell\n        thermal voltage under the desired IV curve conditions (Vth). The\n        thermal voltage of the cell (in volts) may be calculated as\n        ``k*temp_cell/q``, where k is Boltzmann's constant (J/K),\n        temp_cell is the temperature of the p-n junction in Kelvin, and\n        q is the charge of an electron (coulombs).\n        0 < nNsVth\n\n    method : str\n        Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*:\n        ``'brentq'`` is limited to 1st quadrant only.\n\n    Returns\n    -------\n    current : np.ndarray or scalar\n\n    References\n    ----------\n    .. [1] A. Jain, A. Kapoor, \"Exact analytical solutions of the\n       parameters of real solar cells using Lambert W-function\", Solar\n       Energy Materials and Solar Cells, 81 (2004) 269-277.\n    '''\n    args = (current, photocurrent, saturation_current,\n            resistance_series, resistance_shunt, nNsVth)\n    if method.lower() == 'lambertw':\n        return _singlediode._lambertw_v_from_i(*args)\n    else:\n        # Calculate points on the IV curve using either 'newton' or 'brentq'\n        # methods. Voltages are determined by first solving the single diode\n        # equation for the diode voltage V_d then backing out voltage\n        V = _singlediode.bishop88_v_from_i(*args, method=method.lower())\n        if all(map(np.isscalar, args)):\n            return V\n        shape = _singlediode._shape_of_max_size(*args)\n        return np.broadcast_to(V, shape)\n\n\ndef i_from_v(voltage, photocurrent, saturation_current, resistance_series,\n             resistance_shunt, nNsVth, method='lambertw'):\n    '''\n    Device current at the given device voltage for the single diode model.\n\n    Uses the single diode model (SDM) as described in, e.g.,\n    Jain and Kapoor 2004 [1]_.\n    The solution is per Eq 2 of [1] except when resistance_series=0,\n    in which case the explict solution for current is used.\n    Ideal device parameters are specified by resistance_shunt=np.inf and\n    resistance_series=0.\n    Inputs to this function can include scalars and pandas.Series, but it is\n    the caller's responsibility to ensure that the arguments are all float64\n    and within the proper ranges.\n\n    .. versionchanged:: 0.10.0\n       The function's arguments have been reordered.\n\n    Parameters\n    ----------\n    voltage : numeric\n        The voltage in Volts under desired IV curve conditions.\n\n    photocurrent : numeric\n        Light-generated current (photocurrent) in amperes under desired\n        IV curve conditions. Often abbreviated ``I_L``.\n        0 <= photocurrent\n\n    saturation_current : numeric\n        Diode saturation current in amperes under desired IV curve\n        conditions. Often abbreviated ``I_0``.\n        0 < saturation_current\n\n    resistance_series : numeric\n        Series resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rs``.\n        0 <= resistance_series < numpy.inf\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rsh``.\n        0 < resistance_shunt <= numpy.inf\n\n    nNsVth : numeric\n        The product of three components. 1) The usual diode ideal factor\n        (n), 2) the number of cells in series (Ns), and 3) the cell\n        thermal voltage under the desired IV curve conditions (Vth). The\n        thermal voltage of the cell (in volts) may be calculated as\n        ``k*temp_cell/q``, where k is Boltzmann's constant (J/K),\n        temp_cell is the temperature of the p-n junction in Kelvin, and\n        q is the charge of an electron (coulombs).\n        0 < nNsVth\n\n    method : str\n        Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*:\n        ``'brentq'`` is limited to 1st quadrant only.\n\n    Returns\n    -------\n    current : np.ndarray or scalar\n\n    References\n    ----------\n    .. [1] A. Jain, A. Kapoor, \"Exact analytical solutions of the\n       parameters of real solar cells using Lambert W-function\", Solar\n       Energy Materials and Solar Cells, 81 (2004) 269-277.\n    '''\n    args = (voltage, photocurrent, saturation_current,\n            resistance_series, resistance_shunt, nNsVth)\n    if method.lower() == 'lambertw':\n        return _singlediode._lambertw_i_from_v(*args)\n    else:\n        # Calculate points on the IV curve using either 'newton' or 'brentq'\n        # methods. Voltages are determined by first solving the single diode\n        # equation for the diode voltage V_d then backing out voltage\n        current = _singlediode.bishop88_i_from_v(*args, method=method.lower())\n        if all(map(np.isscalar, args)):\n            return current\n        shape = _singlediode._shape_of_max_size(*args)\n        return np.broadcast_to(current, shape)\n\n\ndef scale_voltage_current_power(data, voltage=1, current=1):\n    \"\"\"\n    Scales the voltage, current, and power in data by the voltage\n    and current factors.\n\n    Parameters\n    ----------\n    data: DataFrame\n        May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',\n        'i_sc', 'p_mp'`.\n    voltage: numeric, default 1\n        The amount by which to multiply the voltages.\n    current: numeric, default 1\n        The amount by which to multiply the currents.\n\n    Returns\n    -------\n    scaled_data: DataFrame\n        A scaled copy of the input data.\n        `'p_mp'` is scaled by `voltage * current`.\n    \"\"\"\n\n    # as written, only works with a DataFrame\n    # could make it work with a dict, but it would be more verbose\n    voltage_keys = ['v_mp', 'v_oc']\n    current_keys = ['i_mp', 'i_x', 'i_xx', 'i_sc']\n    power_keys = ['p_mp']\n    voltage_df = data.filter(voltage_keys, axis=1) * voltage\n    current_df = data.filter(current_keys, axis=1) * current\n    power_df = data.filter(power_keys, axis=1) * voltage * current\n    df = pd.concat([voltage_df, current_df, power_df], axis=1)\n    df_sorted = df[data.columns]  # retain original column order\n    return df_sorted\n\n\ndef pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.):\n    r\"\"\"\n    Implements NREL's PVWatts DC power model. The PVWatts DC model [1]_ is:\n\n    .. math::\n\n        P_{dc} = \\frac{G_{poa eff}}{1000} P_{dc0} ( 1 + \\gamma_{pdc} (T_{cell} - T_{ref}))\n\n    Note that ``pdc0`` is also used as a symbol in\n    :py:func:`pvlib.inverter.pvwatts`. ``pdc0`` in this function refers to the DC\n    power of the modules at reference conditions. ``pdc0`` in\n    :py:func:`pvlib.inverter.pvwatts` refers to the DC power input limit of\n    the inverter.\n\n    Parameters\n    ----------\n    g_poa_effective: numeric\n        Irradiance transmitted to the PV cells. To be\n        fully consistent with PVWatts, the user must have already\n        applied angle of incidence losses, but not soiling, spectral,\n        etc. [W/m^2]\n    temp_cell: numeric\n        Cell temperature [C].\n    pdc0: numeric\n        Power of the modules at 1000 W/m^2 and cell reference temperature. [W]\n    gamma_pdc: numeric\n        The temperature coefficient of power. Typically -0.002 to\n        -0.005 per degree C. [1/C]\n    temp_ref: numeric, default 25.0\n        Cell reference temperature. PVWatts defines it to be 25 C and\n        is included here for flexibility. [C]\n\n    Returns\n    -------\n    pdc: numeric\n        DC power. [W]\n\n    References\n    ----------\n    .. [1] A. P. Dobos, \"PVWatts Version 5 Manual\"\n           http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf\n           (2014).\n    \"\"\"  # noqa: E501\n\n    pdc = (g_poa_effective * 0.001 * pdc0 *\n           (1 + gamma_pdc * (temp_cell - temp_ref)))\n\n    return pdc\n\n\ndef pvwatts_losses(soiling=2, shading=3, snow=0, mismatch=2, wiring=2,\n                   connections=0.5, lid=1.5, nameplate_rating=1, age=0,\n                   availability=3):\n    r\"\"\"\n    Implements NREL's PVWatts system loss model.\n    The PVWatts loss model [1]_ is:\n\n    .. math::\n\n        L_{total}(\\%) = 100 [ 1 - \\Pi_i ( 1 - \\frac{L_i}{100} ) ]\n\n    All parameters must be in units of %. Parameters may be\n    array-like, though all array sizes must match.\n\n    Parameters\n    ----------\n    soiling: numeric, default 2\n    shading: numeric, default 3\n    snow: numeric, default 0\n    mismatch: numeric, default 2\n    wiring: numeric, default 2\n    connections: numeric, default 0.5\n    lid: numeric, default 1.5\n        Light induced degradation\n    nameplate_rating: numeric, default 1\n    age: numeric, default 0\n    availability: numeric, default 3\n\n    Returns\n    -------\n    losses: numeric\n        System losses in units of %.\n\n    References\n    ----------\n    .. [1] A. P. Dobos, \"PVWatts Version 5 Manual\"\n           http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf\n           (2014).\n    \"\"\"\n\n    params = [soiling, shading, snow, mismatch, wiring, connections, lid,\n              nameplate_rating, age, availability]\n\n    # manually looping over params allows for numpy/pandas to handle any\n    # array-like broadcasting that might be necessary.\n    perf = 1\n    for param in params:\n        perf *= 1 - param/100\n\n    losses = (1 - perf) * 100.\n\n    return losses\n\n\ndef dc_ohms_from_percent(vmp_ref, imp_ref, dc_ohmic_percent,\n                         modules_per_string=1,\n                         strings=1):\n    \"\"\"\n    Calculates the equivalent resistance of the wires from a percent\n    ohmic loss at STC.\n\n    Equivalent resistance is calculated with the function:\n\n    .. math::\n        Rw = (L_{stc} / 100) * (Varray / Iarray)\n\n    :math:`Rw` is the equivalent resistance in ohms\n    :math:`Varray` is the Vmp of the modules times modules per string\n    :math:`Iarray` is the Imp of the modules times strings per array\n    :math:`L_{stc}` is the input dc loss percent\n\n    Parameters\n    ----------\n    vmp_ref: numeric\n        Voltage at maximum power in reference conditions [V]\n    imp_ref: numeric\n        Current at maximum power in reference conditions [V]\n    dc_ohmic_percent: numeric, default 0\n        input dc loss as a percent, e.g. 1.5% loss is input as 1.5\n    modules_per_string: int, default 1\n        Number of modules per string in the array.\n    strings: int, default 1\n        Number of parallel strings in the array.\n\n    Returns\n    ----------\n    Rw: numeric\n        Equivalent resistance [ohm]\n\n    See Also\n    --------\n    pvlib.pvsystem.dc_ohmic_losses\n\n    References\n    ----------\n    .. [1] PVsyst 7 Help. \"Array ohmic wiring loss\".\n       https://www.pvsyst.com/help/ohmic_loss.htm\n    \"\"\"\n    vmp = modules_per_string * vmp_ref\n\n    imp = strings * imp_ref\n\n    Rw = (dc_ohmic_percent / 100) * (vmp / imp)\n\n    return Rw\n\n\ndef dc_ohmic_losses(resistance, current):\n    \"\"\"\n    Returns ohmic losses in units of power from the equivalent\n    resistance of the wires and the operating current.\n\n    Parameters\n    ----------\n    resistance: numeric\n        Equivalent resistance of wires [ohm]\n    current: numeric, float or array-like\n        Operating current [A]\n\n    Returns\n    ----------\n    loss: numeric\n        Power Loss [W]\n\n    See Also\n    --------\n    pvlib.pvsystem.dc_ohms_from_percent\n\n    References\n    ----------\n    .. [1] PVsyst 7 Help. \"Array ohmic wiring loss\".\n       https://www.pvsyst.com/help/ohmic_loss.htm\n    \"\"\"\n    return resistance * current * current\n\n\ndef combine_loss_factors(index, *losses, fill_method='ffill'):\n    r\"\"\"\n    Combines Series loss fractions while setting a common index.\n\n    The separate losses are compounded using the following equation:\n\n    .. math::\n\n        L_{total} = 1 - [ 1 - \\Pi_i ( 1 - L_i ) ]\n\n    :math:`L_{total}` is the total loss returned\n    :math:`L_i` is each individual loss factor input\n\n    Note the losses must each be a series with a DatetimeIndex.\n    All losses will be resampled to match the index parameter using\n    the fill method specified (defaults to \"fill forward\").\n\n    Parameters\n    ----------\n    index : DatetimeIndex\n        The index of the returned loss factors\n\n    *losses : Series\n        One or more Series of fractions to be compounded\n\n    fill_method : {'ffill', 'bfill', 'nearest'}, default 'ffill'\n        Method to use for filling holes in reindexed DataFrame\n\n    Returns\n    -------\n    Series\n        Fractions resulting from the combination of each loss factor\n    \"\"\"\n    combined_factor = 1\n\n    for loss in losses:\n        loss = loss.reindex(index, method=fill_method)\n        combined_factor *= (1 - loss)\n\n    return 1 - combined_factor\n","header":{"file":{"path":"pvlib/pvsystem.py"},"misc_line":null},"index_line":null,"is_binary_file":false,"binary_line":null,"minus_file":{"path":"a/pvlib/pvsystem.py"},"plus_file":{"path":"b/pvlib/pvsystem.py"},"hunks":[{"descriptor":{"old_range":{"start":101,"length":10},"new_range":{"start":101,"length":11},"section":"class PVSystem:"},"line_group":{"all_lines":[{"content":"","type":"context"},{"content":"    Parameters","type":"context"},{"content":"    ----------","type":"context"},{"content":"    arrays : iterable of Array, optional","type":"deleted"},{"content":"        List of arrays that are part of the system. If not specified","type":"deleted"},{"content":"        a single array is created from the other parameters (e.g.","type":"deleted"},{"content":"        `surface_tilt`, `surface_azimuth`). Must contain at least one Array,","type":"deleted"},{"content":"    arrays : Array or iterable of Array, optional","type":"added"},{"content":"        An Array or list of arrays that are part of the system. If not","type":"added"},{"content":"        specified a single array is created from the other parameters (e.g.","type":"added"},{"content":"        `surface_tilt`, `surface_azimuth`). If specified as a list, the list","type":"added"},{"content":"        must contain at least one Array;","type":"added"},{"content":"        if length of arrays is 0 a ValueError is raised. If `arrays` is","type":"context"},{"content":"        specified the following PVSystem parameters are ignored:","type":"context"},{"content":"","type":"context"}]},"modified_entities":[],"added_entities":[],"deleted_entities":[]},{"descriptor":{"old_range":{"start":220,"length":6},"new_range":{"start":221,"length":8},"section":"def __init__(self,"},"line_group":{"all_lines":[{"content":"                strings_per_inverter,","type":"context"},{"content":"                array_losses_parameters,","type":"context"},{"content":"            ),)","type":"context"},{"content":"        elif isinstance(arrays, Array):","type":"added"},{"content":"            self.arrays = (arrays,)","type":"added"},{"content":"        elif len(arrays) == 0:","type":"context"},{"content":"            raise ValueError(\"PVSystem must have at least one Array. \"","type":"context"},{"content":"                             \"If you want to create a PVSystem instance \"","type":"context"},{"content":"","type":"context"}]},"modified_entities":[],"added_entities":[],"deleted_entities":[]}]}],"old_commit_hash":"27a3a07ebc84b11014d3753e4923902adf9a38c0","new_commit_hash":"af753bc11fb522b5e33af9a6cfe9489c39f26070","commit_message":"whatsnew","commit_date":"2023-09-13T13:23:57-04:00","metadata":{}}
problem_statement: PVSystem with single Array generates an error
  **Is your feature request related to a problem? Please describe.**
  
  When a PVSystem has a single Array, you can't assign just the Array instance when constructing the PVSystem.
  
  ```
  mount = pvlib.pvsystem.FixedMount(surface_tilt=35, surface_azimuth=180)
  array = pvlib.pvsystem.Array(mount=mount)
  pv = pvlib.pvsystem.PVSystem(arrays=array)
  
  ---------------------------------------------------------------------------
  TypeError                                 Traceback (most recent call last)
  <ipython-input-13-f5424e3db16a> in <module>
        3 mount = pvlib.pvsystem.FixedMount(surface_tilt=35, surface_azimuth=180)
        4 array = pvlib.pvsystem.Array(mount=mount)
  ----> 5 pv = pvlib.pvsystem.PVSystem(arrays=array)
  
  ~\anaconda3\lib\site-packages\pvlib\pvsystem.py in __init__(self, arrays, surface_tilt, surface_azimuth, albedo, surface_type, module, module_type, module_parameters, temperature_model_parameters, modules_per_string, strings_per_inverter, inverter, inverter_parameters, racking_model, losses_parameters, name)
      251                 array_losses_parameters,
      252             ),)
  --> 253         elif len(arrays) == 0:
      254             raise ValueError("PVSystem must have at least one Array. "
      255                              "If you want to create a PVSystem instance "
  
  TypeError: object of type 'Array' has no len()
  
  ```
  
  Not a bug per se, since the PVSystem docstring requests that `arrays` be iterable. Still, a bit inconvenient to have to do this
  
  ```
  mount = pvlib.pvsystem.FixedMount(surface_tilt=35, surface_azimuth=180)
  array = pvlib.pvsystem.Array(mount=mount)
  pv = pvlib.pvsystem.PVSystem(arrays=[array])
  ```
  
  **Describe the solution you'd like**
  Handle `arrays=array` where `array` is an instance of `Array`
  
  **Describe alternatives you've considered**
  Status quo - either make the single Array into a list, or use the PVSystem kwargs.
  
  
patch: diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py
  --- a/pvlib/pvsystem.py
  +++ b/pvlib/pvsystem.py
  @@ -101,10 +101,11 @@ class PVSystem:
   
       Parameters
       ----------
  -    arrays : iterable of Array, optional
  -        List of arrays that are part of the system. If not specified
  -        a single array is created from the other parameters (e.g.
  -        `surface_tilt`, `surface_azimuth`). Must contain at least one Array,
  +    arrays : Array or iterable of Array, optional
  +        An Array or list of arrays that are part of the system. If not
  +        specified a single array is created from the other parameters (e.g.
  +        `surface_tilt`, `surface_azimuth`). If specified as a list, the list
  +        must contain at least one Array;
           if length of arrays is 0 a ValueError is raised. If `arrays` is
           specified the following PVSystem parameters are ignored:
   
  @@ -220,6 +221,8 @@ def __init__(self,
                   strings_per_inverter,
                   array_losses_parameters,
               ),)
  +        elif isinstance(arrays, Array):
  +            self.arrays = (arrays,)
           elif len(arrays) == 0:
               raise ValueError("PVSystem must have at least one Array. "
                                "If you want to create a PVSystem instance "
   
  
file_context: {"pvlib/pvsystem.py": "\"\"\"\nThe ``pvsystem`` module contains functions for modeling the output and\nperformance of PV modules and inverters.\n\"\"\"\n\nfrom collections import OrderedDict\nimport functools\nimport io\nimport itertools\nimport os\nimport inspect\nfrom urllib.request import urlopen\nimport numpy as np\nfrom scipy import constants\nimport pandas as pd\nfrom dataclasses import dataclass\nfrom abc import ABC, abstractmethod\nfrom typing import Optional\n\nfrom pvlib._deprecation import deprecated, warn_deprecated\n\nfrom pvlib import (atmosphere, iam, inverter, irradiance,\n                   singlediode as _singlediode, spectrum, temperature)\nfrom pvlib.tools import _build_kwargs, _build_args\nimport pvlib.tools as tools\n\n\n# a dict of required parameter names for each DC power model\n_DC_MODEL_PARAMS = {\n    'sapm': {\n        'A0', 'A1', 'A2', 'A3', 'A4', 'B0', 'B1', 'B2', 'B3',\n        'B4', 'B5', 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6',\n        'C7', 'Isco', 'Impo', 'Voco', 'Vmpo', 'Aisc', 'Aimp', 'Bvoco',\n        'Mbvoc', 'Bvmpo', 'Mbvmp', 'N', 'Cells_in_Series',\n        'IXO', 'IXXO', 'FD'},\n    'desoto': {\n        'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_s'},\n    'cec': {\n        'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_s', 'Adjust'},\n    'pvsyst': {\n        'gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_sh_0', 'R_s', 'alpha_sc', 'EgRef',\n        'cells_in_series'},\n    'singlediode': {\n        'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_s'},\n    'pvwatts': {'pdc0', 'gamma_pdc'}\n}\n\n\ndef _unwrap_single_value(func):\n    \"\"\"Decorator for functions that return iterables.\n\n    If the length of the iterable returned by `func` is 1, then\n    the single member of the iterable is returned. If the length is\n    greater than 1, then entire iterable is returned.\n\n    Adds 'unwrap' as a keyword argument that can be set to False\n    to force the return value to be a tuple, regardless of its length.\n    \"\"\"\n    @functools.wraps(func)\n    def f(*args, **kwargs):\n        unwrap = kwargs.pop('unwrap', True)\n        x = func(*args, **kwargs)\n        if unwrap and len(x) == 1:\n            return x[0]\n        return x\n    return f\n\n\n# not sure if this belongs in the pvsystem module.\n# maybe something more like core.py? It may eventually grow to\n# import a lot more functionality from other modules.\nclass PVSystem:\n    \"\"\"\n    The PVSystem class defines a standard set of PV system attributes\n    and modeling functions. This class describes the collection and\n    interactions of PV system components rather than an installed system\n    on the ground. It is typically used in combination with\n    :py:class:`~pvlib.location.Location` and\n    :py:class:`~pvlib.modelchain.ModelChain`\n    objects.\n\n    The class supports basic system topologies consisting of:\n\n        * `N` total modules arranged in series\n          (`modules_per_string=N`, `strings_per_inverter=1`).\n        * `M` total modules arranged in parallel\n          (`modules_per_string=1`, `strings_per_inverter=M`).\n        * `NxM` total modules arranged in `M` strings of `N` modules each\n          (`modules_per_string=N`, `strings_per_inverter=M`).\n\n    The class is complementary to the module-level functions.\n\n    The attributes should generally be things that don't change about\n    the system, such the type of module and the inverter. The instance\n    methods accept arguments for things that do change, such as\n    irradiance and temperature.\n\n    Parameters\n    ----------\n    arrays : iterable of Array, optional\n        List of arrays that are part of the system. If not specified\n        a single array is created from the other parameters (e.g.\n        `surface_tilt`, `surface_azimuth`). Must contain at least one Array,\n        if length of arrays is 0 a ValueError is raised. If `arrays` is\n        specified the following PVSystem parameters are ignored:\n\n        - `surface_tilt`\n        - `surface_azimuth`\n        - `albedo`\n        - `surface_type`\n        - `module`\n        - `module_type`\n        - `module_parameters`\n        - `temperature_model_parameters`\n        - `modules_per_string`\n        - `strings_per_inverter`\n\n    surface_tilt: float or array-like, default 0\n        Surface tilt angles in decimal degrees.\n        The tilt angle is defined as degrees from horizontal\n        (e.g. surface facing up = 0, surface facing horizon = 90)\n\n    surface_azimuth: float or array-like, default 180\n        Azimuth angle of the module surface.\n        North=0, East=90, South=180, West=270.\n\n    albedo : None or float, default None\n        Ground surface albedo. If ``None``, then ``surface_type`` is used\n        to look up a value in ``irradiance.SURFACE_ALBEDOS``.\n        If ``surface_type`` is also None then a ground surface albedo\n        of 0.25 is used.\n\n    surface_type : None or string, default None\n        The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for\n        valid values.\n\n    module : None or string, default None\n        The model name of the modules.\n        May be used to look up the module_parameters dictionary\n        via some other method.\n\n    module_type : None or string, default 'glass_polymer'\n         Describes the module's construction. Valid strings are 'glass_polymer'\n         and 'glass_glass'. Used for cell and module temperature calculations.\n\n    module_parameters : None, dict or Series, default None\n        Module parameters as defined by the SAPM, CEC, or other.\n\n    temperature_model_parameters : None, dict or Series, default None.\n        Temperature model parameters as required by one of the models in\n        pvlib.temperature (excluding poa_global, temp_air and wind_speed).\n\n    modules_per_string: int or float, default 1\n        See system topology discussion above.\n\n    strings_per_inverter: int or float, default 1\n        See system topology discussion above.\n\n    inverter : None or string, default None\n        The model name of the inverters.\n        May be used to look up the inverter_parameters dictionary\n        via some other method.\n\n    inverter_parameters : None, dict or Series, default None\n        Inverter parameters as defined by the SAPM, CEC, or other.\n\n    racking_model : None or string, default 'open_rack'\n        Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.\n        Used to identify a parameter set for the SAPM cell temperature model.\n\n    losses_parameters : None, dict or Series, default None\n        Losses parameters as defined by PVWatts or other.\n\n    name : None or string, default None\n\n    **kwargs\n        Arbitrary keyword arguments.\n        Included for compatibility, but not used.\n\n    Raises\n    ------\n    ValueError\n        If `arrays` is not None and has length 0.\n\n    See also\n    --------\n    pvlib.location.Location\n    \"\"\"\n\n    def __init__(self,\n                 arrays=None,\n                 surface_tilt=0, surface_azimuth=180,\n                 albedo=None, surface_type=None,\n                 module=None, module_type=None,\n                 module_parameters=None,\n                 temperature_model_parameters=None,\n                 modules_per_string=1, strings_per_inverter=1,\n                 inverter=None, inverter_parameters=None,\n                 racking_model=None, losses_parameters=None, name=None):\n\n        if arrays is None:\n            if losses_parameters is None:\n                array_losses_parameters = {}\n            else:\n                array_losses_parameters = _build_kwargs(['dc_ohmic_percent'],\n                                                        losses_parameters)\n            self.arrays = (Array(\n                FixedMount(surface_tilt, surface_azimuth, racking_model),\n                albedo,\n                surface_type,\n                module,\n                module_type,\n                module_parameters,\n                temperature_model_parameters,\n                modules_per_string,\n                strings_per_inverter,\n                array_losses_parameters,\n            ),)\n        elif len(arrays) == 0:\n            raise ValueError(\"PVSystem must have at least one Array. \"\n                             \"If you want to create a PVSystem instance \"\n                             \"with a single Array pass `arrays=None` and pass \"\n                             \"values directly to PVSystem attributes, e.g., \"\n                             \"`surface_tilt=30`\")\n        else:\n            self.arrays = tuple(arrays)\n\n        self.inverter = inverter\n        if inverter_parameters is None:\n            self.inverter_parameters = {}\n        else:\n            self.inverter_parameters = inverter_parameters\n\n        if losses_parameters is None:\n            self.losses_parameters = {}\n        else:\n            self.losses_parameters = losses_parameters\n\n        self.name = name\n\n    def __repr__(self):\n        repr = f'PVSystem:\\n  name: {self.name}\\n  '\n        for array in self.arrays:\n            repr += '\\n  '.join(array.__repr__().split('\\n'))\n            repr += '\\n  '\n        repr += f'inverter: {self.inverter}'\n        return repr\n\n    def _validate_per_array(self, values, system_wide=False):\n        \"\"\"Check that `values` is a tuple of the same length as\n        `self.arrays`.\n\n        If `values` is not a tuple it is packed in to a length-1 tuple before\n        the check. If the lengths are not the same a ValueError is raised,\n        otherwise the tuple `values` is returned.\n\n        When `system_wide` is True and `values` is not a tuple, `values`\n        is replicated to a tuple of the same length as `self.arrays` and that\n        tuple is returned.\n        \"\"\"\n        if system_wide and not isinstance(values, tuple):\n            return (values,) * self.num_arrays\n        if not isinstance(values, tuple):\n            values = (values,)\n        if len(values) != len(self.arrays):\n            raise ValueError(\"Length mismatch for per-array parameter\")\n        return values\n\n    @_unwrap_single_value\n    def _infer_cell_type(self):\n        \"\"\"\n        Examines module_parameters and maps the Technology key for the CEC\n        database and the Material key for the Sandia database to a common\n        list of strings for cell type.\n\n        Returns\n        -------\n        cell_type: str\n        \"\"\"\n        return tuple(array._infer_cell_type() for array in self.arrays)\n\n    @_unwrap_single_value\n    def get_aoi(self, solar_zenith, solar_azimuth):\n        \"\"\"Get the angle of incidence on the Array(s) in the system.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series.\n            Solar zenith angle.\n        solar_azimuth : float or Series.\n            Solar azimuth angle.\n\n        Returns\n        -------\n        aoi : Series or tuple of Series\n            The angle of incidence\n        \"\"\"\n\n        return tuple(array.get_aoi(solar_zenith, solar_azimuth)\n                     for array in self.arrays)\n\n    @_unwrap_single_value\n    def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,\n                       dni_extra=None, airmass=None, albedo=None,\n                       model='haydavies', **kwargs):\n        \"\"\"\n        Uses the :py:func:`irradiance.get_total_irradiance` function to\n        calculate the plane of array irradiance components on the tilted\n        surfaces defined by each array's ``surface_tilt`` and\n        ``surface_azimuth``.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series\n            Solar zenith angle.\n        solar_azimuth : float or Series\n            Solar azimuth angle.\n        dni : float or Series or tuple of float or Series\n            Direct Normal Irradiance. [W/m2]\n        ghi : float or Series or tuple of float or Series\n            Global horizontal irradiance. [W/m2]\n        dhi : float or Series or tuple of float or Series\n            Diffuse horizontal irradiance. [W/m2]\n        dni_extra : None, float, Series or tuple of float or Series,\\\n            default None\n            Extraterrestrial direct normal irradiance. [W/m2]\n        airmass : None, float or Series, default None\n            Airmass. [unitless]\n        albedo : None, float or Series, default None\n            Ground surface albedo. [unitless]\n        model : String, default 'haydavies'\n            Irradiance model.\n\n        kwargs\n            Extra parameters passed to :func:`irradiance.get_total_irradiance`.\n\n        Notes\n        -----\n        Each of `dni`, `ghi`, and `dni` parameters may be passed as a tuple\n        to provide different irradiance for each array in the system. If not\n        passed as a tuple then the same value is used for input to each Array.\n        If passed as a tuple the length must be the same as the number of\n        Arrays.\n\n        Returns\n        -------\n        poa_irradiance : DataFrame or tuple of DataFrame\n            Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse',\n            'poa_sky_diffuse', 'poa_ground_diffuse'``.\n\n        See also\n        --------\n        pvlib.irradiance.get_total_irradiance\n        \"\"\"\n        dni = self._validate_per_array(dni, system_wide=True)\n        ghi = self._validate_per_array(ghi, system_wide=True)\n        dhi = self._validate_per_array(dhi, system_wide=True)\n\n        albedo = self._validate_per_array(albedo, system_wide=True)\n\n        return tuple(\n            array.get_irradiance(solar_zenith, solar_azimuth,\n                                 dni, ghi, dhi,\n                                 dni_extra=dni_extra, airmass=airmass,\n                                 albedo=albedo, model=model, **kwargs)\n            for array, dni, ghi, dhi, albedo in zip(\n                self.arrays, dni, ghi, dhi, albedo\n            )\n        )\n\n    @_unwrap_single_value\n    def get_iam(self, aoi, iam_model='physical'):\n        \"\"\"\n        Determine the incidence angle modifier using the method specified by\n        ``iam_model``.\n\n        Parameters for the selected IAM model are expected to be in\n        ``PVSystem.module_parameters``. Default parameters are available for\n        the 'physical', 'ashrae' and 'martin_ruiz' models.\n\n        Parameters\n        ----------\n        aoi : numeric or tuple of numeric\n            The angle of incidence in degrees.\n\n        aoi_model : string, default 'physical'\n            The IAM model to be used. Valid strings are 'physical', 'ashrae',\n            'martin_ruiz', 'sapm' and 'interp'.\n        Returns\n        -------\n        iam : numeric or tuple of numeric\n            The AOI modifier.\n\n        Raises\n        ------\n        ValueError\n            if `iam_model` is not a valid model name.\n        \"\"\"\n        aoi = self._validate_per_array(aoi)\n        return tuple(array.get_iam(aoi, iam_model)\n                     for array, aoi in zip(self.arrays, aoi))\n\n    @_unwrap_single_value\n    def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,\n                             effective_irradiance=None):\n        \"\"\"\n        Determine cell temperature using the method specified by ``model``.\n\n        Parameters\n        ----------\n        poa_global : numeric or tuple of numeric\n            Total incident irradiance in W/m^2.\n\n        temp_air : numeric or tuple of numeric\n            Ambient dry bulb temperature in degrees C.\n\n        wind_speed : numeric or tuple of numeric\n            Wind speed in m/s.\n\n        model : str\n            Supported models include ``'sapm'``, ``'pvsyst'``,\n            ``'faiman'``, ``'fuentes'``, and ``'noct_sam'``\n\n        effective_irradiance : numeric or tuple of numeric, optional\n            The irradiance that is converted to photocurrent in W/m^2.\n            Only used for some models.\n\n        Returns\n        -------\n        numeric or tuple of numeric\n            Values in degrees C.\n\n        See Also\n        --------\n        Array.get_cell_temperature\n\n        Notes\n        -----\n        The `temp_air` and `wind_speed` parameters may be passed as tuples\n        to provide different values for each Array in the system. If passed as\n        a tuple the length must be the same as the number of Arrays. If not\n        passed as a tuple then the same value is used for each Array.\n        \"\"\"\n        poa_global = self._validate_per_array(poa_global)\n        temp_air = self._validate_per_array(temp_air, system_wide=True)\n        wind_speed = self._validate_per_array(wind_speed, system_wide=True)\n        # Not used for all models, but Array.get_cell_temperature handles it\n        effective_irradiance = self._validate_per_array(effective_irradiance,\n                                                        system_wide=True)\n\n        return tuple(\n            array.get_cell_temperature(poa_global, temp_air, wind_speed,\n                                       model, effective_irradiance)\n            for array, poa_global, temp_air, wind_speed, effective_irradiance\n            in zip(\n                self.arrays, poa_global, temp_air, wind_speed,\n                effective_irradiance\n            )\n        )\n\n    @_unwrap_single_value\n    def calcparams_desoto(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`calcparams_desoto` function, the input\n        parameters and ``self.module_parameters`` to calculate the\n        module currents and resistances.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.calcparams_desoto for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        build_kwargs = functools.partial(\n            _build_kwargs,\n            ['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',\n             'R_s', 'alpha_sc', 'EgRef', 'dEgdT',\n             'irrad_ref', 'temp_ref']\n        )\n\n        return tuple(\n            calcparams_desoto(\n                effective_irradiance, temp_cell,\n                **build_kwargs(array.module_parameters)\n            )\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def calcparams_cec(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`calcparams_cec` function, the input\n        parameters and ``self.module_parameters`` to calculate the\n        module currents and resistances.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.calcparams_cec for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        build_kwargs = functools.partial(\n            _build_kwargs,\n            ['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',\n             'R_s', 'alpha_sc', 'Adjust', 'EgRef', 'dEgdT',\n             'irrad_ref', 'temp_ref']\n        )\n\n        return tuple(\n            calcparams_cec(\n                effective_irradiance, temp_cell,\n                **build_kwargs(array.module_parameters)\n            )\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def calcparams_pvsyst(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`calcparams_pvsyst` function, the input\n        parameters and ``self.module_parameters`` to calculate the\n        module currents and resistances.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.calcparams_pvsyst for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        build_kwargs = functools.partial(\n            _build_kwargs,\n            ['gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref',\n             'R_sh_ref', 'R_sh_0', 'R_sh_exp',\n             'R_s', 'alpha_sc', 'EgRef',\n             'irrad_ref', 'temp_ref',\n             'cells_in_series']\n        )\n\n        return tuple(\n            calcparams_pvsyst(\n                effective_irradiance, temp_cell,\n                **build_kwargs(array.module_parameters)\n            )\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def sapm(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`sapm` function, the input parameters,\n        and ``self.module_parameters`` to calculate\n        Voc, Isc, Ix, Ixx, Vmp, and Imp.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.sapm for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        return tuple(\n            sapm(effective_irradiance, temp_cell, array.module_parameters)\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def sapm_spectral_loss(self, airmass_absolute):\n        \"\"\"\n        Use the :py:func:`pvlib.spectrum.spectral_factor_sapm` function,\n        the input parameters, and ``self.module_parameters`` to calculate F1.\n\n        Parameters\n        ----------\n        airmass_absolute : numeric\n            Absolute airmass.\n\n        Returns\n        -------\n        F1 : numeric or tuple of numeric\n            The SAPM spectral loss coefficient.\n        \"\"\"\n        return tuple(\n            spectrum.spectral_factor_sapm(airmass_absolute,\n                                          array.module_parameters)\n            for array in self.arrays\n        )\n\n    @_unwrap_single_value\n    def sapm_effective_irradiance(self, poa_direct, poa_diffuse,\n                                  airmass_absolute, aoi,\n                                  reference_irradiance=1000):\n        \"\"\"\n        Use the :py:func:`sapm_effective_irradiance` function, the input\n        parameters, and ``self.module_parameters`` to calculate\n        effective irradiance.\n\n        Parameters\n        ----------\n        poa_direct : numeric or tuple of numeric\n            The direct irradiance incident upon the module.  [W/m2]\n\n        poa_diffuse : numeric or tuple of numeric\n            The diffuse irradiance incident on module.  [W/m2]\n\n        airmass_absolute : numeric\n            Absolute airmass. [unitless]\n\n        aoi : numeric or tuple of numeric\n            Angle of incidence. [degrees]\n\n        Returns\n        -------\n        effective_irradiance : numeric or tuple of numeric\n            The SAPM effective irradiance. [W/m2]\n        \"\"\"\n        poa_direct = self._validate_per_array(poa_direct)\n        poa_diffuse = self._validate_per_array(poa_diffuse)\n        aoi = self._validate_per_array(aoi)\n        return tuple(\n            sapm_effective_irradiance(\n                poa_direct, poa_diffuse, airmass_absolute, aoi,\n                array.module_parameters)\n            for array, poa_direct, poa_diffuse, aoi\n            in zip(self.arrays, poa_direct, poa_diffuse, aoi)\n        )\n\n    @_unwrap_single_value\n    def first_solar_spectral_loss(self, pw, airmass_absolute):\n        \"\"\"\n        Use :py:func:`pvlib.spectrum.spectral_factor_firstsolar` to\n        calculate the spectral loss modifier. The model coefficients are\n        specific to the module's cell type, and are determined by searching\n        for one of the following keys in self.module_parameters (in order):\n\n        - 'first_solar_spectral_coefficients' (user-supplied coefficients)\n        - 'Technology' - a string describing the cell type, can be read from\n          the CEC module parameter database\n        - 'Material' - a string describing the cell type, can be read from\n          the Sandia module database.\n\n        Parameters\n        ----------\n        pw : array-like\n            atmospheric precipitable water (cm).\n\n        airmass_absolute : array-like\n            absolute (pressure corrected) airmass.\n\n        Returns\n        -------\n        modifier: array-like or tuple of array-like\n            spectral mismatch factor (unitless) which can be multiplied\n            with broadband irradiance reaching a module's cells to estimate\n            effective irradiance, i.e., the irradiance that is converted to\n            electrical current.\n        \"\"\"\n        pw = self._validate_per_array(pw, system_wide=True)\n\n        def _spectral_correction(array, pw):\n            if 'first_solar_spectral_coefficients' in \\\n                    array.module_parameters.keys():\n                coefficients = \\\n                    array.module_parameters[\n                        'first_solar_spectral_coefficients'\n                    ]\n                module_type = None\n            else:\n                module_type = array._infer_cell_type()\n                coefficients = None\n\n            return spectrum.spectral_factor_firstsolar(\n                pw, airmass_absolute, module_type, coefficients\n            )\n        return tuple(\n            itertools.starmap(_spectral_correction, zip(self.arrays, pw))\n        )\n\n    def singlediode(self, photocurrent, saturation_current,\n                    resistance_series, resistance_shunt, nNsVth,\n                    ivcurve_pnts=None):\n        \"\"\"Wrapper around the :py:func:`pvlib.pvsystem.singlediode` function.\n\n        See :py:func:`pvsystem.singlediode` for details\n        \"\"\"\n        return singlediode(photocurrent, saturation_current,\n                           resistance_series, resistance_shunt, nNsVth,\n                           ivcurve_pnts=ivcurve_pnts)\n\n    def i_from_v(self, voltage, photocurrent, saturation_current,\n                 resistance_series, resistance_shunt, nNsVth):\n        \"\"\"Wrapper around the :py:func:`pvlib.pvsystem.i_from_v` function.\n\n        See :py:func:`pvlib.pvsystem.i_from_v` for details.\n\n        .. versionchanged:: 0.10.0\n           The function's arguments have been reordered.\n        \"\"\"\n        return i_from_v(voltage, photocurrent, saturation_current,\n                        resistance_series, resistance_shunt, nNsVth)\n\n    def get_ac(self, model, p_dc, v_dc=None):\n        r\"\"\"Calculates AC power from p_dc using the inverter model indicated\n        by model and self.inverter_parameters.\n\n        Parameters\n        ----------\n        model : str\n            Must be one of 'sandia', 'adr', or 'pvwatts'.\n        p_dc : numeric, or tuple, list or array of numeric\n            DC power on each MPPT input of the inverter. Use tuple, list or\n            array for inverters with multiple MPPT inputs. If type is array,\n            p_dc must be 2d with axis 0 being the MPPT inputs. [W]\n        v_dc : numeric, or tuple, list or array of numeric\n            DC voltage on each MPPT input of the inverter. Required when\n            model='sandia' or model='adr'. Use tuple, list or\n            array for inverters with multiple MPPT inputs. If type is array,\n            v_dc must be 2d with axis 0 being the MPPT inputs. [V]\n\n        Returns\n        -------\n        power_ac : numeric\n            AC power output for the inverter. [W]\n\n        Raises\n        ------\n        ValueError\n            If model is not one of 'sandia', 'adr' or 'pvwatts'.\n        ValueError\n            If model='adr' and the PVSystem has more than one array.\n\n        See also\n        --------\n        pvlib.inverter.sandia\n        pvlib.inverter.sandia_multi\n        pvlib.inverter.adr\n        pvlib.inverter.pvwatts\n        pvlib.inverter.pvwatts_multi\n        \"\"\"\n        model = model.lower()\n        multiple_arrays = self.num_arrays > 1\n        if model == 'sandia':\n            p_dc = self._validate_per_array(p_dc)\n            v_dc = self._validate_per_array(v_dc)\n            if multiple_arrays:\n                return inverter.sandia_multi(\n                    v_dc, p_dc, self.inverter_parameters)\n            return inverter.sandia(v_dc[0], p_dc[0], self.inverter_parameters)\n        elif model == 'pvwatts':\n            kwargs = _build_kwargs(['eta_inv_nom', 'eta_inv_ref'],\n                                   self.inverter_parameters)\n            p_dc = self._validate_per_array(p_dc)\n            if multiple_arrays:\n                return inverter.pvwatts_multi(\n                    p_dc, self.inverter_parameters['pdc0'], **kwargs)\n            return inverter.pvwatts(\n                p_dc[0], self.inverter_parameters['pdc0'], **kwargs)\n        elif model == 'adr':\n            if multiple_arrays:\n                raise ValueError(\n                    'The adr inverter function cannot be used for an inverter',\n                    ' with multiple MPPT inputs')\n            # While this is only used for single-array systems, calling\n            # _validate_per_arry lets us pass in singleton tuples.\n            p_dc = self._validate_per_array(p_dc)\n            v_dc = self._validate_per_array(v_dc)\n            return inverter.adr(v_dc[0], p_dc[0], self.inverter_parameters)\n        else:\n            raise ValueError(\n                model + ' is not a valid AC power model.',\n                ' model must be one of \"sandia\", \"adr\" or \"pvwatts\"')\n\n    @_unwrap_single_value\n    def scale_voltage_current_power(self, data):\n        \"\"\"\n        Scales the voltage, current, and power of the `data` DataFrame\n        by `self.modules_per_string` and `self.strings_per_inverter`.\n\n        Parameters\n        ----------\n        data: DataFrame or tuple of DataFrame\n            May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',\n            'i_sc', 'p_mp'`.\n\n        Returns\n        -------\n        scaled_data: DataFrame or tuple of DataFrame\n            A scaled copy of the input data.\n        \"\"\"\n        data = self._validate_per_array(data)\n        return tuple(\n            scale_voltage_current_power(data,\n                                        voltage=array.modules_per_string,\n                                        current=array.strings)\n            for array, data in zip(self.arrays, data)\n        )\n\n    @_unwrap_single_value\n    def pvwatts_dc(self, g_poa_effective, temp_cell):\n        \"\"\"\n        Calcuates DC power according to the PVWatts model using\n        :py:func:`pvlib.pvsystem.pvwatts_dc`, `self.module_parameters['pdc0']`,\n        and `self.module_parameters['gamma_pdc']`.\n\n        See :py:func:`pvlib.pvsystem.pvwatts_dc` for details.\n        \"\"\"\n        g_poa_effective = self._validate_per_array(g_poa_effective)\n        temp_cell = self._validate_per_array(temp_cell)\n        return tuple(\n            pvwatts_dc(g_poa_effective, temp_cell,\n                       array.module_parameters['pdc0'],\n                       array.module_parameters['gamma_pdc'],\n                       **_build_kwargs(['temp_ref'], array.module_parameters))\n            for array, g_poa_effective, temp_cell\n            in zip(self.arrays, g_poa_effective, temp_cell)\n        )\n\n    def pvwatts_losses(self):\n        \"\"\"\n        Calculates DC power losses according the PVwatts model using\n        :py:func:`pvlib.pvsystem.pvwatts_losses` and\n        ``self.losses_parameters``.\n\n        See :py:func:`pvlib.pvsystem.pvwatts_losses` for details.\n        \"\"\"\n        kwargs = _build_kwargs(['soiling', 'shading', 'snow', 'mismatch',\n                                'wiring', 'connections', 'lid',\n                                'nameplate_rating', 'age', 'availability'],\n                               self.losses_parameters)\n        return pvwatts_losses(**kwargs)\n\n    @_unwrap_single_value\n    def dc_ohms_from_percent(self):\n        \"\"\"\n        Calculates the equivalent resistance of the wires for each array using\n        :py:func:`pvlib.pvsystem.dc_ohms_from_percent`\n\n        See :py:func:`pvlib.pvsystem.dc_ohms_from_percent` for details.\n        \"\"\"\n\n        return tuple(array.dc_ohms_from_percent() for array in self.arrays)\n\n    @property\n    def num_arrays(self):\n        \"\"\"The number of Arrays in the system.\"\"\"\n        return len(self.arrays)\n\n\nclass Array:\n    \"\"\"\n    An Array is a set of of modules at the same orientation.\n\n    Specifically, an array is defined by its mount, the\n    module parameters, the number of parallel strings of modules\n    and the number of modules on each string.\n\n    Parameters\n    ----------\n    mount: FixedMount, SingleAxisTrackerMount, or other\n        Mounting for the array, either on fixed-tilt racking or horizontal\n        single axis tracker. Mounting is used to determine module orientation.\n        If not provided, a FixedMount with zero tilt is used.\n\n    albedo : None or float, default None\n        Ground surface albedo. If ``None``, then ``surface_type`` is used\n        to look up a value in ``irradiance.SURFACE_ALBEDOS``.\n        If ``surface_type`` is also None then a ground surface albedo\n        of 0.25 is used.\n\n    surface_type : None or string, default None\n        The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for valid\n        values.\n\n    module : None or string, default None\n        The model name of the modules.\n        May be used to look up the module_parameters dictionary\n        via some other method.\n\n    module_type : None or string, default None\n         Describes the module's construction. Valid strings are 'glass_polymer'\n         and 'glass_glass'. Used for cell and module temperature calculations.\n\n    module_parameters : None, dict or Series, default None\n        Parameters for the module model, e.g., SAPM, CEC, or other.\n\n    temperature_model_parameters : None, dict or Series, default None.\n        Parameters for the module temperature model, e.g., SAPM, Pvsyst, or\n        other.\n\n    modules_per_string: int, default 1\n        Number of modules per string in the array.\n\n    strings: int, default 1\n        Number of parallel strings in the array.\n\n    array_losses_parameters: None, dict or Series, default None.\n        Supported keys are 'dc_ohmic_percent'.\n\n    name: None or str, default None\n        Name of Array instance.\n    \"\"\"\n\n    def __init__(self, mount,\n                 albedo=None, surface_type=None,\n                 module=None, module_type=None,\n                 module_parameters=None,\n                 temperature_model_parameters=None,\n                 modules_per_string=1, strings=1,\n                 array_losses_parameters=None,\n                 name=None):\n        self.mount = mount\n\n        self.surface_type = surface_type\n        if albedo is None:\n            self.albedo = irradiance.SURFACE_ALBEDOS.get(surface_type, 0.25)\n        else:\n            self.albedo = albedo\n\n        self.module = module\n        if module_parameters is None:\n            self.module_parameters = {}\n        else:\n            self.module_parameters = module_parameters\n\n        self.module_type = module_type\n\n        self.strings = strings\n        self.modules_per_string = modules_per_string\n\n        if temperature_model_parameters is None:\n            self.temperature_model_parameters = \\\n                self._infer_temperature_model_params()\n        else:\n            self.temperature_model_parameters = temperature_model_parameters\n\n        if array_losses_parameters is None:\n            self.array_losses_parameters = {}\n        else:\n            self.array_losses_parameters = array_losses_parameters\n\n        self.name = name\n\n    def __repr__(self):\n        attrs = ['name', 'mount', 'module',\n                 'albedo', 'module_type',\n                 'temperature_model_parameters',\n                 'strings', 'modules_per_string']\n\n        return 'Array:\\n  ' + '\\n  '.join(\n            f'{attr}: {getattr(self, attr)}' for attr in attrs\n        )\n\n    def _infer_temperature_model_params(self):\n        # try to infer temperature model parameters from from racking_model\n        # and module_type\n        param_set = f'{self.mount.racking_model}_{self.module_type}'\n        if param_set in temperature.TEMPERATURE_MODEL_PARAMETERS['sapm']:\n            return temperature._temperature_model_params('sapm', param_set)\n        elif 'freestanding' in param_set:\n            return temperature._temperature_model_params('pvsyst',\n                                                         'freestanding')\n        elif 'insulated' in param_set:  # after SAPM to avoid confusing keys\n            return temperature._temperature_model_params('pvsyst',\n                                                         'insulated')\n        else:\n            return {}\n\n    def _infer_cell_type(self):\n        \"\"\"\n        Examines module_parameters and maps the Technology key for the CEC\n        database and the Material key for the Sandia database to a common\n        list of strings for cell type.\n\n        Returns\n        -------\n        cell_type: str\n\n        \"\"\"\n\n        _cell_type_dict = {'Multi-c-Si': 'multisi',\n                           'Mono-c-Si': 'monosi',\n                           'Thin Film': 'cigs',\n                           'a-Si/nc': 'asi',\n                           'CIS': 'cigs',\n                           'CIGS': 'cigs',\n                           '1-a-Si': 'asi',\n                           'CdTe': 'cdte',\n                           'a-Si': 'asi',\n                           '2-a-Si': None,\n                           '3-a-Si': None,\n                           'HIT-Si': 'monosi',\n                           'mc-Si': 'multisi',\n                           'c-Si': 'multisi',\n                           'Si-Film': 'asi',\n                           'EFG mc-Si': 'multisi',\n                           'GaAs': None,\n                           'a-Si / mono-Si': 'monosi'}\n\n        if 'Technology' in self.module_parameters.keys():\n            # CEC module parameter set\n            cell_type = _cell_type_dict[self.module_parameters['Technology']]\n        elif 'Material' in self.module_parameters.keys():\n            # Sandia module parameter set\n            cell_type = _cell_type_dict[self.module_parameters['Material']]\n        else:\n            cell_type = None\n\n        return cell_type\n\n    def get_aoi(self, solar_zenith, solar_azimuth):\n        \"\"\"\n        Get the angle of incidence on the array.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series\n            Solar zenith angle.\n        solar_azimuth : float or Series\n            Solar azimuth angle\n\n        Returns\n        -------\n        aoi : Series\n            Then angle of incidence.\n        \"\"\"\n        orientation = self.mount.get_orientation(solar_zenith, solar_azimuth)\n        return irradiance.aoi(orientation['surface_tilt'],\n                              orientation['surface_azimuth'],\n                              solar_zenith, solar_azimuth)\n\n    def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,\n                       dni_extra=None, airmass=None, albedo=None,\n                       model='haydavies', **kwargs):\n        \"\"\"\n        Get plane of array irradiance components.\n\n        Uses the :py:func:`pvlib.irradiance.get_total_irradiance` function to\n        calculate the plane of array irradiance components for a surface\n        defined by ``self.surface_tilt`` and ``self.surface_azimuth``.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series.\n            Solar zenith angle.\n        solar_azimuth : float or Series.\n            Solar azimuth angle.\n        dni : float or Series\n            Direct normal irradiance. [W/m2]\n        ghi : float or Series. [W/m2]\n            Global horizontal irradiance\n        dhi : float or Series\n            Diffuse horizontal irradiance. [W/m2]\n        dni_extra : None, float or Series, default None\n            Extraterrestrial direct normal irradiance. [W/m2]\n        airmass : None, float or Series, default None\n            Airmass. [unitless]\n        albedo : None, float or Series, default None\n            Ground surface albedo. [unitless]\n        model : String, default 'haydavies'\n            Irradiance model.\n\n        kwargs\n            Extra parameters passed to\n            :py:func:`pvlib.irradiance.get_total_irradiance`.\n\n        Returns\n        -------\n        poa_irradiance : DataFrame\n            Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse',\n            'poa_sky_diffuse', 'poa_ground_diffuse'``.\n\n        See also\n        --------\n        :py:func:`pvlib.irradiance.get_total_irradiance`\n        \"\"\"\n        if albedo is None:\n            albedo = self.albedo\n\n        # not needed for all models, but this is easier\n        if dni_extra is None:\n            dni_extra = irradiance.get_extra_radiation(solar_zenith.index)\n\n        if airmass is None:\n            airmass = atmosphere.get_relative_airmass(solar_zenith)\n\n        orientation = self.mount.get_orientation(solar_zenith, solar_azimuth)\n        return irradiance.get_total_irradiance(orientation['surface_tilt'],\n                                               orientation['surface_azimuth'],\n                                               solar_zenith, solar_azimuth,\n                                               dni, ghi, dhi,\n                                               dni_extra=dni_extra,\n                                               airmass=airmass,\n                                               albedo=albedo,\n                                               model=model,\n                                               **kwargs)\n\n    def get_iam(self, aoi, iam_model='physical'):\n        \"\"\"\n        Determine the incidence angle modifier using the method specified by\n        ``iam_model``.\n\n        Parameters for the selected IAM model are expected to be in\n        ``Array.module_parameters``. Default parameters are available for\n        the 'physical', 'ashrae' and 'martin_ruiz' models.\n\n        Parameters\n        ----------\n        aoi : numeric\n            The angle of incidence in degrees.\n\n        aoi_model : string, default 'physical'\n            The IAM model to be used. Valid strings are 'physical', 'ashrae',\n            'martin_ruiz', 'sapm' and 'interp'.\n\n        Returns\n        -------\n        iam : numeric\n            The AOI modifier.\n\n        Raises\n        ------\n        ValueError\n            if `iam_model` is not a valid model name.\n        \"\"\"\n        model = iam_model.lower()\n        if model in ['ashrae', 'physical', 'martin_ruiz', 'interp']:\n            func = getattr(iam, model)  # get function at pvlib.iam\n            # get all parameters from function signature to retrieve them from\n            # module_parameters if present\n            params = set(inspect.signature(func).parameters.keys())\n            params.discard('aoi')  # exclude aoi so it can't be repeated\n            kwargs = _build_kwargs(params, self.module_parameters)\n            return func(aoi, **kwargs)\n        elif model == 'sapm':\n            return iam.sapm(aoi, self.module_parameters)\n        else:\n            raise ValueError(model + ' is not a valid IAM model')\n\n    def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,\n                             effective_irradiance=None):\n        \"\"\"\n        Determine cell temperature using the method specified by ``model``.\n\n        Parameters\n        ----------\n        poa_global : numeric\n            Total incident irradiance [W/m^2]\n\n        temp_air : numeric\n            Ambient dry bulb temperature [C]\n\n        wind_speed : numeric\n            Wind speed [m/s]\n\n        model : str\n            Supported models include ``'sapm'``, ``'pvsyst'``,\n            ``'faiman'``, ``'fuentes'``, and ``'noct_sam'``\n\n        effective_irradiance : numeric, optional\n            The irradiance that is converted to photocurrent in W/m^2.\n            Only used for some models.\n\n        Returns\n        -------\n        numeric\n            Values in degrees C.\n\n        See Also\n        --------\n        pvlib.temperature.sapm_cell, pvlib.temperature.pvsyst_cell,\n        pvlib.temperature.faiman, pvlib.temperature.fuentes,\n        pvlib.temperature.noct_sam\n\n        Notes\n        -----\n        Some temperature models have requirements for the input types;\n        see the documentation of the underlying model function for details.\n        \"\"\"\n        # convenience wrapper to avoid passing args 2 and 3 every call\n        _build_tcell_args = functools.partial(\n            _build_args, input_dict=self.temperature_model_parameters,\n            dict_name='temperature_model_parameters')\n\n        if model == 'sapm':\n            func = temperature.sapm_cell\n            required = _build_tcell_args(['a', 'b', 'deltaT'])\n            optional = _build_kwargs(['irrad_ref'],\n                                     self.temperature_model_parameters)\n        elif model == 'pvsyst':\n            func = temperature.pvsyst_cell\n            required = tuple()\n            optional = {\n                **_build_kwargs(['module_efficiency', 'alpha_absorption'],\n                                self.module_parameters),\n                **_build_kwargs(['u_c', 'u_v'],\n                                self.temperature_model_parameters)\n            }\n        elif model == 'faiman':\n            func = temperature.faiman\n            required = tuple()\n            optional = _build_kwargs(['u0', 'u1'],\n                                     self.temperature_model_parameters)\n        elif model == 'fuentes':\n            func = temperature.fuentes\n            required = _build_tcell_args(['noct_installed'])\n            optional = _build_kwargs([\n                'wind_height', 'emissivity', 'absorption',\n                'surface_tilt', 'module_width', 'module_length'],\n                self.temperature_model_parameters)\n            if self.mount.module_height is not None:\n                optional['module_height'] = self.mount.module_height\n        elif model == 'noct_sam':\n            func = functools.partial(temperature.noct_sam,\n                                     effective_irradiance=effective_irradiance)\n            required = _build_tcell_args(['noct', 'module_efficiency'])\n            optional = _build_kwargs(['transmittance_absorptance',\n                                      'array_height', 'mount_standoff'],\n                                     self.temperature_model_parameters)\n        else:\n            raise ValueError(f'{model} is not a valid cell temperature model')\n\n        temperature_cell = func(poa_global, temp_air, wind_speed,\n                                *required, **optional)\n        return temperature_cell\n\n    def dc_ohms_from_percent(self):\n        \"\"\"\n        Calculates the equivalent resistance of the wires using\n        :py:func:`pvlib.pvsystem.dc_ohms_from_percent`\n\n        Makes use of array module parameters according to the\n        following DC models:\n\n        CEC:\n\n            * `self.module_parameters[\"V_mp_ref\"]`\n            * `self.module_parameters[\"I_mp_ref\"]`\n\n        SAPM:\n\n            * `self.module_parameters[\"Vmpo\"]`\n            * `self.module_parameters[\"Impo\"]`\n\n        PVsyst-like or other:\n\n            * `self.module_parameters[\"Vmpp\"]`\n            * `self.module_parameters[\"Impp\"]`\n\n        Other array parameters that are used are:\n        `self.losses_parameters[\"dc_ohmic_percent\"]`,\n        `self.modules_per_string`, and\n        `self.strings`.\n\n        See :py:func:`pvlib.pvsystem.dc_ohms_from_percent` for more details.\n        \"\"\"\n\n        # get relevent Vmp and Imp parameters from CEC parameters\n        if all(elem in self.module_parameters\n               for elem in ['V_mp_ref', 'I_mp_ref']):\n            vmp_ref = self.module_parameters['V_mp_ref']\n            imp_ref = self.module_parameters['I_mp_ref']\n\n        # get relevant Vmp and Imp parameters from SAPM parameters\n        elif all(elem in self.module_parameters for elem in ['Vmpo', 'Impo']):\n            vmp_ref = self.module_parameters['Vmpo']\n            imp_ref = self.module_parameters['Impo']\n\n        # get relevant Vmp and Imp parameters if they are PVsyst-like\n        elif all(elem in self.module_parameters for elem in ['Vmpp', 'Impp']):\n            vmp_ref = self.module_parameters['Vmpp']\n            imp_ref = self.module_parameters['Impp']\n\n        # raise error if relevant Vmp and Imp parameters are not found\n        else:\n            raise ValueError('Parameters for Vmp and Imp could not be found '\n                             'in the array module parameters. Module '\n                             'parameters must include one set of '\n                             '{\"V_mp_ref\", \"I_mp_Ref\"}, '\n                             '{\"Vmpo\", \"Impo\"}, or '\n                             '{\"Vmpp\", \"Impp\"}.'\n                             )\n\n        return dc_ohms_from_percent(\n            vmp_ref,\n            imp_ref,\n            self.array_losses_parameters['dc_ohmic_percent'],\n            self.modules_per_string,\n            self.strings)\n\n\n@dataclass\nclass AbstractMount(ABC):\n    \"\"\"\n    A base class for Mount classes to extend. It is not intended to be\n    instantiated directly.\n    \"\"\"\n\n    @abstractmethod\n    def get_orientation(self, solar_zenith, solar_azimuth):\n        \"\"\"\n        Determine module orientation.\n\n        Parameters\n        ----------\n        solar_zenith : numeric\n            Solar apparent zenith angle [degrees]\n        solar_azimuth : numeric\n            Solar azimuth angle [degrees]\n\n        Returns\n        -------\n        orientation : dict-like\n            A dict-like object with keys `'surface_tilt', 'surface_azimuth'`\n            (typically a dict or pandas.DataFrame)\n        \"\"\"\n\n\n@dataclass\nclass FixedMount(AbstractMount):\n    \"\"\"\n    Racking at fixed (static) orientation.\n\n    Parameters\n    ----------\n    surface_tilt : float, default 0\n        Surface tilt angle. The tilt angle is defined as angle from horizontal\n        (e.g. surface facing up = 0, surface facing horizon = 90) [degrees]\n\n    surface_azimuth : float, default 180\n        Azimuth angle of the module surface. North=0, East=90, South=180,\n        West=270. [degrees]\n\n    racking_model : str, optional\n        Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.\n        Used to identify a parameter set for the SAPM cell temperature model.\n\n    module_height : float, optional\n       The height above ground of the center of the module [m]. Used for\n       the Fuentes cell temperature model.\n    \"\"\"\n\n    surface_tilt: float = 0.0\n    surface_azimuth: float = 180.0\n    racking_model: Optional[str] = None\n    module_height: Optional[float] = None\n\n    def get_orientation(self, solar_zenith, solar_azimuth):\n        # note -- docstring is automatically inherited from AbstractMount\n        return {\n            'surface_tilt': self.surface_tilt,\n            'surface_azimuth': self.surface_azimuth,\n        }\n\n\n@dataclass\nclass SingleAxisTrackerMount(AbstractMount):\n    \"\"\"\n    Single-axis tracker racking for dynamic solar tracking.\n\n    Parameters\n    ----------\n    axis_tilt : float, default 0\n        The tilt of the axis of rotation (i.e, the y-axis defined by\n        axis_azimuth) with respect to horizontal. [degrees]\n\n    axis_azimuth : float, default 180\n        A value denoting the compass direction along which the axis of\n        rotation lies, measured east of north. [degrees]\n\n    max_angle : float, default 90\n        A value denoting the maximum rotation angle\n        of the one-axis tracker from its horizontal position (horizontal\n        if axis_tilt = 0). A max_angle of 90 degrees allows the tracker\n        to rotate to a vertical position to point the panel towards a\n        horizon. max_angle of 180 degrees allows for full rotation. [degrees]\n\n    backtrack : bool, default True\n        Controls whether the tracker has the capability to \"backtrack\"\n        to avoid row-to-row shading. False denotes no backtrack\n        capability. True denotes backtrack capability.\n\n    gcr : float, default 2.0/7.0\n        A value denoting the ground coverage ratio of a tracker system\n        which utilizes backtracking; i.e. the ratio between the PV array\n        surface area to total ground area. A tracker system with modules\n        2 meters wide, centered on the tracking axis, with 6 meters\n        between the tracking axes has a gcr of 2/6=0.333. If gcr is not\n        provided, a gcr of 2/7 is default. gcr must be <=1. [unitless]\n\n    cross_axis_tilt : float, default 0.0\n        The angle, relative to horizontal, of the line formed by the\n        intersection between the slope containing the tracker axes and a plane\n        perpendicular to the tracker axes. Cross-axis tilt should be specified\n        using a right-handed convention. For example, trackers with axis\n        azimuth of 180 degrees (heading south) will have a negative cross-axis\n        tilt if the tracker axes plane slopes down to the east and positive\n        cross-axis tilt if the tracker axes plane slopes up to the east. Use\n        :func:`~pvlib.tracking.calc_cross_axis_tilt` to calculate\n        `cross_axis_tilt`. [degrees]\n\n    racking_model : str, optional\n        Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.\n        Used to identify a parameter set for the SAPM cell temperature model.\n\n    module_height : float, optional\n       The height above ground of the center of the module [m]. Used for\n       the Fuentes cell temperature model.\n    \"\"\"\n    axis_tilt: float = 0.0\n    axis_azimuth: float = 0.0\n    max_angle: float = 90.0\n    backtrack: bool = True\n    gcr: float = 2.0/7.0\n    cross_axis_tilt: float = 0.0\n    racking_model: Optional[str] = None\n    module_height: Optional[float] = None\n\n    def get_orientation(self, solar_zenith, solar_azimuth):\n        # note -- docstring is automatically inherited from AbstractMount\n        from pvlib import tracking  # avoid circular import issue\n        tracking_data = tracking.singleaxis(\n            solar_zenith, solar_azimuth,\n            self.axis_tilt, self.axis_azimuth,\n            self.max_angle, self.backtrack,\n            self.gcr, self.cross_axis_tilt\n        )\n        return tracking_data\n\n\ndef calcparams_desoto(effective_irradiance, temp_cell,\n                      alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s,\n                      EgRef=1.121, dEgdT=-0.0002677,\n                      irrad_ref=1000, temp_ref=25):\n    '''\n    Calculates five parameter values for the single diode equation at\n    effective irradiance and cell temperature using the De Soto et al.\n    model described in [1]_. The five values returned by calcparams_desoto\n    can be used by singlediode to calculate an IV curve.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        The irradiance (W/m2) that is converted to photocurrent.\n\n    temp_cell : numeric\n        The average cell temperature of cells within a module in C.\n\n    alpha_sc : float\n        The short-circuit current temperature coefficient of the\n        module in units of A/C.\n\n    a_ref : float\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at reference\n        conditions, in units of V.\n\n    I_L_ref : float\n        The light-generated current (or photocurrent) at reference conditions,\n        in amperes.\n\n    I_o_ref : float\n        The dark or diode reverse saturation current at reference conditions,\n        in amperes.\n\n    R_sh_ref : float\n        The shunt resistance at reference conditions, in ohms.\n\n    R_s : float\n        The series resistance at reference conditions, in ohms.\n\n    EgRef : float\n        The energy bandgap at reference temperature in units of eV.\n        1.121 eV for crystalline silicon. EgRef must be >0.  For parameters\n        from the SAM CEC module database, EgRef=1.121 is implicit for all\n        cell types in the parameter estimation algorithm used by NREL.\n\n    dEgdT : float\n        The temperature dependence of the energy bandgap at reference\n        conditions in units of 1/K. May be either a scalar value\n        (e.g. -0.0002677 as in [1]_) or a DataFrame (this may be useful if\n        dEgdT is a modeled as a function of temperature). For parameters from\n        the SAM CEC module database, dEgdT=-0.0002677 is implicit for all cell\n        types in the parameter estimation algorithm used by NREL.\n\n    irrad_ref : float (optional, default=1000)\n        Reference irradiance in W/m^2.\n\n    temp_ref : float (optional, default=25)\n        Reference cell temperature in C.\n\n    Returns\n    -------\n    Tuple of the following results:\n\n    photocurrent : numeric\n        Light-generated current in amperes\n\n    saturation_current : numeric\n        Diode saturation curent in amperes\n\n    resistance_series : numeric\n        Series resistance in ohms\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms\n\n    nNsVth : numeric\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at\n        specified effective irradiance and cell temperature.\n\n    References\n    ----------\n    .. [1] W. De Soto et al., \"Improvement and validation of a model for\n       photovoltaic array performance\", Solar Energy, vol 80, pp. 78-88,\n       2006.\n\n    .. [2] System Advisor Model web page. https://sam.nrel.gov.\n\n    .. [3] A. Dobos, \"An Improved Coefficient Calculator for the California\n       Energy Commission 6 Parameter Photovoltaic Module Model\", Journal of\n       Solar Energy Engineering, vol 134, 2012.\n\n    .. [4] O. Madelung, \"Semiconductors: Data Handbook, 3rd ed.\" ISBN\n       3-540-40488-0\n\n    See Also\n    --------\n    singlediode\n    retrieve_sam\n\n    Notes\n    -----\n    If the reference parameters in the ModuleParameters struct are read\n    from a database or library of parameters (e.g. System Advisor\n    Model), it is important to use the same EgRef and dEgdT values that\n    were used to generate the reference parameters, regardless of the\n    actual bandgap characteristics of the semiconductor. For example, in\n    the case of the System Advisor Model library, created as described\n    in [3], EgRef and dEgdT for all modules were 1.121 and -0.0002677,\n    respectively.\n\n    This table of reference bandgap energies (EgRef), bandgap energy\n    temperature dependence (dEgdT), and \"typical\" airmass response (M)\n    is provided purely as reference to those who may generate their own\n    reference module parameters (a_ref, IL_ref, I0_ref, etc.) based upon\n    the various PV semiconductors. Again, we stress the importance of\n    using identical EgRef and dEgdT when generation reference parameters\n    and modifying the reference parameters (for irradiance, temperature,\n    and airmass) per DeSoto's equations.\n\n     Crystalline Silicon (Si):\n         * EgRef = 1.121\n         * dEgdT = -0.0002677\n\n         >>> M = np.polyval([-1.26E-4, 2.816E-3, -0.024459, 0.086257, 0.9181],\n         ...                AMa) # doctest: +SKIP\n\n         Source: [1]\n\n     Cadmium Telluride (CdTe):\n         * EgRef = 1.475\n         * dEgdT = -0.0003\n\n         >>> M = np.polyval([-2.46E-5, 9.607E-4, -0.0134, 0.0716, 0.9196],\n         ...                AMa) # doctest: +SKIP\n\n         Source: [4]\n\n     Copper Indium diSelenide (CIS):\n         * EgRef = 1.010\n         * dEgdT = -0.00011\n\n         >>> M = np.polyval([-3.74E-5, 0.00125, -0.01462, 0.0718, 0.9210],\n         ...                AMa) # doctest: +SKIP\n\n         Source: [4]\n\n     Copper Indium Gallium diSelenide (CIGS):\n         * EgRef = 1.15\n         * dEgdT = ????\n\n         >>> M = np.polyval([-9.07E-5, 0.0022, -0.0202, 0.0652, 0.9417],\n         ...                AMa) # doctest: +SKIP\n\n         Source: Wikipedia\n\n     Gallium Arsenide (GaAs):\n         * EgRef = 1.424\n         * dEgdT = -0.000433\n         * M = unknown\n\n         Source: [4]\n    '''\n\n    # Boltzmann constant in eV/K, 8.617332478e-05\n    k = constants.value('Boltzmann constant in eV/K')\n\n    # reference temperature\n    Tref_K = temp_ref + 273.15\n    Tcell_K = temp_cell + 273.15\n\n    E_g = EgRef * (1 + dEgdT*(Tcell_K - Tref_K))\n\n    nNsVth = a_ref * (Tcell_K / Tref_K)\n\n    # In the equation for IL, the single factor effective_irradiance is\n    # used, in place of the product S*M in [1]. effective_irradiance is\n    # equivalent to the product of S (irradiance reaching a module's cells) *\n    # M (spectral adjustment factor) as described in [1].\n    IL = effective_irradiance / irrad_ref * \\\n        (I_L_ref + alpha_sc * (Tcell_K - Tref_K))\n    I0 = (I_o_ref * ((Tcell_K / Tref_K) ** 3) *\n          (np.exp(EgRef / (k*(Tref_K)) - (E_g / (k*(Tcell_K))))))\n    # Note that the equation for Rsh differs from [1]. In [1] Rsh is given as\n    # Rsh = Rsh_ref * (S_ref / S) where S is broadband irradiance reaching\n    # the module's cells. If desired this model behavior can be duplicated\n    # by applying reflection and soiling losses to broadband plane of array\n    # irradiance and not applying a spectral loss modifier, i.e.,\n    # spectral_modifier = 1.0.\n    # use errstate to silence divide by warning\n    with np.errstate(divide='ignore'):\n        Rsh = R_sh_ref * (irrad_ref / effective_irradiance)\n\n    Rs = R_s\n\n    numeric_args = (effective_irradiance, temp_cell)\n    out = (IL, I0, Rs, Rsh, nNsVth)\n\n    if all(map(np.isscalar, numeric_args)):\n        return out\n\n    index = tools.get_pandas_index(*numeric_args)\n\n    if index is None:\n        return np.broadcast_arrays(*out)\n\n    return tuple(pd.Series(a, index=index).rename(None) for a in out)\n\n\ndef calcparams_cec(effective_irradiance, temp_cell,\n                   alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s,\n                   Adjust, EgRef=1.121, dEgdT=-0.0002677,\n                   irrad_ref=1000, temp_ref=25):\n    '''\n    Calculates five parameter values for the single diode equation at\n    effective irradiance and cell temperature using the CEC\n    model. The CEC model [1]_ differs from the De soto et al.\n    model [3]_ by the parameter Adjust. The five values returned by\n    calcparams_cec can be used by singlediode to calculate an IV curve.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        The irradiance (W/m2) that is converted to photocurrent.\n\n    temp_cell : numeric\n        The average cell temperature of cells within a module in C.\n\n    alpha_sc : float\n        The short-circuit current temperature coefficient of the\n        module in units of A/C.\n\n    a_ref : float\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at reference\n        conditions, in units of V.\n\n    I_L_ref : float\n        The light-generated current (or photocurrent) at reference conditions,\n        in amperes.\n\n    I_o_ref : float\n        The dark or diode reverse saturation current at reference conditions,\n        in amperes.\n\n    R_sh_ref : float\n        The shunt resistance at reference conditions, in ohms.\n\n    R_s : float\n        The series resistance at reference conditions, in ohms.\n\n    Adjust : float\n        The adjustment to the temperature coefficient for short circuit\n        current, in percent\n\n    EgRef : float\n        The energy bandgap at reference temperature in units of eV.\n        1.121 eV for crystalline silicon. EgRef must be >0.  For parameters\n        from the SAM CEC module database, EgRef=1.121 is implicit for all\n        cell types in the parameter estimation algorithm used by NREL.\n\n    dEgdT : float\n        The temperature dependence of the energy bandgap at reference\n        conditions in units of 1/K. May be either a scalar value\n        (e.g. -0.0002677 as in [3]) or a DataFrame (this may be useful if\n        dEgdT is a modeled as a function of temperature). For parameters from\n        the SAM CEC module database, dEgdT=-0.0002677 is implicit for all cell\n        types in the parameter estimation algorithm used by NREL.\n\n    irrad_ref : float (optional, default=1000)\n        Reference irradiance in W/m^2.\n\n    temp_ref : float (optional, default=25)\n        Reference cell temperature in C.\n\n    Returns\n    -------\n    Tuple of the following results:\n\n    photocurrent : numeric\n        Light-generated current in amperes\n\n    saturation_current : numeric\n        Diode saturation curent in amperes\n\n    resistance_series : numeric\n        Series resistance in ohms\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms\n\n    nNsVth : numeric\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at\n        specified effective irradiance and cell temperature.\n\n    References\n    ----------\n    .. [1] A. Dobos, \"An Improved Coefficient Calculator for the California\n       Energy Commission 6 Parameter Photovoltaic Module Model\", Journal of\n       Solar Energy Engineering, vol 134, 2012.\n\n    .. [2] System Advisor Model web page. https://sam.nrel.gov.\n\n    .. [3] W. De Soto et al., \"Improvement and validation of a model for\n       photovoltaic array performance\", Solar Energy, vol 80, pp. 78-88,\n       2006.\n\n    See Also\n    --------\n    calcparams_desoto\n    singlediode\n    retrieve_sam\n\n    '''\n\n    # pass adjusted temperature coefficient to desoto\n    return calcparams_desoto(effective_irradiance, temp_cell,\n                             alpha_sc*(1.0 - Adjust/100),\n                             a_ref, I_L_ref, I_o_ref,\n                             R_sh_ref, R_s,\n                             EgRef=EgRef, dEgdT=dEgdT,\n                             irrad_ref=irrad_ref, temp_ref=temp_ref)\n\n\ndef calcparams_pvsyst(effective_irradiance, temp_cell,\n                      alpha_sc, gamma_ref, mu_gamma,\n                      I_L_ref, I_o_ref,\n                      R_sh_ref, R_sh_0, R_s,\n                      cells_in_series,\n                      R_sh_exp=5.5,\n                      EgRef=1.121,\n                      irrad_ref=1000, temp_ref=25):\n    '''\n    Calculates five parameter values for the single diode equation at\n    effective irradiance and cell temperature using the PVsyst v6\n    model.  The PVsyst v6 model is described in [1]_, [2]_, [3]_.\n    The five values returned by calcparams_pvsyst can be used by singlediode\n    to calculate an IV curve.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        The irradiance (W/m2) that is converted to photocurrent.\n\n    temp_cell : numeric\n        The average cell temperature of cells within a module in C.\n\n    alpha_sc : float\n        The short-circuit current temperature coefficient of the\n        module in units of A/C.\n\n    gamma_ref : float\n        The diode ideality factor\n\n    mu_gamma : float\n        The temperature coefficient for the diode ideality factor, 1/K\n\n    I_L_ref : float\n        The light-generated current (or photocurrent) at reference conditions,\n        in amperes.\n\n    I_o_ref : float\n        The dark or diode reverse saturation current at reference conditions,\n        in amperes.\n\n    R_sh_ref : float\n        The shunt resistance at reference conditions, in ohms.\n\n    R_sh_0 : float\n        The shunt resistance at zero irradiance conditions, in ohms.\n\n    R_s : float\n        The series resistance at reference conditions, in ohms.\n\n    cells_in_series : integer\n        The number of cells connected in series.\n\n    R_sh_exp : float\n        The exponent in the equation for shunt resistance, unitless. Defaults\n        to 5.5.\n\n    EgRef : float\n        The energy bandgap at reference temperature in units of eV.\n        1.121 eV for crystalline silicon. EgRef must be >0.\n\n    irrad_ref : float (optional, default=1000)\n        Reference irradiance in W/m^2.\n\n    temp_ref : float (optional, default=25)\n        Reference cell temperature in C.\n\n    Returns\n    -------\n    Tuple of the following results:\n\n    photocurrent : numeric\n        Light-generated current in amperes\n\n    saturation_current : numeric\n        Diode saturation current in amperes\n\n    resistance_series : numeric\n        Series resistance in ohms\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms\n\n    nNsVth : numeric\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at\n        specified effective irradiance and cell temperature.\n\n    References\n    ----------\n    .. [1] K. Sauer, T. Roessler, C. W. Hansen, Modeling the Irradiance and\n       Temperature Dependence of Photovoltaic Modules in PVsyst,\n       IEEE Journal of Photovoltaics v5(1), January 2015.\n\n    .. [2] A. Mermoud, PV modules modelling, Presentation at the 2nd PV\n       Performance Modeling Workshop, Santa Clara, CA, May 2013\n\n    .. [3] A. Mermoud, T. Lejeune, Performance Assessment of a Simulation Model\n       for PV modules of any available technology, 25th European Photovoltaic\n       Solar Energy Conference, Valencia, Spain, Sept. 2010\n\n    See Also\n    --------\n    calcparams_desoto\n    singlediode\n\n    '''\n\n    # Boltzmann constant in J/K\n    k = constants.k\n\n    # elementary charge in coulomb\n    q = constants.e\n\n    # reference temperature\n    Tref_K = temp_ref + 273.15\n    Tcell_K = temp_cell + 273.15\n\n    gamma = gamma_ref + mu_gamma * (Tcell_K - Tref_K)\n    nNsVth = gamma * k / q * cells_in_series * Tcell_K\n\n    IL = effective_irradiance / irrad_ref * \\\n        (I_L_ref + alpha_sc * (Tcell_K - Tref_K))\n\n    I0 = I_o_ref * ((Tcell_K / Tref_K) ** 3) * \\\n        (np.exp((q * EgRef) / (k * gamma) * (1 / Tref_K - 1 / Tcell_K)))\n\n    Rsh_tmp = \\\n        (R_sh_ref - R_sh_0 * np.exp(-R_sh_exp)) / (1.0 - np.exp(-R_sh_exp))\n    Rsh_base = np.maximum(0.0, Rsh_tmp)\n\n    Rsh = Rsh_base + (R_sh_0 - Rsh_base) * \\\n        np.exp(-R_sh_exp * effective_irradiance / irrad_ref)\n\n    Rs = R_s\n\n    numeric_args = (effective_irradiance, temp_cell)\n    out = (IL, I0, Rs, Rsh, nNsVth)\n\n    if all(map(np.isscalar, numeric_args)):\n        return out\n\n    index = tools.get_pandas_index(*numeric_args)\n\n    if index is None:\n        return np.broadcast_arrays(*out)\n\n    return tuple(pd.Series(a, index=index).rename(None) for a in out)\n\n\ndef retrieve_sam(name=None, path=None):\n    '''\n    Retrieve latest module and inverter info from a local file or the\n    SAM website.\n\n    This function will retrieve either:\n\n        * CEC module database\n        * Sandia Module database\n        * CEC Inverter database\n        * Anton Driesse Inverter database\n\n    and return it as a pandas DataFrame.\n\n    Parameters\n    ----------\n    name : None or string, default None\n        Name can be one of:\n\n        * 'CECMod' - returns the CEC module database\n        * 'CECInverter' - returns the CEC Inverter database\n        * 'SandiaInverter' - returns the CEC Inverter database\n          (CEC is only current inverter db available; tag kept for\n          backwards compatibility)\n        * 'SandiaMod' - returns the Sandia Module database\n        * 'ADRInverter' - returns the ADR Inverter database\n\n    path : None or string, default None\n        Path to the SAM file. May also be a URL.\n\n    Returns\n    -------\n    samfile : DataFrame\n        A DataFrame containing all the elements of the desired database.\n        Each column represents a module or inverter, and a specific\n        dataset can be retrieved by the command\n\n    Raises\n    ------\n    ValueError\n        If no name or path is provided.\n\n    Notes\n    -----\n    Files available at\n        https://github.com/NREL/SAM/tree/develop/deploy/libraries\n    Documentation for module and inverter data sets:\n        https://sam.nrel.gov/photovoltaic/pv-sub-page-2.html\n\n    Examples\n    --------\n\n    >>> from pvlib import pvsystem\n    >>> invdb = pvsystem.retrieve_sam('CECInverter')\n    >>> inverter = invdb.AE_Solar_Energy__AE6_0__277V_\n    >>> inverter\n    Vac                          277\n    Pso                    36.197575\n    Paco                      6000.0\n    Pdco                 6158.746094\n    Vdco                       360.0\n    C0                     -0.000002\n    C1                     -0.000026\n    C2                     -0.001253\n    C3                       0.00021\n    Pnt                          1.8\n    Vdcmax                     450.0\n    Idcmax                 17.107628\n    Mppt_low                   100.0\n    Mppt_high                  450.0\n    CEC_Date                     NaN\n    CEC_Type     Utility Interactive\n    Name: AE_Solar_Energy__AE6_0__277V_, dtype: object\n    '''\n\n    if name is not None:\n        name = name.lower()\n        data_path = os.path.join(\n            os.path.dirname(os.path.abspath(__file__)), 'data')\n        if name == 'cecmod':\n            csvdata = os.path.join(\n                data_path, 'sam-library-cec-modules-2019-03-05.csv')\n        elif name == 'sandiamod':\n            csvdata = os.path.join(\n                data_path, 'sam-library-sandia-modules-2015-6-30.csv')\n        elif name == 'adrinverter':\n            csvdata = os.path.join(\n                data_path, 'adr-library-cec-inverters-2019-03-05.csv')\n        elif name in ['cecinverter', 'sandiainverter']:\n            # Allowing either, to provide for old code,\n            # while aligning with current expectations\n            csvdata = os.path.join(\n                data_path, 'sam-library-cec-inverters-2019-03-05.csv')\n        else:\n            raise ValueError(f'invalid name {name}')\n    elif path is not None:\n        if path.startswith('http'):\n            response = urlopen(path)\n            csvdata = io.StringIO(response.read().decode(errors='ignore'))\n        else:\n            csvdata = path\n    elif name is None and path is None:\n        raise ValueError(\"A name or path must be provided!\")\n\n    return _parse_raw_sam_df(csvdata)\n\n\ndef _normalize_sam_product_names(names):\n    '''\n    Replace special characters within the product names to make them more\n    suitable for use as Dataframe column names.\n    '''\n    # Contributed by Anton Driesse (@adriesse), PV Performance Labs. July, 2019\n\n    import warnings\n\n    BAD_CHARS = ' -.()[]:+/\",'\n    GOOD_CHARS = '____________'\n\n    mapping = str.maketrans(BAD_CHARS, GOOD_CHARS)\n    names = pd.Series(data=names)\n    norm_names = names.str.translate(mapping)\n\n    n_duplicates = names.duplicated().sum()\n    if n_duplicates > 0:\n        warnings.warn('Original names contain %d duplicate(s).' % n_duplicates)\n\n    n_duplicates = norm_names.duplicated().sum()\n    if n_duplicates > 0:\n        warnings.warn(\n            'Normalized names contain %d duplicate(s).' % n_duplicates)\n\n    return norm_names.values\n\n\ndef _parse_raw_sam_df(csvdata):\n\n    df = pd.read_csv(csvdata, index_col=0, skiprows=[1, 2])\n\n    df.columns = df.columns.str.replace(' ', '_')\n    df.index = _normalize_sam_product_names(df.index)\n    df = df.transpose()\n\n    if 'ADRCoefficients' in df.index:\n        ad_ce = 'ADRCoefficients'\n        # for each inverter, parses a string of coefficients like\n        # ' 1.33, 2.11, 3.12' into a list containing floats:\n        # [1.33, 2.11, 3.12]\n        df.loc[ad_ce] = df.loc[ad_ce].map(lambda x: list(\n            map(float, x.strip(' []').split())))\n\n    return df\n\n\ndef sapm(effective_irradiance, temp_cell, module):\n    '''\n    The Sandia PV Array Performance Model (SAPM) generates 5 points on a\n    PV module's I-V curve (Voc, Isc, Ix, Ixx, Vmp/Imp) according to\n    SAND2004-3535. Assumes a reference cell temperature of 25 C.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        Irradiance reaching the module's cells, after reflections and\n        adjustment for spectrum. [W/m2]\n\n    temp_cell : numeric\n        Cell temperature [C].\n\n    module : dict-like\n        A dict or Series defining the SAPM parameters. See the notes section\n        for more details.\n\n    Returns\n    -------\n    A DataFrame with the columns:\n\n        * i_sc : Short-circuit current (A)\n        * i_mp : Current at the maximum-power point (A)\n        * v_oc : Open-circuit voltage (V)\n        * v_mp : Voltage at maximum-power point (V)\n        * p_mp : Power at maximum-power point (W)\n        * i_x : Current at module V = 0.5Voc, defines 4th point on I-V\n          curve for modeling curve shape\n        * i_xx : Current at module V = 0.5(Voc+Vmp), defines 5th point on\n          I-V curve for modeling curve shape\n\n    Notes\n    -----\n    The SAPM parameters which are required in ``module`` are\n    listed in the following table.\n\n    The Sandia module database contains parameter values for a limited set\n    of modules. The CEC module database does not contain these parameters.\n    Both databases can be accessed using :py:func:`retrieve_sam`.\n\n    ================   ========================================================\n    Key                Description\n    ================   ========================================================\n    A0-A4              The airmass coefficients used in calculating\n                       effective irradiance\n    B0-B5              The angle of incidence coefficients used in calculating\n                       effective irradiance\n    C0-C7              The empirically determined coefficients relating\n                       Imp, Vmp, Ix, and Ixx to effective irradiance\n    Isco               Short circuit current at reference condition (amps)\n    Impo               Maximum power current at reference condition (amps)\n    Voco               Open circuit voltage at reference condition (amps)\n    Vmpo               Maximum power voltage at reference condition (amps)\n    Aisc               Short circuit current temperature coefficient at\n                       reference condition (1/C)\n    Aimp               Maximum power current temperature coefficient at\n                       reference condition (1/C)\n    Bvoco              Open circuit voltage temperature coefficient at\n                       reference condition (V/C)\n    Mbvoc              Coefficient providing the irradiance dependence for the\n                       BetaVoc temperature coefficient at reference irradiance\n                       (V/C)\n    Bvmpo              Maximum power voltage temperature coefficient at\n                       reference condition\n    Mbvmp              Coefficient providing the irradiance dependence for the\n                       BetaVmp temperature coefficient at reference irradiance\n                       (V/C)\n    N                  Empirically determined \"diode factor\" (dimensionless)\n    Cells_in_Series    Number of cells in series in a module's cell string(s)\n    IXO                Ix at reference conditions\n    IXXO               Ixx at reference conditions\n    FD                 Fraction of diffuse irradiance used by module\n    ================   ========================================================\n\n    References\n    ----------\n    .. [1] King, D. et al, 2004, \"Sandia Photovoltaic Array Performance\n       Model\", SAND Report 3535, Sandia National Laboratories, Albuquerque,\n       NM.\n\n    See Also\n    --------\n    retrieve_sam\n    pvlib.temperature.sapm_cell\n    pvlib.temperature.sapm_module\n    '''\n\n    # TODO: someday, change temp_ref and irrad_ref to reference_temperature and\n    # reference_irradiance and expose\n    temp_ref = 25\n    irrad_ref = 1000\n\n    q = constants.e  # Elementary charge in units of coulombs\n    kb = constants.k  # Boltzmann's constant in units of J/K\n\n    # avoid problem with integer input\n    Ee = np.array(effective_irradiance, dtype='float64') / irrad_ref\n\n    # set up masking for 0, positive, and nan inputs\n    Ee_gt_0 = np.full_like(Ee, False, dtype='bool')\n    Ee_eq_0 = np.full_like(Ee, False, dtype='bool')\n    notnan = ~np.isnan(Ee)\n    np.greater(Ee, 0, where=notnan, out=Ee_gt_0)\n    np.equal(Ee, 0, where=notnan, out=Ee_eq_0)\n\n    Bvmpo = module['Bvmpo'] + module['Mbvmp']*(1 - Ee)\n    Bvoco = module['Bvoco'] + module['Mbvoc']*(1 - Ee)\n    delta = module['N'] * kb * (temp_cell + 273.15) / q\n\n    # avoid repeated computation\n    logEe = np.full_like(Ee, np.nan)\n    np.log(Ee, where=Ee_gt_0, out=logEe)\n    logEe = np.where(Ee_eq_0, -np.inf, logEe)\n    # avoid repeated __getitem__\n    cells_in_series = module['Cells_in_Series']\n\n    out = OrderedDict()\n\n    out['i_sc'] = (\n        module['Isco'] * Ee * (1 + module['Aisc']*(temp_cell - temp_ref)))\n\n    out['i_mp'] = (\n        module['Impo'] * (module['C0']*Ee + module['C1']*(Ee**2)) *\n        (1 + module['Aimp']*(temp_cell - temp_ref)))\n\n    out['v_oc'] = np.maximum(0, (\n        module['Voco'] + cells_in_series * delta * logEe +\n        Bvoco*(temp_cell - temp_ref)))\n\n    out['v_mp'] = np.maximum(0, (\n        module['Vmpo'] +\n        module['C2'] * cells_in_series * delta * logEe +\n        module['C3'] * cells_in_series * ((delta * logEe) ** 2) +\n        Bvmpo*(temp_cell - temp_ref)))\n\n    out['p_mp'] = out['i_mp'] * out['v_mp']\n\n    out['i_x'] = (\n        module['IXO'] * (module['C4']*Ee + module['C5']*(Ee**2)) *\n        (1 + module['Aisc']*(temp_cell - temp_ref)))\n\n    # the Ixx calculation in King 2004 has a typo (mixes up Aisc and Aimp)\n    out['i_xx'] = (\n        module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) *\n        (1 + module['Aisc']*(temp_cell - temp_ref)))\n\n    if isinstance(out['i_sc'], pd.Series):\n        out = pd.DataFrame(out)\n\n    return out\n\n\nsapm_spectral_loss = deprecated(\n    since='0.10.0',\n    alternative='pvlib.spectrum.spectral_factor_sapm'\n)(spectrum.spectral_factor_sapm)\n\n\ndef sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,\n                              module):\n    r\"\"\"\n    Calculates the SAPM effective irradiance using the SAPM spectral\n    loss and SAPM angle of incidence loss functions.\n\n    Parameters\n    ----------\n    poa_direct : numeric\n        The direct irradiance incident upon the module. [W/m2]\n\n    poa_diffuse : numeric\n        The diffuse irradiance incident on module.  [W/m2]\n\n    airmass_absolute : numeric\n        Absolute airmass. [unitless]\n\n    aoi : numeric\n        Angle of incidence. [degrees]\n\n    module : dict-like\n        A dict, Series, or DataFrame defining the SAPM performance\n        parameters. See the :py:func:`sapm` notes section for more\n        details.\n\n    Returns\n    -------\n    effective_irradiance : numeric\n        Effective irradiance accounting for reflections and spectral content.\n        [W/m2]\n\n    Notes\n    -----\n    The SAPM model for effective irradiance [1]_ translates broadband direct\n    and diffuse irradiance on the plane of array to the irradiance absorbed by\n    a module's cells.\n\n    The model is\n    .. math::\n\n        `Ee = f_1(AM_a) (E_b f_2(AOI) + f_d E_d)`\n\n    where :math:`Ee` is effective irradiance (W/m2), :math:`f_1` is a fourth\n    degree polynomial in air mass :math:`AM_a`, :math:`E_b` is beam (direct)\n    irradiance on the plane of array, :math:`E_d` is diffuse irradiance on the\n    plane of array, :math:`f_2` is a fifth degree polynomial in the angle of\n    incidence :math:`AOI`, and :math:`f_d` is the fraction of diffuse\n    irradiance on the plane of array that is not reflected away.\n\n    References\n    ----------\n    .. [1] D. King et al, \"Sandia Photovoltaic Array Performance Model\",\n       SAND2004-3535, Sandia National Laboratories, Albuquerque, NM\n\n    See also\n    --------\n    pvlib.iam.sapm\n    pvlib.spectrum.spectral_factor_sapm\n    pvlib.pvsystem.sapm\n    \"\"\"\n\n    F1 = spectrum.spectral_factor_sapm(airmass_absolute, module)\n    F2 = iam.sapm(aoi, module)\n\n    Ee = F1 * (poa_direct * F2 + module['FD'] * poa_diffuse)\n\n    return Ee\n\n\ndef singlediode(photocurrent, saturation_current, resistance_series,\n                resistance_shunt, nNsVth, ivcurve_pnts=None,\n                method='lambertw'):\n    r\"\"\"\n    Solve the single diode equation to obtain a photovoltaic IV curve.\n\n    Solves the single diode equation [1]_\n\n    .. math::\n\n        I = I_L -\n            I_0 \\left[\n                \\exp \\left(\\frac{V+I R_s}{n N_s V_{th}} \\right)-1\n            \\right] -\n            \\frac{V + I R_s}{R_{sh}}\n\n    for :math:`I` and :math:`V` when given :math:`I_L, I_0, R_s, R_{sh},` and\n    :math:`n N_s V_{th}` which are described later. The five points on the I-V\n    curve specified in [3]_ are returned. If :math:`I_L, I_0, R_s, R_{sh},` and\n    :math:`n N_s V_{th}` are all scalars, a single curve is returned. If any\n    are array-like (of the same length), multiple IV curves are calculated.\n\n    The input parameters can be calculated from meteorological data using a\n    function for a single diode model, e.g.,\n    :py:func:`~pvlib.pvsystem.calcparams_desoto`.\n\n    Parameters\n    ----------\n    photocurrent : numeric\n        Light-generated current :math:`I_L` (photocurrent)\n        ``0 <= photocurrent``. [A]\n\n    saturation_current : numeric\n        Diode saturation :math:`I_0` current under desired IV curve\n        conditions. ``0 < saturation_current``. [A]\n\n    resistance_series : numeric\n        Series resistance :math:`R_s` under desired IV curve conditions.\n        ``0 <= resistance_series < numpy.inf``.  [ohm]\n\n    resistance_shunt : numeric\n        Shunt resistance :math:`R_{sh}` under desired IV curve conditions.\n        ``0 < resistance_shunt <= numpy.inf``.  [ohm]\n\n    nNsVth : numeric\n        The product of three components: 1) the usual diode ideality factor\n        :math:`n`, 2) the number of cells in series :math:`N_s`, and 3)\n        the cell thermal voltage\n        :math:`V_{th}`. The thermal voltage of the cell (in volts) may be\n        calculated as :math:`k_B T_c / q`, where :math:`k_B` is\n        Boltzmann's constant (J/K), :math:`T_c` is the temperature of the p-n\n        junction in Kelvin, and :math:`q` is the charge of an electron\n        (coulombs). ``0 < nNsVth``.  [V]\n\n    ivcurve_pnts : None or int, default None\n        Number of points in the desired IV curve. If None or 0, no points on\n        the IV curves will be produced.\n\n        .. deprecated:: 0.10.0\n           Use :py:func:`pvlib.pvsystem.v_from_i` and\n           :py:func:`pvlib.pvsystem.i_from_v` instead.\n\n    method : str, default 'lambertw'\n        Determines the method used to calculate points on the IV curve. The\n        options are ``'lambertw'``, ``'newton'``, or ``'brentq'``.\n\n    Returns\n    -------\n    dict or pandas.DataFrame\n        The returned dict-like object always contains the keys/columns:\n\n            * i_sc - short circuit current in amperes.\n            * v_oc - open circuit voltage in volts.\n            * i_mp - current at maximum power point in amperes.\n            * v_mp - voltage at maximum power point in volts.\n            * p_mp - power at maximum power point in watts.\n            * i_x - current, in amperes, at ``v = 0.5*v_oc``.\n            * i_xx - current, in amperes, at ``v = 0.5*(v_oc+v_mp)``.\n\n        A dict is returned when the input parameters are scalars or\n        ``ivcurve_pnts > 0``. If ``ivcurve_pnts > 0``, the output dictionary\n        will also include the keys:\n\n            * i - IV curve current in amperes.\n            * v - IV curve voltage in volts.\n\n    See also\n    --------\n    calcparams_desoto\n    calcparams_cec\n    calcparams_pvsyst\n    sapm\n    pvlib.singlediode.bishop88\n\n    Notes\n    -----\n    If the method is ``'lambertw'`` then the solution employed to solve the\n    implicit diode equation utilizes the Lambert W function to obtain an\n    explicit function of :math:`V=f(I)` and :math:`I=f(V)` as shown in [2]_.\n\n    If the method is ``'newton'`` then the root-finding Newton-Raphson method\n    is used. It should be safe for well behaved IV-curves, but the ``'brentq'``\n    method is recommended for reliability.\n\n    If the method is ``'brentq'`` then Brent's bisection search method is used\n    that guarantees convergence by bounding the voltage between zero and\n    open-circuit.\n\n    If the method is either ``'newton'`` or ``'brentq'`` and ``ivcurve_pnts``\n    are indicated, then :func:`pvlib.singlediode.bishop88` [4]_ is used to\n    calculate the points on the IV curve points at diode voltages from zero to\n    open-circuit voltage with a log spacing that gets closer as voltage\n    increases. If the method is ``'lambertw'`` then the calculated points on\n    the IV curve are linearly spaced.\n\n    References\n    ----------\n    .. [1] S.R. Wenham, M.A. Green, M.E. Watt, \"Applied Photovoltaics\" ISBN\n       0 86758 909 4\n\n    .. [2] A. Jain, A. Kapoor, \"Exact analytical solutions of the\n       parameters of real solar cells using Lambert W-function\", Solar\n       Energy Materials and Solar Cells, 81 (2004) 269-277.\n\n    .. [3] D. King et al, \"Sandia Photovoltaic Array Performance Model\",\n       SAND2004-3535, Sandia National Laboratories, Albuquerque, NM\n\n    .. [4] \"Computer simulation of the effects of electrical mismatches in\n       photovoltaic cell interconnection circuits\" JW Bishop, Solar Cell (1988)\n       https://doi.org/10.1016/0379-6787(88)90059-2\n    \"\"\"\n    if ivcurve_pnts:\n        warn_deprecated('0.10.0', name='pvlib.pvsystem.singlediode',\n                        alternative=('pvlib.pvsystem.v_from_i and '\n                                     'pvlib.pvsystem.i_from_v'),\n                        obj_type='parameter ivcurve_pnts',\n                        removal='0.11.0')\n    args = (photocurrent, saturation_current, resistance_series,\n            resistance_shunt, nNsVth)  # collect args\n    # Calculate points on the IV curve using the LambertW solution to the\n    # single diode equation\n    if method.lower() == 'lambertw':\n        out = _singlediode._lambertw(*args, ivcurve_pnts)\n        points = out[:7]\n        if ivcurve_pnts:\n            ivcurve_i, ivcurve_v = out[7:]\n    else:\n        # Calculate points on the IV curve using either 'newton' or 'brentq'\n        # methods. Voltages are determined by first solving the single diode\n        # equation for the diode voltage V_d then backing out voltage\n        v_oc = _singlediode.bishop88_v_from_i(\n            0.0, *args, method=method.lower()\n        )\n        i_mp, v_mp, p_mp = _singlediode.bishop88_mpp(\n            *args, method=method.lower()\n        )\n        i_sc = _singlediode.bishop88_i_from_v(\n            0.0, *args, method=method.lower()\n        )\n        i_x = _singlediode.bishop88_i_from_v(\n            v_oc / 2.0, *args, method=method.lower()\n        )\n        i_xx = _singlediode.bishop88_i_from_v(\n            (v_oc + v_mp) / 2.0, *args, method=method.lower()\n        )\n        points = i_sc, v_oc, i_mp, v_mp, p_mp, i_x, i_xx\n\n        # calculate the IV curve if requested using bishop88\n        if ivcurve_pnts:\n            vd = v_oc * (\n                (11.0 - np.logspace(np.log10(11.0), 0.0, ivcurve_pnts)) / 10.0\n            )\n            ivcurve_i, ivcurve_v, _ = _singlediode.bishop88(vd, *args)\n\n    columns = ('i_sc', 'v_oc', 'i_mp', 'v_mp', 'p_mp', 'i_x', 'i_xx')\n\n    if all(map(np.isscalar, args)) or ivcurve_pnts:\n        out = {c: p for c, p in zip(columns, points)}\n\n        if ivcurve_pnts:\n            out.update(i=ivcurve_i, v=ivcurve_v)\n\n        return out\n\n    points = np.atleast_1d(*points)  # convert scalars to 1d-arrays\n    points = np.vstack(points).T  # collect rows into DataFrame columns\n\n    # save the first available pd.Series index, otherwise set to None\n    index = next((a.index for a in args if isinstance(a, pd.Series)), None)\n\n    out = pd.DataFrame(points, columns=columns, index=index)\n\n    return out\n\n\ndef max_power_point(photocurrent, saturation_current, resistance_series,\n                    resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.Inf,\n                    method='brentq'):\n    \"\"\"\n    Given the single diode equation coefficients, calculates the maximum power\n    point (MPP).\n\n    Parameters\n    ----------\n    photocurrent : numeric\n        photo-generated current [A]\n    saturation_current : numeric\n        diode reverse saturation current [A]\n    resistance_series : numeric\n        series resitance [ohms]\n    resistance_shunt : numeric\n        shunt resitance [ohms]\n    nNsVth : numeric\n        product of thermal voltage ``Vth`` [V], diode ideality factor ``n``,\n        and number of serices cells ``Ns``\n    d2mutau : numeric, default 0\n        PVsyst parameter for cadmium-telluride (CdTe) and amorphous-silicon\n        (a-Si) modules that accounts for recombination current in the\n        intrinsic layer. The value is the ratio of intrinsic layer thickness\n        squared :math:`d^2` to the diffusion length of charge carriers\n        :math:`\\\\mu \\\\tau`. [V]\n    NsVbi : numeric, default np.inf\n        PVsyst parameter for cadmium-telluride (CdTe) and amorphous-silicon\n        (a-Si) modules that is the product of the PV module number of series\n        cells ``Ns`` and the builtin voltage ``Vbi`` of the intrinsic layer.\n        [V].\n    method : str\n        either ``'newton'`` or ``'brentq'``\n\n    Returns\n    -------\n    OrderedDict or pandas.DataFrame\n        ``(i_mp, v_mp, p_mp)``\n\n    Notes\n    -----\n    Use this function when you only want to find the maximum power point. Use\n    :func:`singlediode` when you need to find additional points on the IV\n    curve. This function uses Brent's method by default because it is\n    guaranteed to converge.\n    \"\"\"\n    i_mp, v_mp, p_mp = _singlediode.bishop88_mpp(\n        photocurrent, saturation_current, resistance_series,\n        resistance_shunt, nNsVth, d2mutau, NsVbi, method=method.lower()\n    )\n    if isinstance(photocurrent, pd.Series):\n        ivp = {'i_mp': i_mp, 'v_mp': v_mp, 'p_mp': p_mp}\n        out = pd.DataFrame(ivp, index=photocurrent.index)\n    else:\n        out = OrderedDict()\n        out['i_mp'] = i_mp\n        out['v_mp'] = v_mp\n        out['p_mp'] = p_mp\n    return out\n\n\ndef v_from_i(current, photocurrent, saturation_current, resistance_series,\n             resistance_shunt, nNsVth, method='lambertw'):\n    '''\n    Device voltage at the given device current for the single diode model.\n\n    Uses the single diode model (SDM) as described in, e.g.,\n    Jain and Kapoor 2004 [1]_.\n    The solution is per Eq 3 of [1]_ except when resistance_shunt=numpy.inf,\n    in which case the explict solution for voltage is used.\n    Ideal device parameters are specified by resistance_shunt=np.inf and\n    resistance_series=0.\n    Inputs to this function can include scalars and pandas.Series, but it is\n    the caller's responsibility to ensure that the arguments are all float64\n    and within the proper ranges.\n\n    .. versionchanged:: 0.10.0\n       The function's arguments have been reordered.\n\n    Parameters\n    ----------\n    current : numeric\n        The current in amperes under desired IV curve conditions.\n\n    photocurrent : numeric\n        Light-generated current (photocurrent) in amperes under desired\n        IV curve conditions. Often abbreviated ``I_L``.\n        0 <= photocurrent\n\n    saturation_current : numeric\n        Diode saturation current in amperes under desired IV curve\n        conditions. Often abbreviated ``I_0``.\n        0 < saturation_current\n\n    resistance_series : numeric\n        Series resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rs``.\n        0 <= resistance_series < numpy.inf\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rsh``.\n        0 < resistance_shunt <= numpy.inf\n\n    nNsVth : numeric\n        The product of three components. 1) The usual diode ideal factor\n        (n), 2) the number of cells in series (Ns), and 3) the cell\n        thermal voltage under the desired IV curve conditions (Vth). The\n        thermal voltage of the cell (in volts) may be calculated as\n        ``k*temp_cell/q``, where k is Boltzmann's constant (J/K),\n        temp_cell is the temperature of the p-n junction in Kelvin, and\n        q is the charge of an electron (coulombs).\n        0 < nNsVth\n\n    method : str\n        Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*:\n        ``'brentq'`` is limited to 1st quadrant only.\n\n    Returns\n    -------\n    current : np.ndarray or scalar\n\n    References\n    ----------\n    .. [1] A. Jain, A. Kapoor, \"Exact analytical solutions of the\n       parameters of real solar cells using Lambert W-function\", Solar\n       Energy Materials and Solar Cells, 81 (2004) 269-277.\n    '''\n    args = (current, photocurrent, saturation_current,\n            resistance_series, resistance_shunt, nNsVth)\n    if method.lower() == 'lambertw':\n        return _singlediode._lambertw_v_from_i(*args)\n    else:\n        # Calculate points on the IV curve using either 'newton' or 'brentq'\n        # methods. Voltages are determined by first solving the single diode\n        # equation for the diode voltage V_d then backing out voltage\n        V = _singlediode.bishop88_v_from_i(*args, method=method.lower())\n        if all(map(np.isscalar, args)):\n            return V\n        shape = _singlediode._shape_of_max_size(*args)\n        return np.broadcast_to(V, shape)\n\n\ndef i_from_v(voltage, photocurrent, saturation_current, resistance_series,\n             resistance_shunt, nNsVth, method='lambertw'):\n    '''\n    Device current at the given device voltage for the single diode model.\n\n    Uses the single diode model (SDM) as described in, e.g.,\n    Jain and Kapoor 2004 [1]_.\n    The solution is per Eq 2 of [1] except when resistance_series=0,\n    in which case the explict solution for current is used.\n    Ideal device parameters are specified by resistance_shunt=np.inf and\n    resistance_series=0.\n    Inputs to this function can include scalars and pandas.Series, but it is\n    the caller's responsibility to ensure that the arguments are all float64\n    and within the proper ranges.\n\n    .. versionchanged:: 0.10.0\n       The function's arguments have been reordered.\n\n    Parameters\n    ----------\n    voltage : numeric\n        The voltage in Volts under desired IV curve conditions.\n\n    photocurrent : numeric\n        Light-generated current (photocurrent) in amperes under desired\n        IV curve conditions. Often abbreviated ``I_L``.\n        0 <= photocurrent\n\n    saturation_current : numeric\n        Diode saturation current in amperes under desired IV curve\n        conditions. Often abbreviated ``I_0``.\n        0 < saturation_current\n\n    resistance_series : numeric\n        Series resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rs``.\n        0 <= resistance_series < numpy.inf\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rsh``.\n        0 < resistance_shunt <= numpy.inf\n\n    nNsVth : numeric\n        The product of three components. 1) The usual diode ideal factor\n        (n), 2) the number of cells in series (Ns), and 3) the cell\n        thermal voltage under the desired IV curve conditions (Vth). The\n        thermal voltage of the cell (in volts) may be calculated as\n        ``k*temp_cell/q``, where k is Boltzmann's constant (J/K),\n        temp_cell is the temperature of the p-n junction in Kelvin, and\n        q is the charge of an electron (coulombs).\n        0 < nNsVth\n\n    method : str\n        Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*:\n        ``'brentq'`` is limited to 1st quadrant only.\n\n    Returns\n    -------\n    current : np.ndarray or scalar\n\n    References\n    ----------\n    .. [1] A. Jain, A. Kapoor, \"Exact analytical solutions of the\n       parameters of real solar cells using Lambert W-function\", Solar\n       Energy Materials and Solar Cells, 81 (2004) 269-277.\n    '''\n    args = (voltage, photocurrent, saturation_current,\n            resistance_series, resistance_shunt, nNsVth)\n    if method.lower() == 'lambertw':\n        return _singlediode._lambertw_i_from_v(*args)\n    else:\n        # Calculate points on the IV curve using either 'newton' or 'brentq'\n        # methods. Voltages are determined by first solving the single diode\n        # equation for the diode voltage V_d then backing out voltage\n        current = _singlediode.bishop88_i_from_v(*args, method=method.lower())\n        if all(map(np.isscalar, args)):\n            return current\n        shape = _singlediode._shape_of_max_size(*args)\n        return np.broadcast_to(current, shape)\n\n\ndef scale_voltage_current_power(data, voltage=1, current=1):\n    \"\"\"\n    Scales the voltage, current, and power in data by the voltage\n    and current factors.\n\n    Parameters\n    ----------\n    data: DataFrame\n        May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',\n        'i_sc', 'p_mp'`.\n    voltage: numeric, default 1\n        The amount by which to multiply the voltages.\n    current: numeric, default 1\n        The amount by which to multiply the currents.\n\n    Returns\n    -------\n    scaled_data: DataFrame\n        A scaled copy of the input data.\n        `'p_mp'` is scaled by `voltage * current`.\n    \"\"\"\n\n    # as written, only works with a DataFrame\n    # could make it work with a dict, but it would be more verbose\n    voltage_keys = ['v_mp', 'v_oc']\n    current_keys = ['i_mp', 'i_x', 'i_xx', 'i_sc']\n    power_keys = ['p_mp']\n    voltage_df = data.filter(voltage_keys, axis=1) * voltage\n    current_df = data.filter(current_keys, axis=1) * current\n    power_df = data.filter(power_keys, axis=1) * voltage * current\n    df = pd.concat([voltage_df, current_df, power_df], axis=1)\n    df_sorted = df[data.columns]  # retain original column order\n    return df_sorted\n\n\ndef pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.):\n    r\"\"\"\n    Implements NREL's PVWatts DC power model. The PVWatts DC model [1]_ is:\n\n    .. math::\n\n        P_{dc} = \\frac{G_{poa eff}}{1000} P_{dc0} ( 1 + \\gamma_{pdc} (T_{cell} - T_{ref}))\n\n    Note that ``pdc0`` is also used as a symbol in\n    :py:func:`pvlib.inverter.pvwatts`. ``pdc0`` in this function refers to the DC\n    power of the modules at reference conditions. ``pdc0`` in\n    :py:func:`pvlib.inverter.pvwatts` refers to the DC power input limit of\n    the inverter.\n\n    Parameters\n    ----------\n    g_poa_effective: numeric\n        Irradiance transmitted to the PV cells. To be\n        fully consistent with PVWatts, the user must have already\n        applied angle of incidence losses, but not soiling, spectral,\n        etc. [W/m^2]\n    temp_cell: numeric\n        Cell temperature [C].\n    pdc0: numeric\n        Power of the modules at 1000 W/m^2 and cell reference temperature. [W]\n    gamma_pdc: numeric\n        The temperature coefficient of power. Typically -0.002 to\n        -0.005 per degree C. [1/C]\n    temp_ref: numeric, default 25.0\n        Cell reference temperature. PVWatts defines it to be 25 C and\n        is included here for flexibility. [C]\n\n    Returns\n    -------\n    pdc: numeric\n        DC power. [W]\n\n    References\n    ----------\n    .. [1] A. P. Dobos, \"PVWatts Version 5 Manual\"\n           http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf\n           (2014).\n    \"\"\"  # noqa: E501\n\n    pdc = (g_poa_effective * 0.001 * pdc0 *\n           (1 + gamma_pdc * (temp_cell - temp_ref)))\n\n    return pdc\n\n\ndef pvwatts_losses(soiling=2, shading=3, snow=0, mismatch=2, wiring=2,\n                   connections=0.5, lid=1.5, nameplate_rating=1, age=0,\n                   availability=3):\n    r\"\"\"\n    Implements NREL's PVWatts system loss model.\n    The PVWatts loss model [1]_ is:\n\n    .. math::\n\n        L_{total}(\\%) = 100 [ 1 - \\Pi_i ( 1 - \\frac{L_i}{100} ) ]\n\n    All parameters must be in units of %. Parameters may be\n    array-like, though all array sizes must match.\n\n    Parameters\n    ----------\n    soiling: numeric, default 2\n    shading: numeric, default 3\n    snow: numeric, default 0\n    mismatch: numeric, default 2\n    wiring: numeric, default 2\n    connections: numeric, default 0.5\n    lid: numeric, default 1.5\n        Light induced degradation\n    nameplate_rating: numeric, default 1\n    age: numeric, default 0\n    availability: numeric, default 3\n\n    Returns\n    -------\n    losses: numeric\n        System losses in units of %.\n\n    References\n    ----------\n    .. [1] A. P. Dobos, \"PVWatts Version 5 Manual\"\n           http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf\n           (2014).\n    \"\"\"\n\n    params = [soiling, shading, snow, mismatch, wiring, connections, lid,\n              nameplate_rating, age, availability]\n\n    # manually looping over params allows for numpy/pandas to handle any\n    # array-like broadcasting that might be necessary.\n    perf = 1\n    for param in params:\n        perf *= 1 - param/100\n\n    losses = (1 - perf) * 100.\n\n    return losses\n\n\ndef dc_ohms_from_percent(vmp_ref, imp_ref, dc_ohmic_percent,\n                         modules_per_string=1,\n                         strings=1):\n    \"\"\"\n    Calculates the equivalent resistance of the wires from a percent\n    ohmic loss at STC.\n\n    Equivalent resistance is calculated with the function:\n\n    .. math::\n        Rw = (L_{stc} / 100) * (Varray / Iarray)\n\n    :math:`Rw` is the equivalent resistance in ohms\n    :math:`Varray` is the Vmp of the modules times modules per string\n    :math:`Iarray` is the Imp of the modules times strings per array\n    :math:`L_{stc}` is the input dc loss percent\n\n    Parameters\n    ----------\n    vmp_ref: numeric\n        Voltage at maximum power in reference conditions [V]\n    imp_ref: numeric\n        Current at maximum power in reference conditions [V]\n    dc_ohmic_percent: numeric, default 0\n        input dc loss as a percent, e.g. 1.5% loss is input as 1.5\n    modules_per_string: int, default 1\n        Number of modules per string in the array.\n    strings: int, default 1\n        Number of parallel strings in the array.\n\n    Returns\n    ----------\n    Rw: numeric\n        Equivalent resistance [ohm]\n\n    See Also\n    --------\n    pvlib.pvsystem.dc_ohmic_losses\n\n    References\n    ----------\n    .. [1] PVsyst 7 Help. \"Array ohmic wiring loss\".\n       https://www.pvsyst.com/help/ohmic_loss.htm\n    \"\"\"\n    vmp = modules_per_string * vmp_ref\n\n    imp = strings * imp_ref\n\n    Rw = (dc_ohmic_percent / 100) * (vmp / imp)\n\n    return Rw\n\n\ndef dc_ohmic_losses(resistance, current):\n    \"\"\"\n    Returns ohmic losses in units of power from the equivalent\n    resistance of the wires and the operating current.\n\n    Parameters\n    ----------\n    resistance: numeric\n        Equivalent resistance of wires [ohm]\n    current: numeric, float or array-like\n        Operating current [A]\n\n    Returns\n    ----------\n    loss: numeric\n        Power Loss [W]\n\n    See Also\n    --------\n    pvlib.pvsystem.dc_ohms_from_percent\n\n    References\n    ----------\n    .. [1] PVsyst 7 Help. \"Array ohmic wiring loss\".\n       https://www.pvsyst.com/help/ohmic_loss.htm\n    \"\"\"\n    return resistance * current * current\n\n\ndef combine_loss_factors(index, *losses, fill_method='ffill'):\n    r\"\"\"\n    Combines Series loss fractions while setting a common index.\n\n    The separate losses are compounded using the following equation:\n\n    .. math::\n\n        L_{total} = 1 - [ 1 - \\Pi_i ( 1 - L_i ) ]\n\n    :math:`L_{total}` is the total loss returned\n    :math:`L_i` is each individual loss factor input\n\n    Note the losses must each be a series with a DatetimeIndex.\n    All losses will be resampled to match the index parameter using\n    the fill method specified (defaults to \"fill forward\").\n\n    Parameters\n    ----------\n    index : DatetimeIndex\n        The index of the returned loss factors\n\n    *losses : Series\n        One or more Series of fractions to be compounded\n\n    fill_method : {'ffill', 'bfill', 'nearest'}, default 'ffill'\n        Method to use for filling holes in reindexed DataFrame\n\n    Returns\n    -------\n    Series\n        Fractions resulting from the combination of each loss factor\n    \"\"\"\n    combined_factor = 1\n\n    for loss in losses:\n        loss = loss.reindex(index, method=fill_method)\n        combined_factor *= (1 - loss)\n\n    return 1 - combined_factor\n"}
filelevel_patches: {"pvlib/pvsystem.py": "@@ -101,10 +101,11 @@ class PVSystem:\n \n     Parameters\n     ----------\n-    arrays : iterable of Array, optional\n-        List of arrays that are part of the system. If not specified\n-        a single array is created from the other parameters (e.g.\n-        `surface_tilt`, `surface_azimuth`). Must contain at least one Array,\n+    arrays : Array or iterable of Array, optional\n+        An Array or list of arrays that are part of the system. If not\n+        specified a single array is created from the other parameters (e.g.\n+        `surface_tilt`, `surface_azimuth`). If specified as a list, the list\n+        must contain at least one Array;\n         if length of arrays is 0 a ValueError is raised. If `arrays` is\n         specified the following PVSystem parameters are ignored:\n@@ -220,6 +221,8 @@ def __init__(self,\n                 strings_per_inverter,\n                 array_losses_parameters,\n             ),)\n+        elif isinstance(arrays, Array):\n+            self.arrays = (arrays,)\n         elif len(arrays) == 0:\n             raise ValueError(\"PVSystem must have at least one Array. \"\n                              \"If you want to create a PVSystem instance \""}
messages.content: 
  We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  PVSystem with single Array generates an error
  **Is your feature request related to a problem? Please describe.**
  
  When a PVSystem has a single Array, you can't assign just the Array instance when constructing the PVSystem.
  
  ```
  mount = pvlib.pvsystem.FixedMount(surface_tilt=35, surface_azimuth=180)
  array = pvlib.pvsystem.Array(mount=mount)
  pv = pvlib.pvsystem.PVSystem(arrays=array)
  
  ---------------------------------------------------------------------------
  TypeError                                 Traceback (most recent call last)
  <ipython-input-13-f5424e3db16a> in <module>
        3 mount = pvlib.pvsystem.FixedMount(surface_tilt=35, surface_azimuth=180)
        4 array = pvlib.pvsystem.Array(mount=mount)
  ----> 5 pv = pvlib.pvsystem.PVSystem(arrays=array)
  
  ~\anaconda3\lib\site-packages\pvlib\pvsystem.py in __init__(self, arrays, surface_tilt, surface_azimuth, albedo, surface_type, module, module_type, module_parameters, temperature_model_parameters, modules_per_string, strings_per_inverter, inverter, inverter_parameters, racking_model, losses_parameters, name)
      251                 array_losses_parameters,
      252             ),)
  --> 253         elif len(arrays) == 0:
      254             raise ValueError("PVSystem must have at least one Array. "
      255                              "If you want to create a PVSystem instance "
  
  TypeError: object of type 'Array' has no len()
  
  ```
  
  Not a bug per se, since the PVSystem docstring requests that `arrays` be iterable. Still, a bit inconvenient to have to do this
  
  ```
  mount = pvlib.pvsystem.FixedMount(surface_tilt=35, surface_azimuth=180)
  array = pvlib.pvsystem.Array(mount=mount)
  pv = pvlib.pvsystem.PVSystem(arrays=[array])
  ```
  
  **Describe the solution you'd like**
  Handle `arrays=array` where `array` is an instance of `Array`
  
  **Describe alternatives you've considered**
  Status quo - either make the single Array into a list, or use the PVSystem kwargs.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  
  --- BEGIN FILE ---
  ```
  ### pvlib/pvsystem.py
  """
  The ``pvsystem`` module contains functions for modeling the output and
  performance of PV modules and inverters.
  """
  
  from collections import OrderedDict
  import functools
  import io
  import itertools
  import os
  import inspect
  from urllib.request import urlopen
  import numpy as np
  from scipy import constants
  import pandas as pd
  from dataclasses import dataclass
  from abc import ABC, abstractmethod
  from typing import Optional
  
  from pvlib._deprecation import deprecated, warn_deprecated
  
  from pvlib import (atmosphere, iam, inverter, irradiance,
                     singlediode as _singlediode, spectrum, temperature)
  from pvlib.tools import _build_kwargs, _build_args
  import pvlib.tools as tools
  
  
  # a dict of required parameter names for each DC power model
  _DC_MODEL_PARAMS = {
      'sapm': {
          'A0', 'A1', 'A2', 'A3', 'A4', 'B0', 'B1', 'B2', 'B3',
          'B4', 'B5', 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6',
          'C7', 'Isco', 'Impo', 'Voco', 'Vmpo', 'Aisc', 'Aimp', 'Bvoco',
          'Mbvoc', 'Bvmpo', 'Mbvmp', 'N', 'Cells_in_Series',
          'IXO', 'IXXO', 'FD'},
      'desoto': {
          'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',
          'R_sh_ref', 'R_s'},
      'cec': {
          'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',
          'R_sh_ref', 'R_s', 'Adjust'},
      'pvsyst': {
          'gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref',
          'R_sh_ref', 'R_sh_0', 'R_s', 'alpha_sc', 'EgRef',
          'cells_in_series'},
      'singlediode': {
          'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',
          'R_sh_ref', 'R_s'},
      'pvwatts': {'pdc0', 'gamma_pdc'}
  }
  
  
  def _unwrap_single_value(func):
      """Decorator for functions that return iterables.
  
      If the length of the iterable returned by `func` is 1, then
      the single member of the iterable is returned. If the length is
      greater than 1, then entire iterable is returned.
  
      Adds 'unwrap' as a keyword argument that can be set to False
      to force the return value to be a tuple, regardless of its length.
      """
      @functools.wraps(func)
      def f(*args, **kwargs):
          unwrap = kwargs.pop('unwrap', True)
          x = func(*args, **kwargs)
          if unwrap and len(x) == 1:
              return x[0]
          return x
      return f
  
  
  # not sure if this belongs in the pvsystem module.
  # maybe something more like core.py? It may eventually grow to
  # import a lot more functionality from other modules.
  class PVSystem:
      """
      The PVSystem class defines a standard set of PV system attributes
      and modeling functions. This class describes the collection and
      interactions of PV system components rather than an installed system
      on the ground. It is typically used in combination with
      :py:class:`~pvlib.location.Location` and
      :py:class:`~pvlib.modelchain.ModelChain`
      objects.
  
      The class supports basic system topologies consisting of:
  
          * `N` total modules arranged in series
            (`modules_per_string=N`, `strings_per_inverter=1`).
          * `M` total modules arranged in parallel
            (`modules_per_string=1`, `strings_per_inverter=M`).
          * `NxM` total modules arranged in `M` strings of `N` modules each
            (`modules_per_string=N`, `strings_per_inverter=M`).
  
      The class is complementary to the module-level functions.
  
      The attributes should generally be things that don't change about
      the system, such the type of module and the inverter. The instance
      methods accept arguments for things that do change, such as
      irradiance and temperature.
  
      Parameters
      ----------
      arrays : iterable of Array, optional
          List of arrays that are part of the system. If not specified
          a single array is created from the other parameters (e.g.
          `surface_tilt`, `surface_azimuth`). Must contain at least one Array,
          if length of arrays is 0 a ValueError is raised. If `arrays` is
          specified the following PVSystem parameters are ignored:
  
          - `surface_tilt`
          - `surface_azimuth`
          - `albedo`
          - `surface_type`
          - `module`
          - `module_type`
          - `module_parameters`
          - `temperature_model_parameters`
          - `modules_per_string`
          - `strings_per_inverter`
  
      surface_tilt: float or array-like, default 0
          Surface tilt angles in decimal degrees.
          The tilt angle is defined as degrees from horizontal
          (e.g. surface facing up = 0, surface facing horizon = 90)
  
      surface_azimuth: float or array-like, default 180
          Azimuth angle of the module surface.
          North=0, East=90, South=180, West=270.
  
      albedo : None or float, default None
          Ground surface albedo. If ``None``, then ``surface_type`` is used
          to look up a value in ``irradiance.SURFACE_ALBEDOS``.
          If ``surface_type`` is also None then a ground surface albedo
          of 0.25 is used.
  
      surface_type : None or string, default None
          The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for
          valid values.
  
      module : None or string, default None
          The model name of the modules.
          May be used to look up the module_parameters dictionary
          via some other method.
  
      module_type : None or string, default 'glass_polymer'
           Describes the module's construction. Valid strings are 'glass_polymer'
           and 'glass_glass'. Used for cell and module temperature calculations.
  
      module_parameters : None, dict or Series, default None
          Module parameters as defined by the SAPM, CEC, or other.
  
      temperature_model_parameters : None, dict or Series, default None.
          Temperature model parameters as required by one of the models in
          pvlib.temperature (excluding poa_global, temp_air and wind_speed).
  
      modules_per_string: int or float, default 1
          See system topology discussion above.
  
      strings_per_inverter: int or float, default 1
          See system topology discussion above.
  
      inverter : None or string, default None
          The model name of the inverters.
          May be used to look up the inverter_parameters dictionary
          via some other method.
  
      inverter_parameters : None, dict or Series, default None
          Inverter parameters as defined by the SAPM, CEC, or other.
  
      racking_model : None or string, default 'open_rack'
          Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.
          Used to identify a parameter set for the SAPM cell temperature model.
  
      losses_parameters : None, dict or Series, default None
          Losses parameters as defined by PVWatts or other.
  
      name : None or string, default None
  
      **kwargs
          Arbitrary keyword arguments.
          Included for compatibility, but not used.
  
      Raises
      ------
      ValueError
          If `arrays` is not None and has length 0.
  
      See also
      --------
      pvlib.location.Location
      """
  
      def __init__(self,
                   arrays=None,
                   surface_tilt=0, surface_azimuth=180,
                   albedo=None, surface_type=None,
                   module=None, module_type=None,
                   module_parameters=None,
                   temperature_model_parameters=None,
                   modules_per_string=1, strings_per_inverter=1,
                   inverter=None, inverter_parameters=None,
                   racking_model=None, losses_parameters=None, name=None):
  
          if arrays is None:
              if losses_parameters is None:
                  array_losses_parameters = {}
              else:
                  array_losses_parameters = _build_kwargs(['dc_ohmic_percent'],
                                                          losses_parameters)
              self.arrays = (Array(
                  FixedMount(surface_tilt, surface_azimuth, racking_model),
                  albedo,
                  surface_type,
                  module,
                  module_type,
                  module_parameters,
                  temperature_model_parameters,
                  modules_per_string,
                  strings_per_inverter,
                  array_losses_parameters,
              ),)
          elif len(arrays) == 0:
              raise ValueError("PVSystem must have at least one Array. "
                               "If you want to create a PVSystem instance "
                               "with a single Array pass `arrays=None` and pass "
                               "values directly to PVSystem attributes, e.g., "
                               "`surface_tilt=30`")
          else:
              self.arrays = tuple(arrays)
  
          self.inverter = inverter
          if inverter_parameters is None:
              self.inverter_parameters = {}
          else:
              self.inverter_parameters = inverter_parameters
  
          if losses_parameters is None:
              self.losses_parameters = {}
          else:
              self.losses_parameters = losses_parameters
  
          self.name = name
  
      def __repr__(self):
          repr = f'PVSystem:\n  name: {self.name}\n  '
          for array in self.arrays:
              repr += '\n  '.join(array.__repr__().split('\n'))
              repr += '\n  '
          repr += f'inverter: {self.inverter}'
          return repr
  
      def _validate_per_array(self, values, system_wide=False):
          """Check that `values` is a tuple of the same length as
          `self.arrays`.
  
          If `values` is not a tuple it is packed in to a length-1 tuple before
          the check. If the lengths are not the same a ValueError is raised,
          otherwise the tuple `values` is returned.
  
          When `system_wide` is True and `values` is not a tuple, `values`
          is replicated to a tuple of the same length as `self.arrays` and that
          tuple is returned.
          """
          if system_wide and not isinstance(values, tuple):
              return (values,) * self.num_arrays
          if not isinstance(values, tuple):
              values = (values,)
          if len(values) != len(self.arrays):
              raise ValueError("Length mismatch for per-array parameter")
          return values
  
      @_unwrap_single_value
      def _infer_cell_type(self):
          """
          Examines module_parameters and maps the Technology key for the CEC
          database and the Material key for the Sandia database to a common
          list of strings for cell type.
  
          Returns
          -------
          cell_type: str
          """
          return tuple(array._infer_cell_type() for array in self.arrays)
  
      @_unwrap_single_value
      def get_aoi(self, solar_zenith, solar_azimuth):
          """Get the angle of incidence on the Array(s) in the system.
  
          Parameters
          ----------
          solar_zenith : float or Series.
              Solar zenith angle.
          solar_azimuth : float or Series.
              Solar azimuth angle.
  
          Returns
          -------
          aoi : Series or tuple of Series
              The angle of incidence
          """
  
          return tuple(array.get_aoi(solar_zenith, solar_azimuth)
                       for array in self.arrays)
  
      @_unwrap_single_value
      def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
                         dni_extra=None, airmass=None, albedo=None,
                         model='haydavies', **kwargs):
          """
          Uses the :py:func:`irradiance.get_total_irradiance` function to
          calculate the plane of array irradiance components on the tilted
          surfaces defined by each array's ``surface_tilt`` and
          ``surface_azimuth``.
  
          Parameters
          ----------
          solar_zenith : float or Series
              Solar zenith angle.
          solar_azimuth : float or Series
              Solar azimuth angle.
          dni : float or Series or tuple of float or Series
              Direct Normal Irradiance. [W/m2]
          ghi : float or Series or tuple of float or Series
              Global horizontal irradiance. [W/m2]
          dhi : float or Series or tuple of float or Series
              Diffuse horizontal irradiance. [W/m2]
          dni_extra : None, float, Series or tuple of float or Series,\
              default None
              Extraterrestrial direct normal irradiance. [W/m2]
          airmass : None, float or Series, default None
              Airmass. [unitless]
          albedo : None, float or Series, default None
              Ground surface albedo. [unitless]
          model : String, default 'haydavies'
              Irradiance model.
  
          kwargs
              Extra parameters passed to :func:`irradiance.get_total_irradiance`.
  
          Notes
          -----
          Each of `dni`, `ghi`, and `dni` parameters may be passed as a tuple
          to provide different irradiance for each array in the system. If not
          passed as a tuple then the same value is used for input to each Array.
          If passed as a tuple the length must be the same as the number of
          Arrays.
  
          Returns
          -------
          poa_irradiance : DataFrame or tuple of DataFrame
              Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse',
              'poa_sky_diffuse', 'poa_ground_diffuse'``.
  
          See also
          --------
          pvlib.irradiance.get_total_irradiance
          """
          dni = self._validate_per_array(dni, system_wide=True)
          ghi = self._validate_per_array(ghi, system_wide=True)
          dhi = self._validate_per_array(dhi, system_wide=True)
  
          albedo = self._validate_per_array(albedo, system_wide=True)
  
          return tuple(
              array.get_irradiance(solar_zenith, solar_azimuth,
                                   dni, ghi, dhi,
                                   dni_extra=dni_extra, airmass=airmass,
                                   albedo=albedo, model=model, **kwargs)
              for array, dni, ghi, dhi, albedo in zip(
                  self.arrays, dni, ghi, dhi, albedo
              )
          )
  
      @_unwrap_single_value
      def get_iam(self, aoi, iam_model='physical'):
          """
          Determine the incidence angle modifier using the method specified by
          ``iam_model``.
  
          Parameters for the selected IAM model are expected to be in
          ``PVSystem.module_parameters``. Default parameters are available for
          the 'physical', 'ashrae' and 'martin_ruiz' models.
  
          Parameters
          ----------
          aoi : numeric or tuple of numeric
              The angle of incidence in degrees.
  
          aoi_model : string, default 'physical'
              The IAM model to be used. Valid strings are 'physical', 'ashrae',
              'martin_ruiz', 'sapm' and 'interp'.
          Returns
          -------
          iam : numeric or tuple of numeric
              The AOI modifier.
  
          Raises
          ------
          ValueError
              if `iam_model` is not a valid model name.
          """
          aoi = self._validate_per_array(aoi)
          return tuple(array.get_iam(aoi, iam_model)
                       for array, aoi in zip(self.arrays, aoi))
  
      @_unwrap_single_value
      def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,
                               effective_irradiance=None):
          """
          Determine cell temperature using the method specified by ``model``.
  
          Parameters
          ----------
          poa_global : numeric or tuple of numeric
              Total incident irradiance in W/m^2.
  
          temp_air : numeric or tuple of numeric
              Ambient dry bulb temperature in degrees C.
  
          wind_speed : numeric or tuple of numeric
              Wind speed in m/s.
  
          model : str
              Supported models include ``'sapm'``, ``'pvsyst'``,
              ``'faiman'``, ``'fuentes'``, and ``'noct_sam'``
  
          effective_irradiance : numeric or tuple of numeric, optional
              The irradiance that is converted to photocurrent in W/m^2.
              Only used for some models.
  
          Returns
          -------
          numeric or tuple of numeric
              Values in degrees C.
  
          See Also
          --------
          Array.get_cell_temperature
  
          Notes
          -----
          The `temp_air` and `wind_speed` parameters may be passed as tuples
          to provide different values for each Array in the system. If passed as
          a tuple the length must be the same as the number of Arrays. If not
          passed as a tuple then the same value is used for each Array.
          """
          poa_global = self._validate_per_array(poa_global)
          temp_air = self._validate_per_array(temp_air, system_wide=True)
          wind_speed = self._validate_per_array(wind_speed, system_wide=True)
          # Not used for all models, but Array.get_cell_temperature handles it
          effective_irradiance = self._validate_per_array(effective_irradiance,
                                                          system_wide=True)
  
          return tuple(
              array.get_cell_temperature(poa_global, temp_air, wind_speed,
                                         model, effective_irradiance)
              for array, poa_global, temp_air, wind_speed, effective_irradiance
              in zip(
                  self.arrays, poa_global, temp_air, wind_speed,
                  effective_irradiance
              )
          )
  
      @_unwrap_single_value
      def calcparams_desoto(self, effective_irradiance, temp_cell):
          """
          Use the :py:func:`calcparams_desoto` function, the input
          parameters and ``self.module_parameters`` to calculate the
          module currents and resistances.
  
          Parameters
          ----------
          effective_irradiance : numeric or tuple of numeric
              The irradiance (W/m2) that is converted to photocurrent.
  
          temp_cell : float or Series or tuple of float or Series
              The average cell temperature of cells within a module in C.
  
          Returns
          -------
          See pvsystem.calcparams_desoto for details
          """
          effective_irradiance = self._validate_per_array(effective_irradiance)
          temp_cell = self._validate_per_array(temp_cell)
  
          build_kwargs = functools.partial(
              _build_kwargs,
              ['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',
               'R_s', 'alpha_sc', 'EgRef', 'dEgdT',
               'irrad_ref', 'temp_ref']
          )
  
          return tuple(
              calcparams_desoto(
                  effective_irradiance, temp_cell,
                  **build_kwargs(array.module_parameters)
              )
              for array, effective_irradiance, temp_cell
              in zip(self.arrays, effective_irradiance, temp_cell)
          )
  
      @_unwrap_single_value
      def calcparams_cec(self, effective_irradiance, temp_cell):
          """
          Use the :py:func:`calcparams_cec` function, the input
          parameters and ``self.module_parameters`` to calculate the
          module currents and resistances.
  
          Parameters
          ----------
          effective_irradiance : numeric or tuple of numeric
              The irradiance (W/m2) that is converted to photocurrent.
  
          temp_cell : float or Series or tuple of float or Series
              The average cell temperature of cells within a module in C.
  
          Returns
          -------
          See pvsystem.calcparams_cec for details
          """
          effective_irradiance = self._validate_per_array(effective_irradiance)
          temp_cell = self._validate_per_array(temp_cell)
  
          build_kwargs = functools.partial(
              _build_kwargs,
              ['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',
               'R_s', 'alpha_sc', 'Adjust', 'EgRef', 'dEgdT',
               'irrad_ref', 'temp_ref']
          )
  
          return tuple(
              calcparams_cec(
                  effective_irradiance, temp_cell,
                  **build_kwargs(array.module_parameters)
              )
              for array, effective_irradiance, temp_cell
              in zip(self.arrays, effective_irradiance, temp_cell)
          )
  
      @_unwrap_single_value
      def calcparams_pvsyst(self, effective_irradiance, temp_cell):
          """
          Use the :py:func:`calcparams_pvsyst` function, the input
          parameters and ``self.module_parameters`` to calculate the
          module currents and resistances.
  
          Parameters
          ----------
          effective_irradiance : numeric or tuple of numeric
              The irradiance (W/m2) that is converted to photocurrent.
  
          temp_cell : float or Series or tuple of float or Series
              The average cell temperature of cells within a module in C.
  
          Returns
          -------
          See pvsystem.calcparams_pvsyst for details
          """
          effective_irradiance = self._validate_per_array(effective_irradiance)
          temp_cell = self._validate_per_array(temp_cell)
  
          build_kwargs = functools.partial(
              _build_kwargs,
              ['gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref',
               'R_sh_ref', 'R_sh_0', 'R_sh_exp',
               'R_s', 'alpha_sc', 'EgRef',
               'irrad_ref', 'temp_ref',
               'cells_in_series']
          )
  
          return tuple(
              calcparams_pvsyst(
                  effective_irradiance, temp_cell,
                  **build_kwargs(array.module_parameters)
              )
              for array, effective_irradiance, temp_cell
              in zip(self.arrays, effective_irradiance, temp_cell)
          )
  
      @_unwrap_single_value
      def sapm(self, effective_irradiance, temp_cell):
          """
          Use the :py:func:`sapm` function, the input parameters,
          and ``self.module_parameters`` to calculate
          Voc, Isc, Ix, Ixx, Vmp, and Imp.
  
          Parameters
          ----------
          effective_irradiance : numeric or tuple of numeric
              The irradiance (W/m2) that is converted to photocurrent.
  
          temp_cell : float or Series or tuple of float or Series
              The average cell temperature of cells within a module in C.
  
          Returns
          -------
          See pvsystem.sapm for details
          """
          effective_irradiance = self._validate_per_array(effective_irradiance)
          temp_cell = self._validate_per_array(temp_cell)
  
          return tuple(
              sapm(effective_irradiance, temp_cell, array.module_parameters)
              for array, effective_irradiance, temp_cell
              in zip(self.arrays, effective_irradiance, temp_cell)
          )
  
      @_unwrap_single_value
      def sapm_spectral_loss(self, airmass_absolute):
          """
          Use the :py:func:`pvlib.spectrum.spectral_factor_sapm` function,
          the input parameters, and ``self.module_parameters`` to calculate F1.
  
          Parameters
          ----------
          airmass_absolute : numeric
              Absolute airmass.
  
          Returns
          -------
          F1 : numeric or tuple of numeric
              The SAPM spectral loss coefficient.
          """
          return tuple(
              spectrum.spectral_factor_sapm(airmass_absolute,
                                            array.module_parameters)
              for array in self.arrays
          )
  
      @_unwrap_single_value
      def sapm_effective_irradiance(self, poa_direct, poa_diffuse,
                                    airmass_absolute, aoi,
                                    reference_irradiance=1000):
          """
          Use the :py:func:`sapm_effective_irradiance` function, the input
          parameters, and ``self.module_parameters`` to calculate
          effective irradiance.
  
          Parameters
          ----------
          poa_direct : numeric or tuple of numeric
              The direct irradiance incident upon the module.  [W/m2]
  
          poa_diffuse : numeric or tuple of numeric
              The diffuse irradiance incident on module.  [W/m2]
  
          airmass_absolute : numeric
              Absolute airmass. [unitless]
  
          aoi : numeric or tuple of numeric
              Angle of incidence. [degrees]
  
          Returns
          -------
          effective_irradiance : numeric or tuple of numeric
              The SAPM effective irradiance. [W/m2]
          """
          poa_direct = self._validate_per_array(poa_direct)
          poa_diffuse = self._validate_per_array(poa_diffuse)
          aoi = self._validate_per_array(aoi)
          return tuple(
              sapm_effective_irradiance(
                  poa_direct, poa_diffuse, airmass_absolute, aoi,
                  array.module_parameters)
              for array, poa_direct, poa_diffuse, aoi
              in zip(self.arrays, poa_direct, poa_diffuse, aoi)
          )
  
      @_unwrap_single_value
      def first_solar_spectral_loss(self, pw, airmass_absolute):
          """
          Use :py:func:`pvlib.spectrum.spectral_factor_firstsolar` to
          calculate the spectral loss modifier. The model coefficients are
          specific to the module's cell type, and are determined by searching
          for one of the following keys in self.module_parameters (in order):
  
          - 'first_solar_spectral_coefficients' (user-supplied coefficients)
          - 'Technology' - a string describing the cell type, can be read from
            the CEC module parameter database
          - 'Material' - a string describing the cell type, can be read from
            the Sandia module database.
  
          Parameters
          ----------
          pw : array-like
              atmospheric precipitable water (cm).
  
          airmass_absolute : array-like
              absolute (pressure corrected) airmass.
  
          Returns
          -------
          modifier: array-like or tuple of array-like
              spectral mismatch factor (unitless) which can be multiplied
              with broadband irradiance reaching a module's cells to estimate
              effective irradiance, i.e., the irradiance that is converted to
              electrical current.
          """
          pw = self._validate_per_array(pw, system_wide=True)
  
          def _spectral_correction(array, pw):
              if 'first_solar_spectral_coefficients' in \
                      array.module_parameters.keys():
                  coefficients = \
                      array.module_parameters[
                          'first_solar_spectral_coefficients'
                      ]
                  module_type = None
              else:
                  module_type = array._infer_cell_type()
                  coefficients = None
  
              return spectrum.spectral_factor_firstsolar(
                  pw, airmass_absolute, module_type, coefficients
              )
          return tuple(
              itertools.starmap(_spectral_correction, zip(self.arrays, pw))
          )
  
      def singlediode(self, photocurrent, saturation_current,
                      resistance_series, resistance_shunt, nNsVth,
                      ivcurve_pnts=None):
          """Wrapper around the :py:func:`pvlib.pvsystem.singlediode` function.
  
          See :py:func:`pvsystem.singlediode` for details
          """
          return singlediode(photocurrent, saturation_current,
                             resistance_series, resistance_shunt, nNsVth,
                             ivcurve_pnts=ivcurve_pnts)
  
      def i_from_v(self, voltage, photocurrent, saturation_current,
                   resistance_series, resistance_shunt, nNsVth):
          """Wrapper around the :py:func:`pvlib.pvsystem.i_from_v` function.
  
          See :py:func:`pvlib.pvsystem.i_from_v` for details.
  
          .. versionchanged:: 0.10.0
             The function's arguments have been reordered.
          """
          return i_from_v(voltage, photocurrent, saturation_current,
                          resistance_series, resistance_shunt, nNsVth)
  
      def get_ac(self, model, p_dc, v_dc=None):
          r"""Calculates AC power from p_dc using the inverter model indicated
          by model and self.inverter_parameters.
  
          Parameters
          ----------
          model : str
              Must be one of 'sandia', 'adr', or 'pvwatts'.
          p_dc : numeric, or tuple, list or array of numeric
              DC power on each MPPT input of the inverter. Use tuple, list or
              array for inverters with multiple MPPT inputs. If type is array,
              p_dc must be 2d with axis 0 being the MPPT inputs. [W]
          v_dc : numeric, or tuple, list or array of numeric
              DC voltage on each MPPT input of the inverter. Required when
              model='sandia' or model='adr'. Use tuple, list or
              array for inverters with multiple MPPT inputs. If type is array,
              v_dc must be 2d with axis 0 being the MPPT inputs. [V]
  
          Returns
          -------
          power_ac : numeric
              AC power output for the inverter. [W]
  
          Raises
          ------
          ValueError
              If model is not one of 'sandia', 'adr' or 'pvwatts'.
          ValueError
              If model='adr' and the PVSystem has more than one array.
  
          See also
          --------
          pvlib.inverter.sandia
          pvlib.inverter.sandia_multi
          pvlib.inverter.adr
          pvlib.inverter.pvwatts
          pvlib.inverter.pvwatts_multi
          """
          model = model.lower()
          multiple_arrays = self.num_arrays > 1
          if model == 'sandia':
              p_dc = self._validate_per_array(p_dc)
              v_dc = self._validate_per_array(v_dc)
              if multiple_arrays:
                  return inverter.sandia_multi(
                      v_dc, p_dc, self.inverter_parameters)
              return inverter.sandia(v_dc[0], p_dc[0], self.inverter_parameters)
          elif model == 'pvwatts':
              kwargs = _build_kwargs(['eta_inv_nom', 'eta_inv_ref'],
                                     self.inverter_parameters)
              p_dc = self._validate_per_array(p_dc)
              if multiple_arrays:
                  return inverter.pvwatts_multi(
                      p_dc, self.inverter_parameters['pdc0'], **kwargs)
              return inverter.pvwatts(
                  p_dc[0], self.inverter_parameters['pdc0'], **kwargs)
          elif model == 'adr':
              if multiple_arrays:
                  raise ValueError(
                      'The adr inverter function cannot be used for an inverter',
                      ' with multiple MPPT inputs')
              # While this is only used for single-array systems, calling
              # _validate_per_arry lets us pass in singleton tuples.
              p_dc = self._validate_per_array(p_dc)
              v_dc = self._validate_per_array(v_dc)
              return inverter.adr(v_dc[0], p_dc[0], self.inverter_parameters)
          else:
              raise ValueError(
                  model + ' is not a valid AC power model.',
                  ' model must be one of "sandia", "adr" or "pvwatts"')
  
      @_unwrap_single_value
      def scale_voltage_current_power(self, data):
          """
          Scales the voltage, current, and power of the `data` DataFrame
          by `self.modules_per_string` and `self.strings_per_inverter`.
  
          Parameters
          ----------
          data: DataFrame or tuple of DataFrame
              May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',
              'i_sc', 'p_mp'`.
  
          Returns
          -------
          scaled_data: DataFrame or tuple of DataFrame
              A scaled copy of the input data.
          """
          data = self._validate_per_array(data)
          return tuple(
              scale_voltage_current_power(data,
                                          voltage=array.modules_per_string,
                                          current=array.strings)
              for array, data in zip(self.arrays, data)
          )
  
      @_unwrap_single_value
      def pvwatts_dc(self, g_poa_effective, temp_cell):
          """
          Calcuates DC power according to the PVWatts model using
          :py:func:`pvlib.pvsystem.pvwatts_dc`, `self.module_parameters['pdc0']`,
          and `self.module_parameters['gamma_pdc']`.
  
          See :py:func:`pvlib.pvsystem.pvwatts_dc` for details.
          """
          g_poa_effective = self._validate_per_array(g_poa_effective)
          temp_cell = self._validate_per_array(temp_cell)
          return tuple(
              pvwatts_dc(g_poa_effective, temp_cell,
                         array.module_parameters['pdc0'],
                         array.module_parameters['gamma_pdc'],
                         **_build_kwargs(['temp_ref'], array.module_parameters))
              for array, g_poa_effective, temp_cell
              in zip(self.arrays, g_poa_effective, temp_cell)
          )
  
      def pvwatts_losses(self):
          """
          Calculates DC power losses according the PVwatts model using
          :py:func:`pvlib.pvsystem.pvwatts_losses` and
          ``self.losses_parameters``.
  
          See :py:func:`pvlib.pvsystem.pvwatts_losses` for details.
          """
          kwargs = _build_kwargs(['soiling', 'shading', 'snow', 'mismatch',
                                  'wiring', 'connections', 'lid',
                                  'nameplate_rating', 'age', 'availability'],
                                 self.losses_parameters)
          return pvwatts_losses(**kwargs)
  
      @_unwrap_single_value
      def dc_ohms_from_percent(self):
          """
          Calculates the equivalent resistance of the wires for each array using
          :py:func:`pvlib.pvsystem.dc_ohms_from_percent`
  
          See :py:func:`pvlib.pvsystem.dc_ohms_from_percent` for details.
          """
  
          return tuple(array.dc_ohms_from_percent() for array in self.arrays)
  
      @property
      def num_arrays(self):
          """The number of Arrays in the system."""
          return len(self.arrays)
  
  
  class Array:
      """
      An Array is a set of of modules at the same orientation.
  
      Specifically, an array is defined by its mount, the
      module parameters, the number of parallel strings of modules
      and the number of modules on each string.
  
      Parameters
      ----------
      mount: FixedMount, SingleAxisTrackerMount, or other
          Mounting for the array, either on fixed-tilt racking or horizontal
          single axis tracker. Mounting is used to determine module orientation.
          If not provided, a FixedMount with zero tilt is used.
  
      albedo : None or float, default None
          Ground surface albedo. If ``None``, then ``surface_type`` is used
          to look up a value in ``irradiance.SURFACE_ALBEDOS``.
          If ``surface_type`` is also None then a ground surface albedo
          of 0.25 is used.
  
      surface_type : None or string, default None
          The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for valid
          values.
  
      module : None or string, default None
          The model name of the modules.
          May be used to look up the module_parameters dictionary
          via some other method.
  
      module_type : None or string, default None
           Describes the module's construction. Valid strings are 'glass_polymer'
           and 'glass_glass'. Used for cell and module temperature calculations.
  
      module_parameters : None, dict or Series, default None
          Parameters for the module model, e.g., SAPM, CEC, or other.
  
      temperature_model_parameters : None, dict or Series, default None.
          Parameters for the module temperature model, e.g., SAPM, Pvsyst, or
          other.
  
      modules_per_string: int, default 1
          Number of modules per string in the array.
  
      strings: int, default 1
          Number of parallel strings in the array.
  
      array_losses_parameters: None, dict or Series, default None.
          Supported keys are 'dc_ohmic_percent'.
  
      name: None or str, default None
          Name of Array instance.
      """
  
      def __init__(self, mount,
                   albedo=None, surface_type=None,
                   module=None, module_type=None,
                   module_parameters=None,
                   temperature_model_parameters=None,
                   modules_per_string=1, strings=1,
                   array_losses_parameters=None,
                   name=None):
          self.mount = mount
  
          self.surface_type = surface_type
          if albedo is None:
              self.albedo = irradiance.SURFACE_ALBEDOS.get(surface_type, 0.25)
          else:
              self.albedo = albedo
  
          self.module = module
          if module_parameters is None:
              self.module_parameters = {}
          else:
              self.module_parameters = module_parameters
  
          self.module_type = module_type
  
          self.strings = strings
          self.modules_per_string = modules_per_string
  
          if temperature_model_parameters is None:
              self.temperature_model_parameters = \
                  self._infer_temperature_model_params()
          else:
              self.temperature_model_parameters = temperature_model_parameters
  
          if array_losses_parameters is None:
              self.array_losses_parameters = {}
          else:
              self.array_losses_parameters = array_losses_parameters
  
          self.name = name
  
      def __repr__(self):
          attrs = ['name', 'mount', 'module',
                   'albedo', 'module_type',
                   'temperature_model_parameters',
                   'strings', 'modules_per_string']
  
          return 'Array:\n  ' + '\n  '.join(
              f'{attr}: {getattr(self, attr)}' for attr in attrs
          )
  
      def _infer_temperature_model_params(self):
          # try to infer temperature model parameters from from racking_model
          # and module_type
          param_set = f'{self.mount.racking_model}_{self.module_type}'
          if param_set in temperature.TEMPERATURE_MODEL_PARAMETERS['sapm']:
              return temperature._temperature_model_params('sapm', param_set)
          elif 'freestanding' in param_set:
              return temperature._temperature_model_params('pvsyst',
                                                           'freestanding')
          elif 'insulated' in param_set:  # after SAPM to avoid confusing keys
              return temperature._temperature_model_params('pvsyst',
                                                           'insulated')
          else:
              return {}
  
      def _infer_cell_type(self):
          """
          Examines module_parameters and maps the Technology key for the CEC
          database and the Material key for the Sandia database to a common
          list of strings for cell type.
  
          Returns
          -------
          cell_type: str
  
          """
  
          _cell_type_dict = {'Multi-c-Si': 'multisi',
                             'Mono-c-Si': 'monosi',
                             'Thin Film': 'cigs',
                             'a-Si/nc': 'asi',
                             'CIS': 'cigs',
                             'CIGS': 'cigs',
                             '1-a-Si': 'asi',
                             'CdTe': 'cdte',
                             'a-Si': 'asi',
                             '2-a-Si': None,
                             '3-a-Si': None,
                             'HIT-Si': 'monosi',
                             'mc-Si': 'multisi',
                             'c-Si': 'multisi',
                             'Si-Film': 'asi',
                             'EFG mc-Si': 'multisi',
                             'GaAs': None,
                             'a-Si / mono-Si': 'monosi'}
  
          if 'Technology' in self.module_parameters.keys():
              # CEC module parameter set
              cell_type = _cell_type_dict[self.module_parameters['Technology']]
          elif 'Material' in self.module_parameters.keys():
              # Sandia module parameter set
              cell_type = _cell_type_dict[self.module_parameters['Material']]
          else:
              cell_type = None
  
          return cell_type
  
      def get_aoi(self, solar_zenith, solar_azimuth):
          """
          Get the angle of incidence on the array.
  
          Parameters
          ----------
          solar_zenith : float or Series
              Solar zenith angle.
          solar_azimuth : float or Series
              Solar azimuth angle
  
          Returns
          -------
          aoi : Series
              Then angle of incidence.
          """
          orientation = self.mount.get_orientation(solar_zenith, solar_azimuth)
          return irradiance.aoi(orientation['surface_tilt'],
                                orientation['surface_azimuth'],
                                solar_zenith, solar_azimuth)
  
      def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
                         dni_extra=None, airmass=None, albedo=None,
                         model='haydavies', **kwargs):
          """
          Get plane of array irradiance components.
  
          Uses the :py:func:`pvlib.irradiance.get_total_irradiance` function to
          calculate the plane of array irradiance components for a surface
          defined by ``self.surface_tilt`` and ``self.surface_azimuth``.
  
          Parameters
          ----------
          solar_zenith : float or Series.
              Solar zenith angle.
          solar_azimuth : float or Series.
              Solar azimuth angle.
          dni : float or Series
              Direct normal irradiance. [W/m2]
          ghi : float or Series. [W/m2]
              Global horizontal irradiance
          dhi : float or Series
              Diffuse horizontal irradiance. [W/m2]
          dni_extra : None, float or Series, default None
              Extraterrestrial direct normal irradiance. [W/m2]
          airmass : None, float or Series, default None
              Airmass. [unitless]
          albedo : None, float or Series, default None
              Ground surface albedo. [unitless]
          model : String, default 'haydavies'
              Irradiance model.
  
          kwargs
              Extra parameters passed to
              :py:func:`pvlib.irradiance.get_total_irradiance`.
  
          Returns
          -------
          poa_irradiance : DataFrame
              Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse',
              'poa_sky_diffuse', 'poa_ground_diffuse'``.
  
          See also
          --------
          :py:func:`pvlib.irradiance.get_total_irradiance`
          """
          if albedo is None:
              albedo = self.albedo
  
          # not needed for all models, but this is easier
          if dni_extra is None:
              dni_extra = irradiance.get_extra_radiation(solar_zenith.index)
  
          if airmass is None:
              airmass = atmosphere.get_relative_airmass(solar_zenith)
  
          orientation = self.mount.get_orientation(solar_zenith, solar_azimuth)
          return irradiance.get_total_irradiance(orientation['surface_tilt'],
                                                 orientation['surface_azimuth'],
                                                 solar_zenith, solar_azimuth,
                                                 dni, ghi, dhi,
                                                 dni_extra=dni_extra,
                                                 airmass=airmass,
                                                 albedo=albedo,
                                                 model=model,
                                                 **kwargs)
  
      def get_iam(self, aoi, iam_model='physical'):
          """
          Determine the incidence angle modifier using the method specified by
          ``iam_model``.
  
          Parameters for the selected IAM model are expected to be in
          ``Array.module_parameters``. Default parameters are available for
          the 'physical', 'ashrae' and 'martin_ruiz' models.
  
          Parameters
          ----------
          aoi : numeric
              The angle of incidence in degrees.
  
          aoi_model : string, default 'physical'
              The IAM model to be used. Valid strings are 'physical', 'ashrae',
              'martin_ruiz', 'sapm' and 'interp'.
  
          Returns
          -------
          iam : numeric
              The AOI modifier.
  
          Raises
          ------
          ValueError
              if `iam_model` is not a valid model name.
          """
          model = iam_model.lower()
          if model in ['ashrae', 'physical', 'martin_ruiz', 'interp']:
              func = getattr(iam, model)  # get function at pvlib.iam
              # get all parameters from function signature to retrieve them from
              # module_parameters if present
              params = set(inspect.signature(func).parameters.keys())
              params.discard('aoi')  # exclude aoi so it can't be repeated
              kwargs = _build_kwargs(params, self.module_parameters)
              return func(aoi, **kwargs)
          elif model == 'sapm':
              return iam.sapm(aoi, self.module_parameters)
          else:
              raise ValueError(model + ' is not a valid IAM model')
  
      def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,
                               effective_irradiance=None):
          """
          Determine cell temperature using the method specified by ``model``.
  
          Parameters
          ----------
          poa_global : numeric
              Total incident irradiance [W/m^2]
  
          temp_air : numeric
              Ambient dry bulb temperature [C]
  
          wind_speed : numeric
              Wind speed [m/s]
  
          model : str
              Supported models include ``'sapm'``, ``'pvsyst'``,
              ``'faiman'``, ``'fuentes'``, and ``'noct_sam'``
  
          effective_irradiance : numeric, optional
              The irradiance that is converted to photocurrent in W/m^2.
              Only used for some models.
  
          Returns
          -------
          numeric
              Values in degrees C.
  
          See Also
          --------
          pvlib.temperature.sapm_cell, pvlib.temperature.pvsyst_cell,
          pvlib.temperature.faiman, pvlib.temperature.fuentes,
          pvlib.temperature.noct_sam
  
          Notes
          -----
          Some temperature models have requirements for the input types;
          see the documentation of the underlying model function for details.
          """
          # convenience wrapper to avoid passing args 2 and 3 every call
          _build_tcell_args = functools.partial(
              _build_args, input_dict=self.temperature_model_parameters,
              dict_name='temperature_model_parameters')
  
          if model == 'sapm':
              func = temperature.sapm_cell
              required = _build_tcell_args(['a', 'b', 'deltaT'])
              optional = _build_kwargs(['irrad_ref'],
                                       self.temperature_model_parameters)
          elif model == 'pvsyst':
              func = temperature.pvsyst_cell
              required = tuple()
              optional = {
                  **_build_kwargs(['module_efficiency', 'alpha_absorption'],
                                  self.module_parameters),
                  **_build_kwargs(['u_c', 'u_v'],
                                  self.temperature_model_parameters)
              }
          elif model == 'faiman':
              func = temperature.faiman
              required = tuple()
              optional = _build_kwargs(['u0', 'u1'],
                                       self.temperature_model_parameters)
          elif model == 'fuentes':
              func = temperature.fuentes
              required = _build_tcell_args(['noct_installed'])
              optional = _build_kwargs([
                  'wind_height', 'emissivity', 'absorption',
                  'surface_tilt', 'module_width', 'module_length'],
                  self.temperature_model_parameters)
              if self.mount.module_height is not None:
                  optional['module_height'] = self.mount.module_height
          elif model == 'noct_sam':
              func = functools.partial(temperature.noct_sam,
                                       effective_irradiance=effective_irradiance)
              required = _build_tcell_args(['noct', 'module_efficiency'])
              optional = _build_kwargs(['transmittance_absorptance',
                                        'array_height', 'mount_standoff'],
                                       self.temperature_model_parameters)
          else:
              raise ValueError(f'{model} is not a valid cell temperature model')
  
          temperature_cell = func(poa_global, temp_air, wind_speed,
                                  *required, **optional)
          return temperature_cell
  
      def dc_ohms_from_percent(self):
          """
          Calculates the equivalent resistance of the wires using
          :py:func:`pvlib.pvsystem.dc_ohms_from_percent`
  
          Makes use of array module parameters according to the
          following DC models:
  
          CEC:
  
              * `self.module_parameters["V_mp_ref"]`
              * `self.module_parameters["I_mp_ref"]`
  
          SAPM:
  
              * `self.module_parameters["Vmpo"]`
              * `self.module_parameters["Impo"]`
  
          PVsyst-like or other:
  
              * `self.module_parameters["Vmpp"]`
              * `self.module_parameters["Impp"]`
  
          Other array parameters that are used are:
          `self.losses_parameters["dc_ohmic_percent"]`,
          `self.modules_per_string`, and
          `self.strings`.
  
          See :py:func:`pvlib.pvsystem.dc_ohms_from_percent` for more details.
          """
  
          # get relevent Vmp and Imp parameters from CEC parameters
          if all(elem in self.module_parameters
                 for elem in ['V_mp_ref', 'I_mp_ref']):
              vmp_ref = self.module_parameters['V_mp_ref']
              imp_ref = self.module_parameters['I_mp_ref']
  
          # get relevant Vmp and Imp parameters from SAPM parameters
          elif all(elem in self.module_parameters for elem in ['Vmpo', 'Impo']):
              vmp_ref = self.module_parameters['Vmpo']
              imp_ref = self.module_parameters['Impo']
  
          # get relevant Vmp and Imp parameters if they are PVsyst-like
          elif all(elem in self.module_parameters for elem in ['Vmpp', 'Impp']):
              vmp_ref = self.module_parameters['Vmpp']
              imp_ref = self.module_parameters['Impp']
  
          # raise error if relevant Vmp and Imp parameters are not found
          else:
              raise ValueError('Parameters for Vmp and Imp could not be found '
                               'in the array module parameters. Module '
                               'parameters must include one set of '
                               '{"V_mp_ref", "I_mp_Ref"}, '
                               '{"Vmpo", "Impo"}, or '
                               '{"Vmpp", "Impp"}.'
                               )
  
          return dc_ohms_from_percent(
              vmp_ref,
              imp_ref,
              self.array_losses_parameters['dc_ohmic_percent'],
              self.modules_per_string,
              self.strings)
  
  
  @dataclass
  class AbstractMount(ABC):
      """
      A base class for Mount classes to extend. It is not intended to be
      instantiated directly.
      """
  
      @abstractmethod
      def get_orientation(self, solar_zenith, solar_azimuth):
          """
          Determine module orientation.
  
          Parameters
          ----------
          solar_zenith : numeric
              Solar apparent zenith angle [degrees]
          solar_azimuth : numeric
              Solar azimuth angle [degrees]
  
          Returns
          -------
          orientation : dict-like
              A dict-like object with keys `'surface_tilt', 'surface_azimuth'`
              (typically a dict or pandas.DataFrame)
          """
  
  
  @dataclass
  class FixedMount(AbstractMount):
      """
      Racking at fixed (static) orientation.
  
      Parameters
      ----------
      surface_tilt : float, default 0
          Surface tilt angle. The tilt angle is defined as angle from horizontal
          (e.g. surface facing up = 0, surface facing horizon = 90) [degrees]
  
      surface_azimuth : float, default 180
          Azimuth angle of the module surface. North=0, East=90, South=180,
          West=270. [degrees]
  
      racking_model : str, optional
          Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.
          Used to identify a parameter set for the SAPM cell temperature model.
  
      module_height : float, optional
         The height above ground of the center of the module [m]. Used for
         the Fuentes cell temperature model.
      """
  
      surface_tilt: float = 0.0
      surface_azimuth: float = 180.0
      racking_model: Optional[str] = None
      module_height: Optional[float] = None
  
      def get_orientation(self, solar_zenith, solar_azimuth):
          # note -- docstring is automatically inherited from AbstractMount
          return {
              'surface_tilt': self.surface_tilt,
              'surface_azimuth': self.surface_azimuth,
          }
  
  
  @dataclass
  class SingleAxisTrackerMount(AbstractMount):
      """
      Single-axis tracker racking for dynamic solar tracking.
  
      Parameters
      ----------
      axis_tilt : float, default 0
          The tilt of the axis of rotation (i.e, the y-axis defined by
          axis_azimuth) with respect to horizontal. [degrees]
  
      axis_azimuth : float, default 180
          A value denoting the compass direction along which the axis of
          rotation lies, measured east of north. [degrees]
  
      max_angle : float, default 90
          A value denoting the maximum rotation angle
          of the one-axis tracker from its horizontal position (horizontal
          if axis_tilt = 0). A max_angle of 90 degrees allows the tracker
          to rotate to a vertical position to point the panel towards a
          horizon. max_angle of 180 degrees allows for full rotation. [degrees]
  
      backtrack : bool, default True
          Controls whether the tracker has the capability to "backtrack"
          to avoid row-to-row shading. False denotes no backtrack
          capability. True denotes backtrack capability.
  
      gcr : float, default 2.0/7.0
          A value denoting the ground coverage ratio of a tracker system
          which utilizes backtracking; i.e. the ratio between the PV array
          surface area to total ground area. A tracker system with modules
          2 meters wide, centered on the tracking axis, with 6 meters
          between the tracking axes has a gcr of 2/6=0.333. If gcr is not
          provided, a gcr of 2/7 is default. gcr must be <=1. [unitless]
  
      cross_axis_tilt : float, default 0.0
          The angle, relative to horizontal, of the line formed by the
          intersection between the slope containing the tracker axes and a plane
          perpendicular to the tracker axes. Cross-axis tilt should be specified
          using a right-handed convention. For example, trackers with axis
          azimuth of 180 degrees (heading south) will have a negative cross-axis
          tilt if the tracker axes plane slopes down to the east and positive
          cross-axis tilt if the tracker axes plane slopes up to the east. Use
          :func:`~pvlib.tracking.calc_cross_axis_tilt` to calculate
          `cross_axis_tilt`. [degrees]
  
      racking_model : str, optional
          Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.
          Used to identify a parameter set for the SAPM cell temperature model.
  
      module_height : float, optional
         The height above ground of the center of the module [m]. Used for
         the Fuentes cell temperature model.
      """
      axis_tilt: float = 0.0
      axis_azimuth: float = 0.0
      max_angle: float = 90.0
      backtrack: bool = True
      gcr: float = 2.0/7.0
      cross_axis_tilt: float = 0.0
      racking_model: Optional[str] = None
      module_height: Optional[float] = None
  
      def get_orientation(self, solar_zenith, solar_azimuth):
          # note -- docstring is automatically inherited from AbstractMount
          from pvlib import tracking  # avoid circular import issue
          tracking_data = tracking.singleaxis(
              solar_zenith, solar_azimuth,
              self.axis_tilt, self.axis_azimuth,
              self.max_angle, self.backtrack,
              self.gcr, self.cross_axis_tilt
          )
          return tracking_data
  
  
  def calcparams_desoto(effective_irradiance, temp_cell,
                        alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s,
                        EgRef=1.121, dEgdT=-0.0002677,
                        irrad_ref=1000, temp_ref=25):
      '''
      Calculates five parameter values for the single diode equation at
      effective irradiance and cell temperature using the De Soto et al.
      model described in [1]_. The five values returned by calcparams_desoto
      can be used by singlediode to calculate an IV curve.
  
      Parameters
      ----------
      effective_irradiance : numeric
          The irradiance (W/m2) that is converted to photocurrent.
  
      temp_cell : numeric
          The average cell temperature of cells within a module in C.
  
      alpha_sc : float
          The short-circuit current temperature coefficient of the
          module in units of A/C.
  
      a_ref : float
          The product of the usual diode ideality factor (n, unitless),
          number of cells in series (Ns), and cell thermal voltage at reference
          conditions, in units of V.
  
      I_L_ref : float
          The light-generated current (or photocurrent) at reference conditions,
          in amperes.
  
      I_o_ref : float
          The dark or diode reverse saturation current at reference conditions,
          in amperes.
  
      R_sh_ref : float
          The shunt resistance at reference conditions, in ohms.
  
      R_s : float
          The series resistance at reference conditions, in ohms.
  
      EgRef : float
          The energy bandgap at reference temperature in units of eV.
          1.121 eV for crystalline silicon. EgRef must be >0.  For parameters
          from the SAM CEC module database, EgRef=1.121 is implicit for all
          cell types in the parameter estimation algorithm used by NREL.
  
      dEgdT : float
          The temperature dependence of the energy bandgap at reference
          conditions in units of 1/K. May be either a scalar value
          (e.g. -0.0002677 as in [1]_) or a DataFrame (this may be useful if
          dEgdT is a modeled as a function of temperature). For parameters from
          the SAM CEC module database, dEgdT=-0.0002677 is implicit for all cell
          types in the parameter estimation algorithm used by NREL.
  
      irrad_ref : float (optional, default=1000)
          Reference irradiance in W/m^2.
  
      temp_ref : float (optional, default=25)
          Reference cell temperature in C.
  
      Returns
      -------
      Tuple of the following results:
  
      photocurrent : numeric
          Light-generated current in amperes
  
      saturation_current : numeric
          Diode saturation curent in amperes
  
      resistance_series : numeric
          Series resistance in ohms
  
      resistance_shunt : numeric
          Shunt resistance in ohms
  
      nNsVth : numeric
          The product of the usual diode ideality factor (n, unitless),
          number of cells in series (Ns), and cell thermal voltage at
          specified effective irradiance and cell temperature.
  
      References
      ----------
      .. [1] W. De Soto et al., "Improvement and validation of a model for
         photovoltaic array performance", Solar Energy, vol 80, pp. 78-88,
         2006.
  
      .. [2] System Advisor Model web page. https://sam.nrel.gov.
  
      .. [3] A. Dobos, "An Improved Coefficient Calculator for the California
         Energy Commission 6 Parameter Photovoltaic Module Model", Journal of
         Solar Energy Engineering, vol 134, 2012.
  
      .. [4] O. Madelung, "Semiconductors: Data Handbook, 3rd ed." ISBN
         3-540-40488-0
  
      See Also
      --------
      singlediode
      retrieve_sam
  
      Notes
      -----
      If the reference parameters in the ModuleParameters struct are read
      from a database or library of parameters (e.g. System Advisor
      Model), it is important to use the same EgRef and dEgdT values that
      were used to generate the reference parameters, regardless of the
      actual bandgap characteristics of the semiconductor. For example, in
      the case of the System Advisor Model library, created as described
      in [3], EgRef and dEgdT for all modules were 1.121 and -0.0002677,
      respectively.
  
      This table of reference bandgap energies (EgRef), bandgap energy
      temperature dependence (dEgdT), and "typical" airmass response (M)
      is provided purely as reference to those who may generate their own
      reference module parameters (a_ref, IL_ref, I0_ref, etc.) based upon
      the various PV semiconductors. Again, we stress the importance of
      using identical EgRef and dEgdT when generation reference parameters
      and modifying the reference parameters (for irradiance, temperature,
      and airmass) per DeSoto's equations.
  
       Crystalline Silicon (Si):
           * EgRef = 1.121
           * dEgdT = -0.0002677
  
           >>> M = np.polyval([-1.26E-4, 2.816E-3, -0.024459, 0.086257, 0.9181],
           ...                AMa) # doctest: +SKIP
  
           Source: [1]
  
       Cadmium Telluride (CdTe):
           * EgRef = 1.475
           * dEgdT = -0.0003
  
           >>> M = np.polyval([-2.46E-5, 9.607E-4, -0.0134, 0.0716, 0.9196],
           ...                AMa) # doctest: +SKIP
  
           Source: [4]
  
       Copper Indium diSelenide (CIS):
           * EgRef = 1.010
           * dEgdT = -0.00011
  
           >>> M = np.polyval([-3.74E-5, 0.00125, -0.01462, 0.0718, 0.9210],
           ...                AMa) # doctest: +SKIP
  
           Source: [4]
  
       Copper Indium Gallium diSelenide (CIGS):
           * EgRef = 1.15
           * dEgdT = ????
  
           >>> M = np.polyval([-9.07E-5, 0.0022, -0.0202, 0.0652, 0.9417],
           ...                AMa) # doctest: +SKIP
  
           Source: Wikipedia
  
       Gallium Arsenide (GaAs):
           * EgRef = 1.424
           * dEgdT = -0.000433
           * M = unknown
  
           Source: [4]
      '''
  
      # Boltzmann constant in eV/K, 8.617332478e-05
      k = constants.value('Boltzmann constant in eV/K')
  
      # reference temperature
      Tref_K = temp_ref + 273.15
      Tcell_K = temp_cell + 273.15
  
      E_g = EgRef * (1 + dEgdT*(Tcell_K - Tref_K))
  
      nNsVth = a_ref * (Tcell_K / Tref_K)
  
      # In the equation for IL, the single factor effective_irradiance is
      # used, in place of the product S*M in [1]. effective_irradiance is
      # equivalent to the product of S (irradiance reaching a module's cells) *
      # M (spectral adjustment factor) as described in [1].
      IL = effective_irradiance / irrad_ref * \
          (I_L_ref + alpha_sc * (Tcell_K - Tref_K))
      I0 = (I_o_ref * ((Tcell_K / Tref_K) ** 3) *
            (np.exp(EgRef / (k*(Tref_K)) - (E_g / (k*(Tcell_K))))))
      # Note that the equation for Rsh differs from [1]. In [1] Rsh is given as
      # Rsh = Rsh_ref * (S_ref / S) where S is broadband irradiance reaching
      # the module's cells. If desired this model behavior can be duplicated
      # by applying reflection and soiling losses to broadband plane of array
      # irradiance and not applying a spectral loss modifier, i.e.,
      # spectral_modifier = 1.0.
      # use errstate to silence divide by warning
      with np.errstate(divide='ignore'):
          Rsh = R_sh_ref * (irrad_ref / effective_irradiance)
  
      Rs = R_s
  
      numeric_args = (effective_irradiance, temp_cell)
      out = (IL, I0, Rs, Rsh, nNsVth)
  
      if all(map(np.isscalar, numeric_args)):
          return out
  
      index = tools.get_pandas_index(*numeric_args)
  
      if index is None:
          return np.broadcast_arrays(*out)
  
      return tuple(pd.Series(a, index=index).rename(None) for a in out)
  
  
  def calcparams_cec(effective_irradiance, temp_cell,
                     alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s,
                     Adjust, EgRef=1.121, dEgdT=-0.0002677,
                     irrad_ref=1000, temp_ref=25):
      '''
      Calculates five parameter values for the single diode equation at
      effective irradiance and cell temperature using the CEC
      model. The CEC model [1]_ differs from the De soto et al.
      model [3]_ by the parameter Adjust. The five values returned by
      calcparams_cec can be used by singlediode to calculate an IV curve.
  
      Parameters
      ----------
      effective_irradiance : numeric
          The irradiance (W/m2) that is converted to photocurrent.
  
      temp_cell : numeric
          The average cell temperature of cells within a module in C.
  
      alpha_sc : float
          The short-circuit current temperature coefficient of the
          module in units of A/C.
  
      a_ref : float
          The product of the usual diode ideality factor (n, unitless),
          number of cells in series (Ns), and cell thermal voltage at reference
          conditions, in units of V.
  
      I_L_ref : float
          The light-generated current (or photocurrent) at reference conditions,
          in amperes.
  
      I_o_ref : float
          The dark or diode reverse saturation current at reference conditions,
          in amperes.
  
      R_sh_ref : float
          The shunt resistance at reference conditions, in ohms.
  
      R_s : float
          The series resistance at reference conditions, in ohms.
  
      Adjust : float
          The adjustment to the temperature coefficient for short circuit
          current, in percent
  
      EgRef : float
          The energy bandgap at reference temperature in units of eV.
          1.121 eV for crystalline silicon. EgRef must be >0.  For parameters
          from the SAM CEC module database, EgRef=1.121 is implicit for all
          cell types in the parameter estimation algorithm used by NREL.
  
      dEgdT : float
          The temperature dependence of the energy bandgap at reference
          conditions in units of 1/K. May be either a scalar value
          (e.g. -0.0002677 as in [3]) or a DataFrame (this may be useful if
          dEgdT is a modeled as a function of temperature). For parameters from
          the SAM CEC module database, dEgdT=-0.0002677 is implicit for all cell
          types in the parameter estimation algorithm used by NREL.
  
      irrad_ref : float (optional, default=1000)
          Reference irradiance in W/m^2.
  
      temp_ref : float (optional, default=25)
          Reference cell temperature in C.
  
      Returns
      -------
      Tuple of the following results:
  
      photocurrent : numeric
          Light-generated current in amperes
  
      saturation_current : numeric
          Diode saturation curent in amperes
  
      resistance_series : numeric
          Series resistance in ohms
  
      resistance_shunt : numeric
          Shunt resistance in ohms
  
      nNsVth : numeric
          The product of the usual diode ideality factor (n, unitless),
          number of cells in series (Ns), and cell thermal voltage at
          specified effective irradiance and cell temperature.
  
      References
      ----------
      .. [1] A. Dobos, "An Improved Coefficient Calculator for the California
         Energy Commission 6 Parameter Photovoltaic Module Model", Journal of
         Solar Energy Engineering, vol 134, 2012.
  
      .. [2] System Advisor Model web page. https://sam.nrel.gov.
  
      .. [3] W. De Soto et al., "Improvement and validation of a model for
         photovoltaic array performance", Solar Energy, vol 80, pp. 78-88,
         2006.
  
      See Also
      --------
      calcparams_desoto
      singlediode
      retrieve_sam
  
      '''
  
      # pass adjusted temperature coefficient to desoto
      return calcparams_desoto(effective_irradiance, temp_cell,
                               alpha_sc*(1.0 - Adjust/100),
                               a_ref, I_L_ref, I_o_ref,
                               R_sh_ref, R_s,
                               EgRef=EgRef, dEgdT=dEgdT,
                               irrad_ref=irrad_ref, temp_ref=temp_ref)
  
  
  def calcparams_pvsyst(effective_irradiance, temp_cell,
                        alpha_sc, gamma_ref, mu_gamma,
                        I_L_ref, I_o_ref,
                        R_sh_ref, R_sh_0, R_s,
                        cells_in_series,
                        R_sh_exp=5.5,
                        EgRef=1.121,
                        irrad_ref=1000, temp_ref=25):
      '''
      Calculates five parameter values for the single diode equation at
      effective irradiance and cell temperature using the PVsyst v6
      model.  The PVsyst v6 model is described in [1]_, [2]_, [3]_.
      The five values returned by calcparams_pvsyst can be used by singlediode
      to calculate an IV curve.
  
      Parameters
      ----------
      effective_irradiance : numeric
          The irradiance (W/m2) that is converted to photocurrent.
  
      temp_cell : numeric
          The average cell temperature of cells within a module in C.
  
      alpha_sc : float
          The short-circuit current temperature coefficient of the
          module in units of A/C.
  
      gamma_ref : float
          The diode ideality factor
  
      mu_gamma : float
          The temperature coefficient for the diode ideality factor, 1/K
  
      I_L_ref : float
          The light-generated current (or photocurrent) at reference conditions,
          in amperes.
  
      I_o_ref : float
          The dark or diode reverse saturation current at reference conditions,
          in amperes.
  
      R_sh_ref : float
          The shunt resistance at reference conditions, in ohms.
  
      R_sh_0 : float
          The shunt resistance at zero irradiance conditions, in ohms.
  
      R_s : float
          The series resistance at reference conditions, in ohms.
  
      cells_in_series : integer
          The number of cells connected in series.
  
      R_sh_exp : float
          The exponent in the equation for shunt resistance, unitless. Defaults
          to 5.5.
  
      EgRef : float
          The energy bandgap at reference temperature in units of eV.
          1.121 eV for crystalline silicon. EgRef must be >0.
  
      irrad_ref : float (optional, default=1000)
          Reference irradiance in W/m^2.
  
      temp_ref : float (optional, default=25)
          Reference cell temperature in C.
  
      Returns
      -------
      Tuple of the following results:
  
      photocurrent : numeric
          Light-generated current in amperes
  
      saturation_current : numeric
          Diode saturation current in amperes
  
      resistance_series : numeric
          Series resistance in ohms
  
      resistance_shunt : numeric
          Shunt resistance in ohms
  
      nNsVth : numeric
          The product of the usual diode ideality factor (n, unitless),
          number of cells in series (Ns), and cell thermal voltage at
          specified effective irradiance and cell temperature.
  
      References
      ----------
      .. [1] K. Sauer, T. Roessler, C. W. Hansen, Modeling the Irradiance and
         Temperature Dependence of Photovoltaic Modules in PVsyst,
         IEEE Journal of Photovoltaics v5(1), January 2015.
  
      .. [2] A. Mermoud, PV modules modelling, Presentation at the 2nd PV
         Performance Modeling Workshop, Santa Clara, CA, May 2013
  
      .. [3] A. Mermoud, T. Lejeune, Performance Assessment of a Simulation Model
         for PV modules of any available technology, 25th European Photovoltaic
         Solar Energy Conference, Valencia, Spain, Sept. 2010
  
      See Also
      --------
      calcparams_desoto
      singlediode
  
      '''
  
      # Boltzmann constant in J/K
      k = constants.k
  
      # elementary charge in coulomb
      q = constants.e
  
      # reference temperature
      Tref_K = temp_ref + 273.15
      Tcell_K = temp_cell + 273.15
  
      gamma = gamma_ref + mu_gamma * (Tcell_K - Tref_K)
      nNsVth = gamma * k / q * cells_in_series * Tcell_K
  
      IL = effective_irradiance / irrad_ref * \
          (I_L_ref + alpha_sc * (Tcell_K - Tref_K))
  
      I0 = I_o_ref * ((Tcell_K / Tref_K) ** 3) * \
          (np.exp((q * EgRef) / (k * gamma) * (1 / Tref_K - 1 / Tcell_K)))
  
      Rsh_tmp = \
          (R_sh_ref - R_sh_0 * np.exp(-R_sh_exp)) / (1.0 - np.exp(-R_sh_exp))
      Rsh_base = np.maximum(0.0, Rsh_tmp)
  
      Rsh = Rsh_base + (R_sh_0 - Rsh_base) * \
          np.exp(-R_sh_exp * effective_irradiance / irrad_ref)
  
      Rs = R_s
  
      numeric_args = (effective_irradiance, temp_cell)
      out = (IL, I0, Rs, Rsh, nNsVth)
  
      if all(map(np.isscalar, numeric_args)):
          return out
  
      index = tools.get_pandas_index(*numeric_args)
  
      if index is None:
          return np.broadcast_arrays(*out)
  
      return tuple(pd.Series(a, index=index).rename(None) for a in out)
  
  
  def retrieve_sam(name=None, path=None):
      '''
      Retrieve latest module and inverter info from a local file or the
      SAM website.
  
      This function will retrieve either:
  
          * CEC module database
          * Sandia Module database
          * CEC Inverter database
          * Anton Driesse Inverter database
  
      and return it as a pandas DataFrame.
  
      Parameters
      ----------
      name : None or string, default None
          Name can be one of:
  
          * 'CECMod' - returns the CEC module database
          * 'CECInverter' - returns the CEC Inverter database
          * 'SandiaInverter' - returns the CEC Inverter database
            (CEC is only current inverter db available; tag kept for
            backwards compatibility)
          * 'SandiaMod' - returns the Sandia Module database
          * 'ADRInverter' - returns the ADR Inverter database
  
      path : None or string, default None
          Path to the SAM file. May also be a URL.
  
      Returns
      -------
      samfile : DataFrame
          A DataFrame containing all the elements of the desired database.
          Each column represents a module or inverter, and a specific
          dataset can be retrieved by the command
  
      Raises
      ------
      ValueError
          If no name or path is provided.
  
      Notes
      -----
      Files available at
          https://github.com/NREL/SAM/tree/develop/deploy/libraries
      Documentation for module and inverter data sets:
          https://sam.nrel.gov/photovoltaic/pv-sub-page-2.html
  
      Examples
      --------
  
      >>> from pvlib import pvsystem
      >>> invdb = pvsystem.retrieve_sam('CECInverter')
      >>> inverter = invdb.AE_Solar_Energy__AE6_0__277V_
      >>> inverter
      Vac                          277
      Pso                    36.197575
      Paco                      6000.0
      Pdco                 6158.746094
      Vdco                       360.0
      C0                     -0.000002
      C1                     -0.000026
      C2                     -0.001253
      C3                       0.00021
      Pnt                          1.8
      Vdcmax                     450.0
      Idcmax                 17.107628
      Mppt_low                   100.0
      Mppt_high                  450.0
      CEC_Date                     NaN
      CEC_Type     Utility Interactive
      Name: AE_Solar_Energy__AE6_0__277V_, dtype: object
      '''
  
      if name is not None:
          name = name.lower()
          data_path = os.path.join(
              os.path.dirname(os.path.abspath(__file__)), 'data')
          if name == 'cecmod':
              csvdata = os.path.join(
                  data_path, 'sam-library-cec-modules-2019-03-05.csv')
          elif name == 'sandiamod':
              csvdata = os.path.join(
                  data_path, 'sam-library-sandia-modules-2015-6-30.csv')
          elif name == 'adrinverter':
              csvdata = os.path.join(
                  data_path, 'adr-library-cec-inverters-2019-03-05.csv')
          elif name in ['cecinverter', 'sandiainverter']:
              # Allowing either, to provide for old code,
              # while aligning with current expectations
              csvdata = os.path.join(
                  data_path, 'sam-library-cec-inverters-2019-03-05.csv')
          else:
              raise ValueError(f'invalid name {name}')
      elif path is not None:
          if path.startswith('http'):
              response = urlopen(path)
              csvdata = io.StringIO(response.read().decode(errors='ignore'))
          else:
              csvdata = path
      elif name is None and path is None:
          raise ValueError("A name or path must be provided!")
  
      return _parse_raw_sam_df(csvdata)
  
  
  def _normalize_sam_product_names(names):
      '''
      Replace special characters within the product names to make them more
      suitable for use as Dataframe column names.
      '''
      # Contributed by Anton Driesse (@adriesse), PV Performance Labs. July, 2019
  
      import warnings
  
      BAD_CHARS = ' -.()[]:+/",'
      GOOD_CHARS = '____________'
  
      mapping = str.maketrans(BAD_CHARS, GOOD_CHARS)
      names = pd.Series(data=names)
      norm_names = names.str.translate(mapping)
  
      n_duplicates = names.duplicated().sum()
      if n_duplicates > 0:
          warnings.warn('Original names contain %d duplicate(s).' % n_duplicates)
  
      n_duplicates = norm_names.duplicated().sum()
      if n_duplicates > 0:
          warnings.warn(
              'Normalized names contain %d duplicate(s).' % n_duplicates)
  
      return norm_names.values
  
  
  def _parse_raw_sam_df(csvdata):
  
      df = pd.read_csv(csvdata, index_col=0, skiprows=[1, 2])
  
      df.columns = df.columns.str.replace(' ', '_')
      df.index = _normalize_sam_product_names(df.index)
      df = df.transpose()
  
      if 'ADRCoefficients' in df.index:
          ad_ce = 'ADRCoefficients'
          # for each inverter, parses a string of coefficients like
          # ' 1.33, 2.11, 3.12' into a list containing floats:
          # [1.33, 2.11, 3.12]
          df.loc[ad_ce] = df.loc[ad_ce].map(lambda x: list(
              map(float, x.strip(' []').split())))
  
      return df
  
  
  def sapm(effective_irradiance, temp_cell, module):
      '''
      The Sandia PV Array Performance Model (SAPM) generates 5 points on a
      PV module's I-V curve (Voc, Isc, Ix, Ixx, Vmp/Imp) according to
      SAND2004-3535. Assumes a reference cell temperature of 25 C.
  
      Parameters
      ----------
      effective_irradiance : numeric
          Irradiance reaching the module's cells, after reflections and
          adjustment for spectrum. [W/m2]
  
      temp_cell : numeric
          Cell temperature [C].
  
      module : dict-like
          A dict or Series defining the SAPM parameters. See the notes section
          for more details.
  
      Returns
      -------
      A DataFrame with the columns:
  
          * i_sc : Short-circuit current (A)
          * i_mp : Current at the maximum-power point (A)
          * v_oc : Open-circuit voltage (V)
          * v_mp : Voltage at maximum-power point (V)
          * p_mp : Power at maximum-power point (W)
          * i_x : Current at module V = 0.5Voc, defines 4th point on I-V
            curve for modeling curve shape
          * i_xx : Current at module V = 0.5(Voc+Vmp), defines 5th point on
            I-V curve for modeling curve shape
  
      Notes
      -----
      The SAPM parameters which are required in ``module`` are
      listed in the following table.
  
      The Sandia module database contains parameter values for a limited set
      of modules. The CEC module database does not contain these parameters.
      Both databases can be accessed using :py:func:`retrieve_sam`.
  
      ================   ========================================================
      Key                Description
      ================   ========================================================
      A0-A4              The airmass coefficients used in calculating
                         effective irradiance
      B0-B5              The angle of incidence coefficients used in calculating
                         effective irradiance
      C0-C7              The empirically determined coefficients relating
                         Imp, Vmp, Ix, and Ixx to effective irradiance
      Isco               Short circuit current at reference condition (amps)
      Impo               Maximum power current at reference condition (amps)
      Voco               Open circuit voltage at reference condition (amps)
      Vmpo               Maximum power voltage at reference condition (amps)
      Aisc               Short circuit current temperature coefficient at
                         reference condition (1/C)
      Aimp               Maximum power current temperature coefficient at
                         reference condition (1/C)
      Bvoco              Open circuit voltage temperature coefficient at
                         reference condition (V/C)
      Mbvoc              Coefficient providing the irradiance dependence for the
                         BetaVoc temperature coefficient at reference irradiance
                         (V/C)
      Bvmpo              Maximum power voltage temperature coefficient at
                         reference condition
      Mbvmp              Coefficient providing the irradiance dependence for the
                         BetaVmp temperature coefficient at reference irradiance
                         (V/C)
      N                  Empirically determined "diode factor" (dimensionless)
      Cells_in_Series    Number of cells in series in a module's cell string(s)
      IXO                Ix at reference conditions
      IXXO               Ixx at reference conditions
      FD                 Fraction of diffuse irradiance used by module
      ================   ========================================================
  
      References
      ----------
      .. [1] King, D. et al, 2004, "Sandia Photovoltaic Array Performance
         Model", SAND Report 3535, Sandia National Laboratories, Albuquerque,
         NM.
  
      See Also
      --------
      retrieve_sam
      pvlib.temperature.sapm_cell
      pvlib.temperature.sapm_module
      '''
  
      # TODO: someday, change temp_ref and irrad_ref to reference_temperature and
      # reference_irradiance and expose
      temp_ref = 25
      irrad_ref = 1000
  
      q = constants.e  # Elementary charge in units of coulombs
      kb = constants.k  # Boltzmann's constant in units of J/K
  
      # avoid problem with integer input
      Ee = np.array(effective_irradiance, dtype='float64') / irrad_ref
  
      # set up masking for 0, positive, and nan inputs
      Ee_gt_0 = np.full_like(Ee, False, dtype='bool')
      Ee_eq_0 = np.full_like(Ee, False, dtype='bool')
      notnan = ~np.isnan(Ee)
      np.greater(Ee, 0, where=notnan, out=Ee_gt_0)
      np.equal(Ee, 0, where=notnan, out=Ee_eq_0)
  
      Bvmpo = module['Bvmpo'] + module['Mbvmp']*(1 - Ee)
      Bvoco = module['Bvoco'] + module['Mbvoc']*(1 - Ee)
      delta = module['N'] * kb * (temp_cell + 273.15) / q
  
      # avoid repeated computation
      logEe = np.full_like(Ee, np.nan)
      np.log(Ee, where=Ee_gt_0, out=logEe)
      logEe = np.where(Ee_eq_0, -np.inf, logEe)
      # avoid repeated __getitem__
      cells_in_series = module['Cells_in_Series']
  
      out = OrderedDict()
  
      out['i_sc'] = (
          module['Isco'] * Ee * (1 + module['Aisc']*(temp_cell - temp_ref)))
  
      out['i_mp'] = (
          module['Impo'] * (module['C0']*Ee + module['C1']*(Ee**2)) *
          (1 + module['Aimp']*(temp_cell - temp_ref)))
  
      out['v_oc'] = np.maximum(0, (
          module['Voco'] + cells_in_series * delta * logEe +
          Bvoco*(temp_cell - temp_ref)))
  
      out['v_mp'] = np.maximum(0, (
          module['Vmpo'] +
          module['C2'] * cells_in_series * delta * logEe +
          module['C3'] * cells_in_series * ((delta * logEe) ** 2) +
          Bvmpo*(temp_cell - temp_ref)))
  
      out['p_mp'] = out['i_mp'] * out['v_mp']
  
      out['i_x'] = (
          module['IXO'] * (module['C4']*Ee + module['C5']*(Ee**2)) *
          (1 + module['Aisc']*(temp_cell - temp_ref)))
  
      # the Ixx calculation in King 2004 has a typo (mixes up Aisc and Aimp)
      out['i_xx'] = (
          module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) *
          (1 + module['Aisc']*(temp_cell - temp_ref)))
  
      if isinstance(out['i_sc'], pd.Series):
          out = pd.DataFrame(out)
  
      return out
  
  
  sapm_spectral_loss = deprecated(
      since='0.10.0',
      alternative='pvlib.spectrum.spectral_factor_sapm'
  )(spectrum.spectral_factor_sapm)
  
  
  def sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,
                                module):
      r"""
      Calculates the SAPM effective irradiance using the SAPM spectral
      loss and SAPM angle of incidence loss functions.
  
      Parameters
      ----------
      poa_direct : numeric
          The direct irradiance incident upon the module. [W/m2]
  
      poa_diffuse : numeric
          The diffuse irradiance incident on module.  [W/m2]
  
      airmass_absolute : numeric
          Absolute airmass. [unitless]
  
      aoi : numeric
          Angle of incidence. [degrees]
  
      module : dict-like
          A dict, Series, or DataFrame defining the SAPM performance
          parameters. See the :py:func:`sapm` notes section for more
          details.
  
      Returns
      -------
      effective_irradiance : numeric
          Effective irradiance accounting for reflections and spectral content.
          [W/m2]
  
      Notes
      -----
      The SAPM model for effective irradiance [1]_ translates broadband direct
      and diffuse irradiance on the plane of array to the irradiance absorbed by
      a module's cells.
  
      The model is
      .. math::
  
          `Ee = f_1(AM_a) (E_b f_2(AOI) + f_d E_d)`
  
      where :math:`Ee` is effective irradiance (W/m2), :math:`f_1` is a fourth
      degree polynomial in air mass :math:`AM_a`, :math:`E_b` is beam (direct)
      irradiance on the plane of array, :math:`E_d` is diffuse irradiance on the
      plane of array, :math:`f_2` is a fifth degree polynomial in the angle of
      incidence :math:`AOI`, and :math:`f_d` is the fraction of diffuse
      irradiance on the plane of array that is not reflected away.
  
      References
      ----------
      .. [1] D. King et al, "Sandia Photovoltaic Array Performance Model",
         SAND2004-3535, Sandia National Laboratories, Albuquerque, NM
  
      See also
      --------
      pvlib.iam.sapm
      pvlib.spectrum.spectral_factor_sapm
      pvlib.pvsystem.sapm
      """
  
      F1 = spectrum.spectral_factor_sapm(airmass_absolute, module)
      F2 = iam.sapm(aoi, module)
  
      Ee = F1 * (poa_direct * F2 + module['FD'] * poa_diffuse)
  
      return Ee
  
  
  def singlediode(photocurrent, saturation_current, resistance_series,
                  resistance_shunt, nNsVth, ivcurve_pnts=None,
                  method='lambertw'):
      r"""
      Solve the single diode equation to obtain a photovoltaic IV curve.
  
      Solves the single diode equation [1]_
  
      .. math::
  
          I = I_L -
              I_0 \left[
                  \exp \left(\frac{V+I R_s}{n N_s V_{th}} \right)-1
              \right] -
              \frac{V + I R_s}{R_{sh}}
  
      for :math:`I` and :math:`V` when given :math:`I_L, I_0, R_s, R_{sh},` and
      :math:`n N_s V_{th}` which are described later. The five points on the I-V
      curve specified in [3]_ are returned. If :math:`I_L, I_0, R_s, R_{sh},` and
      :math:`n N_s V_{th}` are all scalars, a single curve is returned. If any
      are array-like (of the same length), multiple IV curves are calculated.
  
      The input parameters can be calculated from meteorological data using a
      function for a single diode model, e.g.,
      :py:func:`~pvlib.pvsystem.calcparams_desoto`.
  
      Parameters
      ----------
      photocurrent : numeric
          Light-generated current :math:`I_L` (photocurrent)
          ``0 <= photocurrent``. [A]
  
      saturation_current : numeric
          Diode saturation :math:`I_0` current under desired IV curve
          conditions. ``0 < saturation_current``. [A]
  
      resistance_series : numeric
          Series resistance :math:`R_s` under desired IV curve conditions.
          ``0 <= resistance_series < numpy.inf``.  [ohm]
  
      resistance_shunt : numeric
          Shunt resistance :math:`R_{sh}` under desired IV curve conditions.
          ``0 < resistance_shunt <= numpy.inf``.  [ohm]
  
      nNsVth : numeric
          The product of three components: 1) the usual diode ideality factor
          :math:`n`, 2) the number of cells in series :math:`N_s`, and 3)
          the cell thermal voltage
          :math:`V_{th}`. The thermal voltage of the cell (in volts) may be
          calculated as :math:`k_B T_c / q`, where :math:`k_B` is
          Boltzmann's constant (J/K), :math:`T_c` is the temperature of the p-n
          junction in Kelvin, and :math:`q` is the charge of an electron
          (coulombs). ``0 < nNsVth``.  [V]
  
      ivcurve_pnts : None or int, default None
          Number of points in the desired IV curve. If None or 0, no points on
          the IV curves will be produced.
  
          .. deprecated:: 0.10.0
             Use :py:func:`pvlib.pvsystem.v_from_i` and
             :py:func:`pvlib.pvsystem.i_from_v` instead.
  
      method : str, default 'lambertw'
          Determines the method used to calculate points on the IV curve. The
          options are ``'lambertw'``, ``'newton'``, or ``'brentq'``.
  
      Returns
      -------
      dict or pandas.DataFrame
          The returned dict-like object always contains the keys/columns:
  
              * i_sc - short circuit current in amperes.
              * v_oc - open circuit voltage in volts.
              * i_mp - current at maximum power point in amperes.
              * v_mp - voltage at maximum power point in volts.
              * p_mp - power at maximum power point in watts.
              * i_x - current, in amperes, at ``v = 0.5*v_oc``.
              * i_xx - current, in amperes, at ``v = 0.5*(v_oc+v_mp)``.
  
          A dict is returned when the input parameters are scalars or
          ``ivcurve_pnts > 0``. If ``ivcurve_pnts > 0``, the output dictionary
          will also include the keys:
  
              * i - IV curve current in amperes.
              * v - IV curve voltage in volts.
  
      See also
      --------
      calcparams_desoto
      calcparams_cec
      calcparams_pvsyst
      sapm
      pvlib.singlediode.bishop88
  
      Notes
      -----
      If the method is ``'lambertw'`` then the solution employed to solve the
      implicit diode equation utilizes the Lambert W function to obtain an
      explicit function of :math:`V=f(I)` and :math:`I=f(V)` as shown in [2]_.
  
      If the method is ``'newton'`` then the root-finding Newton-Raphson method
      is used. It should be safe for well behaved IV-curves, but the ``'brentq'``
      method is recommended for reliability.
  
      If the method is ``'brentq'`` then Brent's bisection search method is used
      that guarantees convergence by bounding the voltage between zero and
      open-circuit.
  
      If the method is either ``'newton'`` or ``'brentq'`` and ``ivcurve_pnts``
      are indicated, then :func:`pvlib.singlediode.bishop88` [4]_ is used to
      calculate the points on the IV curve points at diode voltages from zero to
      open-circuit voltage with a log spacing that gets closer as voltage
      increases. If the method is ``'lambertw'`` then the calculated points on
      the IV curve are linearly spaced.
  
      References
      ----------
      .. [1] S.R. Wenham, M.A. Green, M.E. Watt, "Applied Photovoltaics" ISBN
         0 86758 909 4
  
      .. [2] A. Jain, A. Kapoor, "Exact analytical solutions of the
         parameters of real solar cells using Lambert W-function", Solar
         Energy Materials and Solar Cells, 81 (2004) 269-277.
  
      .. [3] D. King et al, "Sandia Photovoltaic Array Performance Model",
         SAND2004-3535, Sandia National Laboratories, Albuquerque, NM
  
      .. [4] "Computer simulation of the effects of electrical mismatches in
         photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988)
         https://doi.org/10.1016/0379-6787(88)90059-2
      """
      if ivcurve_pnts:
          warn_deprecated('0.10.0', name='pvlib.pvsystem.singlediode',
                          alternative=('pvlib.pvsystem.v_from_i and '
                                       'pvlib.pvsystem.i_from_v'),
                          obj_type='parameter ivcurve_pnts',
                          removal='0.11.0')
      args = (photocurrent, saturation_current, resistance_series,
              resistance_shunt, nNsVth)  # collect args
      # Calculate points on the IV curve using the LambertW solution to the
      # single diode equation
      if method.lower() == 'lambertw':
          out = _singlediode._lambertw(*args, ivcurve_pnts)
          points = out[:7]
          if ivcurve_pnts:
              ivcurve_i, ivcurve_v = out[7:]
      else:
          # Calculate points on the IV curve using either 'newton' or 'brentq'
          # methods. Voltages are determined by first solving the single diode
          # equation for the diode voltage V_d then backing out voltage
          v_oc = _singlediode.bishop88_v_from_i(
              0.0, *args, method=method.lower()
          )
          i_mp, v_mp, p_mp = _singlediode.bishop88_mpp(
              *args, method=method.lower()
          )
          i_sc = _singlediode.bishop88_i_from_v(
              0.0, *args, method=method.lower()
          )
          i_x = _singlediode.bishop88_i_from_v(
              v_oc / 2.0, *args, method=method.lower()
          )
          i_xx = _singlediode.bishop88_i_from_v(
              (v_oc + v_mp) / 2.0, *args, method=method.lower()
          )
          points = i_sc, v_oc, i_mp, v_mp, p_mp, i_x, i_xx
  
          # calculate the IV curve if requested using bishop88
          if ivcurve_pnts:
              vd = v_oc * (
                  (11.0 - np.logspace(np.log10(11.0), 0.0, ivcurve_pnts)) / 10.0
              )
              ivcurve_i, ivcurve_v, _ = _singlediode.bishop88(vd, *args)
  
      columns = ('i_sc', 'v_oc', 'i_mp', 'v_mp', 'p_mp', 'i_x', 'i_xx')
  
      if all(map(np.isscalar, args)) or ivcurve_pnts:
          out = {c: p for c, p in zip(columns, points)}
  
          if ivcurve_pnts:
              out.update(i=ivcurve_i, v=ivcurve_v)
  
          return out
  
      points = np.atleast_1d(*points)  # convert scalars to 1d-arrays
      points = np.vstack(points).T  # collect rows into DataFrame columns
  
      # save the first available pd.Series index, otherwise set to None
      index = next((a.index for a in args if isinstance(a, pd.Series)), None)
  
      out = pd.DataFrame(points, columns=columns, index=index)
  
      return out
  
  
  def max_power_point(photocurrent, saturation_current, resistance_series,
                      resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.Inf,
                      method='brentq'):
      """
      Given the single diode equation coefficients, calculates the maximum power
      point (MPP).
  
      Parameters
      ----------
      photocurrent : numeric
          photo-generated current [A]
      saturation_current : numeric
          diode reverse saturation current [A]
      resistance_series : numeric
          series resitance [ohms]
      resistance_shunt : numeric
          shunt resitance [ohms]
      nNsVth : numeric
          product of thermal voltage ``Vth`` [V], diode ideality factor ``n``,
          and number of serices cells ``Ns``
      d2mutau : numeric, default 0
          PVsyst parameter for cadmium-telluride (CdTe) and amorphous-silicon
          (a-Si) modules that accounts for recombination current in the
          intrinsic layer. The value is the ratio of intrinsic layer thickness
          squared :math:`d^2` to the diffusion length of charge carriers
          :math:`\\mu \\tau`. [V]
      NsVbi : numeric, default np.inf
          PVsyst parameter for cadmium-telluride (CdTe) and amorphous-silicon
          (a-Si) modules that is the product of the PV module number of series
          cells ``Ns`` and the builtin voltage ``Vbi`` of the intrinsic layer.
          [V].
      method : str
          either ``'newton'`` or ``'brentq'``
  
      Returns
      -------
      OrderedDict or pandas.DataFrame
          ``(i_mp, v_mp, p_mp)``
  
      Notes
      -----
      Use this function when you only want to find the maximum power point. Use
      :func:`singlediode` when you need to find additional points on the IV
      curve. This function uses Brent's method by default because it is
      guaranteed to converge.
      """
      i_mp, v_mp, p_mp = _singlediode.bishop88_mpp(
          photocurrent, saturation_current, resistance_series,
          resistance_shunt, nNsVth, d2mutau, NsVbi, method=method.lower()
      )
      if isinstance(photocurrent, pd.Series):
          ivp = {'i_mp': i_mp, 'v_mp': v_mp, 'p_mp': p_mp}
          out = pd.DataFrame(ivp, index=photocurrent.index)
      else:
          out = OrderedDict()
          out['i_mp'] = i_mp
          out['v_mp'] = v_mp
          out['p_mp'] = p_mp
      return out
  
  
  def v_from_i(current, photocurrent, saturation_current, resistance_series,
               resistance_shunt, nNsVth, method='lambertw'):
      '''
      Device voltage at the given device current for the single diode model.
  
      Uses the single diode model (SDM) as described in, e.g.,
      Jain and Kapoor 2004 [1]_.
      The solution is per Eq 3 of [1]_ except when resistance_shunt=numpy.inf,
      in which case the explict solution for voltage is used.
      Ideal device parameters are specified by resistance_shunt=np.inf and
      resistance_series=0.
      Inputs to this function can include scalars and pandas.Series, but it is
      the caller's responsibility to ensure that the arguments are all float64
      and within the proper ranges.
  
      .. versionchanged:: 0.10.0
         The function's arguments have been reordered.
  
      Parameters
      ----------
      current : numeric
          The current in amperes under desired IV curve conditions.
  
      photocurrent : numeric
          Light-generated current (photocurrent) in amperes under desired
          IV curve conditions. Often abbreviated ``I_L``.
          0 <= photocurrent
  
      saturation_current : numeric
          Diode saturation current in amperes under desired IV curve
          conditions. Often abbreviated ``I_0``.
          0 < saturation_current
  
      resistance_series : numeric
          Series resistance in ohms under desired IV curve conditions.
          Often abbreviated ``Rs``.
          0 <= resistance_series < numpy.inf
  
      resistance_shunt : numeric
          Shunt resistance in ohms under desired IV curve conditions.
          Often abbreviated ``Rsh``.
          0 < resistance_shunt <= numpy.inf
  
      nNsVth : numeric
          The product of three components. 1) The usual diode ideal factor
          (n), 2) the number of cells in series (Ns), and 3) the cell
          thermal voltage under the desired IV curve conditions (Vth). The
          thermal voltage of the cell (in volts) may be calculated as
          ``k*temp_cell/q``, where k is Boltzmann's constant (J/K),
          temp_cell is the temperature of the p-n junction in Kelvin, and
          q is the charge of an electron (coulombs).
          0 < nNsVth
  
      method : str
          Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*:
          ``'brentq'`` is limited to 1st quadrant only.
  
      Returns
      -------
      current : np.ndarray or scalar
  
      References
      ----------
      .. [1] A. Jain, A. Kapoor, "Exact analytical solutions of the
         parameters of real solar cells using Lambert W-function", Solar
         Energy Materials and Solar Cells, 81 (2004) 269-277.
      '''
      args = (current, photocurrent, saturation_current,
              resistance_series, resistance_shunt, nNsVth)
      if method.lower() == 'lambertw':
          return _singlediode._lambertw_v_from_i(*args)
      else:
          # Calculate points on the IV curve using either 'newton' or 'brentq'
          # methods. Voltages are determined by first solving the single diode
          # equation for the diode voltage V_d then backing out voltage
          V = _singlediode.bishop88_v_from_i(*args, method=method.lower())
          if all(map(np.isscalar, args)):
              return V
          shape = _singlediode._shape_of_max_size(*args)
          return np.broadcast_to(V, shape)
  
  
  def i_from_v(voltage, photocurrent, saturation_current, resistance_series,
               resistance_shunt, nNsVth, method='lambertw'):
      '''
      Device current at the given device voltage for the single diode model.
  
      Uses the single diode model (SDM) as described in, e.g.,
      Jain and Kapoor 2004 [1]_.
      The solution is per Eq 2 of [1] except when resistance_series=0,
      in which case the explict solution for current is used.
      Ideal device parameters are specified by resistance_shunt=np.inf and
      resistance_series=0.
      Inputs to this function can include scalars and pandas.Series, but it is
      the caller's responsibility to ensure that the arguments are all float64
      and within the proper ranges.
  
      .. versionchanged:: 0.10.0
         The function's arguments have been reordered.
  
      Parameters
      ----------
      voltage : numeric
          The voltage in Volts under desired IV curve conditions.
  
      photocurrent : numeric
          Light-generated current (photocurrent) in amperes under desired
          IV curve conditions. Often abbreviated ``I_L``.
          0 <= photocurrent
  
      saturation_current : numeric
          Diode saturation current in amperes under desired IV curve
          conditions. Often abbreviated ``I_0``.
          0 < saturation_current
  
      resistance_series : numeric
          Series resistance in ohms under desired IV curve conditions.
          Often abbreviated ``Rs``.
          0 <= resistance_series < numpy.inf
  
      resistance_shunt : numeric
          Shunt resistance in ohms under desired IV curve conditions.
          Often abbreviated ``Rsh``.
          0 < resistance_shunt <= numpy.inf
  
      nNsVth : numeric
          The product of three components. 1) The usual diode ideal factor
          (n), 2) the number of cells in series (Ns), and 3) the cell
          thermal voltage under the desired IV curve conditions (Vth). The
          thermal voltage of the cell (in volts) may be calculated as
          ``k*temp_cell/q``, where k is Boltzmann's constant (J/K),
          temp_cell is the temperature of the p-n junction in Kelvin, and
          q is the charge of an electron (coulombs).
          0 < nNsVth
  
      method : str
          Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*:
          ``'brentq'`` is limited to 1st quadrant only.
  
      Returns
      -------
      current : np.ndarray or scalar
  
      References
      ----------
      .. [1] A. Jain, A. Kapoor, "Exact analytical solutions of the
         parameters of real solar cells using Lambert W-function", Solar
         Energy Materials and Solar Cells, 81 (2004) 269-277.
      '''
      args = (voltage, photocurrent, saturation_current,
              resistance_series, resistance_shunt, nNsVth)
      if method.lower() == 'lambertw':
          return _singlediode._lambertw_i_from_v(*args)
      else:
          # Calculate points on the IV curve using either 'newton' or 'brentq'
          # methods. Voltages are determined by first solving the single diode
          # equation for the diode voltage V_d then backing out voltage
          current = _singlediode.bishop88_i_from_v(*args, method=method.lower())
          if all(map(np.isscalar, args)):
              return current
          shape = _singlediode._shape_of_max_size(*args)
          return np.broadcast_to(current, shape)
  
  
  def scale_voltage_current_power(data, voltage=1, current=1):
      """
      Scales the voltage, current, and power in data by the voltage
      and current factors.
  
      Parameters
      ----------
      data: DataFrame
          May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',
          'i_sc', 'p_mp'`.
      voltage: numeric, default 1
          The amount by which to multiply the voltages.
      current: numeric, default 1
          The amount by which to multiply the currents.
  
      Returns
      -------
      scaled_data: DataFrame
          A scaled copy of the input data.
          `'p_mp'` is scaled by `voltage * current`.
      """
  
      # as written, only works with a DataFrame
      # could make it work with a dict, but it would be more verbose
      voltage_keys = ['v_mp', 'v_oc']
      current_keys = ['i_mp', 'i_x', 'i_xx', 'i_sc']
      power_keys = ['p_mp']
      voltage_df = data.filter(voltage_keys, axis=1) * voltage
      current_df = data.filter(current_keys, axis=1) * current
      power_df = data.filter(power_keys, axis=1) * voltage * current
      df = pd.concat([voltage_df, current_df, power_df], axis=1)
      df_sorted = df[data.columns]  # retain original column order
      return df_sorted
  
  
  def pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.):
      r"""
      Implements NREL's PVWatts DC power model. The PVWatts DC model [1]_ is:
  
      .. math::
  
          P_{dc} = \frac{G_{poa eff}}{1000} P_{dc0} ( 1 + \gamma_{pdc} (T_{cell} - T_{ref}))
  
      Note that ``pdc0`` is also used as a symbol in
      :py:func:`pvlib.inverter.pvwatts`. ``pdc0`` in this function refers to the DC
      power of the modules at reference conditions. ``pdc0`` in
      :py:func:`pvlib.inverter.pvwatts` refers to the DC power input limit of
      the inverter.
  
      Parameters
      ----------
      g_poa_effective: numeric
          Irradiance transmitted to the PV cells. To be
          fully consistent with PVWatts, the user must have already
          applied angle of incidence losses, but not soiling, spectral,
          etc. [W/m^2]
      temp_cell: numeric
          Cell temperature [C].
      pdc0: numeric
          Power of the modules at 1000 W/m^2 and cell reference temperature. [W]
      gamma_pdc: numeric
          The temperature coefficient of power. Typically -0.002 to
          -0.005 per degree C. [1/C]
      temp_ref: numeric, default 25.0
          Cell reference temperature. PVWatts defines it to be 25 C and
          is included here for flexibility. [C]
  
      Returns
      -------
      pdc: numeric
          DC power. [W]
  
      References
      ----------
      .. [1] A. P. Dobos, "PVWatts Version 5 Manual"
             http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf
             (2014).
      """  # noqa: E501
  
      pdc = (g_poa_effective * 0.001 * pdc0 *
             (1 + gamma_pdc * (temp_cell - temp_ref)))
  
      return pdc
  
  
  def pvwatts_losses(soiling=2, shading=3, snow=0, mismatch=2, wiring=2,
                     connections=0.5, lid=1.5, nameplate_rating=1, age=0,
                     availability=3):
      r"""
      Implements NREL's PVWatts system loss model.
      The PVWatts loss model [1]_ is:
  
      .. math::
  
          L_{total}(\%) = 100 [ 1 - \Pi_i ( 1 - \frac{L_i}{100} ) ]
  
      All parameters must be in units of %. Parameters may be
      array-like, though all array sizes must match.
  
      Parameters
      ----------
      soiling: numeric, default 2
      shading: numeric, default 3
      snow: numeric, default 0
      mismatch: numeric, default 2
      wiring: numeric, default 2
      connections: numeric, default 0.5
      lid: numeric, default 1.5
          Light induced degradation
      nameplate_rating: numeric, default 1
      age: numeric, default 0
      availability: numeric, default 3
  
      Returns
      -------
      losses: numeric
          System losses in units of %.
  
      References
      ----------
      .. [1] A. P. Dobos, "PVWatts Version 5 Manual"
             http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf
             (2014).
      """
  
      params = [soiling, shading, snow, mismatch, wiring, connections, lid,
                nameplate_rating, age, availability]
  
      # manually looping over params allows for numpy/pandas to handle any
      # array-like broadcasting that might be necessary.
      perf = 1
      for param in params:
          perf *= 1 - param/100
  
      losses = (1 - perf) * 100.
  
      return losses
  
  
  def dc_ohms_from_percent(vmp_ref, imp_ref, dc_ohmic_percent,
                           modules_per_string=1,
                           strings=1):
      """
      Calculates the equivalent resistance of the wires from a percent
      ohmic loss at STC.
  
      Equivalent resistance is calculated with the function:
  
      .. math::
          Rw = (L_{stc} / 100) * (Varray / Iarray)
  
      :math:`Rw` is the equivalent resistance in ohms
      :math:`Varray` is the Vmp of the modules times modules per string
      :math:`Iarray` is the Imp of the modules times strings per array
      :math:`L_{stc}` is the input dc loss percent
  
      Parameters
      ----------
      vmp_ref: numeric
          Voltage at maximum power in reference conditions [V]
      imp_ref: numeric
          Current at maximum power in reference conditions [V]
      dc_ohmic_percent: numeric, default 0
          input dc loss as a percent, e.g. 1.5% loss is input as 1.5
      modules_per_string: int, default 1
          Number of modules per string in the array.
      strings: int, default 1
          Number of parallel strings in the array.
  
      Returns
      ----------
      Rw: numeric
          Equivalent resistance [ohm]
  
      See Also
      --------
      pvlib.pvsystem.dc_ohmic_losses
  
      References
      ----------
      .. [1] PVsyst 7 Help. "Array ohmic wiring loss".
         https://www.pvsyst.com/help/ohmic_loss.htm
      """
      vmp = modules_per_string * vmp_ref
  
      imp = strings * imp_ref
  
      Rw = (dc_ohmic_percent / 100) * (vmp / imp)
  
      return Rw
  
  
  def dc_ohmic_losses(resistance, current):
      """
      Returns ohmic losses in units of power from the equivalent
      resistance of the wires and the operating current.
  
      Parameters
      ----------
      resistance: numeric
          Equivalent resistance of wires [ohm]
      current: numeric, float or array-like
          Operating current [A]
  
      Returns
      ----------
      loss: numeric
          Power Loss [W]
  
      See Also
      --------
      pvlib.pvsystem.dc_ohms_from_percent
  
      References
      ----------
      .. [1] PVsyst 7 Help. "Array ohmic wiring loss".
         https://www.pvsyst.com/help/ohmic_loss.htm
      """
      return resistance * current * current
  
  
  def combine_loss_factors(index, *losses, fill_method='ffill'):
      r"""
      Combines Series loss fractions while setting a common index.
  
      The separate losses are compounded using the following equation:
  
      .. math::
  
          L_{total} = 1 - [ 1 - \Pi_i ( 1 - L_i ) ]
  
      :math:`L_{total}` is the total loss returned
      :math:`L_i` is each individual loss factor input
  
      Note the losses must each be a series with a DatetimeIndex.
      All losses will be resampled to match the index parameter using
      the fill method specified (defaults to "fill forward").
  
      Parameters
      ----------
      index : DatetimeIndex
          The index of the returned loss factors
  
      *losses : Series
          One or more Series of fractions to be compounded
  
      fill_method : {'ffill', 'bfill', 'nearest'}, default 'ffill'
          Method to use for filling holes in reindexed DataFrame
  
      Returns
      -------
      Series
          Fractions resulting from the combination of each loss factor
      """
      combined_factor = 1
  
      for loss in losses:
          loss = loss.reindex(index, method=fill_method)
          combined_factor *= (1 - loss)
  
      return 1 - combined_factor
  
  ```
  --- END FILE ---
  
  Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.
  
  Every *SEARCH/REPLACE* edit must use this format:
  1. The file path
  2. The start of search block: <<<<<<< SEARCH
  3. A contiguous chunk of lines to search for in the existing source code
  4. The dividing line: =======
  5. The lines to replace into the source code
  6. The end of the replace block: >>>>>>> REPLACE
  
  Here is an example:
  
  ```python
  ### mathweb/flask/app.py
  <<<<<<< SEARCH
  from flask import Flask
  =======
  import math
  from flask import Flask
  >>>>>>> REPLACE
  ```
  
  Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
  Wrap the *SEARCH/REPLACE* edit in blocks ```python...```.
  
messages.role: user
verification_info: {"file_context": {"pvlib/pvsystem.py": "\"\"\"\nThe ``pvsystem`` module contains functions for modeling the output and\nperformance of PV modules and inverters.\n\"\"\"\n\nfrom collections import OrderedDict\nimport functools\nimport io\nimport itertools\nimport os\nimport inspect\nfrom urllib.request import urlopen\nimport numpy as np\nfrom scipy import constants\nimport pandas as pd\nfrom dataclasses import dataclass\nfrom abc import ABC, abstractmethod\nfrom typing import Optional\n\nfrom pvlib._deprecation import deprecated, warn_deprecated\n\nfrom pvlib import (atmosphere, iam, inverter, irradiance,\n                   singlediode as _singlediode, spectrum, temperature)\nfrom pvlib.tools import _build_kwargs, _build_args\nimport pvlib.tools as tools\n\n\n# a dict of required parameter names for each DC power model\n_DC_MODEL_PARAMS = {\n    'sapm': {\n        'A0', 'A1', 'A2', 'A3', 'A4', 'B0', 'B1', 'B2', 'B3',\n        'B4', 'B5', 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6',\n        'C7', 'Isco', 'Impo', 'Voco', 'Vmpo', 'Aisc', 'Aimp', 'Bvoco',\n        'Mbvoc', 'Bvmpo', 'Mbvmp', 'N', 'Cells_in_Series',\n        'IXO', 'IXXO', 'FD'},\n    'desoto': {\n        'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_s'},\n    'cec': {\n        'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_s', 'Adjust'},\n    'pvsyst': {\n        'gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_sh_0', 'R_s', 'alpha_sc', 'EgRef',\n        'cells_in_series'},\n    'singlediode': {\n        'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref',\n        'R_sh_ref', 'R_s'},\n    'pvwatts': {'pdc0', 'gamma_pdc'}\n}\n\n\ndef _unwrap_single_value(func):\n    \"\"\"Decorator for functions that return iterables.\n\n    If the length of the iterable returned by `func` is 1, then\n    the single member of the iterable is returned. If the length is\n    greater than 1, then entire iterable is returned.\n\n    Adds 'unwrap' as a keyword argument that can be set to False\n    to force the return value to be a tuple, regardless of its length.\n    \"\"\"\n    @functools.wraps(func)\n    def f(*args, **kwargs):\n        unwrap = kwargs.pop('unwrap', True)\n        x = func(*args, **kwargs)\n        if unwrap and len(x) == 1:\n            return x[0]\n        return x\n    return f\n\n\n# not sure if this belongs in the pvsystem module.\n# maybe something more like core.py? It may eventually grow to\n# import a lot more functionality from other modules.\nclass PVSystem:\n    \"\"\"\n    The PVSystem class defines a standard set of PV system attributes\n    and modeling functions. This class describes the collection and\n    interactions of PV system components rather than an installed system\n    on the ground. It is typically used in combination with\n    :py:class:`~pvlib.location.Location` and\n    :py:class:`~pvlib.modelchain.ModelChain`\n    objects.\n\n    The class supports basic system topologies consisting of:\n\n        * `N` total modules arranged in series\n          (`modules_per_string=N`, `strings_per_inverter=1`).\n        * `M` total modules arranged in parallel\n          (`modules_per_string=1`, `strings_per_inverter=M`).\n        * `NxM` total modules arranged in `M` strings of `N` modules each\n          (`modules_per_string=N`, `strings_per_inverter=M`).\n\n    The class is complementary to the module-level functions.\n\n    The attributes should generally be things that don't change about\n    the system, such the type of module and the inverter. The instance\n    methods accept arguments for things that do change, such as\n    irradiance and temperature.\n\n    Parameters\n    ----------\n    arrays : iterable of Array, optional\n        List of arrays that are part of the system. If not specified\n        a single array is created from the other parameters (e.g.\n        `surface_tilt`, `surface_azimuth`). Must contain at least one Array,\n        if length of arrays is 0 a ValueError is raised. If `arrays` is\n        specified the following PVSystem parameters are ignored:\n\n        - `surface_tilt`\n        - `surface_azimuth`\n        - `albedo`\n        - `surface_type`\n        - `module`\n        - `module_type`\n        - `module_parameters`\n        - `temperature_model_parameters`\n        - `modules_per_string`\n        - `strings_per_inverter`\n\n    surface_tilt: float or array-like, default 0\n        Surface tilt angles in decimal degrees.\n        The tilt angle is defined as degrees from horizontal\n        (e.g. surface facing up = 0, surface facing horizon = 90)\n\n    surface_azimuth: float or array-like, default 180\n        Azimuth angle of the module surface.\n        North=0, East=90, South=180, West=270.\n\n    albedo : None or float, default None\n        Ground surface albedo. If ``None``, then ``surface_type`` is used\n        to look up a value in ``irradiance.SURFACE_ALBEDOS``.\n        If ``surface_type`` is also None then a ground surface albedo\n        of 0.25 is used.\n\n    surface_type : None or string, default None\n        The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for\n        valid values.\n\n    module : None or string, default None\n        The model name of the modules.\n        May be used to look up the module_parameters dictionary\n        via some other method.\n\n    module_type : None or string, default 'glass_polymer'\n         Describes the module's construction. Valid strings are 'glass_polymer'\n         and 'glass_glass'. Used for cell and module temperature calculations.\n\n    module_parameters : None, dict or Series, default None\n        Module parameters as defined by the SAPM, CEC, or other.\n\n    temperature_model_parameters : None, dict or Series, default None.\n        Temperature model parameters as required by one of the models in\n        pvlib.temperature (excluding poa_global, temp_air and wind_speed).\n\n    modules_per_string: int or float, default 1\n        See system topology discussion above.\n\n    strings_per_inverter: int or float, default 1\n        See system topology discussion above.\n\n    inverter : None or string, default None\n        The model name of the inverters.\n        May be used to look up the inverter_parameters dictionary\n        via some other method.\n\n    inverter_parameters : None, dict or Series, default None\n        Inverter parameters as defined by the SAPM, CEC, or other.\n\n    racking_model : None or string, default 'open_rack'\n        Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.\n        Used to identify a parameter set for the SAPM cell temperature model.\n\n    losses_parameters : None, dict or Series, default None\n        Losses parameters as defined by PVWatts or other.\n\n    name : None or string, default None\n\n    **kwargs\n        Arbitrary keyword arguments.\n        Included for compatibility, but not used.\n\n    Raises\n    ------\n    ValueError\n        If `arrays` is not None and has length 0.\n\n    See also\n    --------\n    pvlib.location.Location\n    \"\"\"\n\n    def __init__(self,\n                 arrays=None,\n                 surface_tilt=0, surface_azimuth=180,\n                 albedo=None, surface_type=None,\n                 module=None, module_type=None,\n                 module_parameters=None,\n                 temperature_model_parameters=None,\n                 modules_per_string=1, strings_per_inverter=1,\n                 inverter=None, inverter_parameters=None,\n                 racking_model=None, losses_parameters=None, name=None):\n\n        if arrays is None:\n            if losses_parameters is None:\n                array_losses_parameters = {}\n            else:\n                array_losses_parameters = _build_kwargs(['dc_ohmic_percent'],\n                                                        losses_parameters)\n            self.arrays = (Array(\n                FixedMount(surface_tilt, surface_azimuth, racking_model),\n                albedo,\n                surface_type,\n                module,\n                module_type,\n                module_parameters,\n                temperature_model_parameters,\n                modules_per_string,\n                strings_per_inverter,\n                array_losses_parameters,\n            ),)\n        elif len(arrays) == 0:\n            raise ValueError(\"PVSystem must have at least one Array. \"\n                             \"If you want to create a PVSystem instance \"\n                             \"with a single Array pass `arrays=None` and pass \"\n                             \"values directly to PVSystem attributes, e.g., \"\n                             \"`surface_tilt=30`\")\n        else:\n            self.arrays = tuple(arrays)\n\n        self.inverter = inverter\n        if inverter_parameters is None:\n            self.inverter_parameters = {}\n        else:\n            self.inverter_parameters = inverter_parameters\n\n        if losses_parameters is None:\n            self.losses_parameters = {}\n        else:\n            self.losses_parameters = losses_parameters\n\n        self.name = name\n\n    def __repr__(self):\n        repr = f'PVSystem:\\n  name: {self.name}\\n  '\n        for array in self.arrays:\n            repr += '\\n  '.join(array.__repr__().split('\\n'))\n            repr += '\\n  '\n        repr += f'inverter: {self.inverter}'\n        return repr\n\n    def _validate_per_array(self, values, system_wide=False):\n        \"\"\"Check that `values` is a tuple of the same length as\n        `self.arrays`.\n\n        If `values` is not a tuple it is packed in to a length-1 tuple before\n        the check. If the lengths are not the same a ValueError is raised,\n        otherwise the tuple `values` is returned.\n\n        When `system_wide` is True and `values` is not a tuple, `values`\n        is replicated to a tuple of the same length as `self.arrays` and that\n        tuple is returned.\n        \"\"\"\n        if system_wide and not isinstance(values, tuple):\n            return (values,) * self.num_arrays\n        if not isinstance(values, tuple):\n            values = (values,)\n        if len(values) != len(self.arrays):\n            raise ValueError(\"Length mismatch for per-array parameter\")\n        return values\n\n    @_unwrap_single_value\n    def _infer_cell_type(self):\n        \"\"\"\n        Examines module_parameters and maps the Technology key for the CEC\n        database and the Material key for the Sandia database to a common\n        list of strings for cell type.\n\n        Returns\n        -------\n        cell_type: str\n        \"\"\"\n        return tuple(array._infer_cell_type() for array in self.arrays)\n\n    @_unwrap_single_value\n    def get_aoi(self, solar_zenith, solar_azimuth):\n        \"\"\"Get the angle of incidence on the Array(s) in the system.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series.\n            Solar zenith angle.\n        solar_azimuth : float or Series.\n            Solar azimuth angle.\n\n        Returns\n        -------\n        aoi : Series or tuple of Series\n            The angle of incidence\n        \"\"\"\n\n        return tuple(array.get_aoi(solar_zenith, solar_azimuth)\n                     for array in self.arrays)\n\n    @_unwrap_single_value\n    def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,\n                       dni_extra=None, airmass=None, albedo=None,\n                       model='haydavies', **kwargs):\n        \"\"\"\n        Uses the :py:func:`irradiance.get_total_irradiance` function to\n        calculate the plane of array irradiance components on the tilted\n        surfaces defined by each array's ``surface_tilt`` and\n        ``surface_azimuth``.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series\n            Solar zenith angle.\n        solar_azimuth : float or Series\n            Solar azimuth angle.\n        dni : float or Series or tuple of float or Series\n            Direct Normal Irradiance. [W/m2]\n        ghi : float or Series or tuple of float or Series\n            Global horizontal irradiance. [W/m2]\n        dhi : float or Series or tuple of float or Series\n            Diffuse horizontal irradiance. [W/m2]\n        dni_extra : None, float, Series or tuple of float or Series,\\\n            default None\n            Extraterrestrial direct normal irradiance. [W/m2]\n        airmass : None, float or Series, default None\n            Airmass. [unitless]\n        albedo : None, float or Series, default None\n            Ground surface albedo. [unitless]\n        model : String, default 'haydavies'\n            Irradiance model.\n\n        kwargs\n            Extra parameters passed to :func:`irradiance.get_total_irradiance`.\n\n        Notes\n        -----\n        Each of `dni`, `ghi`, and `dni` parameters may be passed as a tuple\n        to provide different irradiance for each array in the system. If not\n        passed as a tuple then the same value is used for input to each Array.\n        If passed as a tuple the length must be the same as the number of\n        Arrays.\n\n        Returns\n        -------\n        poa_irradiance : DataFrame or tuple of DataFrame\n            Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse',\n            'poa_sky_diffuse', 'poa_ground_diffuse'``.\n\n        See also\n        --------\n        pvlib.irradiance.get_total_irradiance\n        \"\"\"\n        dni = self._validate_per_array(dni, system_wide=True)\n        ghi = self._validate_per_array(ghi, system_wide=True)\n        dhi = self._validate_per_array(dhi, system_wide=True)\n\n        albedo = self._validate_per_array(albedo, system_wide=True)\n\n        return tuple(\n            array.get_irradiance(solar_zenith, solar_azimuth,\n                                 dni, ghi, dhi,\n                                 dni_extra=dni_extra, airmass=airmass,\n                                 albedo=albedo, model=model, **kwargs)\n            for array, dni, ghi, dhi, albedo in zip(\n                self.arrays, dni, ghi, dhi, albedo\n            )\n        )\n\n    @_unwrap_single_value\n    def get_iam(self, aoi, iam_model='physical'):\n        \"\"\"\n        Determine the incidence angle modifier using the method specified by\n        ``iam_model``.\n\n        Parameters for the selected IAM model are expected to be in\n        ``PVSystem.module_parameters``. Default parameters are available for\n        the 'physical', 'ashrae' and 'martin_ruiz' models.\n\n        Parameters\n        ----------\n        aoi : numeric or tuple of numeric\n            The angle of incidence in degrees.\n\n        aoi_model : string, default 'physical'\n            The IAM model to be used. Valid strings are 'physical', 'ashrae',\n            'martin_ruiz', 'sapm' and 'interp'.\n        Returns\n        -------\n        iam : numeric or tuple of numeric\n            The AOI modifier.\n\n        Raises\n        ------\n        ValueError\n            if `iam_model` is not a valid model name.\n        \"\"\"\n        aoi = self._validate_per_array(aoi)\n        return tuple(array.get_iam(aoi, iam_model)\n                     for array, aoi in zip(self.arrays, aoi))\n\n    @_unwrap_single_value\n    def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,\n                             effective_irradiance=None):\n        \"\"\"\n        Determine cell temperature using the method specified by ``model``.\n\n        Parameters\n        ----------\n        poa_global : numeric or tuple of numeric\n            Total incident irradiance in W/m^2.\n\n        temp_air : numeric or tuple of numeric\n            Ambient dry bulb temperature in degrees C.\n\n        wind_speed : numeric or tuple of numeric\n            Wind speed in m/s.\n\n        model : str\n            Supported models include ``'sapm'``, ``'pvsyst'``,\n            ``'faiman'``, ``'fuentes'``, and ``'noct_sam'``\n\n        effective_irradiance : numeric or tuple of numeric, optional\n            The irradiance that is converted to photocurrent in W/m^2.\n            Only used for some models.\n\n        Returns\n        -------\n        numeric or tuple of numeric\n            Values in degrees C.\n\n        See Also\n        --------\n        Array.get_cell_temperature\n\n        Notes\n        -----\n        The `temp_air` and `wind_speed` parameters may be passed as tuples\n        to provide different values for each Array in the system. If passed as\n        a tuple the length must be the same as the number of Arrays. If not\n        passed as a tuple then the same value is used for each Array.\n        \"\"\"\n        poa_global = self._validate_per_array(poa_global)\n        temp_air = self._validate_per_array(temp_air, system_wide=True)\n        wind_speed = self._validate_per_array(wind_speed, system_wide=True)\n        # Not used for all models, but Array.get_cell_temperature handles it\n        effective_irradiance = self._validate_per_array(effective_irradiance,\n                                                        system_wide=True)\n\n        return tuple(\n            array.get_cell_temperature(poa_global, temp_air, wind_speed,\n                                       model, effective_irradiance)\n            for array, poa_global, temp_air, wind_speed, effective_irradiance\n            in zip(\n                self.arrays, poa_global, temp_air, wind_speed,\n                effective_irradiance\n            )\n        )\n\n    @_unwrap_single_value\n    def calcparams_desoto(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`calcparams_desoto` function, the input\n        parameters and ``self.module_parameters`` to calculate the\n        module currents and resistances.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.calcparams_desoto for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        build_kwargs = functools.partial(\n            _build_kwargs,\n            ['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',\n             'R_s', 'alpha_sc', 'EgRef', 'dEgdT',\n             'irrad_ref', 'temp_ref']\n        )\n\n        return tuple(\n            calcparams_desoto(\n                effective_irradiance, temp_cell,\n                **build_kwargs(array.module_parameters)\n            )\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def calcparams_cec(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`calcparams_cec` function, the input\n        parameters and ``self.module_parameters`` to calculate the\n        module currents and resistances.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.calcparams_cec for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        build_kwargs = functools.partial(\n            _build_kwargs,\n            ['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',\n             'R_s', 'alpha_sc', 'Adjust', 'EgRef', 'dEgdT',\n             'irrad_ref', 'temp_ref']\n        )\n\n        return tuple(\n            calcparams_cec(\n                effective_irradiance, temp_cell,\n                **build_kwargs(array.module_parameters)\n            )\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def calcparams_pvsyst(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`calcparams_pvsyst` function, the input\n        parameters and ``self.module_parameters`` to calculate the\n        module currents and resistances.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.calcparams_pvsyst for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        build_kwargs = functools.partial(\n            _build_kwargs,\n            ['gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref',\n             'R_sh_ref', 'R_sh_0', 'R_sh_exp',\n             'R_s', 'alpha_sc', 'EgRef',\n             'irrad_ref', 'temp_ref',\n             'cells_in_series']\n        )\n\n        return tuple(\n            calcparams_pvsyst(\n                effective_irradiance, temp_cell,\n                **build_kwargs(array.module_parameters)\n            )\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def sapm(self, effective_irradiance, temp_cell):\n        \"\"\"\n        Use the :py:func:`sapm` function, the input parameters,\n        and ``self.module_parameters`` to calculate\n        Voc, Isc, Ix, Ixx, Vmp, and Imp.\n\n        Parameters\n        ----------\n        effective_irradiance : numeric or tuple of numeric\n            The irradiance (W/m2) that is converted to photocurrent.\n\n        temp_cell : float or Series or tuple of float or Series\n            The average cell temperature of cells within a module in C.\n\n        Returns\n        -------\n        See pvsystem.sapm for details\n        \"\"\"\n        effective_irradiance = self._validate_per_array(effective_irradiance)\n        temp_cell = self._validate_per_array(temp_cell)\n\n        return tuple(\n            sapm(effective_irradiance, temp_cell, array.module_parameters)\n            for array, effective_irradiance, temp_cell\n            in zip(self.arrays, effective_irradiance, temp_cell)\n        )\n\n    @_unwrap_single_value\n    def sapm_spectral_loss(self, airmass_absolute):\n        \"\"\"\n        Use the :py:func:`pvlib.spectrum.spectral_factor_sapm` function,\n        the input parameters, and ``self.module_parameters`` to calculate F1.\n\n        Parameters\n        ----------\n        airmass_absolute : numeric\n            Absolute airmass.\n\n        Returns\n        -------\n        F1 : numeric or tuple of numeric\n            The SAPM spectral loss coefficient.\n        \"\"\"\n        return tuple(\n            spectrum.spectral_factor_sapm(airmass_absolute,\n                                          array.module_parameters)\n            for array in self.arrays\n        )\n\n    @_unwrap_single_value\n    def sapm_effective_irradiance(self, poa_direct, poa_diffuse,\n                                  airmass_absolute, aoi,\n                                  reference_irradiance=1000):\n        \"\"\"\n        Use the :py:func:`sapm_effective_irradiance` function, the input\n        parameters, and ``self.module_parameters`` to calculate\n        effective irradiance.\n\n        Parameters\n        ----------\n        poa_direct : numeric or tuple of numeric\n            The direct irradiance incident upon the module.  [W/m2]\n\n        poa_diffuse : numeric or tuple of numeric\n            The diffuse irradiance incident on module.  [W/m2]\n\n        airmass_absolute : numeric\n            Absolute airmass. [unitless]\n\n        aoi : numeric or tuple of numeric\n            Angle of incidence. [degrees]\n\n        Returns\n        -------\n        effective_irradiance : numeric or tuple of numeric\n            The SAPM effective irradiance. [W/m2]\n        \"\"\"\n        poa_direct = self._validate_per_array(poa_direct)\n        poa_diffuse = self._validate_per_array(poa_diffuse)\n        aoi = self._validate_per_array(aoi)\n        return tuple(\n            sapm_effective_irradiance(\n                poa_direct, poa_diffuse, airmass_absolute, aoi,\n                array.module_parameters)\n            for array, poa_direct, poa_diffuse, aoi\n            in zip(self.arrays, poa_direct, poa_diffuse, aoi)\n        )\n\n    @_unwrap_single_value\n    def first_solar_spectral_loss(self, pw, airmass_absolute):\n        \"\"\"\n        Use :py:func:`pvlib.spectrum.spectral_factor_firstsolar` to\n        calculate the spectral loss modifier. The model coefficients are\n        specific to the module's cell type, and are determined by searching\n        for one of the following keys in self.module_parameters (in order):\n\n        - 'first_solar_spectral_coefficients' (user-supplied coefficients)\n        - 'Technology' - a string describing the cell type, can be read from\n          the CEC module parameter database\n        - 'Material' - a string describing the cell type, can be read from\n          the Sandia module database.\n\n        Parameters\n        ----------\n        pw : array-like\n            atmospheric precipitable water (cm).\n\n        airmass_absolute : array-like\n            absolute (pressure corrected) airmass.\n\n        Returns\n        -------\n        modifier: array-like or tuple of array-like\n            spectral mismatch factor (unitless) which can be multiplied\n            with broadband irradiance reaching a module's cells to estimate\n            effective irradiance, i.e., the irradiance that is converted to\n            electrical current.\n        \"\"\"\n        pw = self._validate_per_array(pw, system_wide=True)\n\n        def _spectral_correction(array, pw):\n            if 'first_solar_spectral_coefficients' in \\\n                    array.module_parameters.keys():\n                coefficients = \\\n                    array.module_parameters[\n                        'first_solar_spectral_coefficients'\n                    ]\n                module_type = None\n            else:\n                module_type = array._infer_cell_type()\n                coefficients = None\n\n            return spectrum.spectral_factor_firstsolar(\n                pw, airmass_absolute, module_type, coefficients\n            )\n        return tuple(\n            itertools.starmap(_spectral_correction, zip(self.arrays, pw))\n        )\n\n    def singlediode(self, photocurrent, saturation_current,\n                    resistance_series, resistance_shunt, nNsVth,\n                    ivcurve_pnts=None):\n        \"\"\"Wrapper around the :py:func:`pvlib.pvsystem.singlediode` function.\n\n        See :py:func:`pvsystem.singlediode` for details\n        \"\"\"\n        return singlediode(photocurrent, saturation_current,\n                           resistance_series, resistance_shunt, nNsVth,\n                           ivcurve_pnts=ivcurve_pnts)\n\n    def i_from_v(self, voltage, photocurrent, saturation_current,\n                 resistance_series, resistance_shunt, nNsVth):\n        \"\"\"Wrapper around the :py:func:`pvlib.pvsystem.i_from_v` function.\n\n        See :py:func:`pvlib.pvsystem.i_from_v` for details.\n\n        .. versionchanged:: 0.10.0\n           The function's arguments have been reordered.\n        \"\"\"\n        return i_from_v(voltage, photocurrent, saturation_current,\n                        resistance_series, resistance_shunt, nNsVth)\n\n    def get_ac(self, model, p_dc, v_dc=None):\n        r\"\"\"Calculates AC power from p_dc using the inverter model indicated\n        by model and self.inverter_parameters.\n\n        Parameters\n        ----------\n        model : str\n            Must be one of 'sandia', 'adr', or 'pvwatts'.\n        p_dc : numeric, or tuple, list or array of numeric\n            DC power on each MPPT input of the inverter. Use tuple, list or\n            array for inverters with multiple MPPT inputs. If type is array,\n            p_dc must be 2d with axis 0 being the MPPT inputs. [W]\n        v_dc : numeric, or tuple, list or array of numeric\n            DC voltage on each MPPT input of the inverter. Required when\n            model='sandia' or model='adr'. Use tuple, list or\n            array for inverters with multiple MPPT inputs. If type is array,\n            v_dc must be 2d with axis 0 being the MPPT inputs. [V]\n\n        Returns\n        -------\n        power_ac : numeric\n            AC power output for the inverter. [W]\n\n        Raises\n        ------\n        ValueError\n            If model is not one of 'sandia', 'adr' or 'pvwatts'.\n        ValueError\n            If model='adr' and the PVSystem has more than one array.\n\n        See also\n        --------\n        pvlib.inverter.sandia\n        pvlib.inverter.sandia_multi\n        pvlib.inverter.adr\n        pvlib.inverter.pvwatts\n        pvlib.inverter.pvwatts_multi\n        \"\"\"\n        model = model.lower()\n        multiple_arrays = self.num_arrays > 1\n        if model == 'sandia':\n            p_dc = self._validate_per_array(p_dc)\n            v_dc = self._validate_per_array(v_dc)\n            if multiple_arrays:\n                return inverter.sandia_multi(\n                    v_dc, p_dc, self.inverter_parameters)\n            return inverter.sandia(v_dc[0], p_dc[0], self.inverter_parameters)\n        elif model == 'pvwatts':\n            kwargs = _build_kwargs(['eta_inv_nom', 'eta_inv_ref'],\n                                   self.inverter_parameters)\n            p_dc = self._validate_per_array(p_dc)\n            if multiple_arrays:\n                return inverter.pvwatts_multi(\n                    p_dc, self.inverter_parameters['pdc0'], **kwargs)\n            return inverter.pvwatts(\n                p_dc[0], self.inverter_parameters['pdc0'], **kwargs)\n        elif model == 'adr':\n            if multiple_arrays:\n                raise ValueError(\n                    'The adr inverter function cannot be used for an inverter',\n                    ' with multiple MPPT inputs')\n            # While this is only used for single-array systems, calling\n            # _validate_per_arry lets us pass in singleton tuples.\n            p_dc = self._validate_per_array(p_dc)\n            v_dc = self._validate_per_array(v_dc)\n            return inverter.adr(v_dc[0], p_dc[0], self.inverter_parameters)\n        else:\n            raise ValueError(\n                model + ' is not a valid AC power model.',\n                ' model must be one of \"sandia\", \"adr\" or \"pvwatts\"')\n\n    @_unwrap_single_value\n    def scale_voltage_current_power(self, data):\n        \"\"\"\n        Scales the voltage, current, and power of the `data` DataFrame\n        by `self.modules_per_string` and `self.strings_per_inverter`.\n\n        Parameters\n        ----------\n        data: DataFrame or tuple of DataFrame\n            May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',\n            'i_sc', 'p_mp'`.\n\n        Returns\n        -------\n        scaled_data: DataFrame or tuple of DataFrame\n            A scaled copy of the input data.\n        \"\"\"\n        data = self._validate_per_array(data)\n        return tuple(\n            scale_voltage_current_power(data,\n                                        voltage=array.modules_per_string,\n                                        current=array.strings)\n            for array, data in zip(self.arrays, data)\n        )\n\n    @_unwrap_single_value\n    def pvwatts_dc(self, g_poa_effective, temp_cell):\n        \"\"\"\n        Calcuates DC power according to the PVWatts model using\n        :py:func:`pvlib.pvsystem.pvwatts_dc`, `self.module_parameters['pdc0']`,\n        and `self.module_parameters['gamma_pdc']`.\n\n        See :py:func:`pvlib.pvsystem.pvwatts_dc` for details.\n        \"\"\"\n        g_poa_effective = self._validate_per_array(g_poa_effective)\n        temp_cell = self._validate_per_array(temp_cell)\n        return tuple(\n            pvwatts_dc(g_poa_effective, temp_cell,\n                       array.module_parameters['pdc0'],\n                       array.module_parameters['gamma_pdc'],\n                       **_build_kwargs(['temp_ref'], array.module_parameters))\n            for array, g_poa_effective, temp_cell\n            in zip(self.arrays, g_poa_effective, temp_cell)\n        )\n\n    def pvwatts_losses(self):\n        \"\"\"\n        Calculates DC power losses according the PVwatts model using\n        :py:func:`pvlib.pvsystem.pvwatts_losses` and\n        ``self.losses_parameters``.\n\n        See :py:func:`pvlib.pvsystem.pvwatts_losses` for details.\n        \"\"\"\n        kwargs = _build_kwargs(['soiling', 'shading', 'snow', 'mismatch',\n                                'wiring', 'connections', 'lid',\n                                'nameplate_rating', 'age', 'availability'],\n                               self.losses_parameters)\n        return pvwatts_losses(**kwargs)\n\n    @_unwrap_single_value\n    def dc_ohms_from_percent(self):\n        \"\"\"\n        Calculates the equivalent resistance of the wires for each array using\n        :py:func:`pvlib.pvsystem.dc_ohms_from_percent`\n\n        See :py:func:`pvlib.pvsystem.dc_ohms_from_percent` for details.\n        \"\"\"\n\n        return tuple(array.dc_ohms_from_percent() for array in self.arrays)\n\n    @property\n    def num_arrays(self):\n        \"\"\"The number of Arrays in the system.\"\"\"\n        return len(self.arrays)\n\n\nclass Array:\n    \"\"\"\n    An Array is a set of of modules at the same orientation.\n\n    Specifically, an array is defined by its mount, the\n    module parameters, the number of parallel strings of modules\n    and the number of modules on each string.\n\n    Parameters\n    ----------\n    mount: FixedMount, SingleAxisTrackerMount, or other\n        Mounting for the array, either on fixed-tilt racking or horizontal\n        single axis tracker. Mounting is used to determine module orientation.\n        If not provided, a FixedMount with zero tilt is used.\n\n    albedo : None or float, default None\n        Ground surface albedo. If ``None``, then ``surface_type`` is used\n        to look up a value in ``irradiance.SURFACE_ALBEDOS``.\n        If ``surface_type`` is also None then a ground surface albedo\n        of 0.25 is used.\n\n    surface_type : None or string, default None\n        The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for valid\n        values.\n\n    module : None or string, default None\n        The model name of the modules.\n        May be used to look up the module_parameters dictionary\n        via some other method.\n\n    module_type : None or string, default None\n         Describes the module's construction. Valid strings are 'glass_polymer'\n         and 'glass_glass'. Used for cell and module temperature calculations.\n\n    module_parameters : None, dict or Series, default None\n        Parameters for the module model, e.g., SAPM, CEC, or other.\n\n    temperature_model_parameters : None, dict or Series, default None.\n        Parameters for the module temperature model, e.g., SAPM, Pvsyst, or\n        other.\n\n    modules_per_string: int, default 1\n        Number of modules per string in the array.\n\n    strings: int, default 1\n        Number of parallel strings in the array.\n\n    array_losses_parameters: None, dict or Series, default None.\n        Supported keys are 'dc_ohmic_percent'.\n\n    name: None or str, default None\n        Name of Array instance.\n    \"\"\"\n\n    def __init__(self, mount,\n                 albedo=None, surface_type=None,\n                 module=None, module_type=None,\n                 module_parameters=None,\n                 temperature_model_parameters=None,\n                 modules_per_string=1, strings=1,\n                 array_losses_parameters=None,\n                 name=None):\n        self.mount = mount\n\n        self.surface_type = surface_type\n        if albedo is None:\n            self.albedo = irradiance.SURFACE_ALBEDOS.get(surface_type, 0.25)\n        else:\n            self.albedo = albedo\n\n        self.module = module\n        if module_parameters is None:\n            self.module_parameters = {}\n        else:\n            self.module_parameters = module_parameters\n\n        self.module_type = module_type\n\n        self.strings = strings\n        self.modules_per_string = modules_per_string\n\n        if temperature_model_parameters is None:\n            self.temperature_model_parameters = \\\n                self._infer_temperature_model_params()\n        else:\n            self.temperature_model_parameters = temperature_model_parameters\n\n        if array_losses_parameters is None:\n            self.array_losses_parameters = {}\n        else:\n            self.array_losses_parameters = array_losses_parameters\n\n        self.name = name\n\n    def __repr__(self):\n        attrs = ['name', 'mount', 'module',\n                 'albedo', 'module_type',\n                 'temperature_model_parameters',\n                 'strings', 'modules_per_string']\n\n        return 'Array:\\n  ' + '\\n  '.join(\n            f'{attr}: {getattr(self, attr)}' for attr in attrs\n        )\n\n    def _infer_temperature_model_params(self):\n        # try to infer temperature model parameters from from racking_model\n        # and module_type\n        param_set = f'{self.mount.racking_model}_{self.module_type}'\n        if param_set in temperature.TEMPERATURE_MODEL_PARAMETERS['sapm']:\n            return temperature._temperature_model_params('sapm', param_set)\n        elif 'freestanding' in param_set:\n            return temperature._temperature_model_params('pvsyst',\n                                                         'freestanding')\n        elif 'insulated' in param_set:  # after SAPM to avoid confusing keys\n            return temperature._temperature_model_params('pvsyst',\n                                                         'insulated')\n        else:\n            return {}\n\n    def _infer_cell_type(self):\n        \"\"\"\n        Examines module_parameters and maps the Technology key for the CEC\n        database and the Material key for the Sandia database to a common\n        list of strings for cell type.\n\n        Returns\n        -------\n        cell_type: str\n\n        \"\"\"\n\n        _cell_type_dict = {'Multi-c-Si': 'multisi',\n                           'Mono-c-Si': 'monosi',\n                           'Thin Film': 'cigs',\n                           'a-Si/nc': 'asi',\n                           'CIS': 'cigs',\n                           'CIGS': 'cigs',\n                           '1-a-Si': 'asi',\n                           'CdTe': 'cdte',\n                           'a-Si': 'asi',\n                           '2-a-Si': None,\n                           '3-a-Si': None,\n                           'HIT-Si': 'monosi',\n                           'mc-Si': 'multisi',\n                           'c-Si': 'multisi',\n                           'Si-Film': 'asi',\n                           'EFG mc-Si': 'multisi',\n                           'GaAs': None,\n                           'a-Si / mono-Si': 'monosi'}\n\n        if 'Technology' in self.module_parameters.keys():\n            # CEC module parameter set\n            cell_type = _cell_type_dict[self.module_parameters['Technology']]\n        elif 'Material' in self.module_parameters.keys():\n            # Sandia module parameter set\n            cell_type = _cell_type_dict[self.module_parameters['Material']]\n        else:\n            cell_type = None\n\n        return cell_type\n\n    def get_aoi(self, solar_zenith, solar_azimuth):\n        \"\"\"\n        Get the angle of incidence on the array.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series\n            Solar zenith angle.\n        solar_azimuth : float or Series\n            Solar azimuth angle\n\n        Returns\n        -------\n        aoi : Series\n            Then angle of incidence.\n        \"\"\"\n        orientation = self.mount.get_orientation(solar_zenith, solar_azimuth)\n        return irradiance.aoi(orientation['surface_tilt'],\n                              orientation['surface_azimuth'],\n                              solar_zenith, solar_azimuth)\n\n    def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,\n                       dni_extra=None, airmass=None, albedo=None,\n                       model='haydavies', **kwargs):\n        \"\"\"\n        Get plane of array irradiance components.\n\n        Uses the :py:func:`pvlib.irradiance.get_total_irradiance` function to\n        calculate the plane of array irradiance components for a surface\n        defined by ``self.surface_tilt`` and ``self.surface_azimuth``.\n\n        Parameters\n        ----------\n        solar_zenith : float or Series.\n            Solar zenith angle.\n        solar_azimuth : float or Series.\n            Solar azimuth angle.\n        dni : float or Series\n            Direct normal irradiance. [W/m2]\n        ghi : float or Series. [W/m2]\n            Global horizontal irradiance\n        dhi : float or Series\n            Diffuse horizontal irradiance. [W/m2]\n        dni_extra : None, float or Series, default None\n            Extraterrestrial direct normal irradiance. [W/m2]\n        airmass : None, float or Series, default None\n            Airmass. [unitless]\n        albedo : None, float or Series, default None\n            Ground surface albedo. [unitless]\n        model : String, default 'haydavies'\n            Irradiance model.\n\n        kwargs\n            Extra parameters passed to\n            :py:func:`pvlib.irradiance.get_total_irradiance`.\n\n        Returns\n        -------\n        poa_irradiance : DataFrame\n            Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse',\n            'poa_sky_diffuse', 'poa_ground_diffuse'``.\n\n        See also\n        --------\n        :py:func:`pvlib.irradiance.get_total_irradiance`\n        \"\"\"\n        if albedo is None:\n            albedo = self.albedo\n\n        # not needed for all models, but this is easier\n        if dni_extra is None:\n            dni_extra = irradiance.get_extra_radiation(solar_zenith.index)\n\n        if airmass is None:\n            airmass = atmosphere.get_relative_airmass(solar_zenith)\n\n        orientation = self.mount.get_orientation(solar_zenith, solar_azimuth)\n        return irradiance.get_total_irradiance(orientation['surface_tilt'],\n                                               orientation['surface_azimuth'],\n                                               solar_zenith, solar_azimuth,\n                                               dni, ghi, dhi,\n                                               dni_extra=dni_extra,\n                                               airmass=airmass,\n                                               albedo=albedo,\n                                               model=model,\n                                               **kwargs)\n\n    def get_iam(self, aoi, iam_model='physical'):\n        \"\"\"\n        Determine the incidence angle modifier using the method specified by\n        ``iam_model``.\n\n        Parameters for the selected IAM model are expected to be in\n        ``Array.module_parameters``. Default parameters are available for\n        the 'physical', 'ashrae' and 'martin_ruiz' models.\n\n        Parameters\n        ----------\n        aoi : numeric\n            The angle of incidence in degrees.\n\n        aoi_model : string, default 'physical'\n            The IAM model to be used. Valid strings are 'physical', 'ashrae',\n            'martin_ruiz', 'sapm' and 'interp'.\n\n        Returns\n        -------\n        iam : numeric\n            The AOI modifier.\n\n        Raises\n        ------\n        ValueError\n            if `iam_model` is not a valid model name.\n        \"\"\"\n        model = iam_model.lower()\n        if model in ['ashrae', 'physical', 'martin_ruiz', 'interp']:\n            func = getattr(iam, model)  # get function at pvlib.iam\n            # get all parameters from function signature to retrieve them from\n            # module_parameters if present\n            params = set(inspect.signature(func).parameters.keys())\n            params.discard('aoi')  # exclude aoi so it can't be repeated\n            kwargs = _build_kwargs(params, self.module_parameters)\n            return func(aoi, **kwargs)\n        elif model == 'sapm':\n            return iam.sapm(aoi, self.module_parameters)\n        else:\n            raise ValueError(model + ' is not a valid IAM model')\n\n    def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,\n                             effective_irradiance=None):\n        \"\"\"\n        Determine cell temperature using the method specified by ``model``.\n\n        Parameters\n        ----------\n        poa_global : numeric\n            Total incident irradiance [W/m^2]\n\n        temp_air : numeric\n            Ambient dry bulb temperature [C]\n\n        wind_speed : numeric\n            Wind speed [m/s]\n\n        model : str\n            Supported models include ``'sapm'``, ``'pvsyst'``,\n            ``'faiman'``, ``'fuentes'``, and ``'noct_sam'``\n\n        effective_irradiance : numeric, optional\n            The irradiance that is converted to photocurrent in W/m^2.\n            Only used for some models.\n\n        Returns\n        -------\n        numeric\n            Values in degrees C.\n\n        See Also\n        --------\n        pvlib.temperature.sapm_cell, pvlib.temperature.pvsyst_cell,\n        pvlib.temperature.faiman, pvlib.temperature.fuentes,\n        pvlib.temperature.noct_sam\n\n        Notes\n        -----\n        Some temperature models have requirements for the input types;\n        see the documentation of the underlying model function for details.\n        \"\"\"\n        # convenience wrapper to avoid passing args 2 and 3 every call\n        _build_tcell_args = functools.partial(\n            _build_args, input_dict=self.temperature_model_parameters,\n            dict_name='temperature_model_parameters')\n\n        if model == 'sapm':\n            func = temperature.sapm_cell\n            required = _build_tcell_args(['a', 'b', 'deltaT'])\n            optional = _build_kwargs(['irrad_ref'],\n                                     self.temperature_model_parameters)\n        elif model == 'pvsyst':\n            func = temperature.pvsyst_cell\n            required = tuple()\n            optional = {\n                **_build_kwargs(['module_efficiency', 'alpha_absorption'],\n                                self.module_parameters),\n                **_build_kwargs(['u_c', 'u_v'],\n                                self.temperature_model_parameters)\n            }\n        elif model == 'faiman':\n            func = temperature.faiman\n            required = tuple()\n            optional = _build_kwargs(['u0', 'u1'],\n                                     self.temperature_model_parameters)\n        elif model == 'fuentes':\n            func = temperature.fuentes\n            required = _build_tcell_args(['noct_installed'])\n            optional = _build_kwargs([\n                'wind_height', 'emissivity', 'absorption',\n                'surface_tilt', 'module_width', 'module_length'],\n                self.temperature_model_parameters)\n            if self.mount.module_height is not None:\n                optional['module_height'] = self.mount.module_height\n        elif model == 'noct_sam':\n            func = functools.partial(temperature.noct_sam,\n                                     effective_irradiance=effective_irradiance)\n            required = _build_tcell_args(['noct', 'module_efficiency'])\n            optional = _build_kwargs(['transmittance_absorptance',\n                                      'array_height', 'mount_standoff'],\n                                     self.temperature_model_parameters)\n        else:\n            raise ValueError(f'{model} is not a valid cell temperature model')\n\n        temperature_cell = func(poa_global, temp_air, wind_speed,\n                                *required, **optional)\n        return temperature_cell\n\n    def dc_ohms_from_percent(self):\n        \"\"\"\n        Calculates the equivalent resistance of the wires using\n        :py:func:`pvlib.pvsystem.dc_ohms_from_percent`\n\n        Makes use of array module parameters according to the\n        following DC models:\n\n        CEC:\n\n            * `self.module_parameters[\"V_mp_ref\"]`\n            * `self.module_parameters[\"I_mp_ref\"]`\n\n        SAPM:\n\n            * `self.module_parameters[\"Vmpo\"]`\n            * `self.module_parameters[\"Impo\"]`\n\n        PVsyst-like or other:\n\n            * `self.module_parameters[\"Vmpp\"]`\n            * `self.module_parameters[\"Impp\"]`\n\n        Other array parameters that are used are:\n        `self.losses_parameters[\"dc_ohmic_percent\"]`,\n        `self.modules_per_string`, and\n        `self.strings`.\n\n        See :py:func:`pvlib.pvsystem.dc_ohms_from_percent` for more details.\n        \"\"\"\n\n        # get relevent Vmp and Imp parameters from CEC parameters\n        if all(elem in self.module_parameters\n               for elem in ['V_mp_ref', 'I_mp_ref']):\n            vmp_ref = self.module_parameters['V_mp_ref']\n            imp_ref = self.module_parameters['I_mp_ref']\n\n        # get relevant Vmp and Imp parameters from SAPM parameters\n        elif all(elem in self.module_parameters for elem in ['Vmpo', 'Impo']):\n            vmp_ref = self.module_parameters['Vmpo']\n            imp_ref = self.module_parameters['Impo']\n\n        # get relevant Vmp and Imp parameters if they are PVsyst-like\n        elif all(elem in self.module_parameters for elem in ['Vmpp', 'Impp']):\n            vmp_ref = self.module_parameters['Vmpp']\n            imp_ref = self.module_parameters['Impp']\n\n        # raise error if relevant Vmp and Imp parameters are not found\n        else:\n            raise ValueError('Parameters for Vmp and Imp could not be found '\n                             'in the array module parameters. Module '\n                             'parameters must include one set of '\n                             '{\"V_mp_ref\", \"I_mp_Ref\"}, '\n                             '{\"Vmpo\", \"Impo\"}, or '\n                             '{\"Vmpp\", \"Impp\"}.'\n                             )\n\n        return dc_ohms_from_percent(\n            vmp_ref,\n            imp_ref,\n            self.array_losses_parameters['dc_ohmic_percent'],\n            self.modules_per_string,\n            self.strings)\n\n\n@dataclass\nclass AbstractMount(ABC):\n    \"\"\"\n    A base class for Mount classes to extend. It is not intended to be\n    instantiated directly.\n    \"\"\"\n\n    @abstractmethod\n    def get_orientation(self, solar_zenith, solar_azimuth):\n        \"\"\"\n        Determine module orientation.\n\n        Parameters\n        ----------\n        solar_zenith : numeric\n            Solar apparent zenith angle [degrees]\n        solar_azimuth : numeric\n            Solar azimuth angle [degrees]\n\n        Returns\n        -------\n        orientation : dict-like\n            A dict-like object with keys `'surface_tilt', 'surface_azimuth'`\n            (typically a dict or pandas.DataFrame)\n        \"\"\"\n\n\n@dataclass\nclass FixedMount(AbstractMount):\n    \"\"\"\n    Racking at fixed (static) orientation.\n\n    Parameters\n    ----------\n    surface_tilt : float, default 0\n        Surface tilt angle. The tilt angle is defined as angle from horizontal\n        (e.g. surface facing up = 0, surface facing horizon = 90) [degrees]\n\n    surface_azimuth : float, default 180\n        Azimuth angle of the module surface. North=0, East=90, South=180,\n        West=270. [degrees]\n\n    racking_model : str, optional\n        Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.\n        Used to identify a parameter set for the SAPM cell temperature model.\n\n    module_height : float, optional\n       The height above ground of the center of the module [m]. Used for\n       the Fuentes cell temperature model.\n    \"\"\"\n\n    surface_tilt: float = 0.0\n    surface_azimuth: float = 180.0\n    racking_model: Optional[str] = None\n    module_height: Optional[float] = None\n\n    def get_orientation(self, solar_zenith, solar_azimuth):\n        # note -- docstring is automatically inherited from AbstractMount\n        return {\n            'surface_tilt': self.surface_tilt,\n            'surface_azimuth': self.surface_azimuth,\n        }\n\n\n@dataclass\nclass SingleAxisTrackerMount(AbstractMount):\n    \"\"\"\n    Single-axis tracker racking for dynamic solar tracking.\n\n    Parameters\n    ----------\n    axis_tilt : float, default 0\n        The tilt of the axis of rotation (i.e, the y-axis defined by\n        axis_azimuth) with respect to horizontal. [degrees]\n\n    axis_azimuth : float, default 180\n        A value denoting the compass direction along which the axis of\n        rotation lies, measured east of north. [degrees]\n\n    max_angle : float, default 90\n        A value denoting the maximum rotation angle\n        of the one-axis tracker from its horizontal position (horizontal\n        if axis_tilt = 0). A max_angle of 90 degrees allows the tracker\n        to rotate to a vertical position to point the panel towards a\n        horizon. max_angle of 180 degrees allows for full rotation. [degrees]\n\n    backtrack : bool, default True\n        Controls whether the tracker has the capability to \"backtrack\"\n        to avoid row-to-row shading. False denotes no backtrack\n        capability. True denotes backtrack capability.\n\n    gcr : float, default 2.0/7.0\n        A value denoting the ground coverage ratio of a tracker system\n        which utilizes backtracking; i.e. the ratio between the PV array\n        surface area to total ground area. A tracker system with modules\n        2 meters wide, centered on the tracking axis, with 6 meters\n        between the tracking axes has a gcr of 2/6=0.333. If gcr is not\n        provided, a gcr of 2/7 is default. gcr must be <=1. [unitless]\n\n    cross_axis_tilt : float, default 0.0\n        The angle, relative to horizontal, of the line formed by the\n        intersection between the slope containing the tracker axes and a plane\n        perpendicular to the tracker axes. Cross-axis tilt should be specified\n        using a right-handed convention. For example, trackers with axis\n        azimuth of 180 degrees (heading south) will have a negative cross-axis\n        tilt if the tracker axes plane slopes down to the east and positive\n        cross-axis tilt if the tracker axes plane slopes up to the east. Use\n        :func:`~pvlib.tracking.calc_cross_axis_tilt` to calculate\n        `cross_axis_tilt`. [degrees]\n\n    racking_model : str, optional\n        Valid strings are 'open_rack', 'close_mount', and 'insulated_back'.\n        Used to identify a parameter set for the SAPM cell temperature model.\n\n    module_height : float, optional\n       The height above ground of the center of the module [m]. Used for\n       the Fuentes cell temperature model.\n    \"\"\"\n    axis_tilt: float = 0.0\n    axis_azimuth: float = 0.0\n    max_angle: float = 90.0\n    backtrack: bool = True\n    gcr: float = 2.0/7.0\n    cross_axis_tilt: float = 0.0\n    racking_model: Optional[str] = None\n    module_height: Optional[float] = None\n\n    def get_orientation(self, solar_zenith, solar_azimuth):\n        # note -- docstring is automatically inherited from AbstractMount\n        from pvlib import tracking  # avoid circular import issue\n        tracking_data = tracking.singleaxis(\n            solar_zenith, solar_azimuth,\n            self.axis_tilt, self.axis_azimuth,\n            self.max_angle, self.backtrack,\n            self.gcr, self.cross_axis_tilt\n        )\n        return tracking_data\n\n\ndef calcparams_desoto(effective_irradiance, temp_cell,\n                      alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s,\n                      EgRef=1.121, dEgdT=-0.0002677,\n                      irrad_ref=1000, temp_ref=25):\n    '''\n    Calculates five parameter values for the single diode equation at\n    effective irradiance and cell temperature using the De Soto et al.\n    model described in [1]_. The five values returned by calcparams_desoto\n    can be used by singlediode to calculate an IV curve.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        The irradiance (W/m2) that is converted to photocurrent.\n\n    temp_cell : numeric\n        The average cell temperature of cells within a module in C.\n\n    alpha_sc : float\n        The short-circuit current temperature coefficient of the\n        module in units of A/C.\n\n    a_ref : float\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at reference\n        conditions, in units of V.\n\n    I_L_ref : float\n        The light-generated current (or photocurrent) at reference conditions,\n        in amperes.\n\n    I_o_ref : float\n        The dark or diode reverse saturation current at reference conditions,\n        in amperes.\n\n    R_sh_ref : float\n        The shunt resistance at reference conditions, in ohms.\n\n    R_s : float\n        The series resistance at reference conditions, in ohms.\n\n    EgRef : float\n        The energy bandgap at reference temperature in units of eV.\n        1.121 eV for crystalline silicon. EgRef must be >0.  For parameters\n        from the SAM CEC module database, EgRef=1.121 is implicit for all\n        cell types in the parameter estimation algorithm used by NREL.\n\n    dEgdT : float\n        The temperature dependence of the energy bandgap at reference\n        conditions in units of 1/K. May be either a scalar value\n        (e.g. -0.0002677 as in [1]_) or a DataFrame (this may be useful if\n        dEgdT is a modeled as a function of temperature). For parameters from\n        the SAM CEC module database, dEgdT=-0.0002677 is implicit for all cell\n        types in the parameter estimation algorithm used by NREL.\n\n    irrad_ref : float (optional, default=1000)\n        Reference irradiance in W/m^2.\n\n    temp_ref : float (optional, default=25)\n        Reference cell temperature in C.\n\n    Returns\n    -------\n    Tuple of the following results:\n\n    photocurrent : numeric\n        Light-generated current in amperes\n\n    saturation_current : numeric\n        Diode saturation curent in amperes\n\n    resistance_series : numeric\n        Series resistance in ohms\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms\n\n    nNsVth : numeric\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at\n        specified effective irradiance and cell temperature.\n\n    References\n    ----------\n    .. [1] W. De Soto et al., \"Improvement and validation of a model for\n       photovoltaic array performance\", Solar Energy, vol 80, pp. 78-88,\n       2006.\n\n    .. [2] System Advisor Model web page. https://sam.nrel.gov.\n\n    .. [3] A. Dobos, \"An Improved Coefficient Calculator for the California\n       Energy Commission 6 Parameter Photovoltaic Module Model\", Journal of\n       Solar Energy Engineering, vol 134, 2012.\n\n    .. [4] O. Madelung, \"Semiconductors: Data Handbook, 3rd ed.\" ISBN\n       3-540-40488-0\n\n    See Also\n    --------\n    singlediode\n    retrieve_sam\n\n    Notes\n    -----\n    If the reference parameters in the ModuleParameters struct are read\n    from a database or library of parameters (e.g. System Advisor\n    Model), it is important to use the same EgRef and dEgdT values that\n    were used to generate the reference parameters, regardless of the\n    actual bandgap characteristics of the semiconductor. For example, in\n    the case of the System Advisor Model library, created as described\n    in [3], EgRef and dEgdT for all modules were 1.121 and -0.0002677,\n    respectively.\n\n    This table of reference bandgap energies (EgRef), bandgap energy\n    temperature dependence (dEgdT), and \"typical\" airmass response (M)\n    is provided purely as reference to those who may generate their own\n    reference module parameters (a_ref, IL_ref, I0_ref, etc.) based upon\n    the various PV semiconductors. Again, we stress the importance of\n    using identical EgRef and dEgdT when generation reference parameters\n    and modifying the reference parameters (for irradiance, temperature,\n    and airmass) per DeSoto's equations.\n\n     Crystalline Silicon (Si):\n         * EgRef = 1.121\n         * dEgdT = -0.0002677\n\n         >>> M = np.polyval([-1.26E-4, 2.816E-3, -0.024459, 0.086257, 0.9181],\n         ...                AMa) # doctest: +SKIP\n\n         Source: [1]\n\n     Cadmium Telluride (CdTe):\n         * EgRef = 1.475\n         * dEgdT = -0.0003\n\n         >>> M = np.polyval([-2.46E-5, 9.607E-4, -0.0134, 0.0716, 0.9196],\n         ...                AMa) # doctest: +SKIP\n\n         Source: [4]\n\n     Copper Indium diSelenide (CIS):\n         * EgRef = 1.010\n         * dEgdT = -0.00011\n\n         >>> M = np.polyval([-3.74E-5, 0.00125, -0.01462, 0.0718, 0.9210],\n         ...                AMa) # doctest: +SKIP\n\n         Source: [4]\n\n     Copper Indium Gallium diSelenide (CIGS):\n         * EgRef = 1.15\n         * dEgdT = ????\n\n         >>> M = np.polyval([-9.07E-5, 0.0022, -0.0202, 0.0652, 0.9417],\n         ...                AMa) # doctest: +SKIP\n\n         Source: Wikipedia\n\n     Gallium Arsenide (GaAs):\n         * EgRef = 1.424\n         * dEgdT = -0.000433\n         * M = unknown\n\n         Source: [4]\n    '''\n\n    # Boltzmann constant in eV/K, 8.617332478e-05\n    k = constants.value('Boltzmann constant in eV/K')\n\n    # reference temperature\n    Tref_K = temp_ref + 273.15\n    Tcell_K = temp_cell + 273.15\n\n    E_g = EgRef * (1 + dEgdT*(Tcell_K - Tref_K))\n\n    nNsVth = a_ref * (Tcell_K / Tref_K)\n\n    # In the equation for IL, the single factor effective_irradiance is\n    # used, in place of the product S*M in [1]. effective_irradiance is\n    # equivalent to the product of S (irradiance reaching a module's cells) *\n    # M (spectral adjustment factor) as described in [1].\n    IL = effective_irradiance / irrad_ref * \\\n        (I_L_ref + alpha_sc * (Tcell_K - Tref_K))\n    I0 = (I_o_ref * ((Tcell_K / Tref_K) ** 3) *\n          (np.exp(EgRef / (k*(Tref_K)) - (E_g / (k*(Tcell_K))))))\n    # Note that the equation for Rsh differs from [1]. In [1] Rsh is given as\n    # Rsh = Rsh_ref * (S_ref / S) where S is broadband irradiance reaching\n    # the module's cells. If desired this model behavior can be duplicated\n    # by applying reflection and soiling losses to broadband plane of array\n    # irradiance and not applying a spectral loss modifier, i.e.,\n    # spectral_modifier = 1.0.\n    # use errstate to silence divide by warning\n    with np.errstate(divide='ignore'):\n        Rsh = R_sh_ref * (irrad_ref / effective_irradiance)\n\n    Rs = R_s\n\n    numeric_args = (effective_irradiance, temp_cell)\n    out = (IL, I0, Rs, Rsh, nNsVth)\n\n    if all(map(np.isscalar, numeric_args)):\n        return out\n\n    index = tools.get_pandas_index(*numeric_args)\n\n    if index is None:\n        return np.broadcast_arrays(*out)\n\n    return tuple(pd.Series(a, index=index).rename(None) for a in out)\n\n\ndef calcparams_cec(effective_irradiance, temp_cell,\n                   alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s,\n                   Adjust, EgRef=1.121, dEgdT=-0.0002677,\n                   irrad_ref=1000, temp_ref=25):\n    '''\n    Calculates five parameter values for the single diode equation at\n    effective irradiance and cell temperature using the CEC\n    model. The CEC model [1]_ differs from the De soto et al.\n    model [3]_ by the parameter Adjust. The five values returned by\n    calcparams_cec can be used by singlediode to calculate an IV curve.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        The irradiance (W/m2) that is converted to photocurrent.\n\n    temp_cell : numeric\n        The average cell temperature of cells within a module in C.\n\n    alpha_sc : float\n        The short-circuit current temperature coefficient of the\n        module in units of A/C.\n\n    a_ref : float\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at reference\n        conditions, in units of V.\n\n    I_L_ref : float\n        The light-generated current (or photocurrent) at reference conditions,\n        in amperes.\n\n    I_o_ref : float\n        The dark or diode reverse saturation current at reference conditions,\n        in amperes.\n\n    R_sh_ref : float\n        The shunt resistance at reference conditions, in ohms.\n\n    R_s : float\n        The series resistance at reference conditions, in ohms.\n\n    Adjust : float\n        The adjustment to the temperature coefficient for short circuit\n        current, in percent\n\n    EgRef : float\n        The energy bandgap at reference temperature in units of eV.\n        1.121 eV for crystalline silicon. EgRef must be >0.  For parameters\n        from the SAM CEC module database, EgRef=1.121 is implicit for all\n        cell types in the parameter estimation algorithm used by NREL.\n\n    dEgdT : float\n        The temperature dependence of the energy bandgap at reference\n        conditions in units of 1/K. May be either a scalar value\n        (e.g. -0.0002677 as in [3]) or a DataFrame (this may be useful if\n        dEgdT is a modeled as a function of temperature). For parameters from\n        the SAM CEC module database, dEgdT=-0.0002677 is implicit for all cell\n        types in the parameter estimation algorithm used by NREL.\n\n    irrad_ref : float (optional, default=1000)\n        Reference irradiance in W/m^2.\n\n    temp_ref : float (optional, default=25)\n        Reference cell temperature in C.\n\n    Returns\n    -------\n    Tuple of the following results:\n\n    photocurrent : numeric\n        Light-generated current in amperes\n\n    saturation_current : numeric\n        Diode saturation curent in amperes\n\n    resistance_series : numeric\n        Series resistance in ohms\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms\n\n    nNsVth : numeric\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at\n        specified effective irradiance and cell temperature.\n\n    References\n    ----------\n    .. [1] A. Dobos, \"An Improved Coefficient Calculator for the California\n       Energy Commission 6 Parameter Photovoltaic Module Model\", Journal of\n       Solar Energy Engineering, vol 134, 2012.\n\n    .. [2] System Advisor Model web page. https://sam.nrel.gov.\n\n    .. [3] W. De Soto et al., \"Improvement and validation of a model for\n       photovoltaic array performance\", Solar Energy, vol 80, pp. 78-88,\n       2006.\n\n    See Also\n    --------\n    calcparams_desoto\n    singlediode\n    retrieve_sam\n\n    '''\n\n    # pass adjusted temperature coefficient to desoto\n    return calcparams_desoto(effective_irradiance, temp_cell,\n                             alpha_sc*(1.0 - Adjust/100),\n                             a_ref, I_L_ref, I_o_ref,\n                             R_sh_ref, R_s,\n                             EgRef=EgRef, dEgdT=dEgdT,\n                             irrad_ref=irrad_ref, temp_ref=temp_ref)\n\n\ndef calcparams_pvsyst(effective_irradiance, temp_cell,\n                      alpha_sc, gamma_ref, mu_gamma,\n                      I_L_ref, I_o_ref,\n                      R_sh_ref, R_sh_0, R_s,\n                      cells_in_series,\n                      R_sh_exp=5.5,\n                      EgRef=1.121,\n                      irrad_ref=1000, temp_ref=25):\n    '''\n    Calculates five parameter values for the single diode equation at\n    effective irradiance and cell temperature using the PVsyst v6\n    model.  The PVsyst v6 model is described in [1]_, [2]_, [3]_.\n    The five values returned by calcparams_pvsyst can be used by singlediode\n    to calculate an IV curve.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        The irradiance (W/m2) that is converted to photocurrent.\n\n    temp_cell : numeric\n        The average cell temperature of cells within a module in C.\n\n    alpha_sc : float\n        The short-circuit current temperature coefficient of the\n        module in units of A/C.\n\n    gamma_ref : float\n        The diode ideality factor\n\n    mu_gamma : float\n        The temperature coefficient for the diode ideality factor, 1/K\n\n    I_L_ref : float\n        The light-generated current (or photocurrent) at reference conditions,\n        in amperes.\n\n    I_o_ref : float\n        The dark or diode reverse saturation current at reference conditions,\n        in amperes.\n\n    R_sh_ref : float\n        The shunt resistance at reference conditions, in ohms.\n\n    R_sh_0 : float\n        The shunt resistance at zero irradiance conditions, in ohms.\n\n    R_s : float\n        The series resistance at reference conditions, in ohms.\n\n    cells_in_series : integer\n        The number of cells connected in series.\n\n    R_sh_exp : float\n        The exponent in the equation for shunt resistance, unitless. Defaults\n        to 5.5.\n\n    EgRef : float\n        The energy bandgap at reference temperature in units of eV.\n        1.121 eV for crystalline silicon. EgRef must be >0.\n\n    irrad_ref : float (optional, default=1000)\n        Reference irradiance in W/m^2.\n\n    temp_ref : float (optional, default=25)\n        Reference cell temperature in C.\n\n    Returns\n    -------\n    Tuple of the following results:\n\n    photocurrent : numeric\n        Light-generated current in amperes\n\n    saturation_current : numeric\n        Diode saturation current in amperes\n\n    resistance_series : numeric\n        Series resistance in ohms\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms\n\n    nNsVth : numeric\n        The product of the usual diode ideality factor (n, unitless),\n        number of cells in series (Ns), and cell thermal voltage at\n        specified effective irradiance and cell temperature.\n\n    References\n    ----------\n    .. [1] K. Sauer, T. Roessler, C. W. Hansen, Modeling the Irradiance and\n       Temperature Dependence of Photovoltaic Modules in PVsyst,\n       IEEE Journal of Photovoltaics v5(1), January 2015.\n\n    .. [2] A. Mermoud, PV modules modelling, Presentation at the 2nd PV\n       Performance Modeling Workshop, Santa Clara, CA, May 2013\n\n    .. [3] A. Mermoud, T. Lejeune, Performance Assessment of a Simulation Model\n       for PV modules of any available technology, 25th European Photovoltaic\n       Solar Energy Conference, Valencia, Spain, Sept. 2010\n\n    See Also\n    --------\n    calcparams_desoto\n    singlediode\n\n    '''\n\n    # Boltzmann constant in J/K\n    k = constants.k\n\n    # elementary charge in coulomb\n    q = constants.e\n\n    # reference temperature\n    Tref_K = temp_ref + 273.15\n    Tcell_K = temp_cell + 273.15\n\n    gamma = gamma_ref + mu_gamma * (Tcell_K - Tref_K)\n    nNsVth = gamma * k / q * cells_in_series * Tcell_K\n\n    IL = effective_irradiance / irrad_ref * \\\n        (I_L_ref + alpha_sc * (Tcell_K - Tref_K))\n\n    I0 = I_o_ref * ((Tcell_K / Tref_K) ** 3) * \\\n        (np.exp((q * EgRef) / (k * gamma) * (1 / Tref_K - 1 / Tcell_K)))\n\n    Rsh_tmp = \\\n        (R_sh_ref - R_sh_0 * np.exp(-R_sh_exp)) / (1.0 - np.exp(-R_sh_exp))\n    Rsh_base = np.maximum(0.0, Rsh_tmp)\n\n    Rsh = Rsh_base + (R_sh_0 - Rsh_base) * \\\n        np.exp(-R_sh_exp * effective_irradiance / irrad_ref)\n\n    Rs = R_s\n\n    numeric_args = (effective_irradiance, temp_cell)\n    out = (IL, I0, Rs, Rsh, nNsVth)\n\n    if all(map(np.isscalar, numeric_args)):\n        return out\n\n    index = tools.get_pandas_index(*numeric_args)\n\n    if index is None:\n        return np.broadcast_arrays(*out)\n\n    return tuple(pd.Series(a, index=index).rename(None) for a in out)\n\n\ndef retrieve_sam(name=None, path=None):\n    '''\n    Retrieve latest module and inverter info from a local file or the\n    SAM website.\n\n    This function will retrieve either:\n\n        * CEC module database\n        * Sandia Module database\n        * CEC Inverter database\n        * Anton Driesse Inverter database\n\n    and return it as a pandas DataFrame.\n\n    Parameters\n    ----------\n    name : None or string, default None\n        Name can be one of:\n\n        * 'CECMod' - returns the CEC module database\n        * 'CECInverter' - returns the CEC Inverter database\n        * 'SandiaInverter' - returns the CEC Inverter database\n          (CEC is only current inverter db available; tag kept for\n          backwards compatibility)\n        * 'SandiaMod' - returns the Sandia Module database\n        * 'ADRInverter' - returns the ADR Inverter database\n\n    path : None or string, default None\n        Path to the SAM file. May also be a URL.\n\n    Returns\n    -------\n    samfile : DataFrame\n        A DataFrame containing all the elements of the desired database.\n        Each column represents a module or inverter, and a specific\n        dataset can be retrieved by the command\n\n    Raises\n    ------\n    ValueError\n        If no name or path is provided.\n\n    Notes\n    -----\n    Files available at\n        https://github.com/NREL/SAM/tree/develop/deploy/libraries\n    Documentation for module and inverter data sets:\n        https://sam.nrel.gov/photovoltaic/pv-sub-page-2.html\n\n    Examples\n    --------\n\n    >>> from pvlib import pvsystem\n    >>> invdb = pvsystem.retrieve_sam('CECInverter')\n    >>> inverter = invdb.AE_Solar_Energy__AE6_0__277V_\n    >>> inverter\n    Vac                          277\n    Pso                    36.197575\n    Paco                      6000.0\n    Pdco                 6158.746094\n    Vdco                       360.0\n    C0                     -0.000002\n    C1                     -0.000026\n    C2                     -0.001253\n    C3                       0.00021\n    Pnt                          1.8\n    Vdcmax                     450.0\n    Idcmax                 17.107628\n    Mppt_low                   100.0\n    Mppt_high                  450.0\n    CEC_Date                     NaN\n    CEC_Type     Utility Interactive\n    Name: AE_Solar_Energy__AE6_0__277V_, dtype: object\n    '''\n\n    if name is not None:\n        name = name.lower()\n        data_path = os.path.join(\n            os.path.dirname(os.path.abspath(__file__)), 'data')\n        if name == 'cecmod':\n            csvdata = os.path.join(\n                data_path, 'sam-library-cec-modules-2019-03-05.csv')\n        elif name == 'sandiamod':\n            csvdata = os.path.join(\n                data_path, 'sam-library-sandia-modules-2015-6-30.csv')\n        elif name == 'adrinverter':\n            csvdata = os.path.join(\n                data_path, 'adr-library-cec-inverters-2019-03-05.csv')\n        elif name in ['cecinverter', 'sandiainverter']:\n            # Allowing either, to provide for old code,\n            # while aligning with current expectations\n            csvdata = os.path.join(\n                data_path, 'sam-library-cec-inverters-2019-03-05.csv')\n        else:\n            raise ValueError(f'invalid name {name}')\n    elif path is not None:\n        if path.startswith('http'):\n            response = urlopen(path)\n            csvdata = io.StringIO(response.read().decode(errors='ignore'))\n        else:\n            csvdata = path\n    elif name is None and path is None:\n        raise ValueError(\"A name or path must be provided!\")\n\n    return _parse_raw_sam_df(csvdata)\n\n\ndef _normalize_sam_product_names(names):\n    '''\n    Replace special characters within the product names to make them more\n    suitable for use as Dataframe column names.\n    '''\n    # Contributed by Anton Driesse (@adriesse), PV Performance Labs. July, 2019\n\n    import warnings\n\n    BAD_CHARS = ' -.()[]:+/\",'\n    GOOD_CHARS = '____________'\n\n    mapping = str.maketrans(BAD_CHARS, GOOD_CHARS)\n    names = pd.Series(data=names)\n    norm_names = names.str.translate(mapping)\n\n    n_duplicates = names.duplicated().sum()\n    if n_duplicates > 0:\n        warnings.warn('Original names contain %d duplicate(s).' % n_duplicates)\n\n    n_duplicates = norm_names.duplicated().sum()\n    if n_duplicates > 0:\n        warnings.warn(\n            'Normalized names contain %d duplicate(s).' % n_duplicates)\n\n    return norm_names.values\n\n\ndef _parse_raw_sam_df(csvdata):\n\n    df = pd.read_csv(csvdata, index_col=0, skiprows=[1, 2])\n\n    df.columns = df.columns.str.replace(' ', '_')\n    df.index = _normalize_sam_product_names(df.index)\n    df = df.transpose()\n\n    if 'ADRCoefficients' in df.index:\n        ad_ce = 'ADRCoefficients'\n        # for each inverter, parses a string of coefficients like\n        # ' 1.33, 2.11, 3.12' into a list containing floats:\n        # [1.33, 2.11, 3.12]\n        df.loc[ad_ce] = df.loc[ad_ce].map(lambda x: list(\n            map(float, x.strip(' []').split())))\n\n    return df\n\n\ndef sapm(effective_irradiance, temp_cell, module):\n    '''\n    The Sandia PV Array Performance Model (SAPM) generates 5 points on a\n    PV module's I-V curve (Voc, Isc, Ix, Ixx, Vmp/Imp) according to\n    SAND2004-3535. Assumes a reference cell temperature of 25 C.\n\n    Parameters\n    ----------\n    effective_irradiance : numeric\n        Irradiance reaching the module's cells, after reflections and\n        adjustment for spectrum. [W/m2]\n\n    temp_cell : numeric\n        Cell temperature [C].\n\n    module : dict-like\n        A dict or Series defining the SAPM parameters. See the notes section\n        for more details.\n\n    Returns\n    -------\n    A DataFrame with the columns:\n\n        * i_sc : Short-circuit current (A)\n        * i_mp : Current at the maximum-power point (A)\n        * v_oc : Open-circuit voltage (V)\n        * v_mp : Voltage at maximum-power point (V)\n        * p_mp : Power at maximum-power point (W)\n        * i_x : Current at module V = 0.5Voc, defines 4th point on I-V\n          curve for modeling curve shape\n        * i_xx : Current at module V = 0.5(Voc+Vmp), defines 5th point on\n          I-V curve for modeling curve shape\n\n    Notes\n    -----\n    The SAPM parameters which are required in ``module`` are\n    listed in the following table.\n\n    The Sandia module database contains parameter values for a limited set\n    of modules. The CEC module database does not contain these parameters.\n    Both databases can be accessed using :py:func:`retrieve_sam`.\n\n    ================   ========================================================\n    Key                Description\n    ================   ========================================================\n    A0-A4              The airmass coefficients used in calculating\n                       effective irradiance\n    B0-B5              The angle of incidence coefficients used in calculating\n                       effective irradiance\n    C0-C7              The empirically determined coefficients relating\n                       Imp, Vmp, Ix, and Ixx to effective irradiance\n    Isco               Short circuit current at reference condition (amps)\n    Impo               Maximum power current at reference condition (amps)\n    Voco               Open circuit voltage at reference condition (amps)\n    Vmpo               Maximum power voltage at reference condition (amps)\n    Aisc               Short circuit current temperature coefficient at\n                       reference condition (1/C)\n    Aimp               Maximum power current temperature coefficient at\n                       reference condition (1/C)\n    Bvoco              Open circuit voltage temperature coefficient at\n                       reference condition (V/C)\n    Mbvoc              Coefficient providing the irradiance dependence for the\n                       BetaVoc temperature coefficient at reference irradiance\n                       (V/C)\n    Bvmpo              Maximum power voltage temperature coefficient at\n                       reference condition\n    Mbvmp              Coefficient providing the irradiance dependence for the\n                       BetaVmp temperature coefficient at reference irradiance\n                       (V/C)\n    N                  Empirically determined \"diode factor\" (dimensionless)\n    Cells_in_Series    Number of cells in series in a module's cell string(s)\n    IXO                Ix at reference conditions\n    IXXO               Ixx at reference conditions\n    FD                 Fraction of diffuse irradiance used by module\n    ================   ========================================================\n\n    References\n    ----------\n    .. [1] King, D. et al, 2004, \"Sandia Photovoltaic Array Performance\n       Model\", SAND Report 3535, Sandia National Laboratories, Albuquerque,\n       NM.\n\n    See Also\n    --------\n    retrieve_sam\n    pvlib.temperature.sapm_cell\n    pvlib.temperature.sapm_module\n    '''\n\n    # TODO: someday, change temp_ref and irrad_ref to reference_temperature and\n    # reference_irradiance and expose\n    temp_ref = 25\n    irrad_ref = 1000\n\n    q = constants.e  # Elementary charge in units of coulombs\n    kb = constants.k  # Boltzmann's constant in units of J/K\n\n    # avoid problem with integer input\n    Ee = np.array(effective_irradiance, dtype='float64') / irrad_ref\n\n    # set up masking for 0, positive, and nan inputs\n    Ee_gt_0 = np.full_like(Ee, False, dtype='bool')\n    Ee_eq_0 = np.full_like(Ee, False, dtype='bool')\n    notnan = ~np.isnan(Ee)\n    np.greater(Ee, 0, where=notnan, out=Ee_gt_0)\n    np.equal(Ee, 0, where=notnan, out=Ee_eq_0)\n\n    Bvmpo = module['Bvmpo'] + module['Mbvmp']*(1 - Ee)\n    Bvoco = module['Bvoco'] + module['Mbvoc']*(1 - Ee)\n    delta = module['N'] * kb * (temp_cell + 273.15) / q\n\n    # avoid repeated computation\n    logEe = np.full_like(Ee, np.nan)\n    np.log(Ee, where=Ee_gt_0, out=logEe)\n    logEe = np.where(Ee_eq_0, -np.inf, logEe)\n    # avoid repeated __getitem__\n    cells_in_series = module['Cells_in_Series']\n\n    out = OrderedDict()\n\n    out['i_sc'] = (\n        module['Isco'] * Ee * (1 + module['Aisc']*(temp_cell - temp_ref)))\n\n    out['i_mp'] = (\n        module['Impo'] * (module['C0']*Ee + module['C1']*(Ee**2)) *\n        (1 + module['Aimp']*(temp_cell - temp_ref)))\n\n    out['v_oc'] = np.maximum(0, (\n        module['Voco'] + cells_in_series * delta * logEe +\n        Bvoco*(temp_cell - temp_ref)))\n\n    out['v_mp'] = np.maximum(0, (\n        module['Vmpo'] +\n        module['C2'] * cells_in_series * delta * logEe +\n        module['C3'] * cells_in_series * ((delta * logEe) ** 2) +\n        Bvmpo*(temp_cell - temp_ref)))\n\n    out['p_mp'] = out['i_mp'] * out['v_mp']\n\n    out['i_x'] = (\n        module['IXO'] * (module['C4']*Ee + module['C5']*(Ee**2)) *\n        (1 + module['Aisc']*(temp_cell - temp_ref)))\n\n    # the Ixx calculation in King 2004 has a typo (mixes up Aisc and Aimp)\n    out['i_xx'] = (\n        module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) *\n        (1 + module['Aisc']*(temp_cell - temp_ref)))\n\n    if isinstance(out['i_sc'], pd.Series):\n        out = pd.DataFrame(out)\n\n    return out\n\n\nsapm_spectral_loss = deprecated(\n    since='0.10.0',\n    alternative='pvlib.spectrum.spectral_factor_sapm'\n)(spectrum.spectral_factor_sapm)\n\n\ndef sapm_effective_irradiance(poa_direct, poa_diffuse, airmass_absolute, aoi,\n                              module):\n    r\"\"\"\n    Calculates the SAPM effective irradiance using the SAPM spectral\n    loss and SAPM angle of incidence loss functions.\n\n    Parameters\n    ----------\n    poa_direct : numeric\n        The direct irradiance incident upon the module. [W/m2]\n\n    poa_diffuse : numeric\n        The diffuse irradiance incident on module.  [W/m2]\n\n    airmass_absolute : numeric\n        Absolute airmass. [unitless]\n\n    aoi : numeric\n        Angle of incidence. [degrees]\n\n    module : dict-like\n        A dict, Series, or DataFrame defining the SAPM performance\n        parameters. See the :py:func:`sapm` notes section for more\n        details.\n\n    Returns\n    -------\n    effective_irradiance : numeric\n        Effective irradiance accounting for reflections and spectral content.\n        [W/m2]\n\n    Notes\n    -----\n    The SAPM model for effective irradiance [1]_ translates broadband direct\n    and diffuse irradiance on the plane of array to the irradiance absorbed by\n    a module's cells.\n\n    The model is\n    .. math::\n\n        `Ee = f_1(AM_a) (E_b f_2(AOI) + f_d E_d)`\n\n    where :math:`Ee` is effective irradiance (W/m2), :math:`f_1` is a fourth\n    degree polynomial in air mass :math:`AM_a`, :math:`E_b` is beam (direct)\n    irradiance on the plane of array, :math:`E_d` is diffuse irradiance on the\n    plane of array, :math:`f_2` is a fifth degree polynomial in the angle of\n    incidence :math:`AOI`, and :math:`f_d` is the fraction of diffuse\n    irradiance on the plane of array that is not reflected away.\n\n    References\n    ----------\n    .. [1] D. King et al, \"Sandia Photovoltaic Array Performance Model\",\n       SAND2004-3535, Sandia National Laboratories, Albuquerque, NM\n\n    See also\n    --------\n    pvlib.iam.sapm\n    pvlib.spectrum.spectral_factor_sapm\n    pvlib.pvsystem.sapm\n    \"\"\"\n\n    F1 = spectrum.spectral_factor_sapm(airmass_absolute, module)\n    F2 = iam.sapm(aoi, module)\n\n    Ee = F1 * (poa_direct * F2 + module['FD'] * poa_diffuse)\n\n    return Ee\n\n\ndef singlediode(photocurrent, saturation_current, resistance_series,\n                resistance_shunt, nNsVth, ivcurve_pnts=None,\n                method='lambertw'):\n    r\"\"\"\n    Solve the single diode equation to obtain a photovoltaic IV curve.\n\n    Solves the single diode equation [1]_\n\n    .. math::\n\n        I = I_L -\n            I_0 \\left[\n                \\exp \\left(\\frac{V+I R_s}{n N_s V_{th}} \\right)-1\n            \\right] -\n            \\frac{V + I R_s}{R_{sh}}\n\n    for :math:`I` and :math:`V` when given :math:`I_L, I_0, R_s, R_{sh},` and\n    :math:`n N_s V_{th}` which are described later. The five points on the I-V\n    curve specified in [3]_ are returned. If :math:`I_L, I_0, R_s, R_{sh},` and\n    :math:`n N_s V_{th}` are all scalars, a single curve is returned. If any\n    are array-like (of the same length), multiple IV curves are calculated.\n\n    The input parameters can be calculated from meteorological data using a\n    function for a single diode model, e.g.,\n    :py:func:`~pvlib.pvsystem.calcparams_desoto`.\n\n    Parameters\n    ----------\n    photocurrent : numeric\n        Light-generated current :math:`I_L` (photocurrent)\n        ``0 <= photocurrent``. [A]\n\n    saturation_current : numeric\n        Diode saturation :math:`I_0` current under desired IV curve\n        conditions. ``0 < saturation_current``. [A]\n\n    resistance_series : numeric\n        Series resistance :math:`R_s` under desired IV curve conditions.\n        ``0 <= resistance_series < numpy.inf``.  [ohm]\n\n    resistance_shunt : numeric\n        Shunt resistance :math:`R_{sh}` under desired IV curve conditions.\n        ``0 < resistance_shunt <= numpy.inf``.  [ohm]\n\n    nNsVth : numeric\n        The product of three components: 1) the usual diode ideality factor\n        :math:`n`, 2) the number of cells in series :math:`N_s`, and 3)\n        the cell thermal voltage\n        :math:`V_{th}`. The thermal voltage of the cell (in volts) may be\n        calculated as :math:`k_B T_c / q`, where :math:`k_B` is\n        Boltzmann's constant (J/K), :math:`T_c` is the temperature of the p-n\n        junction in Kelvin, and :math:`q` is the charge of an electron\n        (coulombs). ``0 < nNsVth``.  [V]\n\n    ivcurve_pnts : None or int, default None\n        Number of points in the desired IV curve. If None or 0, no points on\n        the IV curves will be produced.\n\n        .. deprecated:: 0.10.0\n           Use :py:func:`pvlib.pvsystem.v_from_i` and\n           :py:func:`pvlib.pvsystem.i_from_v` instead.\n\n    method : str, default 'lambertw'\n        Determines the method used to calculate points on the IV curve. The\n        options are ``'lambertw'``, ``'newton'``, or ``'brentq'``.\n\n    Returns\n    -------\n    dict or pandas.DataFrame\n        The returned dict-like object always contains the keys/columns:\n\n            * i_sc - short circuit current in amperes.\n            * v_oc - open circuit voltage in volts.\n            * i_mp - current at maximum power point in amperes.\n            * v_mp - voltage at maximum power point in volts.\n            * p_mp - power at maximum power point in watts.\n            * i_x - current, in amperes, at ``v = 0.5*v_oc``.\n            * i_xx - current, in amperes, at ``v = 0.5*(v_oc+v_mp)``.\n\n        A dict is returned when the input parameters are scalars or\n        ``ivcurve_pnts > 0``. If ``ivcurve_pnts > 0``, the output dictionary\n        will also include the keys:\n\n            * i - IV curve current in amperes.\n            * v - IV curve voltage in volts.\n\n    See also\n    --------\n    calcparams_desoto\n    calcparams_cec\n    calcparams_pvsyst\n    sapm\n    pvlib.singlediode.bishop88\n\n    Notes\n    -----\n    If the method is ``'lambertw'`` then the solution employed to solve the\n    implicit diode equation utilizes the Lambert W function to obtain an\n    explicit function of :math:`V=f(I)` and :math:`I=f(V)` as shown in [2]_.\n\n    If the method is ``'newton'`` then the root-finding Newton-Raphson method\n    is used. It should be safe for well behaved IV-curves, but the ``'brentq'``\n    method is recommended for reliability.\n\n    If the method is ``'brentq'`` then Brent's bisection search method is used\n    that guarantees convergence by bounding the voltage between zero and\n    open-circuit.\n\n    If the method is either ``'newton'`` or ``'brentq'`` and ``ivcurve_pnts``\n    are indicated, then :func:`pvlib.singlediode.bishop88` [4]_ is used to\n    calculate the points on the IV curve points at diode voltages from zero to\n    open-circuit voltage with a log spacing that gets closer as voltage\n    increases. If the method is ``'lambertw'`` then the calculated points on\n    the IV curve are linearly spaced.\n\n    References\n    ----------\n    .. [1] S.R. Wenham, M.A. Green, M.E. Watt, \"Applied Photovoltaics\" ISBN\n       0 86758 909 4\n\n    .. [2] A. Jain, A. Kapoor, \"Exact analytical solutions of the\n       parameters of real solar cells using Lambert W-function\", Solar\n       Energy Materials and Solar Cells, 81 (2004) 269-277.\n\n    .. [3] D. King et al, \"Sandia Photovoltaic Array Performance Model\",\n       SAND2004-3535, Sandia National Laboratories, Albuquerque, NM\n\n    .. [4] \"Computer simulation of the effects of electrical mismatches in\n       photovoltaic cell interconnection circuits\" JW Bishop, Solar Cell (1988)\n       https://doi.org/10.1016/0379-6787(88)90059-2\n    \"\"\"\n    if ivcurve_pnts:\n        warn_deprecated('0.10.0', name='pvlib.pvsystem.singlediode',\n                        alternative=('pvlib.pvsystem.v_from_i and '\n                                     'pvlib.pvsystem.i_from_v'),\n                        obj_type='parameter ivcurve_pnts',\n                        removal='0.11.0')\n    args = (photocurrent, saturation_current, resistance_series,\n            resistance_shunt, nNsVth)  # collect args\n    # Calculate points on the IV curve using the LambertW solution to the\n    # single diode equation\n    if method.lower() == 'lambertw':\n        out = _singlediode._lambertw(*args, ivcurve_pnts)\n        points = out[:7]\n        if ivcurve_pnts:\n            ivcurve_i, ivcurve_v = out[7:]\n    else:\n        # Calculate points on the IV curve using either 'newton' or 'brentq'\n        # methods. Voltages are determined by first solving the single diode\n        # equation for the diode voltage V_d then backing out voltage\n        v_oc = _singlediode.bishop88_v_from_i(\n            0.0, *args, method=method.lower()\n        )\n        i_mp, v_mp, p_mp = _singlediode.bishop88_mpp(\n            *args, method=method.lower()\n        )\n        i_sc = _singlediode.bishop88_i_from_v(\n            0.0, *args, method=method.lower()\n        )\n        i_x = _singlediode.bishop88_i_from_v(\n            v_oc / 2.0, *args, method=method.lower()\n        )\n        i_xx = _singlediode.bishop88_i_from_v(\n            (v_oc + v_mp) / 2.0, *args, method=method.lower()\n        )\n        points = i_sc, v_oc, i_mp, v_mp, p_mp, i_x, i_xx\n\n        # calculate the IV curve if requested using bishop88\n        if ivcurve_pnts:\n            vd = v_oc * (\n                (11.0 - np.logspace(np.log10(11.0), 0.0, ivcurve_pnts)) / 10.0\n            )\n            ivcurve_i, ivcurve_v, _ = _singlediode.bishop88(vd, *args)\n\n    columns = ('i_sc', 'v_oc', 'i_mp', 'v_mp', 'p_mp', 'i_x', 'i_xx')\n\n    if all(map(np.isscalar, args)) or ivcurve_pnts:\n        out = {c: p for c, p in zip(columns, points)}\n\n        if ivcurve_pnts:\n            out.update(i=ivcurve_i, v=ivcurve_v)\n\n        return out\n\n    points = np.atleast_1d(*points)  # convert scalars to 1d-arrays\n    points = np.vstack(points).T  # collect rows into DataFrame columns\n\n    # save the first available pd.Series index, otherwise set to None\n    index = next((a.index for a in args if isinstance(a, pd.Series)), None)\n\n    out = pd.DataFrame(points, columns=columns, index=index)\n\n    return out\n\n\ndef max_power_point(photocurrent, saturation_current, resistance_series,\n                    resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.Inf,\n                    method='brentq'):\n    \"\"\"\n    Given the single diode equation coefficients, calculates the maximum power\n    point (MPP).\n\n    Parameters\n    ----------\n    photocurrent : numeric\n        photo-generated current [A]\n    saturation_current : numeric\n        diode reverse saturation current [A]\n    resistance_series : numeric\n        series resitance [ohms]\n    resistance_shunt : numeric\n        shunt resitance [ohms]\n    nNsVth : numeric\n        product of thermal voltage ``Vth`` [V], diode ideality factor ``n``,\n        and number of serices cells ``Ns``\n    d2mutau : numeric, default 0\n        PVsyst parameter for cadmium-telluride (CdTe) and amorphous-silicon\n        (a-Si) modules that accounts for recombination current in the\n        intrinsic layer. The value is the ratio of intrinsic layer thickness\n        squared :math:`d^2` to the diffusion length of charge carriers\n        :math:`\\\\mu \\\\tau`. [V]\n    NsVbi : numeric, default np.inf\n        PVsyst parameter for cadmium-telluride (CdTe) and amorphous-silicon\n        (a-Si) modules that is the product of the PV module number of series\n        cells ``Ns`` and the builtin voltage ``Vbi`` of the intrinsic layer.\n        [V].\n    method : str\n        either ``'newton'`` or ``'brentq'``\n\n    Returns\n    -------\n    OrderedDict or pandas.DataFrame\n        ``(i_mp, v_mp, p_mp)``\n\n    Notes\n    -----\n    Use this function when you only want to find the maximum power point. Use\n    :func:`singlediode` when you need to find additional points on the IV\n    curve. This function uses Brent's method by default because it is\n    guaranteed to converge.\n    \"\"\"\n    i_mp, v_mp, p_mp = _singlediode.bishop88_mpp(\n        photocurrent, saturation_current, resistance_series,\n        resistance_shunt, nNsVth, d2mutau, NsVbi, method=method.lower()\n    )\n    if isinstance(photocurrent, pd.Series):\n        ivp = {'i_mp': i_mp, 'v_mp': v_mp, 'p_mp': p_mp}\n        out = pd.DataFrame(ivp, index=photocurrent.index)\n    else:\n        out = OrderedDict()\n        out['i_mp'] = i_mp\n        out['v_mp'] = v_mp\n        out['p_mp'] = p_mp\n    return out\n\n\ndef v_from_i(current, photocurrent, saturation_current, resistance_series,\n             resistance_shunt, nNsVth, method='lambertw'):\n    '''\n    Device voltage at the given device current for the single diode model.\n\n    Uses the single diode model (SDM) as described in, e.g.,\n    Jain and Kapoor 2004 [1]_.\n    The solution is per Eq 3 of [1]_ except when resistance_shunt=numpy.inf,\n    in which case the explict solution for voltage is used.\n    Ideal device parameters are specified by resistance_shunt=np.inf and\n    resistance_series=0.\n    Inputs to this function can include scalars and pandas.Series, but it is\n    the caller's responsibility to ensure that the arguments are all float64\n    and within the proper ranges.\n\n    .. versionchanged:: 0.10.0\n       The function's arguments have been reordered.\n\n    Parameters\n    ----------\n    current : numeric\n        The current in amperes under desired IV curve conditions.\n\n    photocurrent : numeric\n        Light-generated current (photocurrent) in amperes under desired\n        IV curve conditions. Often abbreviated ``I_L``.\n        0 <= photocurrent\n\n    saturation_current : numeric\n        Diode saturation current in amperes under desired IV curve\n        conditions. Often abbreviated ``I_0``.\n        0 < saturation_current\n\n    resistance_series : numeric\n        Series resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rs``.\n        0 <= resistance_series < numpy.inf\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rsh``.\n        0 < resistance_shunt <= numpy.inf\n\n    nNsVth : numeric\n        The product of three components. 1) The usual diode ideal factor\n        (n), 2) the number of cells in series (Ns), and 3) the cell\n        thermal voltage under the desired IV curve conditions (Vth). The\n        thermal voltage of the cell (in volts) may be calculated as\n        ``k*temp_cell/q``, where k is Boltzmann's constant (J/K),\n        temp_cell is the temperature of the p-n junction in Kelvin, and\n        q is the charge of an electron (coulombs).\n        0 < nNsVth\n\n    method : str\n        Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*:\n        ``'brentq'`` is limited to 1st quadrant only.\n\n    Returns\n    -------\n    current : np.ndarray or scalar\n\n    References\n    ----------\n    .. [1] A. Jain, A. Kapoor, \"Exact analytical solutions of the\n       parameters of real solar cells using Lambert W-function\", Solar\n       Energy Materials and Solar Cells, 81 (2004) 269-277.\n    '''\n    args = (current, photocurrent, saturation_current,\n            resistance_series, resistance_shunt, nNsVth)\n    if method.lower() == 'lambertw':\n        return _singlediode._lambertw_v_from_i(*args)\n    else:\n        # Calculate points on the IV curve using either 'newton' or 'brentq'\n        # methods. Voltages are determined by first solving the single diode\n        # equation for the diode voltage V_d then backing out voltage\n        V = _singlediode.bishop88_v_from_i(*args, method=method.lower())\n        if all(map(np.isscalar, args)):\n            return V\n        shape = _singlediode._shape_of_max_size(*args)\n        return np.broadcast_to(V, shape)\n\n\ndef i_from_v(voltage, photocurrent, saturation_current, resistance_series,\n             resistance_shunt, nNsVth, method='lambertw'):\n    '''\n    Device current at the given device voltage for the single diode model.\n\n    Uses the single diode model (SDM) as described in, e.g.,\n    Jain and Kapoor 2004 [1]_.\n    The solution is per Eq 2 of [1] except when resistance_series=0,\n    in which case the explict solution for current is used.\n    Ideal device parameters are specified by resistance_shunt=np.inf and\n    resistance_series=0.\n    Inputs to this function can include scalars and pandas.Series, but it is\n    the caller's responsibility to ensure that the arguments are all float64\n    and within the proper ranges.\n\n    .. versionchanged:: 0.10.0\n       The function's arguments have been reordered.\n\n    Parameters\n    ----------\n    voltage : numeric\n        The voltage in Volts under desired IV curve conditions.\n\n    photocurrent : numeric\n        Light-generated current (photocurrent) in amperes under desired\n        IV curve conditions. Often abbreviated ``I_L``.\n        0 <= photocurrent\n\n    saturation_current : numeric\n        Diode saturation current in amperes under desired IV curve\n        conditions. Often abbreviated ``I_0``.\n        0 < saturation_current\n\n    resistance_series : numeric\n        Series resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rs``.\n        0 <= resistance_series < numpy.inf\n\n    resistance_shunt : numeric\n        Shunt resistance in ohms under desired IV curve conditions.\n        Often abbreviated ``Rsh``.\n        0 < resistance_shunt <= numpy.inf\n\n    nNsVth : numeric\n        The product of three components. 1) The usual diode ideal factor\n        (n), 2) the number of cells in series (Ns), and 3) the cell\n        thermal voltage under the desired IV curve conditions (Vth). The\n        thermal voltage of the cell (in volts) may be calculated as\n        ``k*temp_cell/q``, where k is Boltzmann's constant (J/K),\n        temp_cell is the temperature of the p-n junction in Kelvin, and\n        q is the charge of an electron (coulombs).\n        0 < nNsVth\n\n    method : str\n        Method to use: ``'lambertw'``, ``'newton'``, or ``'brentq'``. *Note*:\n        ``'brentq'`` is limited to 1st quadrant only.\n\n    Returns\n    -------\n    current : np.ndarray or scalar\n\n    References\n    ----------\n    .. [1] A. Jain, A. Kapoor, \"Exact analytical solutions of the\n       parameters of real solar cells using Lambert W-function\", Solar\n       Energy Materials and Solar Cells, 81 (2004) 269-277.\n    '''\n    args = (voltage, photocurrent, saturation_current,\n            resistance_series, resistance_shunt, nNsVth)\n    if method.lower() == 'lambertw':\n        return _singlediode._lambertw_i_from_v(*args)\n    else:\n        # Calculate points on the IV curve using either 'newton' or 'brentq'\n        # methods. Voltages are determined by first solving the single diode\n        # equation for the diode voltage V_d then backing out voltage\n        current = _singlediode.bishop88_i_from_v(*args, method=method.lower())\n        if all(map(np.isscalar, args)):\n            return current\n        shape = _singlediode._shape_of_max_size(*args)\n        return np.broadcast_to(current, shape)\n\n\ndef scale_voltage_current_power(data, voltage=1, current=1):\n    \"\"\"\n    Scales the voltage, current, and power in data by the voltage\n    and current factors.\n\n    Parameters\n    ----------\n    data: DataFrame\n        May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',\n        'i_sc', 'p_mp'`.\n    voltage: numeric, default 1\n        The amount by which to multiply the voltages.\n    current: numeric, default 1\n        The amount by which to multiply the currents.\n\n    Returns\n    -------\n    scaled_data: DataFrame\n        A scaled copy of the input data.\n        `'p_mp'` is scaled by `voltage * current`.\n    \"\"\"\n\n    # as written, only works with a DataFrame\n    # could make it work with a dict, but it would be more verbose\n    voltage_keys = ['v_mp', 'v_oc']\n    current_keys = ['i_mp', 'i_x', 'i_xx', 'i_sc']\n    power_keys = ['p_mp']\n    voltage_df = data.filter(voltage_keys, axis=1) * voltage\n    current_df = data.filter(current_keys, axis=1) * current\n    power_df = data.filter(power_keys, axis=1) * voltage * current\n    df = pd.concat([voltage_df, current_df, power_df], axis=1)\n    df_sorted = df[data.columns]  # retain original column order\n    return df_sorted\n\n\ndef pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.):\n    r\"\"\"\n    Implements NREL's PVWatts DC power model. The PVWatts DC model [1]_ is:\n\n    .. math::\n\n        P_{dc} = \\frac{G_{poa eff}}{1000} P_{dc0} ( 1 + \\gamma_{pdc} (T_{cell} - T_{ref}))\n\n    Note that ``pdc0`` is also used as a symbol in\n    :py:func:`pvlib.inverter.pvwatts`. ``pdc0`` in this function refers to the DC\n    power of the modules at reference conditions. ``pdc0`` in\n    :py:func:`pvlib.inverter.pvwatts` refers to the DC power input limit of\n    the inverter.\n\n    Parameters\n    ----------\n    g_poa_effective: numeric\n        Irradiance transmitted to the PV cells. To be\n        fully consistent with PVWatts, the user must have already\n        applied angle of incidence losses, but not soiling, spectral,\n        etc. [W/m^2]\n    temp_cell: numeric\n        Cell temperature [C].\n    pdc0: numeric\n        Power of the modules at 1000 W/m^2 and cell reference temperature. [W]\n    gamma_pdc: numeric\n        The temperature coefficient of power. Typically -0.002 to\n        -0.005 per degree C. [1/C]\n    temp_ref: numeric, default 25.0\n        Cell reference temperature. PVWatts defines it to be 25 C and\n        is included here for flexibility. [C]\n\n    Returns\n    -------\n    pdc: numeric\n        DC power. [W]\n\n    References\n    ----------\n    .. [1] A. P. Dobos, \"PVWatts Version 5 Manual\"\n           http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf\n           (2014).\n    \"\"\"  # noqa: E501\n\n    pdc = (g_poa_effective * 0.001 * pdc0 *\n           (1 + gamma_pdc * (temp_cell - temp_ref)))\n\n    return pdc\n\n\ndef pvwatts_losses(soiling=2, shading=3, snow=0, mismatch=2, wiring=2,\n                   connections=0.5, lid=1.5, nameplate_rating=1, age=0,\n                   availability=3):\n    r\"\"\"\n    Implements NREL's PVWatts system loss model.\n    The PVWatts loss model [1]_ is:\n\n    .. math::\n\n        L_{total}(\\%) = 100 [ 1 - \\Pi_i ( 1 - \\frac{L_i}{100} ) ]\n\n    All parameters must be in units of %. Parameters may be\n    array-like, though all array sizes must match.\n\n    Parameters\n    ----------\n    soiling: numeric, default 2\n    shading: numeric, default 3\n    snow: numeric, default 0\n    mismatch: numeric, default 2\n    wiring: numeric, default 2\n    connections: numeric, default 0.5\n    lid: numeric, default 1.5\n        Light induced degradation\n    nameplate_rating: numeric, default 1\n    age: numeric, default 0\n    availability: numeric, default 3\n\n    Returns\n    -------\n    losses: numeric\n        System losses in units of %.\n\n    References\n    ----------\n    .. [1] A. P. Dobos, \"PVWatts Version 5 Manual\"\n           http://pvwatts.nrel.gov/downloads/pvwattsv5.pdf\n           (2014).\n    \"\"\"\n\n    params = [soiling, shading, snow, mismatch, wiring, connections, lid,\n              nameplate_rating, age, availability]\n\n    # manually looping over params allows for numpy/pandas to handle any\n    # array-like broadcasting that might be necessary.\n    perf = 1\n    for param in params:\n        perf *= 1 - param/100\n\n    losses = (1 - perf) * 100.\n\n    return losses\n\n\ndef dc_ohms_from_percent(vmp_ref, imp_ref, dc_ohmic_percent,\n                         modules_per_string=1,\n                         strings=1):\n    \"\"\"\n    Calculates the equivalent resistance of the wires from a percent\n    ohmic loss at STC.\n\n    Equivalent resistance is calculated with the function:\n\n    .. math::\n        Rw = (L_{stc} / 100) * (Varray / Iarray)\n\n    :math:`Rw` is the equivalent resistance in ohms\n    :math:`Varray` is the Vmp of the modules times modules per string\n    :math:`Iarray` is the Imp of the modules times strings per array\n    :math:`L_{stc}` is the input dc loss percent\n\n    Parameters\n    ----------\n    vmp_ref: numeric\n        Voltage at maximum power in reference conditions [V]\n    imp_ref: numeric\n        Current at maximum power in reference conditions [V]\n    dc_ohmic_percent: numeric, default 0\n        input dc loss as a percent, e.g. 1.5% loss is input as 1.5\n    modules_per_string: int, default 1\n        Number of modules per string in the array.\n    strings: int, default 1\n        Number of parallel strings in the array.\n\n    Returns\n    ----------\n    Rw: numeric\n        Equivalent resistance [ohm]\n\n    See Also\n    --------\n    pvlib.pvsystem.dc_ohmic_losses\n\n    References\n    ----------\n    .. [1] PVsyst 7 Help. \"Array ohmic wiring loss\".\n       https://www.pvsyst.com/help/ohmic_loss.htm\n    \"\"\"\n    vmp = modules_per_string * vmp_ref\n\n    imp = strings * imp_ref\n\n    Rw = (dc_ohmic_percent / 100) * (vmp / imp)\n\n    return Rw\n\n\ndef dc_ohmic_losses(resistance, current):\n    \"\"\"\n    Returns ohmic losses in units of power from the equivalent\n    resistance of the wires and the operating current.\n\n    Parameters\n    ----------\n    resistance: numeric\n        Equivalent resistance of wires [ohm]\n    current: numeric, float or array-like\n        Operating current [A]\n\n    Returns\n    ----------\n    loss: numeric\n        Power Loss [W]\n\n    See Also\n    --------\n    pvlib.pvsystem.dc_ohms_from_percent\n\n    References\n    ----------\n    .. [1] PVsyst 7 Help. \"Array ohmic wiring loss\".\n       https://www.pvsyst.com/help/ohmic_loss.htm\n    \"\"\"\n    return resistance * current * current\n\n\ndef combine_loss_factors(index, *losses, fill_method='ffill'):\n    r\"\"\"\n    Combines Series loss fractions while setting a common index.\n\n    The separate losses are compounded using the following equation:\n\n    .. math::\n\n        L_{total} = 1 - [ 1 - \\Pi_i ( 1 - L_i ) ]\n\n    :math:`L_{total}` is the total loss returned\n    :math:`L_i` is each individual loss factor input\n\n    Note the losses must each be a series with a DatetimeIndex.\n    All losses will be resampled to match the index parameter using\n    the fill method specified (defaults to \"fill forward\").\n\n    Parameters\n    ----------\n    index : DatetimeIndex\n        The index of the returned loss factors\n\n    *losses : Series\n        One or more Series of fractions to be compounded\n\n    fill_method : {'ffill', 'bfill', 'nearest'}, default 'ffill'\n        Method to use for filling holes in reindexed DataFrame\n\n    Returns\n    -------\n    Series\n        Fractions resulting from the combination of each loss factor\n    \"\"\"\n    combined_factor = 1\n\n    for loss in losses:\n        loss = loss.reindex(index, method=fill_method)\n        combined_factor *= (1 - loss)\n\n    return 1 - combined_factor\n"}, "filelevel_patches": {"pvlib/pvsystem.py": "@@ -101,10 +101,11 @@ class PVSystem:\n \n     Parameters\n     ----------\n-    arrays : iterable of Array, optional\n-        List of arrays that are part of the system. If not specified\n-        a single array is created from the other parameters (e.g.\n-        `surface_tilt`, `surface_azimuth`). Must contain at least one Array,\n+    arrays : Array or iterable of Array, optional\n+        An Array or list of arrays that are part of the system. If not\n+        specified a single array is created from the other parameters (e.g.\n+        `surface_tilt`, `surface_azimuth`). If specified as a list, the list\n+        must contain at least one Array;\n         if length of arrays is 0 a ValueError is raised. If `arrays` is\n         specified the following PVSystem parameters are ignored:\n@@ -220,6 +221,8 @@ def __init__(self,\n                 strings_per_inverter,\n                 array_losses_parameters,\n             ),)\n+        elif isinstance(arrays, Array):\n+            self.arrays = (arrays,)\n         elif len(arrays) == 0:\n             raise ValueError(\"PVSystem must have at least one Array. \"\n                              \"If you want to create a PVSystem instance \""}}