Skip to content

add ASTM E1036 parameter extraction #1585

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 26 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
2 changes: 1 addition & 1 deletion pvlib/ivtools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

"""

from pvlib.ivtools import sde, sdm, utils # noqa: F401
from pvlib.ivtools import sde, sdm, utils, params # noqa: F401
114 changes: 114 additions & 0 deletions pvlib/ivtools/params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""
The ``params`` module contains classes and functions to extract parameters
(e.g. Isc and Voc) from current-voltage curves.
"""
import pandas as pd
import numpy as np
from numpy.polynomial.polynomial import Polynomial as Poly


def astm_e1036(v, i, imax_limits=(0.75, 1.15), vmax_limits=(0.75, 1.15),
voc_points=3, isc_points=3):
'''
Extract photovoltaic IV parameters according to ASTM E1036. Assumes the
curve is in the first quadrant

Parameters
----------
v : array-like
Voltage points
i : array-like
Current points
imax_limits : tuple
Two-element tuple (low, high) specifying the fraction of estimated
Imp within which to fit a polynomial for max power calculation
vmax_limits : tuple
Two-element tuple (low, high) specifying the fraction of estimated
Vmp within which to fit a polynomial for max power calculation
voc_points : int
the number of points near open circuit to use for linear fit
and Voc calculation
isc_points : int
the number of points near short circuit to use for linear fit and
Isc calculation

Returns
-------
dict
Calculated IV parameters

Adapted from https://github.com/NREL/iv_params
Copyright (c) 2022, Alliance for Sustainable Energy, LLC
All rights reserved.
'''

df = pd.DataFrame()
Copy link
Member

@adriesse adriesse Nov 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think everything could all be calculated with numpy only without many changes, if that is of interest.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting suggestion. I don't think I will have time to tackle it, but if you feel it makes a sufficient improvement please go for it. Tests are in place now so it should be easy to verify that it has worked.

df['v'] = v
df['i'] = i
df['p'] = df['v'] * df['i']

# first calculate estimates of voc and isc
voc = np.nan
isc = np.nan

# determine if we can use voc and isc estimates
i_min_ind = df['i'].abs().idxmin()
v_min_ind = df['v'].abs().idxmin()
voc_est = df['v'][i_min_ind]
isc_est = df['i'][v_min_ind]

# accept the estimates if they are close enough
if abs(df['i'][i_min_ind]) <= isc_est * 0.001:
voc = voc_est
if abs(df['v'][v_min_ind]) <= voc_est * 0.005:
isc = isc_est

# perform a linear fit if estimates rejected
if np.isnan(voc):
df['i_abs'] = df['i'].abs()
voc_df = df.nsmallest(voc_points, 'i_abs')
voc_fit = Poly.fit(voc_df['i'], voc_df['v'], 1)
voc = voc_fit(0)

if np.isnan(isc):
df['v_abs'] = df['v'].abs()
isc_df = df.nsmallest(isc_points, 'v_abs')
isc_fit = Poly.fit(isc_df['v'], isc_df['i'], 1)
isc = isc_fit(0)

# estimate max power point
max_index = df['p'].idxmax()
mp_est = df.loc[max_index]

# filter around max power
mask = (
(df['i'] >= imax_limits[0] * mp_est['i']) &
(df['i'] <= imax_limits[1] * mp_est['i']) &
(df['v'] >= vmax_limits[0] * mp_est['v']) &
(df['v'] <= vmax_limits[1] * mp_est['v'])
)
filtered = df[mask]

# fit polynomial and find max
mp_fit = Poly.fit(filtered['v'], filtered['p'], 4)
roots = mp_fit.deriv().roots()
# only coniser real roots
roots = roots.real[abs(roots.imag) < 1e-5]
# only consider roots in the relevant part of the domain
roots = roots[(roots < filtered['v'].max()) &
(roots > filtered['v'].min())]
vmp = roots[np.argmax(mp_fit(roots))]
pmp = mp_fit(vmp)
imp = pmp / vmp

ff = pmp / (voc * isc)

result = {}
result['voc'] = voc
result['isc'] = isc
result['vmp'] = vmp
result['imp'] = imp
result['pmp'] = pmp
result['ff'] = ff

return result