-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Making the irradiation and weather DataFrame of the ModelChain more flexible #239
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
Changes from 22 commits
9f97e50
d99ee18
0c83199
9ffa138
2aae90c
326dc11
f1e17ee
933e57b
00e0e72
0abdde4
ec5ed5f
ecf6c6d
9e54af7
4d8feaf
c572a07
9dac5cf
7fda218
7e0b626
300678c
c74f977
ef5520e
dfe04b1
b691358
5f8ce92
1bff19f
721743d
6a1a61d
f702c73
82a4366
34166c8
9ff9fe4
76d89c3
a63a48f
6097d75
da8076d
d4ff364
bfb2350
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,10 +7,11 @@ | |
""" | ||
|
||
from functools import partial | ||
|
||
import logging | ||
import warnings | ||
import pandas as pd | ||
|
||
from pvlib import solarposition, pvsystem, clearsky, atmosphere | ||
from pvlib import (solarposition, pvsystem, clearsky, atmosphere, tools) | ||
from pvlib.tracking import SingleAxisTracker | ||
import pvlib.irradiance # avoid name conflict with full import | ||
|
||
|
@@ -316,6 +317,10 @@ def __init__(self, system, location, | |
self.losses_model = losses_model | ||
self.orientation_strategy = orientation_strategy | ||
|
||
self.weather = pd.DataFrame() | ||
self.times = None | ||
self.solar_position = None | ||
|
||
def __repr__(self): | ||
return ('ModelChain for: ' + str(self.system) + | ||
' orientation_startegy: ' + str(self.orientation_strategy) + | ||
|
@@ -602,32 +607,101 @@ def effective_irradiance_model(self): | |
fd*self.total_irrad['poa_diffuse']) | ||
return self | ||
|
||
def prepare_inputs(self, times, irradiance=None, weather=None): | ||
def complete_irradiance(self, times, weather): | ||
""" | ||
Determine the missing irradiation columns. Only two of the following | ||
data columns (dni, ghi, dhi) are needed to calculate the missing data. | ||
|
||
This function is not save at the moment. Results can be too high or | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. save --> safe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
negative. Please contribute and help to improve this function on | ||
https://github.com/pvlib/pvlib-python | ||
|
||
Parameters | ||
---------- | ||
times : datetime index | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DatetimeIndex There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
Date and time index of the irradiation data | ||
weather : pandas.DataFrame | ||
Table with at least two columns containing one of the following data | ||
sets: dni, dhi, ghi | ||
|
||
Returns | ||
------- | ||
pandas.DataFrame | ||
Containing the missing column of the data sets passed with the | ||
weather DataFrame. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unneeded extra line here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
""" | ||
self.weather = weather | ||
self.times = times | ||
self.solar_position = self.location.get_solarposition(self.times) | ||
icolumns = set(self.weather.columns) | ||
wrn_txt = "This function is not save at the moment.\n" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. save --> safe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe better to write as
instead of using += There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
wrn_txt += "Results can be too high or negative.\n" | ||
wrn_txt += "Help to improve this function on github." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need a space or \n after the . There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
wrn_txt += "https://github.com/pvlib/pvlib-python" | ||
warnings.warn(wrn_txt, UserWarning) | ||
if {'ghi', 'dhi'} <= icolumns and 'dni' not in icolumns: | ||
logging.debug('Estimate dni from ghi and dhi') | ||
self.weather.loc[:, 'dni'] = ( | ||
(self.weather.loc[:, 'ghi'] - self.weather.loc[:, 'dhi']) / | ||
tools.cosd(self.solar_position.loc[:, 'zenith'])) | ||
elif {'dni', 'dhi'} <= icolumns and 'ghi' not in icolumns: | ||
logging.debug('Estimate ghi from dni and dhi') | ||
self.weather.loc[:, 'ghi'] = ( | ||
self.weather.dni * tools.cosd(self.solar_position.zenith) + | ||
self.weather.dhi) | ||
elif {'dni', 'ghi'} <= icolumns and 'dhi' not in icolumns: | ||
logging.debug('Estimate dhi from dni and ghi') | ||
self.weather.loc[:, 'dhi'] = ( | ||
self.weather.ghi - self.weather.dni * | ||
tools.cosd(self.solar_position.zenith)) | ||
|
||
def prepare_inputs(self, times=None, irradiance=None, weather=None): | ||
""" | ||
Prepare the solar position, irradiance, and weather inputs to | ||
the model. | ||
|
||
Parameters | ||
---------- | ||
times : DatetimeIndex | ||
Times at which to evaluate the model. | ||
irradiance : None or DataFrame | ||
If None, calculates clear sky data. | ||
Columns must be 'dni', 'ghi', 'dhi'. | ||
Times at which to evaluate the model. Can be None if attribute | ||
`times` is already set. | ||
weather : None or DataFrame | ||
If None, assumes air temperature is 20 C and | ||
wind speed is 0 m/s. | ||
Columns must be 'wind_speed', 'temp_air'. | ||
If None, the weather attribute is used. If the weather attribute is | ||
also None assumes air temperature is 20 C, wind speed is 0 m/s and | ||
irradiation calculated from clear sky data. | ||
Column names must be 'wind_speed', 'temp_air', 'dni', 'ghi', 'dhi'. | ||
Do not pass incomplete irradiation data. | ||
Use method | ||
:py:meth:`~pvlib.modelchain.ModelChain.complete_irradiance` | ||
instead. | ||
|
||
Returns | ||
------- | ||
self | ||
|
||
Assigns attributes: times, solar_position, airmass, irradiance, | ||
total_irrad, weather, aoi | ||
Assigns attributes: times, solar_position, airmass, total_irrad, aoi | ||
""" | ||
|
||
self.times = times | ||
# Add columns that does not exist and overwrite existing columns | ||
# Maybe there is a more elegant way to do this. Any ideas? | ||
if weather is not None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be best for pvlib to only use a simple assignment. Users can do this in their own code before passing the parameter or making the assignment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
self.weather = self.weather.combine_first(weather) | ||
self.weather.update(weather) | ||
|
||
# The following part could be removed together with the irradiance | ||
# parameter at version v0.5 or v0.6. | ||
# **** Begin **** | ||
wrn_txt = "The irradiance parameter will be removed soon.\n" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same += comment as above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
wrn_txt += "Please use the weather parameter to pass a DataFrame with " | ||
wrn_txt += "irradiance (ghi, dni, dhi), wind speed and temp_air" | ||
if irradiance is not None: | ||
warnings.warn(wrn_txt, FutureWarning) | ||
for column in irradiance.columns: | ||
self.weather[column] = irradiance.pop(column) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do you pop the column? I think this would mutate the input data, which is usually not a nice thing to do. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
# **** End **** | ||
|
||
if times is not None: | ||
self.times = times | ||
|
||
self.solar_position = self.location.get_solarposition(self.times) | ||
|
||
|
@@ -637,12 +711,15 @@ def prepare_inputs(self, times, irradiance=None, weather=None): | |
self.aoi = self.system.get_aoi(self.solar_position['apparent_zenith'], | ||
self.solar_position['azimuth']) | ||
|
||
if irradiance is None: | ||
irradiance = self.location.get_clearsky( | ||
if not any([x in ['ghi', 'dni', 'dhi'] for x in self.weather.columns]): | ||
self.weather[['ghi', 'dni', 'dhi']] = self.location.get_clearsky( | ||
self.solar_position.index, self.clearsky_model, | ||
zenith_data=self.solar_position['apparent_zenith'], | ||
airmass_data=self.airmass['airmass_absolute']) | ||
self.irradiance = irradiance | ||
|
||
if not {'ghi', 'dni', 'dhi'} <= set(self.weather.columns): | ||
ValueError( | ||
"Uncompleted irradiance data set. Please check you input data") | ||
|
||
# PVSystem.get_irradiance and SingleAxisTracker.get_irradiance | ||
# have different method signatures, so use partial to handle | ||
|
@@ -670,16 +747,16 @@ def prepare_inputs(self, times, irradiance=None, weather=None): | |
self.solar_position['azimuth']) | ||
|
||
self.total_irrad = get_irradiance( | ||
self.irradiance['dni'], | ||
self.irradiance['ghi'], | ||
self.irradiance['dhi'], | ||
self.weather['dni'], | ||
self.weather['ghi'], | ||
self.weather['dhi'], | ||
airmass=self.airmass['airmass_relative'], | ||
model=self.transposition_model) | ||
|
||
if weather is None: | ||
weather = {'wind_speed': 0, 'temp_air': 20} | ||
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 run_model(self, times, irradiance=None, weather=None): | ||
|
@@ -690,15 +767,14 @@ def run_model(self, times, irradiance=None, weather=None): | |
---------- | ||
times : DatetimeIndex | ||
Times at which to evaluate the model. | ||
|
||
irradiance : None or DataFrame | ||
If None, calculates clear sky data. | ||
Columns must be 'dni', 'ghi', 'dhi'. | ||
|
||
weather : None or DataFrame | ||
If None, assumes air temperature is 20 C and | ||
wind speed is 0 m/s. | ||
Columns must be 'wind_speed', 'temp_air'. | ||
If None, assumes air temperature is 20 C, wind speed is 0 m/s and | ||
irradiation calculated from clear sky data. | ||
Column names must be 'wind_speed', 'temp_air', 'dni', 'ghi', 'dhi'. | ||
Do not pass incomplete irradiation data. | ||
Use method | ||
:py:meth:`~pvlib.modelchain.ModelChain.complete_irradiance` | ||
instead. | ||
|
||
Returns | ||
------- | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why make this a DataFrame instead of None (like the values below)? I'm not sure if one way is better than the other, but consistency might be more important.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It saves one if-clause later on. I have check weather the DataFrame exists and if the specific column exists. Creating an empty DataFrame I do not need the first check.
Commit 1bff19f shows the consequences of this change.