From 70ef8eef7b4bf5e57468713f6aa01e2bd79fe47d Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 27 Mar 2020 14:20:55 -0600 Subject: [PATCH 01/23] add _from_poa, _from_effective_irradiance methods --- pvlib/modelchain.py | 224 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 194 insertions(+), 30 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index dea4cc0ac3..925ca988cd 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -778,8 +778,8 @@ def complete_irradiance(self, weather, times=None): Examples -------- This example does not work until the parameters `my_system`, - `my_location`, `my_datetime` and `my_weather` are not defined - properly but shows the basic idea how this method can be used. + `my_location`, and `my_weather` are defined but shows the basic idea + how this method can be used. >>> from pvlib.modelchain import ModelChain @@ -829,18 +829,59 @@ def complete_irradiance(self, weather, times=None): return self + def _prep_inputs_solar_pos(self): + """ + Assign solar position + """ + self.solar_position = self.location.get_solarposition( + self.weather.index, method=self.solar_position_method) + return self + + def _prep_inputs_airmass(self): + """ + Assign airmass + """ + self.airmass = self.location.get_airmass( + solar_position=self.solar_position, model=self.airmass_model) + return self + + def _prep_inputs_tracking(self): + """ + Calculate tracker position and AOI + """ + self.tracking = self.system.singleaxis( + self.solar_position['apparent_zenith'], + self.solar_position['azimuth']) + self.tracking['surface_tilt'] = ( + self.tracking['surface_tilt'] + .fillna(self.system.axis_tilt)) + self.tracking['surface_azimuth'] = ( + self.tracking['surface_azimuth'] + .fillna(self.system.axis_azimuth)) + self.aoi = self.tracking['aoi'] + return self + + def _prep_inputs_fixed(self): + """ + Calculate AOI for fixed tilt system + """ + self.aoi = self.system.get_aoi( + self.solar_position['apparent_zenith'], + self.solar_position['azimuth']) + return self + def prepare_inputs(self, weather, times=None): """ Prepare the solar position, irradiance, and weather inputs to - the model. + the model, starting with GHI, DNI and DHI. Parameters ---------- weather : DataFrame - Column names must be ``'dni'``, ``'ghi'``, ``'dhi'``, - ``'wind_speed'``, ``'temp_air'``. All irradiance components - are required. Air temperature of 20 C and wind speed - of 0 m/s will be added to the DataFrame if not provided. + Irradiance column names must include ``'dni'``, ``'ghi'``, and + ``'dhi'``. Optional column names include ``'temp_air'`` and + ``'wind_speed'``; if not provided, air temperature of 20 C and wind + speed of 0 m/s are added to the DataFrame. times : None, deprecated Deprecated argument included for API compatibility, but not used internally. The index of the weather DataFrame is used @@ -871,27 +912,15 @@ def prepare_inputs(self, weather, times=None): self.times = self.weather.index - self.solar_position = self.location.get_solarposition( - self.weather.index, method=self.solar_position_method) - - self.airmass = self.location.get_airmass( - solar_position=self.solar_position, model=self.airmass_model) + self._prep_inputs_solar_pos() + self._prep_inputs_airmass() # PVSystem.get_irradiance and SingleAxisTracker.get_irradiance # and PVSystem.get_aoi and SingleAxisTracker.get_aoi # have different method signatures. Use partial to handle # the differences. if isinstance(self.system, SingleAxisTracker): - self.tracking = self.system.singleaxis( - self.solar_position['apparent_zenith'], - self.solar_position['azimuth']) - self.tracking['surface_tilt'] = ( - self.tracking['surface_tilt'] - .fillna(self.system.axis_tilt)) - self.tracking['surface_azimuth'] = ( - self.tracking['surface_azimuth'] - .fillna(self.system.axis_azimuth)) - self.aoi = self.tracking['aoi'] + self._prep_inputs_tracking() get_irradiance = partial( self.system.get_irradiance, self.tracking['surface_tilt'], @@ -899,9 +928,7 @@ def prepare_inputs(self, weather, times=None): self.solar_position['apparent_zenith'], self.solar_position['azimuth']) else: - self.aoi = self.system.get_aoi( - self.solar_position['apparent_zenith'], - self.solar_position['azimuth']) + self._prep_inputs_fixed() get_irradiance = partial( self.system.get_irradiance, self.solar_position['apparent_zenith'], @@ -920,17 +947,67 @@ def prepare_inputs(self, weather, times=None): self.weather['temp_air'] = 20 return self + def prepare_inputs_from_poa(self, weather): + """ + Prepare the solar position, irradiance, and weather inputs to + the model, starting with plane-of-array irradiance. + + Parameters + ---------- + weather : DataFrame + Irradiance column names must include ``'poa_global'``, + ``'poa_direct'`` and ``'poa_diffuse'``. Optional column names + include ``'temp_air'`` and ``'wind_speed'``; if not provided, + air temperature of 20 C and wind speed of 0 m/s are added to the + DataFrame. + + Notes + ----- + Assigns attributes: ``solar_position``, ``airmass``, ``aoi`` + + See also + -------- + pvlib.modelchain.ModelChain.prepare_inputs + """ + + if not {'poa_global', 'poa_direct', 'poa_diffuse'} <= set(weather.columns): + raise ValueError( + "Incomplete irradiance data. Weather data must include " + "'poa_global', 'poa_direct' and 'poa_diffuse'.\n" + "Detected data: {0}".format(list(weather.columns))) + + self.weather = weather + + self._prep_inputs_solar_pos() + self._prep_inputs_airmass() + + # PVSystem.get_irradiance and SingleAxisTracker.get_irradiance + # and PVSystem.get_aoi and SingleAxisTracker.get_aoi + # have different method signatures. Use partial to handle + # the differences. + if isinstance(self.system, SingleAxisTracker): + self._prep_inputs_tracking() + else: + self._prep_inputs_fixed() + + if self.weather.get('wind_speed') is None: + self.weather['wind_speed'] = 0 + if self.weather.get('temp_air') is None: + self.weather['temp_air'] = 20 + return self + def run_model(self, weather, times=None): """ - Run the model. + Run the model chain starting with broadband global, diffuse and/or + direct irradiance. Parameters ---------- weather : DataFrame - Column names must be ``'dni'``, ``'ghi'``, ``'dhi'``, - ``'wind_speed'``, ``'temp_air'``. All irradiance components - are required. Air temperature of 20 C and wind speed - of 0 m/s will be added to the DataFrame if not provided. + Irradiance column names must include ``'dni'``, ``'ghi'``, and + ``'dhi'``. Optional column names include ``'temp_air'`` and + ``'wind_speed'``; if not provided, air temperature of 20 C and wind + speed of 0 m/s are added to the DataFrame. times : None, deprecated Deprecated argument included for API compatibility, but not used internally. The index of the weather DataFrame is used @@ -955,6 +1032,93 @@ def run_model(self, weather, times=None): self.aoi_model() self.spectral_model() self.effective_irradiance_model() + + self.run_model_from_effective_irradiance() + + return self + + def run_model_from_poa(self, weather): + """ + Run the model starting with broadband irradiance in the plane of array. + + `weather` must have columns `poa_global`, `poa_direct` and + `poa_diffuse`.`poa_global` is broadband irradiance (W/m2) in the plane + of array without accounting for soiling or reflections. + + This method calculates: + * effective irradiance + * cell temperature + * DC output + * AC output + + Parameters + ---------- + weather : DataFrame + Column names must include ``'poa_global'``, ``'poa_direct'`` + ``'poa_diffuse'``. Optional column names include ``'temp_air'`` and + ``'wind_speed'``; if not provided, air temperature of 20 C and wind + speed of 0 m/s are added to the DataFrame. + + Returns + ------- + self + + Assigns attributes: ``weather``, ``effective_irradiance``, + ``cell_temperature``, ``dc``, ``ac``, ``losses``, ``diode_params`` + (if dc_model is a single diode model) + """ + + self.prepare_inputs_from_poa(weather) + + self.aoi_model() + self.spectral_model() + self.effective_irradiance_model() + + self.run_model_from_effective_irradiance() + + return self + +def run_model_from_effective_irradiance(self): + """ + Run the model starting with effective irradiance in the plane of array. + + `weather` must have columns `poa_global` and `effective_irradiance`. + `poa_global` is broadband irradiance (W/m2) in the plane of array + without accounting for soiling or reflections. `effective irradiance` + (W/m2) has taken into account soiling, reflections and spectrum + adjustments. + + This method calculates: + * cell temperature + * DC output + * AC output + + Parameters + ---------- + weather : DataFrame + Column names must include ``'effective_irradiance'``, ``'wind_speed'``, + and ``'temp_air'``. Air temperature of 20 C and wind speed of + 0 m/s will be added to the DataFrame if not provided. + + Returns + ------- + self + + Assigns attributes: ``cell_temperature``, ``dc``, ``ac``, + ``losses``, ``diode_params`` (if dc_model is a single diode model) + """ + + # assign poa_global column for the temperature model + if 'poa_global' not in self.weather.columns: + try: + self.weather['poa_global'] = self.weather['effective_irradiance'] + except KeyError: + # both missing + raise ValueError( + "Incomplete irradiance data. Weather must include " + "'effective_irradiance'.\n" + "Detected data: {0}".format(list(self.weather.columns))) + self.temperature_model() self.dc_model() self.losses_model() From 4bdbb42f9aa6b7afb14b55a2796a76ee453fda4d Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 28 Mar 2020 08:46:44 -0600 Subject: [PATCH 02/23] indentation, lint --- pvlib/modelchain.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 925ca988cd..fb5b74b101 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -865,9 +865,8 @@ def _prep_inputs_fixed(self): """ Calculate AOI for fixed tilt system """ - self.aoi = self.system.get_aoi( - self.solar_position['apparent_zenith'], - self.solar_position['azimuth']) + self.aoi = self.system.get_aoi(self.solar_position['apparent_zenith'], + self.solar_position['azimuth']) return self def prepare_inputs(self, weather, times=None): @@ -970,7 +969,8 @@ def prepare_inputs_from_poa(self, weather): pvlib.modelchain.ModelChain.prepare_inputs """ - if not {'poa_global', 'poa_direct', 'poa_diffuse'} <= set(weather.columns): + if not {'poa_global', 'poa_direct', 'poa_diffuse'} \ + <= set(weather.columns): raise ValueError( "Incomplete irradiance data. Weather data must include " "'poa_global', 'poa_direct' and 'poa_diffuse'.\n" @@ -1078,7 +1078,7 @@ def run_model_from_poa(self, weather): return self -def run_model_from_effective_irradiance(self): + def run_model_from_effective_irradiance(self): """ Run the model starting with effective irradiance in the plane of array. @@ -1096,9 +1096,9 @@ def run_model_from_effective_irradiance(self): Parameters ---------- weather : DataFrame - Column names must include ``'effective_irradiance'``, ``'wind_speed'``, - and ``'temp_air'``. Air temperature of 20 C and wind speed of - 0 m/s will be added to the DataFrame if not provided. + Column names must include ``'effective_irradiance'``, + ``'wind_speed'``, and ``'temp_air'``. Air temperature of 20 C and + wind speed of 0 m/s will be added to the DataFrame if not provided. Returns ------- @@ -1111,7 +1111,8 @@ def run_model_from_effective_irradiance(self): # assign poa_global column for the temperature model if 'poa_global' not in self.weather.columns: try: - self.weather['poa_global'] = self.weather['effective_irradiance'] + self.weather['poa_global'] = \ + self.weather['effective_irradiance'] except KeyError: # both missing raise ValueError( From 49e2275a74c6457f7b12a32a1743bcd48d33c921 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sun, 29 Mar 2020 09:56:19 -0600 Subject: [PATCH 03/23] sort out weather, total_irrad, effective_irradiance --- pvlib/modelchain.py | 62 ++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index fb5b74b101..6fdbbf39c8 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -946,19 +946,16 @@ def prepare_inputs(self, weather, times=None): self.weather['temp_air'] = 20 return self - def prepare_inputs_from_poa(self, weather): + def prepare_inputs_from_poa(self, total_irrad): """ - Prepare the solar position, irradiance, and weather inputs to + Prepare the solar position, irradiance, and irradiance inputs to the model, starting with plane-of-array irradiance. Parameters ---------- - weather : DataFrame + total_irrad : DataFrame Irradiance column names must include ``'poa_global'``, - ``'poa_direct'`` and ``'poa_diffuse'``. Optional column names - include ``'temp_air'`` and ``'wind_speed'``; if not provided, - air temperature of 20 C and wind speed of 0 m/s are added to the - DataFrame. + ``'poa_direct'`` and ``'poa_diffuse'``. Notes ----- @@ -969,14 +966,14 @@ def prepare_inputs_from_poa(self, weather): pvlib.modelchain.ModelChain.prepare_inputs """ - if not {'poa_global', 'poa_direct', 'poa_diffuse'} \ - <= set(weather.columns): + req_keys = {'poa_global', 'poa_direct', 'poa_diffuse'} + if not req_keys <= set(total_irrad.columns): raise ValueError( "Incomplete irradiance data. Weather data must include " "'poa_global', 'poa_direct' and 'poa_diffuse'.\n" - "Detected data: {0}".format(list(weather.columns))) + "Detected data: {0}".format(list(total_irrad.columns))) - self.weather = weather + self.total_irrad = total_irrad self._prep_inputs_solar_pos() self._prep_inputs_airmass() @@ -1037,13 +1034,17 @@ def run_model(self, weather, times=None): return self - def run_model_from_poa(self, weather): + def run_model_from_poa(self, total_irrad): """ Run the model starting with broadband irradiance in the plane of array. - `weather` must have columns `poa_global`, `poa_direct` and - `poa_diffuse`.`poa_global` is broadband irradiance (W/m2) in the plane - of array without accounting for soiling or reflections. + Inputs must include direct, diffuse and total irradiance (W/m2) in the + plane of array. Reflections and spectral adjustments are made to + calculate effective irradiance (W/m2). + + Attribute `ModelChain.weather` can include columns ``'temp_air'`` and + ``'wind_speed'``; if not provided, air temperature of 20 C and wind + speed of 0 m/s are added to `weather`. This method calculates: * effective irradiance @@ -1053,11 +1054,9 @@ def run_model_from_poa(self, weather): Parameters ---------- - weather : DataFrame + total_irrad : DataFrame Column names must include ``'poa_global'``, ``'poa_direct'`` - ``'poa_diffuse'``. Optional column names include ``'temp_air'`` and - ``'wind_speed'``; if not provided, air temperature of 20 C and wind - speed of 0 m/s are added to the DataFrame. + ``'poa_diffuse'``. Returns ------- @@ -1068,7 +1067,7 @@ def run_model_from_poa(self, weather): (if dc_model is a single diode model) """ - self.prepare_inputs_from_poa(weather) + self.prepare_inputs_from_poa(total_irrad) self.aoi_model() self.spectral_model() @@ -1082,12 +1081,17 @@ def run_model_from_effective_irradiance(self): """ Run the model starting with effective irradiance in the plane of array. - `weather` must have columns `poa_global` and `effective_irradiance`. - `poa_global` is broadband irradiance (W/m2) in the plane of array - without accounting for soiling or reflections. `effective irradiance` - (W/m2) has taken into account soiling, reflections and spectrum + Attribute `ModelChain.effective_irradiance` is required. + + Plane-of-array irradiance is broadband irradiance (W/m2) in the plane + of array without accounting for soiling or reflections. Effective + irradiance (W/m2) takes into account soiling, reflections and spectrum adjustments. + Attribute `ModelChain.weather` (DataFrame) can include columns + ``'wind_speed'`` and ``'temp_air'``; if not, air temperature of 20 C + and wind speed of 0 m/s will be added to `weather`. + This method calculates: * cell temperature * DC output @@ -1095,10 +1099,6 @@ def run_model_from_effective_irradiance(self): Parameters ---------- - weather : DataFrame - Column names must include ``'effective_irradiance'``, - ``'wind_speed'``, and ``'temp_air'``. Air temperature of 20 C and - wind speed of 0 m/s will be added to the DataFrame if not provided. Returns ------- @@ -1109,10 +1109,10 @@ def run_model_from_effective_irradiance(self): """ # assign poa_global column for the temperature model - if 'poa_global' not in self.weather.columns: + if 'poa_global' not in self.total_irrad.columns: try: - self.weather['poa_global'] = \ - self.weather['effective_irradiance'] + self.total_irrad['poa_global'] = \ + self.effective_irradiance except KeyError: # both missing raise ValueError( From d6789786f10915b6d44b4ddf30ccf009aa2b5f7f Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 30 Jul 2020 11:01:29 -0600 Subject: [PATCH 04/23] fix _prep_inputs_solar_pos --- pvlib/modelchain.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index d6fa60d123..ae19f5ceaf 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -840,13 +840,13 @@ def complete_irradiance(self, weather, times=None): return self - def _prep_inputs_solar_pos(self, press_temp=None): + def _prep_inputs_solar_pos(self, **kwargs): """ Assign solar position """ self.solar_position = self.location.get_solarposition( self.weather.index, method=self.solar_position_method, - **press_temp) + **kwargs) return self def _prep_inputs_airmass(self): @@ -995,10 +995,6 @@ def prepare_inputs_from_poa(self, total_irrad): self._prep_inputs_solar_pos() self._prep_inputs_airmass() - # PVSystem.get_irradiance and SingleAxisTracker.get_irradiance - # and PVSystem.get_aoi and SingleAxisTracker.get_aoi - # have different method signatures. Use partial to handle - # the differences. if isinstance(self.system, SingleAxisTracker): self._prep_inputs_tracking() else: From ad23a37c29a1fbc652b98e663e4059d7354f5dc9 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 4 Aug 2020 11:47:56 -0600 Subject: [PATCH 05/23] press_temp --- pvlib/modelchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index ae19f5ceaf..5a56852b94 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -926,7 +926,7 @@ def prepare_inputs(self, weather, times=None): press_temp = _build_kwargs(['pressure', 'temp_air'], weather) press_temp['temperature'] = press_temp.pop('temp_air') except KeyError: - press_temp = None + pass self._prep_inputs_solar_pos(press_temp) self._prep_inputs_airmass() From 6e91a7816c4d628618e76d8c0a26a60cc1fd1f71 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 17 Aug 2020 14:04:32 -0600 Subject: [PATCH 06/23] reorganize code that assigns weather and total_irrad --- pvlib/modelchain.py | 95 ++++++++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 39 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 5a56852b94..a2289ee534 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -881,6 +881,33 @@ def _prep_inputs_fixed(self): self.solar_position['azimuth']) return self + def _verify_df(self, data, req): + """ Checks data for column names in req + + Parameters + ---------- + data : Dataframe + req : List of str + + Raises + ------ + ValueError if any of req are not in data.columns. + """ + if not set(req) <= set(data.columns): + raise ValueError( + "Incomplete weather or irradiance data.\n" + "Data set needs to contain {0}.\n" + "Detected data: {1}".format(req, list(data.columns))) + return + + def _assign_weather(self, weather): + self.weather = weather + if self.weather.get('wind_speed') is None: + self.weather['wind_speed'] = 0 + if self.weather.get('temp_air') is None: + self.weather['temp_air'] = 20 + return self + def prepare_inputs(self, weather, times=None): """ Prepare the solar position, irradiance, and weather inputs to @@ -900,7 +927,7 @@ def prepare_inputs(self, weather, times=None): Notes ----- - Assigns attributes: ``solar_position``, ``airmass``, + Assigns attributes: ``weather``, ``solar_position``, ``airmass``, ``total_irrad``, ``aoi`` See also @@ -908,20 +935,16 @@ def prepare_inputs(self, weather, times=None): ModelChain.complete_irradiance """ - if not {'ghi', 'dni', 'dhi'} <= set(weather.columns): - raise ValueError( - "Uncompleted irradiance data set. Please check your input " - "data.\nData set needs to have 'dni', 'dhi' and 'ghi'.\n" - "Detected data: {0}".format(list(weather.columns))) - - self.weather = weather + self._verify_df(weather, req=['ghi', 'dni', 'ghi']) + self._assign_weather(weather) if times is not None: warnings.warn('times keyword argument is deprecated and will be ' 'removed in 0.8. The index of the weather DataFrame ' 'is used for times.', pvlibDeprecationWarning) - self.times = self.weather.index + + # build kwargs for solar position calculation try: press_temp = _build_kwargs(['pressure', 'temp_air'], weather) press_temp['temperature'] = press_temp.pop('temp_air') @@ -957,40 +980,39 @@ def prepare_inputs(self, weather, times=None): airmass=self.airmass['airmass_relative'], model=self.transposition_model) - if self.weather.get('wind_speed') is None: - self.weather['wind_speed'] = 0 - if self.weather.get('temp_air') is None: - self.weather['temp_air'] = 20 return self - def prepare_inputs_from_poa(self, total_irrad): + def prepare_inputs_from_poa(self, data, weather): """ Prepare the solar position, irradiance, and irradiance inputs to the model, starting with plane-of-array irradiance. Parameters ---------- - total_irrad : DataFrame - Irradiance column names must include ``'poa_global'``, - ``'poa_direct'`` and ``'poa_diffuse'``. + data : DataFrame + Contains plane-of-array irradiance data. Required column names + include ``'poa_global'``, ``'poa_direct'`` and ``'poa_diffuse'``. + weather : DataFrame + Contains temperature and other weather data. Assigned to the + ``weather`` attribute. If column names ``'temp_air'`` and + ``'wind_speed'`` are not provided, air temperature of 20 C and wind + speed of 0 m/s are added to the DataFrame. Notes ----- - Assigns attributes: ``solar_position``, ``airmass``, ``aoi`` + Assigns attributes: ``weather``, ``total_irrad``, ``solar_position``, + ``airmass``, ``aoi`` See also -------- pvlib.modelchain.ModelChain.prepare_inputs """ - req_keys = {'poa_global', 'poa_direct', 'poa_diffuse'} - if not req_keys <= set(total_irrad.columns): - raise ValueError( - "Incomplete irradiance data. Weather data must include " - "'poa_global', 'poa_direct' and 'poa_diffuse'.\n" - "Detected data: {0}".format(list(total_irrad.columns))) + self._assign_weather(weather) - self.total_irrad = total_irrad + self._verify_df(data, req=['poa_global', 'poa_direct', 'poa_diffuse']) + + self.total_irrad = data[['poa_global', 'poa_direct', 'poa_diffuse']] self._prep_inputs_solar_pos() self._prep_inputs_airmass() @@ -1000,10 +1022,6 @@ def prepare_inputs_from_poa(self, total_irrad): else: self._prep_inputs_fixed() - if self.weather.get('wind_speed') is None: - self.weather['wind_speed'] = 0 - if self.weather.get('temp_air') is None: - self.weather['temp_air'] = 20 return self def run_model(self, weather, times=None): @@ -1047,17 +1065,16 @@ def run_model(self, weather, times=None): return self - def run_model_from_poa(self, total_irrad): + def run_model_from_poa(self, data): """ Run the model starting with broadband irradiance in the plane of array. - Inputs must include direct, diffuse and total irradiance (W/m2) in the + Data must include direct, diffuse and total irradiance (W/m2) in the plane of array. Reflections and spectral adjustments are made to calculate effective irradiance (W/m2). - Attribute `ModelChain.weather` can include columns ``'temp_air'`` and - ``'wind_speed'``; if not provided, air temperature of 20 C and wind - speed of 0 m/s are added to `weather`. + Data can include columns ``'temp_air'`` and ``'wind_speed'``; if not + provided, air temperature of 20 C and wind speed of 0 m/s are assumed. This method calculates: * effective irradiance @@ -1067,9 +1084,9 @@ def run_model_from_poa(self, total_irrad): Parameters ---------- - total_irrad : DataFrame - Column names must include ``'poa_global'``, ``'poa_direct'`` - ``'poa_diffuse'``. + data : DataFrame + Required column names must include ``'poa_global'``, + ``'poa_direct'`` and ``'poa_diffuse'``. Returns ------- @@ -1080,7 +1097,7 @@ def run_model_from_poa(self, total_irrad): (if dc_model is a single diode model) """ - self.prepare_inputs_from_poa(total_irrad) + self.prepare_inputs_from_poa(data) self.aoi_model() self.spectral_model() @@ -1103,7 +1120,7 @@ def run_model_from_effective_irradiance(self): Attribute `ModelChain.weather` (DataFrame) can include columns ``'wind_speed'`` and ``'temp_air'``; if not, air temperature of 20 C - and wind speed of 0 m/s will be added to `weather`. + and wind speed of 0 m/s are assumed. This method calculates: * cell temperature From b8d9c3d7b0a2265f98c9fafc3571c39be1a4a296 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 24 Aug 2020 11:19:11 -0600 Subject: [PATCH 07/23] add prepare_temperature --- pvlib/modelchain.py | 189 ++++++++++++++++++++++++++++++-------------- 1 file changed, 130 insertions(+), 59 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index a2289ee534..286a358fd8 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -18,6 +18,16 @@ from pvlib._deprecation import pvlibDeprecationWarning from pvlib.tools import _build_kwargs +WEATHER_KEYS = set(['ghi', 'dhi', 'dni', 'wind_speed', 'temp_air', + 'precipitable_water']) + +POA_DATA_KEYS = set(['poa_global', 'poa_direct', 'poa_diffuse']) + +TEMPERATURE_KEYS = set(['module_temperature', 'cell_temperature']) + +DATA_KEYS = WEATHER_KEYS | POA_DATA_KEYS | TEMPERATURE_KEYS + + def basic_chain(times, latitude, longitude, module_parameters, temperature_model_parameters, inverter_parameters, @@ -895,19 +905,25 @@ def _verify_df(self, data, req): """ if not set(req) <= set(data.columns): raise ValueError( - "Incomplete weather or irradiance data.\n" - "Data set needs to contain {0}.\n" - "Detected data: {1}".format(req, list(data.columns))) + "Incomplete input data.\n" + "Data needs to contain {0}.\n" + "Detected data contains: {1}".format(req, list(data.columns))) return - def _assign_weather(self, weather): - self.weather = weather + def _assign_weather(self, data): + key_list = [k for k in WEATHER_KEYS if k in data] + self.weather = data[key_list].copy() if self.weather.get('wind_speed') is None: self.weather['wind_speed'] = 0 if self.weather.get('temp_air') is None: self.weather['temp_air'] = 20 return self + def _assign_total_irrad(self, data): + key_list = [k for k in POA_DATA_KEYS if k in data] + self.total_irrad = data[key_list].copy() + return self + def prepare_inputs(self, weather, times=None): """ Prepare the solar position, irradiance, and weather inputs to @@ -916,10 +932,10 @@ def prepare_inputs(self, weather, times=None): Parameters ---------- weather : DataFrame - Irradiance column names must include ``'dni'``, ``'ghi'``, and - ``'dhi'``. Optional column names include ``'temp_air'`` and + Column names must include ``'dni'``, ``'ghi'``, and ``'dhi'``. + Optional column names include ``'temp_air'`` and ``'wind_speed'``; if not provided, air temperature of 20 C and wind - speed of 0 m/s are added to the DataFrame. + speed of 0 m/s are assumed. times : None, deprecated Deprecated argument included for API compatibility, but not used internally. The index of the weather DataFrame is used @@ -982,7 +998,7 @@ def prepare_inputs(self, weather, times=None): return self - def prepare_inputs_from_poa(self, data, weather): + def prepare_inputs_from_poa(self, data): """ Prepare the solar position, irradiance, and irradiance inputs to the model, starting with plane-of-array irradiance. @@ -992,27 +1008,25 @@ def prepare_inputs_from_poa(self, data, weather): data : DataFrame Contains plane-of-array irradiance data. Required column names include ``'poa_global'``, ``'poa_direct'`` and ``'poa_diffuse'``. - weather : DataFrame - Contains temperature and other weather data. Assigned to the - ``weather`` attribute. If column names ``'temp_air'`` and + Columns with weather-related data are ssigned to the + ``weather`` attribute. If columns for ``'temp_air'`` and ``'wind_speed'`` are not provided, air temperature of 20 C and wind - speed of 0 m/s are added to the DataFrame. + speed of 0 m/s are assumed. Notes ----- Assigns attributes: ``weather``, ``total_irrad``, ``solar_position``, - ``airmass``, ``aoi`` + ``airmass``, ``aoi``, ``temperature``. See also -------- pvlib.modelchain.ModelChain.prepare_inputs """ - self._assign_weather(weather) + self._assign_weather(data) self._verify_df(data, req=['poa_global', 'poa_direct', 'poa_diffuse']) - - self.total_irrad = data[['poa_global', 'poa_direct', 'poa_diffuse']] + self._assign_total_irrad(data) self._prep_inputs_solar_pos() self._prep_inputs_airmass() @@ -1024,6 +1038,68 @@ def prepare_inputs_from_poa(self, data, weather): return self + def prepare_temperature(self, data=None): + """ + Sets cell_temperature using inputs in data and the specified + temperature model. + + If 'data' contains 'cell_temperature', these values are assigned to + attribute ``cell_temperature``. If 'data' contains 'module_temperature` + and `temperature_model' is 'sapm', cell temperature is calculated using + :py:func:`pvlib.temperature.sapm_celL_from_module`. Otherwise, cell + temperature is calculated by 'temperature_model'. + + Parameters + ---------- + data : DataFrame, default None + May contain columns ``'cell_temperature'`` or + ``'module_temperaure'``. + + Returns + ------- + self + + Assigns attribute ``cell_temperature``. + + """ + try: + self.cell_temperature = data['cell_temperature'] + if self.temperature_model is not None: + warnings.warn('using ''cell_temperature'' provided in input', + ' rather than specified temperature model: {0}' + .format(self.temperature_model.__name__)) + return self + except: + pass + + # cell_temperature is not in input. Calculate cell_temperature using + # a temperature_model. + # If module_temperature is in input data we can use the SAPM cell + # temperature model. + if ('module_temperature' in data) and \ + (self.temperature_model.__name__ == 'sapm'): + # use SAPM cell temperature model only + self.cell_temperature = pvlib.temperature.sapm_cell_from_module( + module_temperature=data['module_temperature'], + poa_global=self.total_irrad['poa_global'], + deltaT=self.temperature_model_parameters['deltaT']) + return self + + # Calculate cell temperature from weather data. Cell temperature models + # expect total_irrad['poa_global']. If not found, substitute + # self.effective_irradiance. + if 'poa_global' not in self.total_irrad: + try: + self.total_irrad['poa_global'] = self.effective_irradiance + except AttributeError: + # poa_global nor effective_irradiance is found. + raise ValueError( + "Incomplete irradiance data for cell temperature model." + " Provide total_irrad['poa_global'] (preferred) or" + " attribute effective_irradiance.") + self.cell_temperature = self.temperature_model() + return self + def run_model(self, weather, times=None): """ Run the model chain starting with broadband global, diffuse and/or @@ -1033,9 +1109,12 @@ def run_model(self, weather, times=None): ---------- weather : DataFrame Irradiance column names must include ``'dni'``, ``'ghi'``, and - ``'dhi'``. Optional column names include ``'temp_air'`` and - ``'wind_speed'``; if not provided, air temperature of 20 C and wind - speed of 0 m/s are added to the DataFrame. + ``'dhi'``. If optional columns ``'temp_air'`` and ``'wind_speed'`` + are not provided, air temperature of 20 C and wind speed of 0 m/s + are added to the DataFrame. If optional column + ``'cell_temperature'`` is provided, these values are used instead + of `temperature_model`. If optional column `module_temperature` + is provided, `temperature_model` must be ``'sapm'``. times : None, deprecated Deprecated argument included for API compatibility, but not used internally. The index of the weather DataFrame is used @@ -1045,11 +1124,12 @@ def run_model(self, weather, times=None): ------- self - Assigns attributes: ``solar_position``, ``airmass``, ``irradiance``, - ``total_irrad``, ``effective_irradiance``, ``weather``, - ``cell_temperature``, ``aoi``, ``aoi_modifier``, ``spectral_modifier``, - ``dc``, ``ac``, ``losses``, - ``diode_params`` (if dc_model is a single diode model) + Assigns attributes: ``solar_position``, ``airmass``, ``weather``, + ``total_irrad``, ``aoi``, ``aoi_modifier``, ``spectral_modifier``, + and ``effective_irradiance``. + Calls method `run_model_from_effective_irradiance` + to assign ``cell_temperature``, ``dc``, ``ac``, + ``losses``, ``diode_params`` (if dc_model is a single diode model). """ if times is not None: warnings.warn('times keyword argument is deprecated and will be ' @@ -1061,7 +1141,7 @@ def run_model(self, weather, times=None): self.spectral_model() self.effective_irradiance_model() - self.run_model_from_effective_irradiance() + self.run_model_from_effective_irradiance(weather) return self @@ -1073,9 +1153,6 @@ def run_model_from_poa(self, data): plane of array. Reflections and spectral adjustments are made to calculate effective irradiance (W/m2). - Data can include columns ``'temp_air'`` and ``'wind_speed'``; if not - provided, air temperature of 20 C and wind speed of 0 m/s are assumed. - This method calculates: * effective irradiance * cell temperature @@ -1086,15 +1163,23 @@ def run_model_from_poa(self, data): ---------- data : DataFrame Required column names must include ``'poa_global'``, - ``'poa_direct'`` and ``'poa_diffuse'``. + ``'poa_direct'`` and ``'poa_diffuse'``. If optional columns + ``'temp_air'`` and ``'wind_speed'`` are not provided, air + temperature of 20 C and wind speed of 0 m/s are assumed. + If optional column ``'cell_temperature'`` is provided, these values + are used instead of `temperature_model`. If optional column + `module_temperature` is provided, `temperature_model` must be + ``'sapm'``. Returns ------- self - Assigns attributes: ``weather``, ``effective_irradiance``, - ``cell_temperature``, ``dc``, ``ac``, ``losses``, ``diode_params`` - (if dc_model is a single diode model) + Assigns attributes: ``weather``, ``total_irrad``, ``solar_position``, + ``airmass``, ``aoi`` and ``effective_irradiance``. + Calls `run_model_from_effective_irradiance` + to assign ``cell_temperature``, ``dc``, ``ac``, + ``losses``, ``diode_params`` (if dc_model is a single diode model) """ self.prepare_inputs_from_poa(data) @@ -1103,24 +1188,18 @@ def run_model_from_poa(self, data): self.spectral_model() self.effective_irradiance_model() - self.run_model_from_effective_irradiance() + self.run_model_from_effective_irradiance(data) return self - def run_model_from_effective_irradiance(self): + def run_model_from_effective_irradiance(self, data=None): """ Run the model starting with effective irradiance in the plane of array. - Attribute `ModelChain.effective_irradiance` is required. - - Plane-of-array irradiance is broadband irradiance (W/m2) in the plane - of array without accounting for soiling or reflections. Effective - irradiance (W/m2) takes into account soiling, reflections and spectrum - adjustments. + Effective irradiance is irradiance in the plane-of-array after any + adjustments for soiling, reflections and spectrum. - Attribute `ModelChain.weather` (DataFrame) can include columns - ``'wind_speed'`` and ``'temp_air'``; if not, air temperature of 20 C - and wind speed of 0 m/s are assumed. + Attribute `ModelChain.effective_irradiance` is required. This method calculates: * cell temperature @@ -1129,28 +1208,20 @@ def run_model_from_effective_irradiance(self): Parameters ---------- + data : DataFrame, default None + May contain columns ``'cell_temperature'`` or + ``'module_temperaure'``. Returns ------- self - Assigns attributes: ``cell_temperature``, ``dc``, ``ac``, - ``losses``, ``diode_params`` (if dc_model is a single diode model) + Assigns attributes: ``effective_irradiance``, ``cell_temperature``, + ``dc``, ``ac``, ``losses``, ``diode_params`` (if dc_model is a single + diode model). """ - # assign poa_global column for the temperature model - if 'poa_global' not in self.total_irrad.columns: - try: - self.total_irrad['poa_global'] = \ - self.effective_irradiance - except KeyError: - # both missing - raise ValueError( - "Incomplete irradiance data. Weather must include " - "'effective_irradiance'.\n" - "Detected data: {0}".format(list(self.weather.columns))) - - self.temperature_model() + self.prepare_temperature(data) self.dc_model() self.losses_model() self.ac_model() From 73854a7698ebdedf43b7be062b6e80a3d3319846 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 1 Sep 2020 16:08:11 -0600 Subject: [PATCH 08/23] start fixing tests --- pvlib/modelchain.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index ed3e554853..9315baec75 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1013,8 +1013,7 @@ def complete_irradiance(self, weather): return self - - def _prep_inputs_solar_pos(self, **kwargs): + def _prep_inputs_solar_pos(self, kwargs): """ Assign solar position """ @@ -1088,7 +1087,6 @@ def _assign_total_irrad(self, data): self.total_irrad = data[key_list].copy() return self - def prepare_inputs(self, weather): """ Prepare the solar position, irradiance, and weather inputs to @@ -1254,7 +1252,7 @@ def prepare_temperature(self, data=None): "Incomplete irradiance data for cell temperature model." " Provide total_irrad['poa_global'] (preferred) or" " attribute effective_irradiance.") - self.cell_temperature = self.temperature_model() + self.temperature_model() return self def run_model(self, weather): From 7452d6bbbcdf631222ec6c79ca089a0ea469aecf Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 2 Sep 2020 13:04:02 -0600 Subject: [PATCH 09/23] start on tests --- pvlib/modelchain.py | 22 +++++++------- pvlib/tests/test_modelchain.py | 54 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 9315baec75..40cd8b78e1 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1217,22 +1217,20 @@ def prepare_temperature(self, data=None): Assigns attribute ``cell_temperature``. """ - try: + if 'cell_temperature' in data: self.cell_temperature = data['cell_temperature'] if self.temperature_model is not None: warnings.warn('using ''cell_temperature'' provided in input', ' rather than specified temperature model: {0}' .format(self.temperature_model.__name__)) return self - except: - pass # cell_temperature is not in input. Calculate cell_temperature using # a temperature_model. # If module_temperature is in input data we can use the SAPM cell # temperature model. - if ('module_temperature' in data) and \ - (self.temperature_model.__name__ == 'sapm'): + if (('module_temperature' in data) and + (self.temperature_model.__name__ == 'sapm')): # use SAPM cell temperature model only self.cell_temperature = pvlib.temperature.sapm_cell_from_module( module_temperature=data['module_temperature'], @@ -1345,7 +1343,8 @@ def run_model_from_effective_irradiance(self, data=None): Effective irradiance is irradiance in the plane-of-array after any adjustments for soiling, reflections and spectrum. - Attribute `ModelChain.effective_irradiance` is required. + Expects attributes `ModelChain.effective_irradiance` and + `ModelChain.weather` to be assigned. This method calculates: * cell temperature @@ -1355,16 +1354,17 @@ def run_model_from_effective_irradiance(self, data=None): Parameters ---------- data : DataFrame, default None - May contain columns ``'cell_temperature'`` or - ``'module_temperaure'``. + If optional column ``'cell_temperature'`` is provided, these values + are used instead of `temperature_model`. If optional column + `module_temperature` is provided, `temperature_model` must be + ``'sapm'``. Returns ------- self - Assigns attributes: ``effective_irradiance``, ``cell_temperature``, - ``dc``, ``ac``, ``losses``, ``diode_params`` (if dc_model is a single - diode model). + Assigns attributes: ``cell_temperature``, ``dc``, ``ac``, ``losses``, + ``diode_params`` (if dc_model is a single diode model). """ self.prepare_temperature(data) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index af7d631dc9..8435c3eb7c 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -178,6 +178,13 @@ def weather(): return weather +@pytest.fixture +def total_irrad(weather): + return pd.DataFrame({'poa_global': [800., 500.], + 'poa_diffuse': [300., 200.], + 'poa_direct': [500., 300.]}, index=weather.index) + + def test_ModelChain_creation(sapm_dc_snl_ac_system, location): ModelChain(sapm_dc_snl_ac_system, location) @@ -327,6 +334,53 @@ def test_run_model_tracker(sapm_dc_snl_ac_system, location, weather, mocker): assert np.isnan(mc.ac[1]) +def test__assign_total_irrad(sapm_dc_snl_ac_system, location, weather, + total_irrad): + weather[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad + mc = ModelChain(sapm_dc_snl_ac_system, location) + mc._assign_total_irrad(weather) + for k in modelchain.POA_DATA_KEYS: + assert_series_equal(mc.total_irrad[k], total_irrad[k]) + + +def test_prepare_inputs_from_poa(sapm_dc_snl_ac_system, location, + total_irrad): + data= weather.copy() + data[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad + mc = ModelChain(sapm_dc_snl_ac_system, location) + mc.prepare_inputs_from_poa(data) + # weather attribute + for k in weather: + assert_series_equal(mc.weather[k], weather[k]) + # total_irrad attribute + for k in total_irrad: + assert_series_equal(mc.total_irrad[k], total_irrad[k]) + + +def test_run_model_from_poa(sapm_dc_snl_ac_system, location, total_irrad): + mc = ModelChain(sapm_dc_snl_ac_system, location, aoi_model='no_loss', + spectral_model='no_loss') + ac = mc.run_model_from_poa(total_irrad).ac + expected = pd.Series(np.array([149.280238, 96.678385]), + index=total_irrad.index) + assert_series_equal(ac, expected) + + +def test_run_model_from_effective_irradiance(sapm_dc_snl_ac_system, location, + total_irrad): + mc = ModelChain(sapm_dc_snl_ac_system, location, aoi_model='no_loss', + spectral_model='no_loss') + # run_model_from_effective_irradiance requires mc.weather and + # mc.effective_irradiance to be assigned + mc.weather = pd.DataFrame({'temp_air': 20, 'wind_speed': 0}, + index=total_irrad.index) + mc.effective_irradiance = total_irrad['poa_global'] + ac = mc.run_model_effective_irradiance(total_irrad).ac + expected = pd.Series(np.array([149.280238, 96.678385]), + index=total_irrad.index) + assert_series_equal(ac, expected) + + def poadc(mc): mc.dc = mc.total_irrad['poa_global'] * 0.2 mc.dc.name = None # assert_series_equal will fail without this From 8bda69afb07a2c80be0425eadb524baf165b5050 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 2 Sep 2020 13:35:33 -0600 Subject: [PATCH 10/23] split run_model_from_effective_irradiance --- pvlib/modelchain.py | 79 +++++++++++++++++++++++----------- pvlib/tests/test_modelchain.py | 12 +++--- 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 40cd8b78e1..2b6817cbcb 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -18,11 +18,20 @@ from pvlib._deprecation import pvlibDeprecationWarning from pvlib.tools import _build_kwargs +# keys that are used to detect input data and assign data to appropriate +# ModelChain attribute +# for ModelChain.weather WEATHER_KEYS = set(['ghi', 'dhi', 'dni', 'wind_speed', 'temp_air', 'precipitable_water']) +# for ModelChain.total_irrad POA_DATA_KEYS = set(['poa_global', 'poa_direct', 'poa_diffuse']) +# Optional keys to communicate temperature data. If provided, +# 'cell_temperature' overrides ModelChain.temperature_model and sets +# ModelChain.cell_temperature to the data. If 'module_temperature' is provdied, +# overrides ModelChain.temperature_model with +# pvlib.temperature.sapm_celL_from_module TEMPERATURE_KEYS = set(['module_temperature', 'cell_temperature']) DATA_KEYS = WEATHER_KEYS | POA_DATA_KEYS | TEMPERATURE_KEYS @@ -1054,23 +1063,23 @@ def _prep_inputs_fixed(self): self.solar_position['azimuth']) return self - def _verify_df(self, data, req): - """ Checks data for column names in req + def _verify_df(self, data, required): + """ Checks data for column names in required Parameters ---------- data : Dataframe - req : List of str + required : List of str Raises ------ ValueError if any of req are not in data.columns. """ - if not set(req) <= set(data.columns): + if not set(required) <= set(data.columns): raise ValueError( - "Incomplete input data.\n" - "Data needs to contain {0}.\n" - "Detected data contains: {1}".format(req, list(data.columns))) + "Incomplete input data. Data needs to contain {0}. " + "Detected data contains: {1}".format(required, + list(data.columns))) return def _assign_weather(self, data): @@ -1155,7 +1164,7 @@ def prepare_inputs(self, weather): def prepare_inputs_from_poa(self, data): """ - Prepare the solar position, irradiance, and irradiance inputs to + Prepare the solar position, irradiance and weather inputs to the model, starting with plane-of-array irradiance. Parameters @@ -1171,7 +1180,7 @@ def prepare_inputs_from_poa(self, data): Notes ----- Assigns attributes: ``weather``, ``total_irrad``, ``solar_position``, - ``airmass``, ``aoi``, ``temperature``. + ``airmass``, ``aoi``. See also -------- @@ -1219,10 +1228,6 @@ def prepare_temperature(self, data=None): """ if 'cell_temperature' in data: self.cell_temperature = data['cell_temperature'] - if self.temperature_model is not None: - warnings.warn('using ''cell_temperature'' provided in input', - ' rather than specified temperature model: {0}' - .format(self.temperature_model.__name__)) return self # cell_temperature is not in input. Calculate cell_temperature using @@ -1285,7 +1290,7 @@ def run_model(self, weather): self.spectral_model() self.effective_irradiance_model() - self.run_model_from_effective_irradiance(weather) + self._run_from_effective_irrad(weather) return self @@ -1332,7 +1337,33 @@ def run_model_from_poa(self, data): self.spectral_model() self.effective_irradiance_model() - self.run_model_from_effective_irradiance(data) + self._run_from_effective_irrad(data) + + return self + + def _run_from_effective_irrad(self, data=None): + """ + Executes the temperature, DC, losses and AC models. + + Parameters + ---------- + data : DataFrame, default None + If optional column ``'cell_temperature'`` is provided, these values + are used instead of `temperature_model`. If optional column + `module_temperature` is provided, `temperature_model` must be + ``'sapm'``. + + Returns + ------- + self + + Assigns ``cell_temperature``, ``dc``, ``ac``, ``losses``, + ``diode_params`` (if dc_model is a single diode model). + """ + self.prepare_temperature(data) + self.dc_model() + self.losses_model() + self.ac_model() return self @@ -1343,9 +1374,6 @@ def run_model_from_effective_irradiance(self, data=None): Effective irradiance is irradiance in the plane-of-array after any adjustments for soiling, reflections and spectrum. - Expects attributes `ModelChain.effective_irradiance` and - `ModelChain.weather` to be assigned. - This method calculates: * cell temperature * DC output @@ -1354,6 +1382,7 @@ def run_model_from_effective_irradiance(self, data=None): Parameters ---------- data : DataFrame, default None + Required column is ``'effective_irradiance'``. If optional column ``'cell_temperature'`` is provided, these values are used instead of `temperature_model`. If optional column `module_temperature` is provided, `temperature_model` must be @@ -1363,13 +1392,15 @@ def run_model_from_effective_irradiance(self, data=None): ------- self - Assigns attributes: ``cell_temperature``, ``dc``, ``ac``, ``losses``, - ``diode_params`` (if dc_model is a single diode model). + Assigns attributes: ``weather``, ``total_irrad``, + ``effective_irradiance``, ``cell_temperature``, ``dc``, ``ac``, + ``losses``, ``diode_params`` (if dc_model is a single diode model). """ - self.prepare_temperature(data) - self.dc_model() - self.losses_model() - self.ac_model() + self._assign_weather(data) + self._assign_total_irrad(data) + self.effective_irradiance = data['effective_irradiance'] + self._run_from_effective_irrad(data) return self + diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 8435c3eb7c..88f2179119 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -367,15 +367,13 @@ def test_run_model_from_poa(sapm_dc_snl_ac_system, location, total_irrad): def test_run_model_from_effective_irradiance(sapm_dc_snl_ac_system, location, - total_irrad): + weather, total_irrad): + 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') - # run_model_from_effective_irradiance requires mc.weather and - # mc.effective_irradiance to be assigned - mc.weather = pd.DataFrame({'temp_air': 20, 'wind_speed': 0}, - index=total_irrad.index) - mc.effective_irradiance = total_irrad['poa_global'] - ac = mc.run_model_effective_irradiance(total_irrad).ac + ac = mc.run_model_from_effective_irradiance(total_irrad).ac expected = pd.Series(np.array([149.280238, 96.678385]), index=total_irrad.index) assert_series_equal(ac, expected) From 018c2e9a8a56d4e3b38630581b402459eb7ff4e9 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 2 Sep 2020 15:36:38 -0600 Subject: [PATCH 11/23] fix tests, sticker --- pvlib/modelchain.py | 9 ++++----- pvlib/tests/test_modelchain.py | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 2b6817cbcb..c15e774282 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1073,7 +1073,7 @@ def _verify_df(self, data, required): Raises ------ - ValueError if any of req are not in data.columns. + ValueError if any of required are not in data.columns. """ if not set(required) <= set(data.columns): raise ValueError( @@ -1119,7 +1119,7 @@ def prepare_inputs(self, weather): ModelChain.complete_irradiance """ - self._verify_df(weather, req=['ghi', 'dni', 'ghi']) + self._verify_df(weather, required=['ghi', 'dni', 'ghi']) self._assign_weather(weather) self.times = self.weather.index @@ -1189,7 +1189,7 @@ def prepare_inputs_from_poa(self, data): self._assign_weather(data) - self._verify_df(data, req=['poa_global', 'poa_direct', 'poa_diffuse']) + self._verify_df(data, required=['poa_global', 'poa_direct', 'poa_diffuse']) self._assign_total_irrad(data) self._prep_inputs_solar_pos() @@ -1235,7 +1235,7 @@ def prepare_temperature(self, data=None): # If module_temperature is in input data we can use the SAPM cell # temperature model. if (('module_temperature' in data) and - (self.temperature_model.__name__ == 'sapm')): + (self.temperature_model.__name__ == 'sapm')): # use SAPM cell temperature model only self.cell_temperature = pvlib.temperature.sapm_cell_from_module( module_temperature=data['module_temperature'], @@ -1403,4 +1403,3 @@ def run_model_from_effective_irradiance(self, data=None): self._run_from_effective_irrad(data) return self - diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 88f2179119..20b43ca1aa 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -345,7 +345,7 @@ def test__assign_total_irrad(sapm_dc_snl_ac_system, location, weather, def test_prepare_inputs_from_poa(sapm_dc_snl_ac_system, location, total_irrad): - data= weather.copy() + data = weather.copy() data[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad mc = ModelChain(sapm_dc_snl_ac_system, location) mc.prepare_inputs_from_poa(data) @@ -368,7 +368,7 @@ def test_run_model_from_poa(sapm_dc_snl_ac_system, location, total_irrad): def test_run_model_from_effective_irradiance(sapm_dc_snl_ac_system, location, weather, total_irrad): - data= weather.copy() + 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', From 1cb712c99e2b8ea5586ef7f1c98efa2db3b7a68e Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 2 Sep 2020 15:48:30 -0600 Subject: [PATCH 12/23] more test fixes --- pvlib/modelchain.py | 2 +- pvlib/tests/test_modelchain.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index c15e774282..068bd16cd3 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1022,7 +1022,7 @@ def complete_irradiance(self, weather): return self - def _prep_inputs_solar_pos(self, kwargs): + def _prep_inputs_solar_pos(self, kwargs={}): """ Assign solar position """ diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 20b43ca1aa..e4bb122e5b 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -344,7 +344,7 @@ def test__assign_total_irrad(sapm_dc_snl_ac_system, location, weather, def test_prepare_inputs_from_poa(sapm_dc_snl_ac_system, location, - total_irrad): + weather, total_irrad): data = weather.copy() data[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad mc = ModelChain(sapm_dc_snl_ac_system, location) From 07b8c4d885dfd55c0aeefce64948d75333ffe22f Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 2 Sep 2020 15:59:30 -0600 Subject: [PATCH 13/23] another test fix --- pvlib/tests/test_modelchain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index e4bb122e5b..61098c98e6 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -373,9 +373,9 @@ def test_run_model_from_effective_irradiance(sapm_dc_snl_ac_system, location, 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(total_irrad).ac + ac = mc.run_model_from_effective_irradiance(data).ac expected = pd.Series(np.array([149.280238, 96.678385]), - index=total_irrad.index) + index=data.index) assert_series_equal(ac, expected) From 4b947ee0e4f14db4fef3a290476117b7a39b5953 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 2 Sep 2020 22:25:56 -0600 Subject: [PATCH 14/23] add test for prepare_temperature --- pvlib/modelchain.py | 3 ++- pvlib/tests/test_modelchain.py | 36 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 068bd16cd3..178811babe 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1189,7 +1189,8 @@ def prepare_inputs_from_poa(self, data): self._assign_weather(data) - self._verify_df(data, required=['poa_global', 'poa_direct', 'poa_diffuse']) + self._verify_df(data, required=['poa_global', 'poa_direct', + 'poa_diffuse']) self._assign_total_irrad(data) self._prep_inputs_solar_pos() diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 61098c98e6..460927df2f 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -357,6 +357,24 @@ def test_prepare_inputs_from_poa(sapm_dc_snl_ac_system, location, assert_series_equal(mc.total_irrad[k], total_irrad[k]) +def test_prepare_temperature(sapm_dc_snl_ac_system, location, weather, + total_irrad): + data = weather.copy() + data[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad + mc = ModelChain(sapm_dc_snl_ac_system, location, aoi_model='no_loss', + spectral_model='no_loss') + mc.prepare_temperature(data) + expected = pd.Series([48.928025, 38.080016], index=data.index) + assert_series_equal(mc.cell_temperature, expected) + data['module_temperature'] = [40., 30.] + mc.prepare_inputs(data) + expected = pd.Series([42.4, 31.5], index=data.index) + assert_series_equal(mc.cell_temperature, expected) + data['cell_temperature'] = [50., 35.] + mc.prepare_temperature(data) + assert_series_equal(mc.cell_temperature, data['cell_temperature']) + + def test_run_model_from_poa(sapm_dc_snl_ac_system, location, total_irrad): mc = ModelChain(sapm_dc_snl_ac_system, location, aoi_model='no_loss', spectral_model='no_loss') @@ -366,6 +384,24 @@ def test_run_model_from_poa(sapm_dc_snl_ac_system, location, total_irrad): assert_series_equal(ac, expected) +def test_run_model_from_poa_tracking(sapm_dc_snl_ac_system, location, + total_irrad): + system = SingleAxisTracker( + module_parameters=sapm_dc_snl_ac_system.module_parameters, + temperature_model_parameters=( + sapm_dc_snl_ac_system.temperature_model_parameters + ), + inverter_parameters=sapm_dc_snl_ac_system.inverter_parameters) + mc = ModelChain(system, location, aoi_model='no_loss', + spectral_model='no_loss') + ac = mc.run_model_from_poa(total_irrad).ac + assert (mc.tracking.columns == ['tracker_theta', 'aoi', 'surface_azimuth', + 'surface_tilt']).all() + expected = pd.Series(np.array([149.280238, 96.678385]), + index=total_irrad.index) + assert_series_equal(ac, expected) + + def test_run_model_from_effective_irradiance(sapm_dc_snl_ac_system, location, weather, total_irrad): data = weather.copy() From 169a28e0afae6e6cd94ea4c0896ad8f56f7a3d9f Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 3 Sep 2020 10:00:36 -0600 Subject: [PATCH 15/23] don't replace missing poa_global with effective_irradiance --- pvlib/modelchain.py | 16 +++------------- pvlib/tests/test_modelchain.py | 2 ++ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 178811babe..0776d7437b 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1203,7 +1203,7 @@ def prepare_inputs_from_poa(self, data): return self - def prepare_temperature(self, data=None): + def _prepare_temperature(self, data=None): """ Sets cell_temperature using inputs in data and the specified temperature model. @@ -1245,17 +1245,7 @@ def prepare_temperature(self, data=None): return self # Calculate cell temperature from weather data. Cell temperature models - # expect total_irrad['poa_global']. If not found, substitute - # self.effective_irradiance. - if 'poa_global' not in self.total_irrad: - try: - self.total_irrad['poa_global'] = self.effective_irradiance - except AttributeError: - # poa_global nor effective_irradiance is found. - raise ValueError( - "Incomplete irradiance data for cell temperature model." - " Provide total_irrad['poa_global'] (preferred) or" - " attribute effective_irradiance.") + # expect total_irrad['poa_global']. self.temperature_model() return self @@ -1361,7 +1351,7 @@ def _run_from_effective_irrad(self, data=None): Assigns ``cell_temperature``, ``dc``, ``ac``, ``losses``, ``diode_params`` (if dc_model is a single diode model). """ - self.prepare_temperature(data) + self._prepare_temperature(data) self.dc_model() self.losses_model() self.ac_model() diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 460927df2f..2f8474c3ba 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -363,6 +363,8 @@ def test_prepare_temperature(sapm_dc_snl_ac_system, location, weather, data[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad mc = ModelChain(sapm_dc_snl_ac_system, location, aoi_model='no_loss', spectral_model='no_loss') + # prepare_temperature expects mc.total_irrad to be set + mc._assign_total_irrad(data) mc.prepare_temperature(data) expected = pd.Series([48.928025, 38.080016], index=data.index) assert_series_equal(mc.cell_temperature, expected) From a67d268426505b326d5c09f62d9b8a559d83a3a4 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 3 Sep 2020 11:00:54 -0600 Subject: [PATCH 16/23] make _prepare_temperature private --- pvlib/tests/test_modelchain.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 2f8474c3ba..9113cc8b0e 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -357,7 +357,7 @@ def test_prepare_inputs_from_poa(sapm_dc_snl_ac_system, location, assert_series_equal(mc.total_irrad[k], total_irrad[k]) -def test_prepare_temperature(sapm_dc_snl_ac_system, location, weather, +def test__prepare_temperature(sapm_dc_snl_ac_system, location, weather, total_irrad): data = weather.copy() data[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad @@ -365,15 +365,15 @@ def test_prepare_temperature(sapm_dc_snl_ac_system, location, weather, spectral_model='no_loss') # prepare_temperature expects mc.total_irrad to be set mc._assign_total_irrad(data) - mc.prepare_temperature(data) + mc._prepare_temperature(data) expected = pd.Series([48.928025, 38.080016], index=data.index) assert_series_equal(mc.cell_temperature, expected) data['module_temperature'] = [40., 30.] - mc.prepare_inputs(data) + mc._prepare_temperature(data) expected = pd.Series([42.4, 31.5], index=data.index) assert_series_equal(mc.cell_temperature, expected) data['cell_temperature'] = [50., 35.] - mc.prepare_temperature(data) + mc._prepare_temperature(data) assert_series_equal(mc.cell_temperature, data['cell_temperature']) From de548012065c2749493af3f85434b07db790a2f2 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 3 Sep 2020 11:50:58 -0600 Subject: [PATCH 17/23] add _assign_weather() to test --- pvlib/tests/test_modelchain.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 9113cc8b0e..d6047d6454 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -363,7 +363,8 @@ def test__prepare_temperature(sapm_dc_snl_ac_system, location, weather, data[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad mc = ModelChain(sapm_dc_snl_ac_system, location, aoi_model='no_loss', spectral_model='no_loss') - # prepare_temperature expects mc.total_irrad to be set + # prepare_temperature expects mc.total_irrad and mc.weather to be set + mc._assign_weather(data) mc._assign_total_irrad(data) mc._prepare_temperature(data) expected = pd.Series([48.928025, 38.080016], index=data.index) From 436b689ccdaeafa7f1c4dcc6f0088b54fa00d350 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 3 Sep 2020 12:08:11 -0600 Subject: [PATCH 18/23] method name to sapm_temp --- pvlib/modelchain.py | 2 +- pvlib/tests/test_modelchain.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 0776d7437b..96e40da943 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1236,7 +1236,7 @@ def _prepare_temperature(self, data=None): # If module_temperature is in input data we can use the SAPM cell # temperature model. if (('module_temperature' in data) and - (self.temperature_model.__name__ == 'sapm')): + (self.temperature_model.__name__ == 'sapm_temp')): # use SAPM cell temperature model only self.cell_temperature = pvlib.temperature.sapm_cell_from_module( module_temperature=data['module_temperature'], diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index d6047d6454..55a6b1efbd 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -358,7 +358,7 @@ def test_prepare_inputs_from_poa(sapm_dc_snl_ac_system, location, def test__prepare_temperature(sapm_dc_snl_ac_system, location, weather, - total_irrad): + total_irrad): data = weather.copy() data[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad mc = ModelChain(sapm_dc_snl_ac_system, location, aoi_model='no_loss', From ea38d99cd345bfcab945e0ad01584a21e4d7e7f8 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 3 Sep 2020 12:40:15 -0600 Subject: [PATCH 19/23] use the correct class --- pvlib/modelchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 96e40da943..92c8fa72f3 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1241,7 +1241,7 @@ def _prepare_temperature(self, data=None): self.cell_temperature = pvlib.temperature.sapm_cell_from_module( module_temperature=data['module_temperature'], poa_global=self.total_irrad['poa_global'], - deltaT=self.temperature_model_parameters['deltaT']) + deltaT=self.system.temperature_model_parameters['deltaT']) return self # Calculate cell temperature from weather data. Cell temperature models From 14771386715b8605c9a95fa9676feea428934aed Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 3 Sep 2020 13:43:01 -0600 Subject: [PATCH 20/23] api, whatsnew --- docs/sphinx/source/api.rst | 13 ++++++++++++- docs/sphinx/source/whatsnew/v0.8.0.rst | 5 +++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index 5ccc5d4062..aa37153a0e 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -545,14 +545,25 @@ Creating a ModelChain object. Running ------- -Running a ModelChain. +A ModelChain can be run from a number of starting points, depending on the +input data available. .. autosummary:: :toctree: generated/ modelchain.ModelChain.run_model + modelchain.ModelChain.run_model_from_poa + modelchain.ModelChain.run_model_from_effective_irradiance + +Functions to assist with setting up ModelChains to run + +.. autosummary:: + :toctree: generated/ + modelchain.ModelChain.complete_irradiance modelchain.ModelChain.prepare_inputs + modelchain.ModelChain.prepare_inputs_from_poa + Attributes ---------- diff --git a/docs/sphinx/source/whatsnew/v0.8.0.rst b/docs/sphinx/source/whatsnew/v0.8.0.rst index e19ddb71f9..a7e476f631 100644 --- a/docs/sphinx/source/whatsnew/v0.8.0.rst +++ b/docs/sphinx/source/whatsnew/v0.8.0.rst @@ -96,6 +96,11 @@ Enhancements * Add :py:func:`pvlib.pvsystem.combine_loss_factors` as general purpose function to combine loss factors with a common index. Partialy addresses :issue:`988`. Contributed by Brock Taute :ghuser:`btaute` +* Add capability to run a ModelChain starting with plane-of-array or effective + irradiance, or with back-of-module or cell temperature data. New methods are + :py:meth:`pvlib.modelchain.ModelChain.run_model_from_poa`, + :py:meth:`pvlib.modelchain.ModelChain.run_model_from_effective_irradiance`, + and :py:meth:`pvlib.modelchain.ModelChain.prepare_inputs_from_poa` (:pull:943) Bug fixes ~~~~~~~~~ From 9113da9eb07b762ee21fab7abe9010e6fbddfdca Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 3 Sep 2020 16:18:30 -0600 Subject: [PATCH 21/23] docstring improvements, use assert_frame_equal --- pvlib/modelchain.py | 55 ++++++++++++++++++++-------------- pvlib/tests/test_modelchain.py | 8 ++--- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 92c8fa72f3..1f362dcfa8 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1269,12 +1269,17 @@ def run_model(self, weather): ------- self + Notes + ----- Assigns attributes: ``solar_position``, ``airmass``, ``weather``, ``total_irrad``, ``aoi``, ``aoi_modifier``, ``spectral_modifier``, - and ``effective_irradiance``. - Calls method `run_model_from_effective_irradiance` - to assign ``cell_temperature``, ``dc``, ``ac``, + and ``effective_irradiance``, ``cell_temperature``, ``dc``, ``ac``, ``losses``, ``diode_params`` (if dc_model is a single diode model). + + See also + -------- + pvlib.modelchain.ModelChain.run_model_from_poa + pvlib.modelchain.ModelChain.run_model_from_effective_irradiance """ self.prepare_inputs(weather) self.aoi_model() @@ -1293,33 +1298,33 @@ def run_model_from_poa(self, data): plane of array. Reflections and spectral adjustments are made to calculate effective irradiance (W/m2). - This method calculates: - * effective irradiance - * cell temperature - * DC output - * AC output - Parameters ---------- data : DataFrame - Required column names must include ``'poa_global'``, + Required column names include ``'poa_global'``, ``'poa_direct'`` and ``'poa_diffuse'``. If optional columns ``'temp_air'`` and ``'wind_speed'`` are not provided, air temperature of 20 C and wind speed of 0 m/s are assumed. If optional column ``'cell_temperature'`` is provided, these values are used instead of `temperature_model`. If optional column - `module_temperature` is provided, `temperature_model` must be + ``'module_temperature'`` is provided, `temperature_model` must be ``'sapm'``. Returns ------- self - Assigns attributes: ``weather``, ``total_irrad``, ``solar_position``, - ``airmass``, ``aoi`` and ``effective_irradiance``. - Calls `run_model_from_effective_irradiance` - to assign ``cell_temperature``, ``dc``, ``ac``, - ``losses``, ``diode_params`` (if dc_model is a single diode model) + Notes + ----- + Assigns attributes: ``solar_position``, ``airmass``, ``weather``, + ``total_irrad``, ``aoi``, ``aoi_modifier``, ``spectral_modifier``, + and ``effective_irradiance``, ``cell_temperature``, ``dc``, ``ac``, + ``losses``, ``diode_params`` (if dc_model is a single diode model). + + See also + -------- + pvlib.modelchain.ModelChain.run_model + pvlib.modelchain.ModelChain.run_model_from_effective_irradiance """ self.prepare_inputs_from_poa(data) @@ -1348,7 +1353,9 @@ def _run_from_effective_irrad(self, data=None): ------- self - Assigns ``cell_temperature``, ``dc``, ``ac``, ``losses``, + Notes + ----- + Assigns attributes:``cell_temperature``, ``dc``, ``ac``, ``losses``, ``diode_params`` (if dc_model is a single diode model). """ self._prepare_temperature(data) @@ -1365,27 +1372,29 @@ def run_model_from_effective_irradiance(self, data=None): Effective irradiance is irradiance in the plane-of-array after any adjustments for soiling, reflections and spectrum. - This method calculates: - * cell temperature - * DC output - * AC output - Parameters ---------- data : DataFrame, default None Required column is ``'effective_irradiance'``. If optional column ``'cell_temperature'`` is provided, these values are used instead of `temperature_model`. If optional column - `module_temperature` is provided, `temperature_model` must be + ``'module_temperature'`` is provided, `temperature_model` must be ``'sapm'``. Returns ------- self + Notes + ----- Assigns attributes: ``weather``, ``total_irrad``, ``effective_irradiance``, ``cell_temperature``, ``dc``, ``ac``, ``losses``, ``diode_params`` (if dc_model is a single diode model). + + See also + -------- + pvlib.modelchain.ModelChain.run_model_from + pvlib.modelchain.ModelChain.run_model_from_poa """ self._assign_weather(data) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 55a6b1efbd..9148eb4272 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -10,7 +10,7 @@ from pvlib.location import Location from pvlib._deprecation import pvlibDeprecationWarning -from conftest import assert_series_equal +from conftest import assert_series_equal, assert_frame_equal import pytest from conftest import fail_on_pvlib_version, requires_scipy @@ -350,11 +350,9 @@ def test_prepare_inputs_from_poa(sapm_dc_snl_ac_system, location, mc = ModelChain(sapm_dc_snl_ac_system, location) mc.prepare_inputs_from_poa(data) # weather attribute - for k in weather: - assert_series_equal(mc.weather[k], weather[k]) + assert_frame_equal(mc.weather[k], weather[k]) # total_irrad attribute - for k in total_irrad: - assert_series_equal(mc.total_irrad[k], total_irrad[k]) + assert_frame_equal(mc.total_irrad[k], total_irrad[k]) def test__prepare_temperature(sapm_dc_snl_ac_system, location, weather, From c72e3d0695ecc04b8456a43853c83e3ddc858f1b Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 3 Sep 2020 16:19:58 -0600 Subject: [PATCH 22/23] delete [k] --- pvlib/tests/test_modelchain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 9148eb4272..0869beeb8b 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -350,9 +350,9 @@ def test_prepare_inputs_from_poa(sapm_dc_snl_ac_system, location, mc = ModelChain(sapm_dc_snl_ac_system, location) mc.prepare_inputs_from_poa(data) # weather attribute - assert_frame_equal(mc.weather[k], weather[k]) + assert_frame_equal(mc.weather, weather) # total_irrad attribute - assert_frame_equal(mc.total_irrad[k], total_irrad[k]) + assert_frame_equal(mc.total_irrad, total_irrad) def test__prepare_temperature(sapm_dc_snl_ac_system, location, weather, From 11cb93b5b445a0df28515e6fc565cea374879e8b Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 5 Sep 2020 15:51:22 -0600 Subject: [PATCH 23/23] add issue to whatsnew --- docs/sphinx/source/whatsnew/v0.8.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx/source/whatsnew/v0.8.0.rst b/docs/sphinx/source/whatsnew/v0.8.0.rst index 3bbb8f6ad2..e3a013562b 100644 --- a/docs/sphinx/source/whatsnew/v0.8.0.rst +++ b/docs/sphinx/source/whatsnew/v0.8.0.rst @@ -109,7 +109,7 @@ Enhancements irradiance, or with back-of-module or cell temperature data. New methods are :py:meth:`pvlib.modelchain.ModelChain.run_model_from_poa`, :py:meth:`pvlib.modelchain.ModelChain.run_model_from_effective_irradiance`, - and :py:meth:`pvlib.modelchain.ModelChain.prepare_inputs_from_poa` (:pull:943) + and :py:meth:`pvlib.modelchain.ModelChain.prepare_inputs_from_poa` (:issue:`536`, :pull:`943`) Bug fixes ~~~~~~~~~