-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add functions to fit and convert IAM models #1827
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
Changes from all commits
Commits
Show all changes
72 commits
Select commit
Hold shift + click to select a range
9816a3f
Adding functions, docstrings draft
ajonesr dacc1f0
Updating imports, adding docstrings
ajonesr fc968af
Updated rst file
ajonesr 441d8cc
Make linter edits
ajonesr 77641d0
Docstring experiment
ajonesr d7cbf5e
Added tests
ajonesr b4dcecf
More linter edits
ajonesr 6d054f4
Docstrings and linter edits
ajonesr 21a66ee
Docstrings and linter
ajonesr b4714a4
LINTER
ajonesr 96de7d9
Docstrings edit
ajonesr de20629
Added more tests
ajonesr 91f811e
Annihilate spaces
ajonesr e4d4cdc
Spacing
ajonesr 3789890
Changed default weight function
ajonesr 2efa830
Silence numpy warning
ajonesr c5c2d09
Updating tests to work with new default
ajonesr af65bc0
Forgot a comment
ajonesr d505ac9
Return dict contains scalars now, instead of arrays
ajonesr 109e20e
Adding option to not fix n
ajonesr 554d862
Adding straggler tests
ajonesr e8d83b6
Removing examples specific to old default weight function
ajonesr ee9c686
Linter nitpicks
ajonesr e95993d
Update docstrings
ajonesr 991e962
Experimenting with example
ajonesr 484cb5a
Adjusting figure size
ajonesr 47ebdac
Edit gallery example
ajonesr 317fb35
Fixing bounds
ajonesr 3996cab
Linter
ajonesr ba87f7e
Example experimentation
ajonesr ac4e717
Merge branch 'main' of https://github.com/pvlib/pvlib-python into con…
cwhanse 529e512
exact ashrae intercept
cwhanse 6b211fd
Merge branch 'main' of https://github.com/pvlib/pvlib-python into con…
cwhanse 3fc2c00
editing docstrings mostly
cwhanse a9f9b74
whatsnew
cwhanse ac160b8
fix errors
cwhanse 536cb9f
remove test for weight function size
cwhanse 2882912
editing
cwhanse 9bb36b5
simplify weight function
cwhanse 753d72b
Merge branch 'main' of https://github.com/pvlib/pvlib-python into con…
cwhanse fc1316c
improve martin_ruiz to physical, generalize tests
cwhanse 935443b
fix examples, split convert and fit examples
cwhanse c9f697d
linter, improve coverage
cwhanse 88a9dfc
spacing
cwhanse 8ace9d6
fix reverse order test
cwhanse 3475bf4
improve examples
cwhanse f216d94
print parameters
cwhanse cb4cb05
whatsnew
cwhanse ed35731
remove v0.10.2 whatsnew
cwhanse fdcc952
Revert "remove v0.10.2 whatsnew"
cwhanse 9b1cfd8
put v0.10.2.rst right again
cwhanse d78265a
Merge branch 'main' of https://github.com/pvlib/pvlib-python into con…
cwhanse 38bfb58
require scipy>=1.5.0
cwhanse 04121de
linter
cwhanse 69cd00a
linter
cwhanse 520a74e
Merge branch 'main' of https://github.com/pvlib/pvlib-python into con…
cwhanse e5cd24b
suggestions from review
cwhanse 6ce34e4
add reference
cwhanse 743931d
edits to examples
cwhanse fe9a39c
add note to convert
cwhanse d56cbcf
edit note on convert
cwhanse 2a0b815
edit both notes
cwhanse 4e165a0
polish the notes
cwhanse d3d8cfd
sum not Sum
cwhanse 313386c
edits
cwhanse b0e45dd
Merge branch 'main' of https://github.com/pvlib/pvlib-python into con…
cwhanse 32aa64a
remove test for scipy
cwhanse 8df7bf6
edits from review
cwhanse 6250182
its not it's
cwhanse 74c2e54
Merge branch 'main' of https://github.com/pvlib/pvlib-python into con…
cwhanse f9c8888
change internal linspace to one degree intervals
cwhanse 79af432
use linspace(0, 90, 91)
cwhanse File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
|
||
""" | ||
IAM Model Conversion | ||
==================== | ||
|
||
Illustrates how to convert from one IAM model to a different model using | ||
:py:func:`~pvlib.iam.convert`. | ||
|
||
""" | ||
|
||
# %% | ||
# An incidence angle modifier (IAM) model quantifies the fraction of direct | ||
# irradiance that is reflected away from a module's surface. Three popular | ||
# IAM models are Martin-Ruiz :py:func:`~pvlib.iam.martin_ruiz`, physical | ||
# :py:func:`~pvlib.iam.physical`, and ASHRAE :py:func:`~pvlib.iam.ashrae`. | ||
# Each model requires one or more parameters. | ||
# | ||
# Here, we show how to use | ||
# :py:func:`~pvlib.iam.convert` to estimate parameters for a desired target | ||
# IAM model from a source IAM model. Model conversion uses a weight | ||
# function that can assign more influence to some AOI values than others. | ||
# We illustrate how to provide a custom weight function to | ||
# :py:func:`~pvlib.iam.convert`. | ||
|
||
import numpy as np | ||
import matplotlib.pyplot as plt | ||
|
||
from pvlib.tools import cosd | ||
from pvlib.iam import (ashrae, martin_ruiz, physical, convert) | ||
|
||
# %% | ||
# Converting from one IAM model to another model | ||
# ---------------------------------------------- | ||
# | ||
# Here we'll show how to convert from the Martin-Ruiz model to the | ||
# physical and the ASHRAE models. | ||
|
||
# Compute IAM values using the martin_ruiz model. | ||
aoi = np.linspace(0, 90, 100) | ||
martin_ruiz_params = {'a_r': 0.16} | ||
martin_ruiz_iam = martin_ruiz(aoi, **martin_ruiz_params) | ||
|
||
# Get parameters for the physical model and compute IAM using these parameters. | ||
physical_params = convert('martin_ruiz', martin_ruiz_params, 'physical') | ||
physical_iam = physical(aoi, **physical_params) | ||
|
||
# Get parameters for the ASHRAE model and compute IAM using these parameters. | ||
ashrae_params = convert('martin_ruiz', martin_ruiz_params, 'ashrae') | ||
ashrae_iam = ashrae(aoi, **ashrae_params) | ||
|
||
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 5), sharey=True) | ||
|
||
# Plot each model's IAM vs. angle-of-incidence (AOI). | ||
ax1.plot(aoi, martin_ruiz_iam, label='Martin-Ruiz') | ||
ax1.plot(aoi, physical_iam, label='physical') | ||
ax1.set_xlabel('AOI (degrees)') | ||
ax1.set_title('Convert from Martin-Ruiz to physical') | ||
ax1.legend() | ||
|
||
ax2.plot(aoi, martin_ruiz_iam, label='Martin-Ruiz') | ||
ax2.plot(aoi, ashrae_iam, label='ASHRAE') | ||
ax2.set_xlabel('AOI (degrees)') | ||
ax2.set_title('Convert from Martin-Ruiz to ASHRAE') | ||
ax2.legend() | ||
|
||
ax1.set_ylabel('IAM') | ||
plt.show() | ||
|
||
|
||
# %% | ||
# The weight function | ||
# ------------------- | ||
# :py:func:`pvlib.iam.convert` uses a weight function when computing residuals | ||
# between the two models. The default weight | ||
# function is :math:`1 - \sin(aoi)`. We can instead pass a custom weight | ||
# function to :py:func:`pvlib.iam.convert`. | ||
# | ||
# In some cases, the choice of weight function has a minimal effect on the | ||
# returned model parameters. This is especially true when converting between | ||
# the Martin-Ruiz and physical models, because the curves described by these | ||
# models can match quite closely. However, when conversion involves the ASHRAE | ||
# model, the choice of weight function can have a meaningful effect on the | ||
# returned parameters for the target model. | ||
# | ||
# Here we'll show examples of both of these cases, starting with an example | ||
# where the choice of weight function does not have much impact. In doing | ||
# so, we'll show how to pass in a custom weight function of our choice. | ||
|
||
# Compute IAM using the Martin-Ruiz model. | ||
aoi = np.linspace(0, 90, 100) | ||
martin_ruiz_params = {'a_r': 0.16} | ||
martin_ruiz_iam = martin_ruiz(aoi, **martin_ruiz_params) | ||
|
||
# Get parameters for the physical model ... | ||
|
||
# ... using the default weight function. | ||
physical_params_default = convert('martin_ruiz', martin_ruiz_params, | ||
'physical') | ||
physical_iam_default = physical(aoi, **physical_params_default) | ||
|
||
|
||
# ... using a custom weight function. The weight function must take ``aoi`` | ||
# as its argument and return a vector of the same length as ``aoi``. | ||
def weight_function(aoi): | ||
return cosd(aoi) | ||
|
||
|
||
physical_params_custom = convert('martin_ruiz', martin_ruiz_params, 'physical', | ||
weight=weight_function) | ||
physical_iam_custom = physical(aoi, **physical_params_custom) | ||
|
||
# Plot IAM vs AOI. | ||
plt.plot(aoi, martin_ruiz_iam, label='Martin-Ruiz') | ||
plt.plot(aoi, physical_iam_default, label='Default weight function') | ||
plt.plot(aoi, physical_iam_custom, label='Custom weight function') | ||
plt.xlabel('AOI (degrees)') | ||
plt.ylabel('IAM') | ||
plt.title('Martin-Ruiz to physical') | ||
plt.legend() | ||
plt.show() | ||
|
||
# %% | ||
# For this choice of source and target models, the weight function has little | ||
# effect on the target model's parameters. | ||
# | ||
# Now we'll look at an example where the weight function does affect the | ||
# output. | ||
|
||
# Get parameters for the ASHRAE model ... | ||
|
||
# ... using the default weight function. | ||
ashrae_params_default = convert('martin_ruiz', martin_ruiz_params, 'ashrae') | ||
ashrae_iam_default = ashrae(aoi, **ashrae_params_default) | ||
|
||
# ... using the custom weight function | ||
ashrae_params_custom = convert('martin_ruiz', martin_ruiz_params, 'ashrae', | ||
weight=weight_function) | ||
ashrae_iam_custom = ashrae(aoi, **ashrae_params_custom) | ||
|
||
# Plot IAM vs AOI. | ||
plt.plot(aoi, martin_ruiz_iam, label='Martin-Ruiz') | ||
plt.plot(aoi, ashrae_iam_default, label='Default weight function') | ||
plt.plot(aoi, ashrae_iam_custom, label='Custom weight function') | ||
plt.xlabel('AOI (degrees)') | ||
plt.ylabel('IAM') | ||
plt.title('Martin-Ruiz to ASHRAE') | ||
plt.legend() | ||
plt.show() | ||
|
||
# %% | ||
# In this case, each of the two ASHRAE looks quite different. | ||
# Finding the right weight function and parameters in such cases will require | ||
# knowing where you want the target model to be more accurate. The default | ||
# weight function was chosen because it yielded IAM models that produce | ||
# similar annual insolation for a simulated PV system. | ||
|
||
# %% | ||
# Reference | ||
# --------- | ||
# .. [1] Jones, A. R., Hansen, C. W., Anderson, K. S. Parameter estimation | ||
# for incidence angle modifier models for photovoltaic modules. Sandia | ||
# report SAND2023-13944 (2023). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
|
||
""" | ||
IAM Model Fitting | ||
================================ | ||
|
||
Illustrates how to fit an IAM model to data using :py:func:`~pvlib.iam.fit`. | ||
|
||
""" | ||
|
||
# %% | ||
# An incidence angle modifier (IAM) model quantifies the fraction of direct | ||
# irradiance is that is reflected away from a module's surface. Three popular | ||
# IAM models are Martin-Ruiz :py:func:`~pvlib.iam.martin_ruiz`, physical | ||
# :py:func:`~pvlib.iam.physical`, and ASHRAE :py:func:`~pvlib.iam.ashrae`. | ||
# Each model requires one or more parameters. | ||
# | ||
# Here, we show how to use | ||
# :py:func:`~pvlib.iam.fit` to estimate a model's parameters from data. | ||
# | ||
# Model fitting require a weight function that can assign | ||
# more influence to some AOI values than others. We illustrate how to provide | ||
# a custom weight function to :py:func:`~pvlib.iam.fit`. | ||
|
||
import numpy as np | ||
from random import uniform | ||
import matplotlib.pyplot as plt | ||
|
||
from pvlib.tools import cosd | ||
from pvlib.iam import (martin_ruiz, physical, fit) | ||
|
||
|
||
# %% | ||
# Fitting an IAM model to data | ||
# ---------------------------- | ||
# | ||
# Here, we'll show how to fit an IAM model to data. | ||
# We'll generate some data by perturbing output from the Martin-Ruiz model to | ||
# mimic measured data and then we'll fit the physical model to the perturbed | ||
# data. | ||
|
||
# Create some IAM data. | ||
aoi = np.linspace(0, 85, 10) | ||
params = {'a_r': 0.16} | ||
iam = martin_ruiz(aoi, **params) | ||
data = iam * np.array([uniform(0.98, 1.02) for _ in range(len(iam))]) | ||
|
||
# Get parameters for the physical model by fitting to the perturbed data. | ||
physical_params = fit(aoi, data, 'physical') | ||
|
||
# Compute IAM with the fitted physical model parameters. | ||
physical_iam = physical(aoi, **physical_params) | ||
|
||
# Plot IAM vs. AOI | ||
plt.scatter(aoi, data, c='darkorange', label='Data') | ||
plt.plot(aoi, physical_iam, label='physical') | ||
plt.xlabel('AOI (degrees)') | ||
plt.ylabel('IAM') | ||
plt.title('Fitting the physical model to data') | ||
plt.legend() | ||
plt.show() | ||
|
||
|
||
# %% | ||
# The weight function | ||
# ------------------- | ||
# :py:func:`pvlib.iam.fit` uses a weight function when computing residuals | ||
# between the model and data. The default weight | ||
# function is :math:`1 - \sin(aoi)`. We can instead pass a custom weight | ||
# function to :py:func:`pvlib.iam.fit`. | ||
# | ||
|
||
# Define a custom weight function. The weight function must take ``aoi`` | ||
# as its argument and return a vector of the same length as ``aoi``. | ||
def weight_function(aoi): | ||
return cosd(aoi) | ||
|
||
|
||
physical_params_custom = fit(aoi, data, 'physical', weight=weight_function) | ||
|
||
physical_iam_custom = physical(aoi, **physical_params_custom) | ||
|
||
# Plot IAM vs AOI. | ||
fig, ax = plt.subplots(2, 1, figsize=(5, 8)) | ||
ax[0].plot(aoi, data, '.', label='Data (from Martin-Ruiz model)') | ||
ax[0].plot(aoi, physical_iam, label='With default weight function') | ||
ax[0].plot(aoi, physical_iam_custom, label='With custom weight function') | ||
ax[0].set_xlabel('AOI (degrees)') | ||
ax[0].set_ylabel('IAM') | ||
ax[0].legend() | ||
|
||
ax[1].plot(aoi, physical_iam_custom - physical_iam, label='Custom - default') | ||
ax[1].set_xlabel('AOI (degrees)') | ||
ax[1].set_ylabel('Diff. in IAM') | ||
ax[1].legend() | ||
plt.tight_layout() | ||
plt.show() | ||
|
||
print("Parameters with default weights: " + str(physical_params)) | ||
print("Parameters with custom weights: " + str(physical_params_custom)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,3 +17,5 @@ Incident angle modifiers | |
iam.marion_integrate | ||
iam.schlick | ||
iam.schlick_diffuse | ||
iam.convert | ||
iam.fit |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
I suggest you say a bit more about the shape and purpose of these functions. I see there is more in the other example, but I think it is more relevant or important here.