diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index 8805d199a4..e88cb66dde 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -609,7 +609,6 @@ ModelChain properties that are aliases for your specific modeling functions. .. autosummary:: :toctree: generated/ - modelchain.ModelChain.orientation_strategy modelchain.ModelChain.dc_model modelchain.ModelChain.ac_model modelchain.ModelChain.aoi_model diff --git a/docs/sphinx/source/introtutorial.rst b/docs/sphinx/source/introtutorial.rst index c45260a5b4..495b0c6e40 100644 --- a/docs/sphinx/source/introtutorial.rst +++ b/docs/sphinx/source/introtutorial.rst @@ -190,12 +190,6 @@ by examining the parameters defined for the module. from pvlib.location import Location from pvlib.modelchain import ModelChain - system = PVSystem( - module_parameters=module, - inverter_parameters=inverter, - temperature_model_parameters=temperature_model_parameters, - ) - energies = {} for location, weather in zip(coordinates, tmys): latitude, longitude, name, altitude, timezone = location @@ -206,11 +200,15 @@ by examining the parameters defined for the module. altitude=altitude, tz=timezone, ) - mc = ModelChain( - system, - location, - orientation_strategy='south_at_latitude_tilt', + system = PVSystem( + surface_tilt=latitude, + surface_azimuth=180, + module_parameters=module, + inverter_parameters=inverter, + temperature_model_parameters=temperature_model_parameters, ) + + mc = ModelChain(system, location) results = mc.run_model(weather) annual_energy = results.ac.sum() energies[name] = annual_energy diff --git a/docs/sphinx/source/whatsnew/v0.9.0.rst b/docs/sphinx/source/whatsnew/v0.9.0.rst index 81e7a0c60b..22c1a3ba3d 100644 --- a/docs/sphinx/source/whatsnew/v0.9.0.rst +++ b/docs/sphinx/source/whatsnew/v0.9.0.rst @@ -40,6 +40,11 @@ Breaking changes ``ModelChain.adrinverter`` changed to ``ModelChain.adr_inverter``. (:pull:`1150`) +* The ``orientation_strategy`` parameter has been removed from the various + :py:class:`pvlib.modelchain.ModelChain` constructors and ``surface_tilt``, + ``surface_azimuth`` are now required parameters for + :py:func:`pvlib.modelchain.basic_chain` (:issue:`1028`, :pull:`1181`) + Deprecations ~~~~~~~~~~~~ diff --git a/docs/tutorials/forecast.ipynb b/docs/tutorials/forecast.ipynb index 58c3cfbcf5..be40e3dd42 100644 --- a/docs/tutorials/forecast.ipynb +++ b/docs/tutorials/forecast.ipynb @@ -6664,7 +6664,6 @@ "text/plain": [ "ModelChain: \n", " name: None\n", - " orientation_strategy: south_at_latitude_tilt\n", " clearsky_model: ineichen\n", " transposition_model: haydavies\n", " solar_position_method: nrel_numpy\n", @@ -6692,15 +6691,16 @@ "inverter = sapm_inverters['ABB__MICRO_0_25_I_OUTD_US_208__208V_']\n", "\n", "system = PVSystem(module_parameters=module,\n", - " inverter_parameters=inverter)\n", + " inverter_parameters=inverter,\n", + " surface_tilt=latitude,\n", + " surface_azimuth=180)\n", "\n", "# fx is a common abbreviation for forecast\n", "fx_model = GFS()\n", "fx_data = fx_model.get_processed_data(latitude, longitude, start, end)\n", "\n", "# use a ModelChain object to calculate modeling intermediates\n", - "mc = ModelChain(system, fx_model.location,\n", - " orientation_strategy='south_at_latitude_tilt')\n", + "mc = ModelChain(system, fx_model.location)\n", "\n", "# extract relevant data for model chain\n", "mc.run_model(weather=fx_data)" diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 1c07857745..885353a230 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -63,11 +63,10 @@ def basic_chain(times, latitude, longitude, + surface_tilt, surface_azimuth, module_parameters, temperature_model_parameters, inverter_parameters, irradiance=None, weather=None, - surface_tilt=None, surface_azimuth=None, - orientation_strategy=None, transposition_model='haydavies', solar_position_method='nrel_numpy', airmass_model='kastenyoung1989', @@ -91,6 +90,17 @@ def basic_chain(times, latitude, longitude, Positive is east of the prime meridian. Use decimal degrees notation. + surface_tilt : numeric + 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 : numeric + Surface azimuth angles in decimal degrees. + The azimuth convention is defined + as degrees east of north + (North=0, South=180, East=90, West=270). + module_parameters : None, dict or Series Module parameters as defined by the SAPM. See pvsystem.sapm for details. @@ -112,23 +122,6 @@ def basic_chain(times, latitude, longitude, wind speed is 0 m/s. Columns must be 'wind_speed', 'temp_air'. - surface_tilt : None, float or Series, default None - 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 : None, float or Series, default None - Surface azimuth angles in decimal degrees. - The azimuth convention is defined - as degrees east of north - (North=0, South=180, East=90, West=270). - - orientation_strategy : None or str, default None - The strategy for aligning the modules. - If not None, sets the ``surface_azimuth`` and ``surface_tilt`` - properties of the ``system``. Allowed strategies include 'flat', - 'south_at_latitude_tilt'. Ignored for SingleAxisTracker systems. - transposition_model : str, default 'haydavies' Passed to system.get_irradiance. @@ -157,17 +150,6 @@ def basic_chain(times, latitude, longitude, power (Series). """ - # use surface_tilt and surface_azimuth if provided, - # otherwise set them using the orientation_strategy - if surface_tilt is not None and surface_azimuth is not None: - pass - elif orientation_strategy is not None: - surface_tilt, surface_azimuth = \ - get_orientation(orientation_strategy, latitude=latitude) - else: - raise ValueError('orientation_strategy or surface_tilt and ' - 'surface_azimuth must be provided') - if altitude is None and pressure is None: altitude = 0. pressure = 101325. @@ -332,12 +314,6 @@ class ModelChain: A :py:class:`~pvlib.location.Location` object that represents the physical location at which to evaluate the model. - orientation_strategy : None or str, default None - The strategy for aligning the modules. If not None, sets the - ``surface_azimuth`` and ``surface_tilt`` properties of the - ``system``. Allowed strategies include 'flat', - 'south_at_latitude_tilt'. Ignored for SingleAxisTracker systems. - clearsky_model : str, default 'ineichen' Passed to location.get_clearsky. @@ -395,7 +371,6 @@ class ModelChain: 'dc', 'ac', 'diode_params', 'tracking'] def __init__(self, system, location, - orientation_strategy=None, clearsky_model='ineichen', transposition_model='haydavies', solar_position_method='nrel_numpy', @@ -421,7 +396,6 @@ def __init__(self, system, location, self.temperature_model = temperature_model self.losses_model = losses_model - self.orientation_strategy = orientation_strategy self.weather = None self.times = None @@ -451,7 +425,6 @@ def __setattr__(self, key, value): @classmethod def with_pvwatts(cls, system, location, - orientation_strategy=None, clearsky_model='ineichen', airmass_model='kastenyoung1989', name=None, @@ -469,12 +442,6 @@ def with_pvwatts(cls, system, location, A :py:class:`~pvlib.location.Location` object that represents the physical location at which to evaluate the model. - orientation_strategy : None or str, default None - The strategy for aligning the modules. If not None, sets the - ``surface_azimuth`` and ``surface_tilt`` properties of the - ``system``. Allowed strategies include 'flat', - 'south_at_latitude_tilt'. Ignored for SingleAxisTracker systems. - clearsky_model : str, default 'ineichen' Passed to location.get_clearsky. @@ -502,7 +469,6 @@ def with_pvwatts(cls, system, location, >>> ModelChain.with_pvwatts(system, location) ModelChain: name: None - orientation_strategy: None clearsky_model: ineichen transposition_model: perez solar_position_method: nrel_numpy @@ -518,7 +484,6 @@ def with_pvwatts(cls, system, location, config.update(kwargs) return ModelChain( system, location, - orientation_strategy=orientation_strategy, clearsky_model=clearsky_model, airmass_model=airmass_model, name=name, @@ -527,7 +492,6 @@ def with_pvwatts(cls, system, location, @classmethod def with_sapm(cls, system, location, - orientation_strategy=None, clearsky_model='ineichen', transposition_model='haydavies', solar_position_method='nrel_numpy', @@ -548,12 +512,6 @@ def with_sapm(cls, system, location, A :py:class:`~pvlib.location.Location` object that represents the physical location at which to evaluate the model. - orientation_strategy : None or str, default None - The strategy for aligning the modules. If not None, sets the - ``surface_azimuth`` and ``surface_tilt`` properties of the - ``system``. Allowed strategies include 'flat', - 'south_at_latitude_tilt'. Ignored for SingleAxisTracker systems. - clearsky_model : str, default 'ineichen' Passed to location.get_clearsky. @@ -589,7 +547,6 @@ def with_sapm(cls, system, location, >>> ModelChain.with_sapm(system, location) ModelChain: name: None - orientation_strategy: None clearsky_model: ineichen transposition_model: haydavies solar_position_method: nrel_numpy @@ -605,7 +562,6 @@ def with_sapm(cls, system, location, config.update(kwargs) return ModelChain( system, location, - orientation_strategy=orientation_strategy, clearsky_model=clearsky_model, transposition_model=transposition_model, solar_position_method=solar_position_method, @@ -616,7 +572,7 @@ def with_sapm(cls, system, location, def __repr__(self): attrs = [ - 'name', 'orientation_strategy', 'clearsky_model', + 'name', 'clearsky_model', 'transposition_model', 'solar_position_method', 'airmass_model', 'dc_model', 'ac_model', 'aoi_model', 'spectral_model', 'temperature_model', 'losses_model' @@ -634,21 +590,6 @@ def getmcattr(self, attr): return ('ModelChain: \n ' + '\n '.join( f'{attr}: {getmcattr(self, attr)}' for attr in attrs)) - @property - def orientation_strategy(self): - return self._orientation_strategy - - @orientation_strategy.setter - def orientation_strategy(self, strategy): - if strategy == 'None': - strategy = None - - if strategy is not None: - self.system.surface_tilt, self.system.surface_azimuth = \ - get_orientation(strategy, latitude=self.location.latitude) - - self._orientation_strategy = strategy - @property def dc_model(self): return self._dc_model diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index c45fd5026f..7b96b409d7 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -333,22 +333,6 @@ def test_with_pvwatts(pvwatts_dc_pvwatts_ac_system, location, weather): mc.run_model(weather) -@pytest.mark.parametrize('strategy, expected', [ - (None, (32.2, 180)), ('None', (32.2, 180)), ('flat', (0, 180)), - ('south_at_latitude_tilt', (32.2, 180)) -]) -def test_orientation_strategy(strategy, expected, sapm_dc_snl_ac_system, - location): - mc = ModelChain(sapm_dc_snl_ac_system, location, - orientation_strategy=strategy) - - # the || accounts for the coercion of 'None' to None - assert (mc.orientation_strategy == strategy or - mc.orientation_strategy is None) - assert sapm_dc_snl_ac_system.surface_tilt == expected[0] - assert sapm_dc_snl_ac_system.surface_azimuth == expected[1] - - 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') @@ -1235,8 +1219,7 @@ def test_infer_spectral_model(location, sapm_dc_snl_ac_system, 'cec': cec_dc_snl_ac_system, 'cec_native': cec_dc_native_snl_ac_system} system = dc_systems[dc_model] - mc = ModelChain(system, location, - orientation_strategy='None', aoi_model='physical') + mc = ModelChain(system, location, aoi_model='physical') assert isinstance(mc, ModelChain) @@ -1252,8 +1235,7 @@ def test_infer_temp_model(location, sapm_dc_snl_ac_system, 'faiman_temp': pvwatts_dc_pvwatts_ac_faiman_temp_system, 'fuentes_temp': pvwatts_dc_pvwatts_ac_fuentes_temp_system} system = dc_systems[temp_model] - mc = ModelChain(system, location, - orientation_strategy='None', aoi_model='physical', + mc = ModelChain(system, location, aoi_model='physical', spectral_model='no_loss') assert temp_model == mc.temperature_model.__name__ assert isinstance(mc, ModelChain) @@ -1263,14 +1245,12 @@ def test_infer_temp_model_invalid(location, sapm_dc_snl_ac_system): sapm_dc_snl_ac_system.temperature_model_parameters.pop('a') with pytest.raises(ValueError): ModelChain(sapm_dc_snl_ac_system, location, - orientation_strategy='None', aoi_model='physical', - spectral_model='no_loss') + aoi_model='physical', spectral_model='no_loss') def test_temperature_model_inconsistent(location, sapm_dc_snl_ac_system): with pytest.raises(ValueError): - ModelChain(sapm_dc_snl_ac_system, location, - orientation_strategy='None', aoi_model='physical', + ModelChain(sapm_dc_snl_ac_system, location, aoi_model='physical', spectral_model='no_loss', temperature_model='pvsyst') @@ -1441,17 +1421,14 @@ def test_aoi_model_user_func(sapm_dc_snl_ac_system, location, weather, mocker): def test_infer_aoi_model(location, system_no_aoi, aoi_model): for k in iam._IAM_MODEL_PARAMS[aoi_model]: system_no_aoi.module_parameters.update({k: 1.0}) - mc = ModelChain(system_no_aoi, location, - orientation_strategy='None', - spectral_model='no_loss') + mc = ModelChain(system_no_aoi, location, spectral_model='no_loss') assert isinstance(mc, ModelChain) def test_infer_aoi_model_invalid(location, system_no_aoi): exc_text = 'could not infer AOI model' with pytest.raises(ValueError, match=exc_text): - ModelChain(system_no_aoi, location, orientation_strategy='None', - spectral_model='no_loss') + ModelChain(system_no_aoi, location, spectral_model='no_loss') def constant_spectral_loss(mc): @@ -1623,23 +1600,6 @@ def test_ModelChain_attributes_deprecated_10(sapm_dc_snl_ac_system, location): mc.aoi = 5 -def test_basic_chain_required(sam_data, cec_inverter_parameters, - sapm_temperature_cs5p_220m): - times = pd.date_range(start='20160101 1200-0700', - end='20160101 1800-0700', freq='6H') - latitude = 32 - longitude = -111 - altitude = 700 - modules = sam_data['sandiamod'] - module_parameters = modules['Canadian_Solar_CS5P_220M___2009_'] - temp_model_params = sapm_temperature_cs5p_220m.copy() - with pytest.raises(ValueError): - dc, ac = modelchain.basic_chain( - times, latitude, longitude, module_parameters, temp_model_params, - cec_inverter_parameters, altitude=altitude - ) - - @requires_tables def test_basic_chain_alt_az(sam_data, cec_inverter_parameters, sapm_temperature_cs5p_220m): @@ -1653,37 +1613,15 @@ def test_basic_chain_alt_az(sam_data, cec_inverter_parameters, module_parameters = modules['Canadian_Solar_CS5P_220M___2009_'] temp_model_params = sapm_temperature_cs5p_220m.copy() dc, ac = modelchain.basic_chain(times, latitude, longitude, - module_parameters, temp_model_params, - cec_inverter_parameters, - surface_tilt=surface_tilt, - surface_azimuth=surface_azimuth) + surface_tilt, surface_azimuth, + module_parameters, temp_model_params, + cec_inverter_parameters) expected = pd.Series(np.array([111.621405, -2.00000000e-02]), index=times) assert_series_equal(ac, expected) -@requires_tables -def test_basic_chain_strategy(sam_data, cec_inverter_parameters, - sapm_temperature_cs5p_220m): - times = pd.date_range(start='20160101 1200-0700', - end='20160101 1800-0700', freq='6H') - latitude = 32.2 - longitude = -111 - altitude = 700 - modules = sam_data['sandiamod'] - module_parameters = modules['Canadian_Solar_CS5P_220M___2009_'] - temp_model_params = sapm_temperature_cs5p_220m.copy() - dc, ac = modelchain.basic_chain( - times, latitude, longitude, module_parameters, temp_model_params, - cec_inverter_parameters, orientation_strategy='south_at_latitude_tilt', - altitude=altitude) - - expected = pd.Series(np.array([178.382754, -2.00000000e-02]), - index=times) - assert_series_equal(ac, expected) - - @requires_tables def test_basic_chain_altitude_pressure(sam_data, cec_inverter_parameters, sapm_temperature_cs5p_220m): @@ -1698,10 +1636,9 @@ def test_basic_chain_altitude_pressure(sam_data, cec_inverter_parameters, module_parameters = modules['Canadian_Solar_CS5P_220M___2009_'] temp_model_params = sapm_temperature_cs5p_220m.copy() dc, ac = modelchain.basic_chain(times, latitude, longitude, + surface_tilt, surface_azimuth, module_parameters, temp_model_params, cec_inverter_parameters, - surface_tilt=surface_tilt, - surface_azimuth=surface_azimuth, pressure=93194) expected = pd.Series(np.array([113.190045, -2.00000000e-02]), @@ -1709,10 +1646,9 @@ def test_basic_chain_altitude_pressure(sam_data, cec_inverter_parameters, assert_series_equal(ac, expected) dc, ac = modelchain.basic_chain(times, latitude, longitude, + surface_tilt, surface_azimuth, module_parameters, temp_model_params, cec_inverter_parameters, - surface_tilt=surface_tilt, - surface_azimuth=surface_azimuth, altitude=altitude) expected = pd.Series(np.array([113.189814, -2.00000000e-02]), @@ -1720,34 +1656,6 @@ def test_basic_chain_altitude_pressure(sam_data, cec_inverter_parameters, assert_series_equal(ac, expected) -@pytest.mark.parametrize('strategy, strategy_str', [ - ('south_at_latitude_tilt', 'south_at_latitude_tilt'), - (None, 'None')]) # GitHub issue 352 -def test_ModelChain___repr__(sapm_dc_snl_ac_system, location, strategy, - strategy_str): - - mc = ModelChain(sapm_dc_snl_ac_system, location, - orientation_strategy=strategy, name='my mc') - - expected = '\n'.join([ - 'ModelChain: ', - ' name: my mc', - ' orientation_strategy: ' + strategy_str, - ' clearsky_model: ineichen', - ' transposition_model: haydavies', - ' solar_position_method: nrel_numpy', - ' airmass_model: kastenyoung1989', - ' dc_model: sapm', - ' ac_model: sandia_inverter', - ' aoi_model: sapm_aoi_loss', - ' spectral_model: sapm_spectral_loss', - ' temperature_model: sapm_temp', - ' losses_model: no_extra_losses' - ]) - - assert mc.__repr__() == expected - - def test_complete_irradiance_clean_run(sapm_dc_snl_ac_system, location): """The DataFrame should not change if all columns are passed""" mc = ModelChain(sapm_dc_snl_ac_system, location)