diff --git a/docs/examples/bifacial/plot_bifi_model_pvwatts.py b/docs/examples/bifacial/plot_bifi_model_pvwatts.py index 3a0a7674a6..6d7bdc6974 100644 --- a/docs/examples/bifacial/plot_bifi_model_pvwatts.py +++ b/docs/examples/bifacial/plot_bifi_model_pvwatts.py @@ -7,7 +7,7 @@ # %% # This example shows how to complete a bifacial modeling example using the -# :py:func:`pvlib.pvsystem.pvwatts_dc` with the +# :py:func:`pvlib.pvsystem.pvwattsv5_dc` with the # :py:func:`pvlib.bifacial.pvfactors.pvfactors_timeseries` function to # transpose GHI data to both front and rear Plane of Array (POA) irradiance. @@ -83,15 +83,15 @@ temp_cell = temperature.faiman(effective_irrad_bifi, temp_air=25, wind_speed=1) -# using the pvwatts_dc model and parameters detailed above, +# using the pvwattsv5_dc model and parameters detailed above, # set pdc0 and return DC power for both bifacial and monofacial pdc0 = 1 gamma_pdc = -0.0043 -pdc_bifi = pvsystem.pvwatts_dc(effective_irrad_bifi, - temp_cell, - pdc0, - gamma_pdc=gamma_pdc - ).fillna(0) +pdc_bifi = pvsystem.pvwattsv5_dc(effective_irrad_bifi, + temp_cell, + pdc0, + gamma_pdc=gamma_pdc + ).fillna(0) pdc_bifi.plot(title='Bifacial Simulation on June Solstice', ylabel='DC Power') # %% @@ -99,11 +99,11 @@ # irradiance (AOI-corrected), and plot along with bifacial results. effective_irrad_mono = irrad['total_abs_front'] -pdc_mono = pvsystem.pvwatts_dc(effective_irrad_mono, - temp_cell, - pdc0, - gamma_pdc=gamma_pdc - ).fillna(0) +pdc_mono = pvsystem.pvwattsv5_dc(effective_irrad_mono, + temp_cell, + pdc0, + gamma_pdc=gamma_pdc + ).fillna(0) # plot monofacial results plt.figure() diff --git a/docs/sphinx/source/reference/modelchain.rst b/docs/sphinx/source/reference/modelchain.rst index b8ce6744fb..1a05ee1658 100644 --- a/docs/sphinx/source/reference/modelchain.rst +++ b/docs/sphinx/source/reference/modelchain.rst @@ -81,9 +81,11 @@ ModelChain model definitions. modelchain.ModelChain.desoto modelchain.ModelChain.pvsyst modelchain.ModelChain.pvwatts_dc + modelchain.ModelChain.pvwattsv5_dc modelchain.ModelChain.sandia_inverter modelchain.ModelChain.adr_inverter modelchain.ModelChain.pvwatts_inverter + modelchain.ModelChain.pvwattsv5_inverter modelchain.ModelChain.ashrae_aoi_loss modelchain.ModelChain.physical_aoi_loss modelchain.ModelChain.sapm_aoi_loss @@ -98,6 +100,7 @@ ModelChain model definitions. modelchain.ModelChain.dc_ohmic_model modelchain.ModelChain.no_dc_ohmic_loss modelchain.ModelChain.pvwatts_losses + modelchain.ModelChain.pvwattsv5_losses modelchain.ModelChain.no_extra_losses Inference methods diff --git a/docs/sphinx/source/reference/pv_modeling.rst b/docs/sphinx/source/reference/pv_modeling.rst index 2208e932bd..4e57130711 100644 --- a/docs/sphinx/source/reference/pv_modeling.rst +++ b/docs/sphinx/source/reference/pv_modeling.rst @@ -107,7 +107,9 @@ Inverter models (DC to AC conversion) inverter.sandia_multi inverter.adr inverter.pvwatts + inverter.pvwattsv5 inverter.pvwatts_multi + inverter.pvwattsv5_multi Functions for fitting inverter models @@ -152,8 +154,11 @@ PVWatts model :toctree: generated/ pvsystem.pvwatts_dc + pvsystem.pvwattsv5_dc inverter.pvwatts + inverter.pvwattsv5 pvsystem.pvwatts_losses + pvsystem.pvwattsv5_losses Estimating PV model parameters ------------------------------ diff --git a/docs/sphinx/source/user_guide/modelchain.rst b/docs/sphinx/source/user_guide/modelchain.rst index 63a1d0618a..54e74e6a23 100644 --- a/docs/sphinx/source/user_guide/modelchain.rst +++ b/docs/sphinx/source/user_guide/modelchain.rst @@ -290,7 +290,7 @@ Wrapping methods into a unified API Readers may notice that the source code of the :py:meth:`~pvlib.modelchain.ModelChain.run_model` method is model-agnostic. :py:meth:`~pvlib.modelchain.ModelChain.run_model` calls generic methods such as ``self.dc_model`` rather than a specific model such as -``pvwatts_dc``. So how does :py:meth:`~pvlib.modelchain.ModelChain.run_model` know what models +``pvwattsv5_dc``. So how does :py:meth:`~pvlib.modelchain.ModelChain.run_model` know what models it’s supposed to run? The answer comes in two parts, and allows us to explore more of the ModelChain API along the way. @@ -298,17 +298,17 @@ First, ModelChain has a set of methods that wrap the PVSystem methods that perform the calculations (or further wrap the pvsystem.py module’s functions). Each of these methods takes the same arguments (``self``) and sets the same attributes, thus creating a uniform API. For example, -the :py:meth:`~pvlib.modelchain.ModelChain.pvwatts_dc` method is shown below. Its only argument is +the :py:meth:`~pvlib.modelchain.ModelChain.pvwattsv5_dc` method is shown below. Its only argument is ``self``, and it sets the ``dc`` attribute. .. ipython:: python - mc.pvwatts_dc?? + mc.pvwattsv5_dc?? -The :py:meth:`~pvlib.modelchain.ModelChain.pvwatts_dc` method calls the pvwatts_dc method of the +The :py:meth:`~pvlib.modelchain.ModelChain.pvwattsv5_dc` method calls the pvwattsv5_dc method of the PVSystem object that we supplied when we created the ModelChain instance, using data that is stored in the ModelChain ``effective_irradiance`` and -``cell_temperature`` attributes. The :py:meth:`~pvlib.modelchain.ModelChain.pvwatts_dc` method assigns its +``cell_temperature`` attributes. The :py:meth:`~pvlib.modelchain.ModelChain.pvwattsv5_dc` method assigns its result to the ``dc`` attribute of the ModelChain's ``results`` object. The code below shows a simple example of this. @@ -322,21 +322,21 @@ below shows a simple example of this. mc = ModelChain(pvwatts_system, location, aoi_model='no_loss', spectral_model='no_loss') - # manually assign data to the attributes that ModelChain.pvwatts_dc will need. + # manually assign data to the attributes that ModelChain.pvwattsv5_dc will need. # for standard workflows, run_model would assign these attributes. mc.results.effective_irradiance = pd.Series(1000, index=[pd.Timestamp('20170401 1200-0700')]) mc.results.cell_temperature = pd.Series(50, index=[pd.Timestamp('20170401 1200-0700')]) - # run ModelChain.pvwatts_dc and look at the result - mc.pvwatts_dc(); + # run ModelChain.pvwattsv5_dc and look at the result + mc.pvwattsv5_dc(); mc.results.dc The :py:meth:`~pvlib.modelchain.ModelChain.sapm` method works in a manner similar -to the :py:meth:`~pvlib.modelchain.ModelChain.pvwatts_dc` +to the :py:meth:`~pvlib.modelchain.ModelChain.pvwattsv5_dc` method. It calls the :py:meth:`~pvlib.pvsystem.PVSystem.sapm` method using stored data, then assigns the result to the ``dc`` attribute of ``ModelChain.results``. The :py:meth:`~pvlib.modelchain.ModelChain.sapm` method differs from the -:py:meth:`~pvlib.modelchain.ModelChain.pvwatts_dc` method in +:py:meth:`~pvlib.modelchain.ModelChain.pvwattsv5_dc` method in a notable way: the PVSystem.sapm method returns a DataFrame with current, voltage, and power results, rather than a simple Series of power. The ModelChain methods for single diode models (e.g., @@ -370,7 +370,7 @@ DC quantities to the output of the full PVSystem. mc.sapm(); mc.results.dc -We’ve established that the ``ModelChain.pvwatts_dc`` and +We’ve established that the ``ModelChain.pvwattsv5_dc`` and ``ModelChain.sapm`` have the same API: they take the same arugments (``self``) and they both set the ``dc`` attribute.\* Because the methods have the same API, we can call them in the same way. ModelChain includes @@ -381,7 +381,7 @@ Again, so how does :py:meth:`~pvlib.modelchain.ModelChain.run_model` know which models it’s supposed to run? At object construction, ModelChain assigns the desired model’s method -(e.g. ``ModelChain.pvwatts_dc``) to the corresponding generic attribute +(e.g. ``ModelChain.pvwattsv5_dc``) to the corresponding generic attribute (e.g. ``ModelChain.dc_model``) either with the value assigned to the ``dc_model`` parameter at construction, or by inference as described in the next section. @@ -428,7 +428,7 @@ method. mc.infer_ac_model?? pvlib.modelchain._snl_params?? pvlib.modelchain._adr_params?? - pvlib.modelchain._pvwatts_params?? + pvlib.modelchain._pvwattsv5_params?? ModelChain for a PVSystem with multiple Arrays ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -436,7 +436,7 @@ ModelChain for a PVSystem with multiple Arrays The PVSystem can represent a PV system with a single array of modules, or with multiple arrays (see :ref:`multiarray`). The same models are applied to all ``PVSystem.array`` objects, so each ``Array`` must contain the appropriate model -parameters. For example, if ``ModelChain.dc_model='pvwatts'``, then each +parameters. For example, if ``ModelChain.dc_model='pvwattsv5'``, then each ``Array.module_parameters`` must contain ``'pdc0'``. When the PVSystem contains multiple arrays, ``ModelChain.results`` attributes diff --git a/docs/sphinx/source/user_guide/pvsystem.rst b/docs/sphinx/source/user_guide/pvsystem.rst index ae9344483c..3a28fd25ef 100644 --- a/docs/sphinx/source/user_guide/pvsystem.rst +++ b/docs/sphinx/source/user_guide/pvsystem.rst @@ -70,28 +70,28 @@ that describe a PV system's modules and inverter are stored in Extrinsic data is passed to the arguments of PVSystem methods. For example, -the :py:meth:`~pvlib.pvsystem.PVSystem.pvwatts_dc` method accepts extrinsic +the :py:meth:`~pvlib.pvsystem.PVSystem.pvwattsv5_dc` method accepts extrinsic data irradiance and temperature. .. ipython:: python - pdc = system.pvwatts_dc(g_poa_effective=1000, temp_cell=30) + pdc = system.pvwattsv5_dc(g_poa_effective=1000, temp_cell=30) print(pdc) Methods attached to a PVSystem object wrap the corresponding functions in :py:mod:`~pvlib.pvsystem`. The methods simplify the argument list by using data stored in the PVSystem attributes. Compare the -:py:meth:`~pvlib.pvsystem.PVSystem.pvwatts_dc` method signature to the -:py:func:`~pvlib.pvsystem.pvwatts_dc` function signature: +:py:meth:`~pvlib.pvsystem.PVSystem.pvwattsv5_dc` method signature to the +:py:func:`~pvlib.pvsystem.pvwattsv5_dc` function signature: - * :py:meth:`PVSystem.pvwatts_dc(g_poa_effective, temp_cell) ` - * :py:func:`pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.) ` + * :py:meth:`PVSystem.pvwattsv5_dc(g_poa_effective, temp_cell) ` + * :py:func:`pvwattsv5_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.) ` -How does this work? The :py:meth:`~pvlib.pvsystem.PVSystem.pvwatts_dc` +How does this work? The :py:meth:`~pvlib.pvsystem.PVSystem.pvwattsv5_dc` method looks in `PVSystem.module_parameters` for the `pdc0`, and -`gamma_pdc` arguments. Then the :py:meth:`PVSystem.pvwatts_dc -` method calls the -:py:func:`pvsystem.pvwatts_dc ` function with +`gamma_pdc` arguments. Then the :py:meth:`PVSystem.pvwattsv5_dc +` method calls the +:py:func:`pvsystem.pvwattsv5_dc ` function with all of the arguments and returns the result to the user. Note that the function includes a default value for the parameter `temp_ref`. This default value may be overridden by specifying the `temp_ref` key in the @@ -101,7 +101,7 @@ default value may be overridden by specifying the `temp_ref` key in the system.arrays[0].module_parameters['temp_ref'] = 0 # lower temp_ref should lead to lower DC power than calculated above - pdc = system.pvwatts_dc(1000, 30) + pdc = system.pvwattsv5_dc(1000, 30) print(pdc) Multiple methods may pull data from the same attribute. For example, the @@ -320,8 +320,8 @@ Losses The `losses_parameters` attribute contains data that may be used with methods that calculate system losses. At present, these methods include -only :py:meth:`pvlib.pvsystem.PVSystem.pvwatts_losses` and -:py:func:`pvlib.pvsystem.pvwatts_losses`, but we hope to add more related functions +only :py:meth:`pvlib.pvsystem.PVSystem.pvwattsv5_losses` and +:py:func:`pvlib.pvsystem.pvwattsv5_losses`, but we hope to add more related functions and methods in the future. diff --git a/docs/sphinx/source/whatsnew/v0.9.4.rst b/docs/sphinx/source/whatsnew/v0.9.4.rst index 6524c1745f..ecb659ccc9 100644 --- a/docs/sphinx/source/whatsnew/v0.9.4.rst +++ b/docs/sphinx/source/whatsnew/v0.9.4.rst @@ -5,6 +5,29 @@ v0.9.4 (anticipated December 2022) Deprecations ~~~~~~~~~~~~ +* In anticipation of implementing newer versions of the PVWatts model + in the future and to clarify the relevant PVWatts model version for + existing functionality, several parts of pvlib have been renamed from + `pvwatts` to `pvwattsv5`: + + * ``pvlib.inverter.pvwatts`` is now :py:func:`pvlib.inverter.pvwattsv5` + * ``pvlib.inverter.pvwatts_multi`` is now :py:func:`pvlib.inverter.pvwattsv5_multi` + * ``pvlib.pvsystem.pvwatts_dc`` is now :py:func:`pvlib.pvsystem.pvwattsv5_dc` + * ``pvlib.pvsystem.pvwatts_losses`` is now :py:func:`pvlib.pvsystem.pvwattsv5_losses` + * ``pvlib.pvsystem.PVSystem.pvwatts_dc`` is now :py:meth:`pvlib.pvsystem.PVSystem.pvwattsv5_dc` + * ``pvlib.pvsystem.PVSystem.pvwatts_losses`` is now :py:meth:`pvlib.pvsystem.PVSystem.pvwattsv5_losses` + * ``pvlib.modelchain.ModelChain.pvwatts_dc`` is now :py:meth:`pvlib.modelchain.ModelChain.pvwattsv5_dc` + * ``pvlib.modelchain.ModelChain.pvwatts_inverter`` is now :py:meth:`pvlib.modelchain.ModelChain.pvwattsv5_inverter` + * ``pvlib.modelchain.ModelChain.pvwatts_losses`` is now :py:meth:`pvlib.modelchain.ModelChain.pvwattsv5_losses` + + * The ``model`` parameter to :py:meth:`pvlib.pvsystem.PVSystem.get_ac` should + now be ``'pvwattsv5'`` instead of ``'pvwatts'``. + * The ``dc_model``, ``ac_model``, and ``losses_model`` parameters of + :py:class:`pvlib.modelchain.ModelChain` should now be ``'pvwattsv5'`` + instead of ``'pvwatts'``. + + The originals should continue to work for now (emitting a deprecation warning), + but will be removed in a future release. (:issue:`1350`, :pull:`1558`) Enhancements diff --git a/pvlib/inverter.py b/pvlib/inverter.py index cfac1682c7..0410154eb0 100644 --- a/pvlib/inverter.py +++ b/pvlib/inverter.py @@ -14,6 +14,7 @@ import pandas as pd from numpy.polynomial.polynomial import polyfit # different than np.polyfit +from pvlib._deprecation import deprecated def _sandia_eff(v_dc, p_dc, inverter): r''' @@ -330,9 +331,9 @@ def adr(v_dc, p_dc, inverter, vtol=0.10): return power_ac -def pvwatts(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): +def pvwattsv5(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): r""" - NREL's PVWatts inverter model. + NREL's PVWatts v5 inverter model. The PVWatts inverter model [1]_ calculates inverter efficiency :math:`\eta` as a function of input DC power @@ -370,14 +371,14 @@ def pvwatts(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): Notes ----- Note that ``pdc0`` is also used as a symbol in - :py:func:`pvlib.pvsystem.pvwatts_dc`. ``pdc0`` in this function refers to + :py:func:`pvlib.pvsystem.pvwattsv5_dc`. ``pdc0`` in this function refers to the DC power input limit of the inverter. ``pdc0`` in - :py:func:`pvlib.pvsystem.pvwatts_dc` refers to the DC power of the modules - at reference conditions. + :py:func:`pvlib.pvsystem.pvwattsv5_dc` refers to the DC power of the + modules at reference conditions. See Also -------- - pvlib.inverter.pvwatts_multi + pvlib.inverter.pvwattsv5_multi References ---------- @@ -404,13 +405,19 @@ def pvwatts(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): return power_ac -def pvwatts_multi(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): +pvwatts = deprecated(since='0.9.4', + name='pvwatts', + alternative='pvwattsv5', + removal='0.11')(pvwattsv5) + + +def pvwattsv5_multi(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): r""" - Extend NREL's PVWatts inverter model for multiple MPP inputs. + Extend NREL's PVWatts v5 inverter model for multiple MPPT inputs. - DC input power is summed over MPP inputs to obtain the DC power - input to the PVWatts inverter model. See :py:func:`pvlib.inverter.pvwatts` - for details. + DC input power is summed over MPPT inputs to obtain the DC power + input to the PVWatts v5 inverter model. + See :py:func:`pvlib.inverter.pvwattsv5` for details. Parameters ---------- @@ -432,9 +439,15 @@ def pvwatts_multi(pdc, pdc0, eta_inv_nom=0.96, eta_inv_ref=0.9637): See Also -------- - pvlib.inverter.pvwatts + pvlib.inverter.pvwattsv5 """ - return pvwatts(sum(pdc), pdc0, eta_inv_nom, eta_inv_ref) + return pvwattsv5(sum(pdc), pdc0, eta_inv_nom, eta_inv_ref) + + +pvwatts_multi = deprecated(since='0.9.4', + name='pvwatts_multi', + alternative='pvwattsv5_multi', + removal='0.11')(pvwattsv5_multi) def fit_sandia(ac_power, dc_power, dc_voltage, dc_voltage_level, p_ac_0, p_nt): diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index ccf2e614f3..a8d3f224d7 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -46,14 +46,11 @@ # basic_chain and ModelChain. They are used by the ModelChain methods # ModelChain.with_pvwatts, ModelChain.with_sapm, etc. -# pvwatts documentation states that it uses the following reference for -# a temperature model: Fuentes, M. K. (1987). A Simplified Thermal Model -# for Flat-Plate Photovoltaic Arrays. SAND85-0330. Albuquerque, NM: -# Sandia National Laboratories. Accessed September 3, 2013: -# http://prod.sandia.gov/techlib/access-control.cgi/1985/850330.pdf -# pvlib python does not implement that model, so use the SAPM instead. -PVWATTS_CONFIG = dict( - dc_model='pvwatts', ac_model='pvwatts', losses_model='pvwatts', +# Technically the pvwatts v5 model uses the fuentes temperature model; +# however, fuentes is harder to use and much slower than SAPM, so we +# use SAPM here. +PVWATTS_V5_CONFIG = dict( + dc_model='pvwattsv5', ac_model='pvwattsv5', losses_model='pvwattsv5', transposition_model='perez', aoi_model='physical', spectral_model='no_loss', temperature_model='sapm' ) @@ -392,7 +389,7 @@ class ModelChain: interface for all of the modeling steps necessary for calculating PV power from a time series of weather inputs. The same models are applied to all ``pvsystem.Array`` objects, so each Array must contain the - appropriate model parameters. For example, if ``dc_model='pvwatts'``, + appropriate model parameters. For example, if ``dc_model='pvwattsv5'``, then each ``Array.module_parameters`` must contain ``'pdc0'``. See :ref:`modelchaindoc` for examples. @@ -422,14 +419,14 @@ class ModelChain: dc_model: None, str, or function, default None If None, the model will be inferred from the parameters that are common to all of system.arrays[i].module_parameters. - Valid strings are 'sapm', 'desoto', 'cec', 'pvsyst', 'pvwatts'. + Valid strings are 'sapm', 'desoto', 'cec', 'pvsyst', 'pvwattsv5'. The ModelChain instance will be passed as the first argument to a user-defined function. ac_model: None, str, or function, default None If None, the model will be inferred from the parameters that are common to all of system.inverter_parameters. - Valid strings are 'sandia', 'adr', 'pvwatts'. The + Valid strings are 'sandia', 'adr', 'pvwattsv5'. The ModelChain instance will be passed as the first argument to a user-defined function. @@ -458,7 +455,7 @@ class ModelChain: function. losses_model: str or function, default 'no_loss' - Valid strings are 'pvwatts', 'no_loss'. The ModelChain instance + Valid strings are 'pvwattsv5', 'no_loss'. The ModelChain instance will be passed as the first argument to a user-defined function. name: None or str, default None @@ -529,6 +526,7 @@ def with_pvwatts(cls, system, location, clearsky_model='ineichen', airmass_model='kastenyoung1989', name=None, + version=5, **kwargs): """ ModelChain that follows the PVWatts methods. @@ -552,6 +550,10 @@ def with_pvwatts(cls, system, location, name: None or str, default None Name of ModelChain instance. + version: int, default 5 + The version of PVWatts to emulate. Currently, version 5 + is the only option. + **kwargs Parameters supplied here are passed to the ModelChain constructor and take precedence over the default @@ -574,14 +576,18 @@ def with_pvwatts(cls, system, location, transposition_model: perez solar_position_method: nrel_numpy airmass_model: kastenyoung1989 - dc_model: pvwatts_dc - ac_model: pvwatts_inverter + dc_model: pvwattsv5_dc + ac_model: pvwattsv5_inverter aoi_model: physical_aoi_loss spectral_model: no_spectral_loss temperature_model: sapm_temp - losses_model: pvwatts_losses + losses_model: pvwattsv5_losses """ # noqa: E501 - config = PVWATTS_CONFIG.copy() + if version == 5: + config = PVWATTS_V5_CONFIG.copy() + else: + raise ValueError(f'Invalid PVWatts version: {version}') + config.update(kwargs) return ModelChain( system, location, @@ -723,8 +729,14 @@ def dc_model(self, model): self._dc_model = self.cec elif model == 'pvsyst': self._dc_model = self.pvsyst - elif model == 'pvwatts': - self._dc_model = self.pvwatts_dc + elif model in ['pvwatts', 'pvwattsv5']: + if model == 'pvwatts': + warnings.warn( + "model='pvwatts' is now called model='pvwattsv5'; " + "use the new model name to silence this warning", + pvlibDeprecationWarning) + + self._dc_model = self.pvwattsv5_dc else: raise ValueError(model + ' is not a valid DC power model') else: @@ -745,7 +757,7 @@ def infer_dc_model(self): 'R_sh_0', 'R_sh_exp', 'R_s'} <= params: return self.pvsyst, 'pvsyst' elif {'pdc0', 'gamma_pdc'} <= params: - return self.pvwatts_dc, 'pvwatts' + return self.pvwattsv5_dc, 'pvwattsv5' else: raise ValueError( 'Could not infer DC model from the module_parameters ' @@ -796,8 +808,8 @@ def cec(self): def pvsyst(self): return self._singlediode(self.system.calcparams_pvsyst) - def pvwatts_dc(self): - """Calculate DC power using the PVWatts model. + def pvwattsv5_dc(self): + """Calculate DC power using the PVWatts v5 model. Results are stored in ModelChain.results.dc. DC power is computed from PVSystem.arrays[i].module_parameters['pdc0'] and then scaled by @@ -809,10 +821,10 @@ def pvwatts_dc(self): See also -------- - pvlib.pvsystem.PVSystem.pvwatts_dc + pvlib.pvsystem.PVSystem.pvwattsv5_dc pvlib.pvsystem.PVSystem.scale_voltage_current_power """ - dc = self.system.pvwatts_dc( + dc = self.system.pvwattsv5_dc( self.results.effective_irradiance, self.results.cell_temperature, unwrap=False @@ -822,6 +834,10 @@ def pvwatts_dc(self): self.results.dc = _tuple_from_dfs(scaled, "p_mp") return self + @deprecated('0.9.4', alternative='ModelChain.pvwattsv5_dc', removal='0.11') + def pvwatts_dc(self): + return self.pvwattsv5_dc() + @property def ac_model(self): return self._ac_model @@ -836,8 +852,14 @@ def ac_model(self, model): self._ac_model = self.sandia_inverter elif model in 'adr': self._ac_model = self.adr_inverter - elif model == 'pvwatts': - self._ac_model = self.pvwatts_inverter + elif model in ['pvwatts', 'pvwattsv5']: + if model == 'pvwatts': + warnings.warn( + "model='pvwatts' is now called model='pvwattsv5'; " + "use the new model name to silence this warning", + pvlibDeprecationWarning) + + self._ac_model = self.pvwattsv5_inverter else: raise ValueError(model + ' is not a valid AC power model') else: @@ -855,8 +877,8 @@ def infer_ac_model(self): ' with multiple MPPT inputs') else: return self.adr_inverter - if _pvwatts_params(inverter_params): - return self.pvwatts_inverter + if _pvwattsv5_params(inverter_params): + return self.pvwattsv5_inverter raise ValueError('could not infer AC model from ' 'system.inverter_parameters. Check ' 'system.inverter_parameters or explicitly ' @@ -878,11 +900,16 @@ def adr_inverter(self): ) return self - def pvwatts_inverter(self): - ac = self.system.get_ac('pvwatts', self.results.dc) + def pvwattsv5_inverter(self): + ac = self.system.get_ac('pvwattsv5', self.results.dc) self.results.ac = ac.fillna(0) return self + @deprecated('0.9.4', alternative='ModelChain.pvwattsv5_inverter', + removal='0.11') + def pvwatts_inverter(self): + return self.pvwattsv5_inverter() + @property def aoi_model(self): return self._aoi_model @@ -1183,8 +1210,14 @@ def losses_model(self, model): self._losses_model = self.infer_losses_model() elif isinstance(model, str): model = model.lower() - if model == 'pvwatts': - self._losses_model = self.pvwatts_losses + if model in ['pvwatts', 'pvwattsv5']: + if model == 'pvwatts': + warnings.warn( + "model='pvwatts' is now called model='pvwattsv5'; " + "use the new model name to silence this warning", + pvlibDeprecationWarning) + + self._losses_model = self.pvwattsv5_losses elif model == 'no_loss': self._losses_model = self.no_extra_losses else: @@ -1195,8 +1228,8 @@ def losses_model(self, model): def infer_losses_model(self): raise NotImplementedError - def pvwatts_losses(self): - self.results.losses = (100 - self.system.pvwatts_losses()) / 100. + def pvwattsv5_losses(self): + self.results.losses = (100 - self.system.pvwattsv5_losses()) / 100. if isinstance(self.results.dc, tuple): for dc in self.results.dc: dc *= self.results.losses @@ -1204,6 +1237,11 @@ def pvwatts_losses(self): self.results.dc *= self.results.losses return self + @deprecated('0.9.4', alternative='ModelChain.pvwattsv5_losses', + removal='0.11') + def pvwatts_losses(self): + return self.pvwattsv5_losses() + def no_extra_losses(self): self.results.losses = 1 return self @@ -2010,9 +2048,9 @@ def _adr_params(inverter_params): return {'ADRCoefficients'} <= inverter_params -def _pvwatts_params(inverter_params): +def _pvwattsv5_params(inverter_params): """Return True if `inverter_params` includes parameters for the - PVWatts inverter model.""" + PVWatts v5 inverter model.""" return {'pdc0'} <= inverter_params diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 77560e04c0..5c72e279f5 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -15,12 +15,13 @@ from abc import ABC, abstractmethod from typing import Optional -from pvlib._deprecation import deprecated +from pvlib._deprecation import deprecated, pvlibDeprecationWarning from pvlib import (atmosphere, iam, inverter, irradiance, singlediode as _singlediode, temperature) from pvlib.tools import _build_kwargs, _build_args +import warnings # a dict of required parameter names for each DC power model _DC_MODEL_PARAMS = { @@ -43,9 +44,12 @@ 'singlediode': { 'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref', 'R_s'}, - 'pvwatts': {'pdc0', 'gamma_pdc'} + 'pvwattsv5': {'pdc0', 'gamma_pdc'} } +# temporary alias during the deprecation period +_DC_MODEL_PARAMS['pvwatts'] = _DC_MODEL_PARAMS['pvwattsv5'] + def _unwrap_single_value(func): """Decorator for functions that return iterables. @@ -823,9 +827,9 @@ def fuentes_celltemp(self, poa_global, temp_air, wind_speed): Notes ----- The Fuentes thermal model uses the module surface tilt for convection - modeling. The SAM implementation of PVWatts hardcodes the surface tilt + modeling. SAM's implementation of PVWatts v5 hardcodes the surface tilt value at 30 degrees, ignoring whatever value is used for irradiance - transposition. If you want to match the PVWatts behavior you can + transposition. If you want to match the PVWatts v5 behavior you can either leave ``surface_tilt`` unspecified to use the PVWatts default of 30, or specify a ``surface_tilt`` value in the Array's ``temperature_model_parameters``. @@ -959,7 +963,7 @@ def get_ac(self, model, p_dc, v_dc=None): Parameters ---------- model : str - Must be one of 'sandia', 'adr', or 'pvwatts'. + Must be one of 'sandia', 'adr', or 'pvwattsv5'. 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, @@ -978,7 +982,7 @@ def get_ac(self, model, p_dc, v_dc=None): Raises ------ ValueError - If model is not one of 'sandia', 'adr' or 'pvwatts'. + If model is not one of 'sandia', 'adr' or 'pvwattsv5'. ValueError If model='adr' and the PVSystem has more than one array. @@ -987,8 +991,8 @@ def get_ac(self, model, p_dc, v_dc=None): pvlib.inverter.sandia pvlib.inverter.sandia_multi pvlib.inverter.adr - pvlib.inverter.pvwatts - pvlib.inverter.pvwatts_multi + pvlib.inverter.pvwattsv5 + pvlib.inverter.pvwattsv5_multi """ model = model.lower() multiple_arrays = self.num_arrays > 1 @@ -999,14 +1003,19 @@ def get_ac(self, model, p_dc, v_dc=None): 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': + elif model in ['pvwatts', 'pvwattsv5']: + if model == 'pvwatts': + warnings.warn( + "model='pvwatts' is now called model='pvwattsv5'; " + "use the new model name to silence this warning", + pvlibDeprecationWarning) 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( + return inverter.pvwattsv5_multi( p_dc, self.inverter_parameters['pdc0'], **kwargs) - return inverter.pvwatts( + return inverter.pvwattsv5( p_dc[0], self.inverter_parameters['pdc0'], **kwargs) elif model == 'adr': if multiple_arrays: @@ -1021,7 +1030,7 @@ def get_ac(self, model, p_dc, v_dc=None): else: raise ValueError( model + ' is not a valid AC power model.', - ' model must be one of "sandia", "adr" or "pvwatts"') + ' model must be one of "sandia", "adr" or "pvwattsv5"') @deprecated('0.9', alternative='PVSystem.get_ac', removal='0.10') def snlinverter(self, v_dc, p_dc): @@ -1067,53 +1076,83 @@ def scale_voltage_current_power(self, data): ) @_unwrap_single_value - def pvwatts_dc(self, g_poa_effective, temp_cell): + def pvwattsv5_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']`, + Calculates DC power according to the PVWatts v5 model using + :py:func:`pvlib.pvsystem.pvwattsv5_dc`, + `self.module_parameters['pdc0']`, and `self.module_parameters['gamma_pdc']`. - See :py:func:`pvlib.pvsystem.pvwatts_dc` for details. + See :py:func:`pvlib.pvsystem.pvwattsv5_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)) + pvwattsv5_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) ) + @deprecated('0.9.4', alternative='PVSystem.pvwattsv5_dc', removal='0.11') + def pvwatts_dc(self, g_poa_effective, temp_cell): + """ + Calculates DC power according to the PVWatts v5 model using + :py:func:`pvlib.pvsystem.pvwattsv5_dc`, + `self.module_parameters['pdc0']`, + and `self.module_parameters['gamma_pdc']`. + + See :py:func:`pvlib.pvsystem.pvwattsv5_dc` for details. + """ + return self.pvwattsv5_dc(g_poa_effective, temp_cell) + + def pvwattsv5_losses(self): + """ + Calculates DC power losses according the PVwatts v5 model using + :py:func:`pvlib.pvsystem.pvwattsv5_losses` and + ``self.losses_parameters``. + + See :py:func:`pvlib.pvsystem.pvwattsv5_losses` for details. + """ + kwargs = _build_kwargs(['soiling', 'shading', 'snow', 'mismatch', + 'wiring', 'connections', 'lid', + 'nameplate_rating', 'age', 'availability'], + self.losses_parameters) + return pvwattsv5_losses(**kwargs) + + @deprecated('0.9.4', alternative='PVSystem.pvwattsv5_losses', + removal='0.11') def pvwatts_losses(self): """ - Calculates DC power losses according the PVwatts model using - :py:func:`pvlib.pvsystem.pvwatts_losses` and + Calculates DC power losses according the PVwatts v5 model using + :py:func:`pvlib.pvsystem.pvwattsv5_losses` and ``self.losses_parameters``. - See :py:func:`pvlib.pvsystem.pvwatts_losses` for details. + See :py:func:`pvlib.pvsystem.pvwattsv5_losses` for details. """ kwargs = _build_kwargs(['soiling', 'shading', 'snow', 'mismatch', 'wiring', 'connections', 'lid', 'nameplate_rating', 'age', 'availability'], self.losses_parameters) - return pvwatts_losses(**kwargs) + return pvwattsv5_losses(**kwargs) @deprecated('0.9', alternative='PVSystem.get_ac', removal='0.10') def pvwatts_ac(self, pdc): """ Calculates AC power according to the PVWatts model using - :py:func:`pvlib.inverter.pvwatts`, `self.module_parameters["pdc0"]`, + :py:func:`pvlib.inverter.pvwattsv5`, `self.module_parameters["pdc0"]`, and `eta_inv_nom=self.inverter_parameters["eta_inv_nom"]`. - See :py:func:`pvlib.inverter.pvwatts` for details. + See :py:func:`pvlib.inverter.pvwattsv5` for details. """ kwargs = _build_kwargs(['eta_inv_nom', 'eta_inv_ref'], self.inverter_parameters) - return inverter.pvwatts(pdc, self.inverter_parameters['pdc0'], - **kwargs) + return inverter.pvwattsv5(pdc, self.inverter_parameters['pdc0'], + **kwargs) @_unwrap_single_value def dc_ohms_from_percent(self): @@ -3171,18 +3210,18 @@ def scale_voltage_current_power(data, voltage=1, current=1): return df_sorted -def pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.): +def pvwattsv5_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: + Implements NREL's PVWatts v5 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 the pdc0 is also used as a symbol in - :py:func:`pvlib.inverter.pvwatts`. pdc0 in this function refers to the DC + :py:func:`pvlib.inverter.pvwattsv5`. 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 + :py:func:`pvlib.inverter.pvwattsv5` refers to the DC power input limit of the inverter. Parameters @@ -3221,11 +3260,17 @@ def pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.): 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): +pvwatts_dc = deprecated(since='0.9.4', + name='pvwatts_dc', + alternative='pvwattsv5_dc', + removal='0.11')(pvwattsv5_dc) + + +def pvwattsv5_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. + Implements NREL's PVWatts v5 system loss model. The PVWatts loss model [1]_ is: .. math:: @@ -3275,6 +3320,12 @@ def pvwatts_losses(soiling=2, shading=3, snow=0, mismatch=2, wiring=2, return losses +pvwatts_losses = deprecated(since='0.9.4', + name='pvwatts_losses', + alternative='pvwattsv5_losses', + removal='0.11')(pvwattsv5_losses) + + def dc_ohms_from_percent(vmp_ref, imp_ref, dc_ohmic_percent, modules_per_string=1, strings=1): diff --git a/pvlib/temperature.py b/pvlib/temperature.py index 9748965ccd..aa1080ade9 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -660,7 +660,7 @@ def fuentes(poa_global, temp_air, wind_speed, noct_installed, module_height=5, Calculate cell or module temperature using the Fuentes model. The Fuentes model is a first-principles heat transfer energy balance - model [1]_ that is used in PVWatts for cell temperature modeling [2]_. + model [1]_ that is used in PVWatts v5 for cell temperature modeling [2]_. Parameters ---------- @@ -675,17 +675,17 @@ def fuentes(poa_global, temp_air, wind_speed, noct_installed, module_height=5, noct_installed : float The "installed" nominal operating cell temperature as defined in [1]_. - PVWatts assumes this value to be 45 C for rack-mounted arrays and + PVWatts v5 assumes this value to be 45 C for rack-mounted arrays and 49 C for roof mount systems with restricted air flow around the module. [C] module_height : float, default 5.0 - The height above ground of the center of the module. The PVWatts + The height above ground of the center of the module. The PVWatts v5 default is 5.0 [m] wind_height : float, default 9.144 The height above ground at which ``wind_speed`` is measured. The - PVWatts defauls is 9.144 [m] + PVWatts v5 default is 9.144 [m] emissivity : float, default 0.84 The effectiveness of the module at radiating thermal energy. [unitless] @@ -715,7 +715,7 @@ def fuentes(poa_global, temp_air, wind_speed, noct_installed, module_height=5, Notes ----- - This function returns slightly different values from PVWatts at night + This function returns slightly different values from PVWatts v5 at night and just after dawn. This is because the SAM SSC assumes that module temperature equals ambient temperature when irradiance is zero so it can skip the heat balance calculation at night. diff --git a/pvlib/tests/test_inverter.py b/pvlib/tests/test_inverter.py index 4962d3e495..f8c8a41116 100644 --- a/pvlib/tests/test_inverter.py +++ b/pvlib/tests/test_inverter.py @@ -4,10 +4,11 @@ from .conftest import assert_series_equal from numpy.testing import assert_allclose -from .conftest import DATA_DIR +from .conftest import DATA_DIR, fail_on_pvlib_version import pytest from pvlib import inverter +from pvlib._deprecation import pvlibDeprecationWarning def test_adr(adr_inverter_parameters): @@ -134,62 +135,81 @@ def test_sandia_multi_array(cec_inverter_parameters): assert_allclose(pacs, np.array([-0.020000, 132.004278, 250.000000])) -def test_pvwatts_scalars(): +def test_pvwattsv5_scalars(): expected = 85.58556604752516 - out = inverter.pvwatts(90, 100, 0.95) + out = inverter.pvwattsv5(90, 100, 0.95) assert_allclose(out, expected) # GH 675 expected = 0. - out = inverter.pvwatts(0., 100) + out = inverter.pvwattsv5(0., 100) assert_allclose(out, expected) -def test_pvwatts_possible_negative(): - # pvwatts could return a negative value for (pdc / pdc0) < 0.006 +@fail_on_pvlib_version('0.11.0') +def test_pvwatts_deprecated(): + expected = 85.58556604752516 + with pytest.warns(pvlibDeprecationWarning, match='Use pvwattsv5 instead'): + out = inverter.pvwatts(90, 100, 0.95) + assert_allclose(out, expected) + + +def test_pvwattsv5_possible_negative(): + # pvwattsv5 could return a negative value for (pdc / pdc0) < 0.006 # unless it is clipped. see GH 541 for more expected = 0 - out = inverter.pvwatts(0.001, 1) + out = inverter.pvwattsv5(0.001, 1) assert_allclose(out, expected) -def test_pvwatts_arrays(): +def test_pvwattsv5_arrays(): pdc = np.array([[np.nan], [0], [50], [100]]) pdc0 = 100 expected = np.array([[np.nan], [0.], [47.60843624], [95.]]) - out = inverter.pvwatts(pdc, pdc0, 0.95) + out = inverter.pvwattsv5(pdc, pdc0, 0.95) assert_allclose(out, expected, equal_nan=True) -def test_pvwatts_series(): +def test_pvwattsv5_series(): pdc = pd.Series([np.nan, 0, 50, 100]) pdc0 = 100 expected = pd.Series(np.array([np.nan, 0., 47.608436, 95.])) - out = inverter.pvwatts(pdc, pdc0, 0.95) + out = inverter.pvwattsv5(pdc, pdc0, 0.95) assert_series_equal(expected, out) -def test_pvwatts_multi(): +def test_pvwattsv5_multi(): pdc = np.array([np.nan, 0, 50, 100]) / 2 pdc0 = 100 expected = np.array([np.nan, 0., 47.608436, 95.]) - out = inverter.pvwatts_multi((pdc, pdc), pdc0, 0.95) + out = inverter.pvwattsv5_multi((pdc, pdc), pdc0, 0.95) assert_allclose(expected, out) # with 2D array pdc_2d = np.array([pdc, pdc]) - out = inverter.pvwatts_multi(pdc_2d, pdc0, 0.95) + out = inverter.pvwattsv5_multi(pdc_2d, pdc0, 0.95) assert_allclose(expected, out) # with Series pdc = pd.Series(pdc) - out = inverter.pvwatts_multi((pdc, pdc), pdc0, 0.95) + out = inverter.pvwattsv5_multi((pdc, pdc), pdc0, 0.95) assert_series_equal(pd.Series(expected), out) # with list instead of tuple - out = inverter.pvwatts_multi([pdc, pdc], pdc0, 0.95) + out = inverter.pvwattsv5_multi([pdc, pdc], pdc0, 0.95) assert_series_equal(pd.Series(expected), out) +@fail_on_pvlib_version('0.11.0') +def test_pvwatts_multi_deprecated(): + pdc = np.array([np.nan, 0, 50, 100]) / 2 + pdc0 = 100 + expected = np.array([np.nan, 0., 47.608436, 95.]) + with pytest.warns(pvlibDeprecationWarning, + match='Use pvwattsv5_multi instead'): + out = inverter.pvwatts_multi((pdc, pdc), pdc0, 0.95) + assert_allclose(expected, out) + + INVERTER_TEST_MEAS = DATA_DIR / 'inverter_fit_snl_meas.csv' INVERTER_TEST_SIM = DATA_DIR / 'inverter_fit_snl_sim.csv' diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 62b71f2042..359b5298d5 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -344,11 +344,17 @@ def test_with_sapm(sapm_dc_snl_ac_system, location, weather): def test_with_pvwatts(pvwatts_dc_pvwatts_ac_system, location, weather): mc = ModelChain.with_pvwatts(pvwatts_dc_pvwatts_ac_system, location) - assert mc.dc_model == mc.pvwatts_dc + assert mc.dc_model == mc.pvwattsv5_dc assert mc.temperature_model == mc.sapm_temp mc.run_model(weather) +def test_with_pvwatts_invalid_version(pvwatts_dc_pvwatts_ac_system, location): + with pytest.raises(ValueError, match='Invalid PVWatts version'): + _ = ModelChain.with_pvwatts(pvwatts_dc_pvwatts_ac_system, location, + version='bad') + + def test_run_model_with_irradiance(sapm_dc_snl_ac_system, location): mc = ModelChain(sapm_dc_snl_ac_system, location) times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') @@ -609,13 +615,13 @@ def test_prepare_inputs_missing_irrad_component( mc.prepare_inputs(weather) -@pytest.mark.parametrize('ac_model', ['sandia', 'pvwatts']) +@pytest.mark.parametrize('ac_model', ['sandia', 'pvwattsv5']) @pytest.mark.parametrize("input_type", [tuple, list]) def test_run_model_arrays_weather(sapm_dc_snl_ac_system_same_arrays, pvwatts_dc_pvwatts_ac_system_arrays, location, ac_model, input_type): system = {'sandia': sapm_dc_snl_ac_system_same_arrays, - 'pvwatts': pvwatts_dc_pvwatts_ac_system_arrays} + 'pvwattsv5': pvwatts_dc_pvwatts_ac_system_arrays} mc = ModelChain(system[ac_model], location, aoi_model='no_loss', spectral_model='no_loss') times = pd.date_range('20200101 1200-0700', periods=2, freq='2H') @@ -1245,7 +1251,7 @@ def poadc(mc): @pytest.mark.parametrize('dc_model', [ - 'sapm', 'cec', 'desoto', 'pvsyst', 'singlediode', 'pvwatts_dc']) + 'sapm', 'cec', 'desoto', 'pvsyst', 'singlediode', 'pvwattsv5_dc']) def test_infer_dc_model(sapm_dc_snl_ac_system, cec_dc_snl_ac_system, pvsyst_dc_snl_ac_system, pvwatts_dc_pvwatts_ac_system, location, dc_model, weather, mocker): @@ -1254,19 +1260,19 @@ def test_infer_dc_model(sapm_dc_snl_ac_system, cec_dc_snl_ac_system, 'desoto': cec_dc_snl_ac_system, 'pvsyst': pvsyst_dc_snl_ac_system, 'singlediode': cec_dc_snl_ac_system, - 'pvwatts_dc': pvwatts_dc_pvwatts_ac_system} + 'pvwattsv5_dc': pvwatts_dc_pvwatts_ac_system} dc_model_function = {'sapm': 'sapm', 'cec': 'calcparams_cec', 'desoto': 'calcparams_desoto', 'pvsyst': 'calcparams_pvsyst', 'singlediode': 'calcparams_desoto', - 'pvwatts_dc': 'pvwatts_dc'} + 'pvwattsv5_dc': 'pvwattsv5_dc'} temp_model_function = {'sapm': 'sapm', 'cec': 'sapm', 'desoto': 'sapm', 'pvsyst': 'pvsyst', 'singlediode': 'sapm', - 'pvwatts_dc': 'sapm'} + 'pvwattsv5_dc': 'sapm'} temp_model_params = {'sapm': {'a': -3.40641, 'b': -0.0842075, 'deltaT': 3}, 'pvsyst': {'u_c': 29.0, 'u_v': 0}} system = dc_systems[dc_model] @@ -1382,8 +1388,8 @@ def test_dc_model_user_func(pvwatts_dc_pvwatts_ac_system, location, weather, assert not mc.results.ac.empty -def test_pvwatts_dc_multiple_strings(pvwatts_dc_pvwatts_ac_system, location, - weather, mocker): +def test_pvwattsv5_dc_multiple_strings(pvwatts_dc_pvwatts_ac_system, location, + weather, mocker): system = pvwatts_dc_pvwatts_ac_system m = mocker.spy(system, 'scale_voltage_current_power') mc1 = ModelChain(system, location, @@ -1406,8 +1412,8 @@ def acdc(mc): @pytest.mark.parametrize('inverter_model', ['sandia', 'adr', - 'pvwatts', 'sandia_multi', - 'pvwatts_multi']) + 'pvwattsv5', 'sandia_multi', + 'pvwattsv5_multi']) def test_ac_models(sapm_dc_snl_ac_system, cec_dc_adr_ac_system, pvwatts_dc_pvwatts_ac_system, cec_dc_snl_ac_arrays, pvwatts_dc_pvwatts_ac_system_arrays, @@ -1415,14 +1421,14 @@ def test_ac_models(sapm_dc_snl_ac_system, cec_dc_adr_ac_system, ac_systems = {'sandia': sapm_dc_snl_ac_system, 'sandia_multi': cec_dc_snl_ac_arrays, 'adr': cec_dc_adr_ac_system, - 'pvwatts': pvwatts_dc_pvwatts_ac_system, - 'pvwatts_multi': pvwatts_dc_pvwatts_ac_system_arrays} + 'pvwattsv5': pvwatts_dc_pvwatts_ac_system, + 'pvwattsv5_multi': pvwatts_dc_pvwatts_ac_system_arrays} inverter_to_ac_model = { 'sandia': 'sandia', 'sandia_multi': 'sandia', 'adr': 'adr', - 'pvwatts': 'pvwatts', - 'pvwatts_multi': 'pvwatts'} + 'pvwattsv5': 'pvwattsv5', + 'pvwattsv5_multi': 'pvwattsv5'} ac_model = inverter_to_ac_model[inverter_model] system = ac_systems[inverter_model] @@ -1671,14 +1677,14 @@ def test_dc_ohmic_not_a_model(cec_dc_snl_ac_system, location, dc_ohmic_model='not_a_dc_model') -def test_losses_models_pvwatts(pvwatts_dc_pvwatts_ac_system, location, weather, - mocker): +def test_losses_models_pvwattsv5(pvwatts_dc_pvwatts_ac_system, location, + weather, mocker): age = 1 pvwatts_dc_pvwatts_ac_system.losses_parameters = dict(age=age) - m = mocker.spy(pvsystem, 'pvwatts_losses') - mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts', - aoi_model='no_loss', spectral_model='no_loss', - losses_model='pvwatts') + m = mocker.spy(pvsystem, 'pvwattsv5_losses') + mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, + dc_model='pvwattsv5', aoi_model='no_loss', + spectral_model='no_loss', losses_model='pvwattsv5') mc.run_model(weather) assert m.call_count == 1 m.assert_called_with(age=age) @@ -1687,21 +1693,21 @@ def test_losses_models_pvwatts(pvwatts_dc_pvwatts_ac_system, location, weather, # check that we're applying correction to dc # GH 696 dc_with_loss = mc.results.dc - mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts', - aoi_model='no_loss', spectral_model='no_loss', - losses_model='no_loss') + mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, + dc_model='pvwattsv5', aoi_model='no_loss', + spectral_model='no_loss', losses_model='no_loss') mc.run_model(weather) assert not np.allclose(mc.results.dc, dc_with_loss, equal_nan=True) -def test_losses_models_pvwatts_arrays(multi_array_sapm_dc_snl_ac_system, - location, weather): +def test_losses_models_pvwattsv5_arrays(multi_array_sapm_dc_snl_ac_system, + location, weather): age = 1 system_both = multi_array_sapm_dc_snl_ac_system['two_array_system'] system_both.losses_parameters = dict(age=age) mc = ModelChain(system_both, location, aoi_model='no_loss', spectral_model='no_loss', - losses_model='pvwatts') + losses_model='pvwattsv5') mc.run_model(weather) dc_with_loss = mc.results.dc mc = ModelChain(system_both, location, @@ -1716,7 +1722,8 @@ def test_losses_models_pvwatts_arrays(multi_array_sapm_dc_snl_ac_system, def test_losses_models_ext_def(pvwatts_dc_pvwatts_ac_system, location, weather, mocker): m = mocker.spy(sys.modules[__name__], 'constant_losses') - mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts', + mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, + dc_model='pvwattsv5', aoi_model='no_loss', spectral_model='no_loss', losses_model=constant_losses) mc.run_model(weather) @@ -1728,8 +1735,9 @@ def test_losses_models_ext_def(pvwatts_dc_pvwatts_ac_system, location, weather, def test_losses_models_no_loss(pvwatts_dc_pvwatts_ac_system, location, weather, mocker): - m = mocker.spy(pvsystem, 'pvwatts_losses') - mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts', + m = mocker.spy(pvsystem, 'pvwattsv5_losses') + mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, + dc_model='pvwattsv5', aoi_model='no_loss', spectral_model='no_loss', losses_model='no_loss') assert mc.losses_model == mc.no_extra_losses @@ -1754,8 +1762,8 @@ def test_invalid_dc_model_params(sapm_dc_snl_ac_system, cec_dc_snl_ac_system, with pytest.raises(ValueError): ModelChain(cec_dc_snl_ac_system, location, **kwargs) - kwargs['dc_model'] = 'pvwatts' - kwargs['ac_model'] = 'pvwatts' + kwargs['dc_model'] = 'pvwattsv5' + kwargs['ac_model'] = 'pvwattsv5' for array in pvwatts_dc_pvwatts_ac_system.arrays: array.module_parameters.pop('pdc0') @@ -1769,7 +1777,7 @@ def test_invalid_dc_model_params(sapm_dc_snl_ac_system, cec_dc_snl_ac_system, 'temperature_model', 'losses_model' ]) def test_invalid_models(model, sapm_dc_snl_ac_system, location): - kwargs = {'dc_model': 'pvwatts', 'ac_model': 'pvwatts', + kwargs = {'dc_model': 'pvwattsv5', 'ac_model': 'pvwattsv5', 'aoi_model': 'no_loss', 'spectral_model': 'no_loss', 'temperature_model': 'sapm', 'losses_model': 'no_loss'} kwargs[model] = 'invalid' @@ -2063,3 +2071,40 @@ def test__irrad_for_celltemp(): assert len(poa) == 2 assert_series_equal(poa[0], effect_irrad) assert_series_equal(poa[1], effect_irrad) + + +@fail_on_pvlib_version('0.11.0') +def test_modelchain_pvwatts_methods_deprecated(pvwatts_dc_pvwatts_ac_system, + location, weather): + mc = ModelChain.with_pvwatts(pvwatts_dc_pvwatts_ac_system, location) + mc.run_model(weather) + with pytest.warns(pvlibDeprecationWarning, + match='Use ModelChain.pvwattsv5_dc instead'): + mc.pvwatts_dc() + + with pytest.warns(pvlibDeprecationWarning, + match='Use ModelChain.pvwattsv5_inverter instead'): + mc.pvwatts_inverter() + + with pytest.warns(pvlibDeprecationWarning, + match='Use ModelChain.pvwattsv5_losses instead'): + mc.pvwatts_losses() + + +@fail_on_pvlib_version('0.11.0') +def test_modelchain_pvwatts_modelnames_deprecated(pvwatts_dc_pvwatts_ac_system, + location): + with pytest.warns(pvlibDeprecationWarning, + match="model='pvwatts' is now called model='pvwattsv5'"): + _ = ModelChain.with_pvwatts(pvwatts_dc_pvwatts_ac_system, location, + dc_model='pvwatts') + + with pytest.warns(pvlibDeprecationWarning, + match="model='pvwatts' is now called model='pvwattsv5'"): + _ = ModelChain.with_pvwatts(pvwatts_dc_pvwatts_ac_system, location, + ac_model='pvwatts') + + with pytest.warns(pvlibDeprecationWarning, + match="model='pvwatts' is now called model='pvwattsv5'"): + _ = ModelChain.with_pvwatts(pvwatts_dc_pvwatts_ac_system, location, + losses_model='pvwatts') diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index 7fa013d0dc..1bd723edd8 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -1521,26 +1521,38 @@ def test_PVSystem_get_ac_sandia_multi(cec_inverter_parameters, mocker): def test_PVSystem_get_ac_pvwatts(pvwatts_system_defaults, mocker): - mocker.spy(inverter, 'pvwatts') + mocker.spy(inverter, 'pvwattsv5') pdc = 50 - out = pvwatts_system_defaults.get_ac('pvwatts', pdc) - inverter.pvwatts.assert_called_once_with( + out = pvwatts_system_defaults.get_ac('pvwattsv5', pdc) + inverter.pvwattsv5.assert_called_once_with( pdc, **pvwatts_system_defaults.inverter_parameters) assert out < pdc def test_PVSystem_get_ac_pvwatts_kwargs(pvwatts_system_kwargs, mocker): - mocker.spy(inverter, 'pvwatts') + mocker.spy(inverter, 'pvwattsv5') pdc = 50 - out = pvwatts_system_kwargs.get_ac('pvwatts', pdc) - inverter.pvwatts.assert_called_once_with( + out = pvwatts_system_kwargs.get_ac('pvwattsv5', pdc) + inverter.pvwattsv5.assert_called_once_with( pdc, **pvwatts_system_kwargs.inverter_parameters) assert out < pdc +@fail_on_pvlib_version('0.11') +def test_PVSystem_get_ac_pvwatts_deprecated(pvwatts_system_defaults, mocker): + mocker.spy(inverter, 'pvwattsv5') + pdc = 50 + with pytest.warns(pvlibDeprecationWarning, + match="model='pvwatts' is now called model='pvwattsv5'"): + out = pvwatts_system_defaults.get_ac('pvwatts', pdc) + inverter.pvwattsv5.assert_called_once_with( + pdc, **pvwatts_system_defaults.inverter_parameters) + assert out < pdc + + def test_PVSystem_get_ac_pvwatts_multi( pvwatts_system_defaults, pvwatts_system_kwargs, mocker): - mocker.spy(inverter, 'pvwatts_multi') + mocker.spy(inverter, 'pvwattsv5_multi') expected = [pd.Series([0.0, 48.123524, 86.400000]), pd.Series([0.0, 45.893550, 85.500000])] systems = [pvwatts_system_defaults, pvwatts_system_kwargs] @@ -1551,21 +1563,42 @@ def test_PVSystem_get_ac_pvwatts_multi( inverter_parameters=base_sys.inverter_parameters, ) pdcs = pd.Series([0., 25., 50.]) - pacs = system.get_ac('pvwatts', (pdcs, pdcs)) + pacs = system.get_ac('pvwattsv5', (pdcs, pdcs)) assert_series_equal(pacs, exp) - assert inverter.pvwatts_multi.call_count == 2 + assert inverter.pvwattsv5_multi.call_count == 2 with pytest.raises(ValueError, match="Length mismatch for per-array parameter"): - system.get_ac('pvwatts', (pdcs,)) + system.get_ac('pvwattsv5', (pdcs,)) with pytest.raises(ValueError, match="Length mismatch for per-array parameter"): - system.get_ac('pvwatts', pdcs) + system.get_ac('pvwattsv5', pdcs) with pytest.raises(ValueError, match="Length mismatch for per-array parameter"): - system.get_ac('pvwatts', (pdcs, pdcs, pdcs)) + system.get_ac('pvwattsv5', (pdcs, pdcs, pdcs)) + + +@fail_on_pvlib_version('0.11') +def test_PVSystem_get_ac_pvwatts_multi_deprecated( + pvwatts_system_defaults, pvwatts_system_kwargs, mocker): + mocker.spy(inverter, 'pvwatts_multi') + expected = [pd.Series([0.0, 48.123524, 86.400000]), + pd.Series([0.0, 45.893550, 85.500000])] + systems = [pvwatts_system_defaults, pvwatts_system_kwargs] + for base_sys, exp in zip(systems, expected): + system = pvsystem.PVSystem( + arrays=[pvsystem.Array(pvsystem.FixedMount(0, 180)), + pvsystem.Array(pvsystem.FixedMount(0, 180),)], + inverter_parameters=base_sys.inverter_parameters, + ) + pdcs = pd.Series([0., 25., 50.]) + match = "model='pvwatts' is now called model='pvwattsv5'" + with pytest.warns(pvlibDeprecationWarning, match=match): + pacs = system.get_ac('pvwatts', (pdcs, pdcs)) + assert_series_equal(pacs, exp) + assert inverter.pvwatts_multi.call_count == 0 -@pytest.mark.parametrize('model', ['sandia', 'adr', 'pvwatts']) +@pytest.mark.parametrize('model', ['sandia', 'adr', 'pvwattsv5']) def test_PVSystem_get_ac_single_array_tuple_input( model, pvwatts_system_defaults, @@ -1573,16 +1606,16 @@ def test_PVSystem_get_ac_single_array_tuple_input( adr_inverter_parameters): vdcs = { 'sandia': pd.Series(np.linspace(0, 50, 3)), - 'pvwatts': None, + 'pvwattsv5': None, 'adr': pd.Series([135, 154, 390, 420, 551]) } pdcs = {'adr': pd.Series([135, 1232, 1170, 420, 551]), 'sandia': pd.Series(np.linspace(0, 11, 3)) * vdcs['sandia'], - 'pvwatts': 50} + 'pvwattsv5': 50} inverter_parameters = { 'sandia': cec_inverter_parameters, 'adr': adr_inverter_parameters, - 'pvwatts': pvwatts_system_defaults.inverter_parameters + 'pvwattsv5': pvwatts_system_defaults.inverter_parameters } expected = { 'adr': pd.Series([np.nan, 1161.5745, 1116.4459, 382.6679, np.nan]), @@ -1593,8 +1626,8 @@ def test_PVSystem_get_ac_single_array_tuple_input( inverter_parameters=inverter_parameters[model] ) ac = system.get_ac(p_dc=(pdcs[model],), v_dc=(vdcs[model],), model=model) - if model == 'pvwatts': - assert ac < pdcs['pvwatts'] + if model == 'pvwattsv5': + assert ac < pdcs['pvwattsv5'] else: assert_series_equal(ac, expected[model]) @@ -1981,51 +2014,71 @@ def test_Array___repr__(): assert array.__repr__() == expected -def test_pvwatts_dc_scalars(): +def test_pvwattsv5_dc_scalars(): expected = 88.65 - out = pvsystem.pvwatts_dc(900, 30, 100, -0.003) + out = pvsystem.pvwattsv5_dc(900, 30, 100, -0.003) assert_allclose(out, expected) -def test_pvwatts_dc_arrays(): +def test_pvwattsv5_dc_arrays(): irrad_trans = np.array([np.nan, 900, 900]) temp_cell = np.array([30, np.nan, 30]) irrad_trans, temp_cell = np.meshgrid(irrad_trans, temp_cell) expected = np.array([[nan, 88.65, 88.65], [nan, nan, nan], [nan, 88.65, 88.65]]) - out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003) + out = pvsystem.pvwattsv5_dc(irrad_trans, temp_cell, 100, -0.003) assert_allclose(out, expected, equal_nan=True) -def test_pvwatts_dc_series(): +def test_pvwattsv5_dc_series(): + irrad_trans = pd.Series([np.nan, 900, 900]) + temp_cell = pd.Series([30, np.nan, 30]) + expected = pd.Series(np.array([ nan, nan, 88.65])) + out = pvsystem.pvwattsv5_dc(irrad_trans, temp_cell, 100, -0.003) + assert_series_equal(expected, out) + + +@fail_on_pvlib_version('0.11.0') +def test_pvwatts_dc_deprecated(): irrad_trans = pd.Series([np.nan, 900, 900]) temp_cell = pd.Series([30, np.nan, 30]) expected = pd.Series(np.array([ nan, nan, 88.65])) - out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003) + with pytest.warns(pvlibDeprecationWarning, + match='Use pvwattsv5_dc instead'): + out = pvsystem.pvwatts_dc(irrad_trans, temp_cell, 100, -0.003) assert_series_equal(expected, out) -def test_pvwatts_losses_default(): +def test_pvwattsv5_losses_default(): expected = 14.075660688264469 - out = pvsystem.pvwatts_losses() + out = pvsystem.pvwattsv5_losses() assert_allclose(out, expected) -def test_pvwatts_losses_arrays(): +def test_pvwattsv5_losses_arrays(): expected = np.array([nan, 14.934904]) age = np.array([nan, 1]) - out = pvsystem.pvwatts_losses(age=age) + out = pvsystem.pvwattsv5_losses(age=age) assert_allclose(out, expected) -def test_pvwatts_losses_series(): +def test_pvwattsv5_losses_series(): expected = pd.Series([nan, 14.934904]) age = pd.Series([nan, 1]) - out = pvsystem.pvwatts_losses(age=age) + out = pvsystem.pvwattsv5_losses(age=age) assert_series_equal(expected, out) +@fail_on_pvlib_version('0.11.0') +def test_pvwatts_losses_deprecated(): + expected = 14.075660688264469 + with pytest.warns(pvlibDeprecationWarning, + match='Use pvwattsv5_losses instead'): + out = pvsystem.pvwatts_losses() + assert_allclose(out, expected) + + @pytest.fixture def pvwatts_system_defaults(): module_parameters = {'pdc0': 100, 'gamma_pdc': -0.003} @@ -2044,30 +2097,43 @@ def pvwatts_system_kwargs(): return system -def test_PVSystem_pvwatts_dc(pvwatts_system_defaults, mocker): - mocker.spy(pvsystem, 'pvwatts_dc') +def test_PVSystem_pvwatts_dcv5(pvwatts_system_defaults, mocker): + mocker.spy(pvsystem, 'pvwattsv5_dc') irrad = 900 temp_cell = 30 expected = 90 - out = pvwatts_system_defaults.pvwatts_dc(irrad, temp_cell) - pvsystem.pvwatts_dc.assert_called_once_with( + out = pvwatts_system_defaults.pvwattsv5_dc(irrad, temp_cell) + pvsystem.pvwattsv5_dc.assert_called_once_with( irrad, temp_cell, **pvwatts_system_defaults.arrays[0].module_parameters) assert_allclose(expected, out, atol=10) -def test_PVSystem_pvwatts_dc_kwargs(pvwatts_system_kwargs, mocker): +@fail_on_pvlib_version('0.11.0') +def test_PVSystem_pvwatts_dc_deprecated(pvwatts_system_defaults, mocker): mocker.spy(pvsystem, 'pvwatts_dc') irrad = 900 temp_cell = 30 expected = 90 - out = pvwatts_system_kwargs.pvwatts_dc(irrad, temp_cell) - pvsystem.pvwatts_dc.assert_called_once_with( + match = "Use PVSystem.pvwattsv5_dc instead" + with pytest.warns(pvlibDeprecationWarning, match=match): + out = pvwatts_system_defaults.pvwatts_dc(irrad, temp_cell) + assert_allclose(expected, out, atol=10) + assert pvsystem.pvwatts_dc.call_count == 0 + + +def test_PVSystem_pvwattsv5_dc_kwargs(pvwatts_system_kwargs, mocker): + mocker.spy(pvsystem, 'pvwattsv5_dc') + irrad = 900 + temp_cell = 30 + expected = 90 + out = pvwatts_system_kwargs.pvwattsv5_dc(irrad, temp_cell) + pvsystem.pvwattsv5_dc.assert_called_once_with( irrad, temp_cell, **pvwatts_system_kwargs.arrays[0].module_parameters) assert_allclose(expected, out, atol=10) -def test_PVSystem_multiple_array_pvwatts_dc(): +def test_PVSystem_multiple_array_pvwattsv5_dc(): array_one_module_parameters = { 'pdc0': 100, 'gamma_pdc': -0.003, 'temp_ref': 20 } @@ -2087,17 +2153,17 @@ def test_PVSystem_multiple_array_pvwatts_dc(): irrad_two = 500 temp_cell_one = 30 temp_cell_two = 20 - expected_one = pvsystem.pvwatts_dc(irrad_one, temp_cell_one, - **array_one_module_parameters) - expected_two = pvsystem.pvwatts_dc(irrad_two, temp_cell_two, - **array_two_module_parameters) - dc_one, dc_two = system.pvwatts_dc((irrad_one, irrad_two), - (temp_cell_one, temp_cell_two)) + expected_one = pvsystem.pvwattsv5_dc(irrad_one, temp_cell_one, + **array_one_module_parameters) + expected_two = pvsystem.pvwattsv5_dc(irrad_two, temp_cell_two, + **array_two_module_parameters) + dc_one, dc_two = system.pvwattsv5_dc((irrad_one, irrad_two), + (temp_cell_one, temp_cell_two)) assert dc_one == expected_one assert dc_two == expected_two -def test_PVSystem_multiple_array_pvwatts_dc_value_error(): +def test_PVSystem_multiple_array_pvwattsv5_dc_value_error(): system = pvsystem.PVSystem( arrays=[pvsystem.Array(pvsystem.FixedMount(0, 180)), pvsystem.Array(pvsystem.FixedMount(0, 180)), @@ -2105,54 +2171,67 @@ def test_PVSystem_multiple_array_pvwatts_dc_value_error(): ) error_message = 'Length mismatch for per-array parameter' with pytest.raises(ValueError, match=error_message): - system.pvwatts_dc(10, (1, 1, 1)) + system.pvwattsv5_dc(10, (1, 1, 1)) with pytest.raises(ValueError, match=error_message): - system.pvwatts_dc((10, 10), (1, 1, 1)) + system.pvwattsv5_dc((10, 10), (1, 1, 1)) with pytest.raises(ValueError, match=error_message): - system.pvwatts_dc((10, 10, 10, 10), (1, 1, 1)) + system.pvwattsv5_dc((10, 10, 10, 10), (1, 1, 1)) with pytest.raises(ValueError, match=error_message): - system.pvwatts_dc((1, 1, 1), 1) + system.pvwattsv5_dc((1, 1, 1), 1) with pytest.raises(ValueError, match=error_message): - system.pvwatts_dc((1, 1, 1), (1,)) + system.pvwattsv5_dc((1, 1, 1), (1,)) with pytest.raises(ValueError, match=error_message): - system.pvwatts_dc((1,), 1) + system.pvwattsv5_dc((1,), 1) with pytest.raises(ValueError, match=error_message): - system.pvwatts_dc((1, 1, 1, 1), (1, 1)) + system.pvwattsv5_dc((1, 1, 1, 1), (1, 1)) with pytest.raises(ValueError, match=error_message): - system.pvwatts_dc(2, 3) + system.pvwattsv5_dc(2, 3) with pytest.raises(ValueError, match=error_message): # ValueError is raised for non-tuple iterable with correct length - system.pvwatts_dc((1, 1, 1), pd.Series([1, 2, 3])) + system.pvwattsv5_dc((1, 1, 1), pd.Series([1, 2, 3])) + + +def test_PVSystem_pvwattsv5_losses(pvwatts_system_defaults, mocker): + mocker.spy(pvsystem, 'pvwattsv5_losses') + age = 1 + pvwatts_system_defaults.losses_parameters = dict(age=age) + expected = 15 + out = pvwatts_system_defaults.pvwattsv5_losses() + pvsystem.pvwattsv5_losses.assert_called_once_with(age=age) + assert out < expected -def test_PVSystem_pvwatts_losses(pvwatts_system_defaults, mocker): +@fail_on_pvlib_version('0.11.0') +def test_PVSystem_pvwatts_losses_deprecated(pvwatts_system_defaults, mocker): mocker.spy(pvsystem, 'pvwatts_losses') age = 1 pvwatts_system_defaults.losses_parameters = dict(age=age) expected = 15 - out = pvwatts_system_defaults.pvwatts_losses() - pvsystem.pvwatts_losses.assert_called_once_with(age=age) + with pytest.warns(pvlibDeprecationWarning, + match='Use PVSystem.pvwattsv5_losses instead'): + out = pvwatts_system_defaults.pvwatts_losses() + pvsystem.pvwatts_losses.call_count == 0 assert out < expected @fail_on_pvlib_version('0.10') def test_PVSystem_pvwatts_ac(pvwatts_system_defaults, mocker): - mocker.spy(inverter, 'pvwatts') + mocker.spy(inverter, 'pvwattsv5') pdc = 50 with pytest.warns(pvlibDeprecationWarning): out = pvwatts_system_defaults.pvwatts_ac(pdc) - inverter.pvwatts.assert_called_once_with( + inverter.pvwattsv5.assert_called_once_with( pdc, **pvwatts_system_defaults.inverter_parameters) assert out < pdc @fail_on_pvlib_version('0.10') def test_PVSystem_pvwatts_ac_kwargs(pvwatts_system_kwargs, mocker): - mocker.spy(inverter, 'pvwatts') + mocker.spy(inverter, 'pvwattsv5') pdc = 50 with pytest.warns(pvlibDeprecationWarning): out = pvwatts_system_kwargs.pvwatts_ac(pdc) - inverter.pvwatts.assert_called_once_with( + inverter.pvwattsv5.assert_called_once_with( pdc, **pvwatts_system_kwargs.inverter_parameters) assert out < pdc