Skip to content

Run ModelChain from POA irradiance #943

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Sep 5, 2020
Merged
Changes from 4 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
70ef8ee
add _from_poa, _from_effective_irradiance methods
cwhanse Mar 27, 2020
4bdbb42
indentation, lint
cwhanse Mar 28, 2020
49e2275
sort out weather, total_irrad, effective_irradiance
cwhanse Mar 29, 2020
49f3c47
Merge branch 'master' of https://github.com/pvlib/pvlib-python into poa
cwhanse May 15, 2020
c524921
Merge branch 'master' of https://github.com/pvlib/pvlib-python into poa
cwhanse Jul 30, 2020
d678978
fix _prep_inputs_solar_pos
cwhanse Jul 30, 2020
ad23a37
press_temp
cwhanse Aug 4, 2020
c30f26b
Merge branch 'master' of https://github.com/pvlib/pvlib-python into poa
cwhanse Aug 12, 2020
6e91a78
reorganize code that assigns weather and total_irrad
cwhanse Aug 17, 2020
b8d9c3d
add prepare_temperature
cwhanse Aug 24, 2020
8149cda
merge master
cwhanse Sep 1, 2020
73854a7
start fixing tests
cwhanse Sep 1, 2020
7452d6b
start on tests
cwhanse Sep 2, 2020
8bda69a
split run_model_from_effective_irradiance
cwhanse Sep 2, 2020
018c2e9
fix tests, sticker
cwhanse Sep 2, 2020
1cb712c
more test fixes
cwhanse Sep 2, 2020
07b8c4d
another test fix
cwhanse Sep 2, 2020
4b947ee
add test for prepare_temperature
cwhanse Sep 3, 2020
169a28e
don't replace missing poa_global with effective_irradiance
cwhanse Sep 3, 2020
a67d268
make _prepare_temperature private
cwhanse Sep 3, 2020
de54801
add _assign_weather() to test
cwhanse Sep 3, 2020
436b689
method name to sapm_temp
cwhanse Sep 3, 2020
ea38d99
use the correct class
cwhanse Sep 3, 2020
74c12bb
Merge branch 'master' of https://github.com/pvlib/pvlib-python into poa
cwhanse Sep 3, 2020
1477138
api, whatsnew
cwhanse Sep 3, 2020
9113da9
docstring improvements, use assert_frame_equal
cwhanse Sep 3, 2020
c72e3d0
delete [k]
cwhanse Sep 3, 2020
91b7c7d
Merge branch 'master' of https://github.com/pvlib/pvlib-python into poa
cwhanse Sep 5, 2020
11cb93b
add issue to whatsnew
cwhanse Sep 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 199 additions & 34 deletions pvlib/modelchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -829,18 +829,59 @@ def complete_irradiance(self, weather, times=None):

return self

def _prep_inputs_solar_pos(self, press_temp=None):
"""
Assign solar position
"""
self.solar_position = self.location.get_solarposition(
self.weather.index, method=self.solar_position_method,
**press_temp)
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
Expand Down Expand Up @@ -871,43 +912,28 @@ def prepare_inputs(self, weather, times=None):

self.times = self.weather.index
try:
kwargs = _build_kwargs(['pressure', 'temp_air'], weather)
kwargs['temperature'] = kwargs.pop('temp_air')
press_temp = _build_kwargs(['pressure', 'temp_air'], weather)
press_temp['temperature'] = press_temp.pop('temp_air')
except KeyError:
pass
press_temp = None

self.solar_position = self.location.get_solarposition(
self.weather.index, method=self.solar_position_method,
**kwargs)

self.airmass = self.location.get_airmass(
solar_position=self.solar_position, model=self.airmass_model)
self._prep_inputs_solar_pos(press_temp)
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'],
self.tracking['surface_azimuth'],
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'],
Expand All @@ -926,17 +952,65 @@ def prepare_inputs(self, weather, times=None):
self.weather['temp_air'] = 20
return self

def prepare_inputs_from_poa(self, total_irrad):
"""
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'``.

Notes
-----
Assigns attributes: ``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.total_irrad = 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:
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
Expand All @@ -961,6 +1035,97 @@ 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, total_irrad):
"""
Run the model starting with broadband irradiance in the plane of array.

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
* cell temperature
* DC output
* AC output

Parameters
----------
total_irrad : DataFrame
Column names must include ``'poa_global'``, ``'poa_direct'``
``'poa_diffuse'``.

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(total_irrad)

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.

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
* AC output

Parameters
----------

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.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.dc_model()
self.losses_model()
Expand Down