diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 6a8903be85..1c07857745 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1572,11 +1572,13 @@ def _prepare_temperature(self, data=None): """ poa = _irrad_for_celltemp(self.results.total_irrad, self.results.effective_irradiance) - if not isinstance(data, tuple) and self.system.num_arrays > 1: + # handle simple case first, single array, data not iterable + if not isinstance(data, tuple) and self.system.num_arrays == 1: + return self._prepare_temperature_single_array(data, poa) + if not isinstance(data, tuple): # broadcast data to all arrays data = (data,) * self.system.num_arrays - elif not isinstance(data, tuple): - return self._prepare_temperature_single_array(data, poa) + # find where cell or module temperature is specified in input data given_cell_temperature = tuple(itertools.starmap( self._get_cell_temperature, zip(data, poa, self.system.temperature_model_parameters) @@ -1587,23 +1589,7 @@ def _prepare_temperature(self, data=None): self.results.cell_temperature = given_cell_temperature return self # Calculate cell temperature from weather data. If cell_temperature - # has not been provided for some arrays then it is computed with - # ModelChain.temperature_model(). Because this operates on all Arrays - # simultaneously, 'poa_global' must be known for all arrays, including - # those that have a known cell temperature. - try: - self._verify_df(self.results.total_irrad, ['poa_global']) - except ValueError: - # Provide a more informative error message. Because only - # run_model_from_effective_irradiance() can get to this point - # without known POA we can suggest a very specific remedy in the - # error message. - raise ValueError("Incomplete input data. Data must contain " - "'poa_global'. For systems with multiple Arrays " - "if you have provided 'cell_temperature' for " - "only a subset of Arrays you must provide " - "'poa_global' for all Arrays, including those " - "that have a known 'cell_temperature'.") + # has not been provided for some arrays then it is computed. self.temperature_model() # replace calculated cell temperature with temperature given in `data` # where available. @@ -1814,6 +1800,7 @@ def run_model_from_effective_irradiance(self, data=None): """ data = _to_tuple(data) self._check_multiple_input(data) + self._verify_df(data, required=['effective_irradiance']) self._assign_weather(data) self._assign_total_irrad(data) self.results.effective_irradiance = _tuple_from_dfs( diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 1e627b541b..c45fd5026f 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -985,26 +985,44 @@ def test_run_model_from_poa_tracking(sapm_dc_snl_ac_system, location, assert_series_equal(ac, expected) +@pytest.mark.parametrize("input_type", [lambda x: x[0], tuple, list]) def test_run_model_from_effective_irradiance(sapm_dc_snl_ac_system, location, - weather, total_irrad): + weather, total_irrad, input_type): data = weather.copy() data[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad data['effective_irradiance'] = data['poa_global'] mc = ModelChain(sapm_dc_snl_ac_system, location, aoi_model='no_loss', spectral_model='no_loss') - ac = mc.run_model_from_effective_irradiance(data).results.ac + ac = mc.run_model_from_effective_irradiance(input_type((data,))).results.ac expected = pd.Series(np.array([149.280238, 96.678385]), index=data.index) assert_series_equal(ac, expected) +@pytest.mark.parametrize("input_type", [tuple, list]) +def test_run_model_from_effective_irradiance_multi_array( + sapm_dc_snl_ac_system_Array, location, weather, total_irrad, + input_type): + data = weather.copy() + data[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad + data['effective_irradiance'] = data['poa_global'] + mc = ModelChain(sapm_dc_snl_ac_system_Array, location, aoi_model='no_loss', + spectral_model='no_loss') + mc.run_model_from_effective_irradiance(input_type((data, data))) + # arrays have different orientation, but should give same dc power + # because we are the same passing POA irradiance and air + # temperature. + assert_frame_equal(mc.results.dc[0], mc.results.dc[1]) + + +@pytest.mark.parametrize("input_type", [lambda x: x[0], tuple, list]) def test_run_model_from_effective_irradiance_no_poa_global( - sapm_dc_snl_ac_system, location, weather, total_irrad): + sapm_dc_snl_ac_system, location, weather, total_irrad, input_type): data = weather.copy() data['effective_irradiance'] = total_irrad['poa_global'] mc = ModelChain(sapm_dc_snl_ac_system, location, aoi_model='no_loss', spectral_model='no_loss') - ac = mc.run_model_from_effective_irradiance(data).results.ac + ac = mc.run_model_from_effective_irradiance(input_type((data,))).results.ac expected = pd.Series(np.array([149.280238, 96.678385]), index=data.index) assert_series_equal(ac, expected) @@ -1087,23 +1105,6 @@ def test_run_model_from_effective_irradiance_minimal_input( assert not mc.results.ac.empty -def test_run_model_from_effective_irradiance_missing_poa( - sapm_dc_snl_ac_system_Array, location, total_irrad): - data_incomplete = pd.DataFrame( - {'effective_irradiance': total_irrad['poa_global'], - 'poa_global': total_irrad['poa_global']}, - index=total_irrad.index) - data_complete = pd.DataFrame( - {'effective_irradiance': total_irrad['poa_global'], - 'cell_temperature': 30}, - index=total_irrad.index) - mc = ModelChain(sapm_dc_snl_ac_system_Array, location) - with pytest.raises(ValueError, - match="you must provide 'poa_global' for all Arrays"): - mc.run_model_from_effective_irradiance( - (data_complete, data_incomplete)) - - def test_run_model_singleton_weather_single_array(cec_dc_snl_ac_system, location, weather): mc = ModelChain(cec_dc_snl_ac_system, location,