Skip to content

Add mismatch.py and function to combine curves #1781

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

Draft
wants to merge 30 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
06abfaa
Added mismatch.py
ajonesr Jun 20, 2023
3159bc1
Make method in bishop functions explicit
ajonesr Jun 21, 2023
76efbac
Move clipping to prepare_curves
ajonesr Jun 21, 2023
586de8f
Change assert to ValueError
ajonesr Jun 21, 2023
3038f86
Added docstrings
ajonesr Jun 22, 2023
c0e57da
Fixed missing parenthesis
ajonesr Jun 22, 2023
0670a6f
Updated comments, fixed typo
ajonesr Jun 22, 2023
a3a135b
Updated imports
ajonesr Jun 22, 2023
c27f558
Updated .rst file
ajonesr Jun 22, 2023
5dfda15
Updated docstrings
ajonesr Jun 22, 2023
2f11a16
Update docstring of prepare_curves
ajonesr Jun 23, 2023
3effafe
Update docstring of combine_curves
ajonesr Jun 23, 2023
b969581
Fixed spacing and clipped_voltage typo
ajonesr Jun 23, 2023
7ab461c
Reverse order of currents array
ajonesr Jun 23, 2023
d87f9df
Add tests
ajonesr Jun 23, 2023
cf955a3
Fixed types, updated tests
ajonesr Jun 23, 2023
a014b2a
Updated whatsnew
ajonesr Jun 23, 2023
1f5914a
Added contributor
ajonesr Jun 23, 2023
94e923c
Update prepare_curves docstring
ajonesr Jun 26, 2023
31d1188
Update prepare_curves docstring
ajonesr Jun 26, 2023
0880f24
Update prepare_curves docstring
ajonesr Jun 26, 2023
e87b4aa
Update spacing
ajonesr Jun 26, 2023
057cada
Update spacing
ajonesr Jun 26, 2023
606de91
Add tests for ValueErrors
ajonesr Jun 26, 2023
baeac3f
Merge branch 'main' into mismatch
ajonesr Jun 26, 2023
ded4548
Update init
ajonesr Jun 26, 2023
52b7455
Added example
ajonesr Jul 10, 2023
2ca0cb0
Merge remote-tracking branch 'upstream/main' into mismatch
ajonesr Jul 13, 2023
8cca7b7
Update whatsnew, example
ajonesr Jul 14, 2023
17d738c
Merge branch 'main' of https://github.com/pvlib/pvlib-python into mis…
cwhanse Aug 11, 2023
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
72 changes: 72 additions & 0 deletions docs/examples/iv-modeling/plot_mismatch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""
Calculating a combined IV curve
===============================

Here we show how to use pvlib to combine IV curves in series.

Differences in weather (irradiance or temperature) or module condition can
cause two modules (or cells) to produce different current-voltage (IV)
characteristics or curves. Series-connected modules produce a string-level IV
curve, which can be obtained by combining the curves for the individual
modules. Combining the curves involves modeling IV curves at negative voltage,
because some modules (cells) in the series circuit will produce more
photocurrent than others but the combined current cannot exceed that of the
lowest-current module (cell).
"""

# %%
#
# pvlib provides two functions to combine series-connected curves:
#
#* :py:func:`pvlib.ivtools.mismatch.prepare_curves` uses parameters for the
# single diode equation and a simple model for negative voltage behavior to
# compute IV curves at a common set of currents
#
#* :py:func:`pvlib.ivtools.mismatch.combine_curves` produces the combined IV
# curve from a set of IV curves with common current values.


import numpy as np
import matplotlib.pyplot as plt
from scipy.constants import Boltzmann, elementary_charge

from pvlib.ivtools.mismatch import prepare_curves, combine_curves


# set up parameter array

# the parameters should be in the order: photocurrent, saturation
# current, series resistance, shunt resistance, and n*Vth*Ns
# these are the parameters for the single diode function

# example array of parameters
vth = 298.15 * Boltzmann / elementary_charge
params = np.array([[1.0, 3e-08, 1.0, 300, 1.3*vth*72],
[3, 3e-08, 0.1, 300, 1.01*vth*72],
[2, 5e-10, 0.1, 300, 1.1*vth*72]])

# prepare inputs for combine_curves
brk_voltage = -1.5
currents, voltages_array = prepare_curves(params, num_pts=100,
breakdown_voltage=brk_voltage)

# compute combined curve
combined_curve_dict = combine_curves(currents, voltages_array)

# plot all curves and combined curve
for idx in range(len(voltages_array)):
v = voltages_array[idx]
plt.plot(v, currents, label=f"Panel {idx+1}")

plt.plot(combined_curve_dict['v'], combined_curve_dict['i'],
label="Combined curve")

# plot vertical line at breakdown voltage (used in simplified
# reverse bias model)
plt.vlines(brk_voltage, ymin=0.0, ymax=combined_curve_dict['i_sc'], ls='--',
color='k', linewidth=1, label="Breakdown voltage")

plt.xlabel("Voltage [V]")
plt.ylabel("Current [A]")
plt.legend()
plt.show()
8 changes: 8 additions & 0 deletions docs/sphinx/source/reference/pv_modeling/sdm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ Functions relevant for single diode models.
pvsystem.max_power_point
ivtools.sdm.pvsyst_temperature_coeff

Functions for combining IV curves in series.

.. autosummary::
:toctree: ../generated/

ivtools.mismatch.prepare_curves
ivtools.mismatch.combine_curves

Low-level functions for solving the single diode equation.

.. autosummary::
Expand Down
1 change: 1 addition & 0 deletions docs/sphinx/source/whatsnew/v0.10.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Enhancements
:py:func:`~pvlib.singlediode.bishop88_v_from_i`. Among others,
tolerance and number of iterations can be set.
(:issue:`1249`, :pull:`1764`)
* Improved `ModelChainResult.__repr__` (:pull:`1236`)
* Improved ``ModelChainResult.__repr__`` (:pull:`1236`)
* Exposes several functions useful for bifacial and shading calculations (:pull:`1666`):

Expand Down
4 changes: 4 additions & 0 deletions docs/sphinx/source/whatsnew/v0.10.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ Enhancements
:py:func:`pvlib.iotools.get_pvgis_hourly`, :py:func:`pvlib.iotools.get_cams`,
:py:func:`pvlib.iotools.get_bsrn`, and :py:func:`pvlib.iotools.read_midc_raw_data_from_nrel`.
(:pull:`1800`)
* Added a new module :py:mod:`pvlib.ivtools.mismatch` to contain functions for
combining IV curves. Added functions
:py:func:`pvlib.ivtools.mismatch.prepare_curves` and
:py:func:`pvlib.ivtools.mismatch.combine_curves`. (:pull:`1781`)
* Added option to infer threshold values for
:py:func:`pvlib.clearsky.detect_clearsky` (:issue:`1808`, :pull:`1784`)

Expand Down
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 mismatch, sde, sdm, utils # noqa: F401
194 changes: 194 additions & 0 deletions pvlib/ivtools/mismatch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
"""
The `mismatch` module contains functions for combining curves in
series using the single diode model.
"""

import numpy as np
from pvlib.singlediode import bishop88_i_from_v, bishop88_v_from_i


def prepare_curves(params, num_pts, breakdown_voltage=-0.5):
"""
Calculates currents and voltages on IV curves with the given
parameters, using the single diode equation, and a simple
model for reverse bias behavior.

The current values are linearly spaced from the maximum Isc for all
curves to 0. All curves have the same current values.

Returns currents and voltages in the format needed for input to
:func:`combine_curves`.

Parameters
----------
params : array-like
An array of parameters representing a set of :math:`n` IV
curves. The array should contain :math:`n` rows and five
columns. Each row contains the five parameters needed for a
single curve in the following order:

photocurrent : numeric
photo-generated current :math:`I_{L}` [A]

saturation_current : numeric
diode reverse saturation current :math:`I_{0}` [A]

resistance_series : numeric
series resistance :math:`R_{s}` [ohms]

resistance_shunt : numeric
shunt resistance :math:`R_{sh}` [ohms]

nNsVth : numeric
product of thermal voltage :math:`V_{th}` [V], diode
ideality factor :math:`n`, and number of series cells
:math:`N_{s}` [V]

num_pts : int
Number of points to compute for each IV curve.

breakdown_voltage : float
Vertical asymptote to use left of the y-axis. Any voltages that
are smaller than ``breakdown_voltage`` will be replaced by it.

Returns
-------
tuple
currents : np.ndarray
A 1D array of current values. Has shape (``num_pts``,).

voltages : np.ndarray
A 2D array of voltage values, where each row corresponds to
a single IV curve. Has shape (:math:`n`, ``num_pts``), where
:math:`n` is the number of IV curves passed in.

Notes
-----
This function assumes a simplified reverse bias model. IV curves are
solved using :func:`pvlib.singlediode.bishop88_v_from_i` with
``breakdown_factor`` left at the default value of 0., which excludes
the reverse bias term from the calculation. Here, voltages that are
less than ``breakdown_voltage`` are set equal to ``breakdown_voltage``
effectively modeling the reverse bias curve by a vertical line
at ``breakdown_voltage``.

"""

params = np.asarray(params)
# in case params is a list containing scalars, add a dimension
if params.ndim == 1:
params = params[np.newaxis,:]

# get range of currents from 0 to max_isc
max_isc = np.max(bishop88_i_from_v(0.0, *params.T, method='newton'))
currents = np.linspace(max_isc, 0, num=num_pts, endpoint=True)

# prepare inputs for bishop88
bishop_inputs = np.array([[currents[idx]]*len(params) for idx in
range(num_pts)])
# each row of bishop_inputs contains n copies of a single current
# value, where n is the number of curves being added together
# there is a row for each current value

# get voltages for each curve
# (note: expecting to vectorize for both the inputted currents and
# the inputted curve parameters)
# transpose result so each row contains voltages for a single curve
voltages = bishop88_v_from_i(bishop_inputs, *params.T, method='newton').T

# any voltages in array that are smaller than breakdown_voltage are
# clipped to be breakdown_voltage
voltages = np.clip(voltages, a_min=breakdown_voltage, a_max=None)

return currents, voltages


def combine_curves(currents, voltages):
"""
Combines IV curves in series.

Parameters
----------
currents : array-like
A 1D array-like object. Its last element must be zero, and it
should be decreasing.

voltages : array-like
A 2D array-like object. Each row corresponds to a single IV
curve and contains the voltages for that curve that are
associated to elements of ``currents``. Each row must be
increasing.

Returns
-------
dict
Contains the following keys:
i_sc : scalar
short circuit current of combined curve [A]

v_oc : scalar
open circuit voltage of combined curve [V]

i_mp : scalar
current at maximum power point of combined curve [A]

v_mp : scalar
voltage at maximum power point of combined curve [V]

p_mp : scalar
power at maximum power point of combined curve [W]

i : np.ndarray
currents of combined curve [A]

v : np.ndarray
voltages of combined curve [V]

Notes
-----
If the combined curve does not cross the y-axis, then the first (and
hence largest) current is returned for short circuit current.

The maximum power point that is returned is the maximum power point
of the dataset. Its accuracy will improve as more points are passed
in.

"""

currents = np.asarray(currents)
voltages = np.asarray(voltages)
assert currents.ndim == 1
assert voltages.ndim == 2

# for each current, add the associated voltages of all the curves
# in our setup, this means summing each column of the voltage array
combined_voltages = np.sum(voltages, axis=0)

# combined_voltages should now have same shape as currents
assert np.shape(combined_voltages) == np.shape(currents)

# find max power point (in the dataset)
powers = currents*combined_voltages
mpp_idx = np.argmax(powers)
vmp = combined_voltages[mpp_idx]
imp = currents[mpp_idx]
pmp = powers[mpp_idx]

# we're assuming voltages are increasing, so combined_voltages
# should also be increasing
if not np.all(np.diff(combined_voltages) > 0):
raise ValueError("Each row of voltages array must be increasing.")
# get isc
# note that np.interp requires second argument is increasing (which
# we just checked)
isc = np.interp(0., combined_voltages, currents)

# the last element of currents must be zero
if currents[-1] != 0:
raise ValueError("Last element of currents array must be zero.")
# get voc
voc = combined_voltages[-1]

return {'i_sc': isc, 'v_oc': voc, 'i_mp': imp, 'v_mp': vmp, 'p_mp': pmp,
'i': currents, 'v': combined_voltages}

Loading