Skip to content

Implement calcparams_pvsyst and tests #486

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 16 commits into from
Jul 9, 2018
Merged
1 change: 1 addition & 0 deletions docs/sphinx/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ Functions relevant for the single diode model.
:toctree: generated/

pvsystem.calcparams_desoto
pvsystem.calcparams_pvsyst
pvsystem.i_from_v
pvsystem.singlediode
pvsystem.v_from_i
Expand Down
1 change: 1 addition & 0 deletions docs/sphinx/source/whatsnew/v0.6.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Enhancements
* Add sea surface albedo in irradiance.py (:issue:`458`)
* Implement first_solar_spectral_loss in modelchain.py (:issue:`359`)
* Clarify arguments Egref and dEgdT for calcparams_desoto (:issue:`462`)
* Add pvsystem.calcparams_pvsyst to compute values for the single diode equation using the PVsyst v6 model.
Copy link
Member

Choose a reason for hiding this comment

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

(:issue:470)



Bug fixes
Expand Down
190 changes: 174 additions & 16 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,36 @@ def calcparams_desoto(self, effective_irradiance, temp_cell, **kwargs):

return calcparams_desoto(effective_irradiance, temp_cell, **kwargs)

def calcparams_pvsyst(self, effective_irradiance, temp_cell, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

similar to what was pointed out by @thunderfish24 in #478... the **kwargs in this method signature don't do anything, so we should remove **kwargs from the method signature. To be clear, we need to keep them in the function call below.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

"""
Use the :py:func:`calcparams_pvsyst` function, the input
parameters and ``self.module_parameters`` to calculate the
module currents and resistances.

Parameters
----------
effective_irradiance : numeric
The irradiance (W/m2) that is converted to photocurrent.

temp_cell : float or Series
The average cell temperature of cells within a module in C.

**kwargs
See pvsystem.calcparams_pvsyst for details
Copy link
Member

Choose a reason for hiding this comment

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

and delete **kwargs from the docstring.

Copy link
Member Author

Choose a reason for hiding this comment

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

done


Returns
-------
See pvsystem.calcparams_pvsyst for details
"""

kwargs = _build_kwargs(['gamma_ref', 'mugamma', 'I_L_ref', 'I_o_ref',
'R_sh_ref', 'R_sh_0', 'R_sh_exp',
'R_s', 'alpha_sc', 'EgRef',
'cells_in_series'],
Copy link
Member

Choose a reason for hiding this comment

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

should 'irrad_ref' and 'temp_ref' be added to this lookup list? I'd say yes because they seem intrinsic to the module specification.

Copy link
Member Author

Choose a reason for hiding this comment

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

done. I also added these keywords to calcparams_desoto

self.module_parameters)

return calcparams_pvsyst(effective_irradiance, temp_cell, **kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be done in a separate issue (see #476), but I think the PVSystem methods should scale the outputs to the modules_per_string and strings_per_inverter.

Copy link
Member Author

Choose a reason for hiding this comment

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

We've chosen not to scale inside dc_model functions to offer flexibility, e.g., model a system with strings of different lengths, to implement module-to-module mismatch models, etc. Granted some of these capabilities are not presently in pvlib.


def sapm(self, effective_irradiance, temp_cell, **kwargs):
"""
Use the :py:func:`sapm` function, the input parameters,
Expand Down Expand Up @@ -1011,28 +1041,21 @@ def calcparams_desoto(effective_irradiance, temp_cell,
Tuple of the following results:

photocurrent : numeric
Light-generated current in amperes at irradiance=S and
cell temperature=Tcell.
Light-generated current in amperes

saturation_current : numeric
Diode saturation curent in amperes at irradiance
S and cell temperature Tcell.
Diode saturation curent in amperes

resistance_series : float
Series resistance in ohms at irradiance S and cell temperature
Tcell.
Series resistance in ohms
Copy link
Contributor

Choose a reason for hiding this comment

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

While passing through this territory, seems like this should be changed to "numeric" from "float".

Copy link
Member Author

Choose a reason for hiding this comment

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

Cut and paste inheritance? I don't think there is a difference here. numeric would admit long, complex and int types as well as float. Probably should change to float unless there's a reason otherwise.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm good with float instead of numeric, because that seems to be most accurate.


resistance_shunt : numeric
Shunt resistance in ohms at irradiance S and cell temperature
Tcell.
Shunt resistance in ohms

nNsVth : numeric
Modified diode ideality factor at irradiance S and cell
temperature Tcell. Note that in source [1] nNsVth = a (equation
2). nNsVth is the product of the usual diode ideality factor
(n), the number of series-connected cells in the module (Ns),
and the thermal voltage of a cell in the module (Vth) at a cell
temperature of Tcell.
The product of the usual diode ideality factor (n, unitless),
number of cells in series (Ns), and cell thermal voltage at
specified effective irradiance and cell temperature.

References
----------
Expand All @@ -1051,8 +1074,6 @@ def calcparams_desoto(effective_irradiance, temp_cell,

See Also
--------
sapm
sapm_celltemp
singlediode
retrieve_sam

Expand Down Expand Up @@ -1172,6 +1193,143 @@ def calcparams_desoto(effective_irradiance, temp_cell,
return IL, I0, Rs, Rsh, nNsVth


def calcparams_pvsyst(effective_irradiance, temp_cell,
alpha_sc, gamma_ref, mugamma,
I_L_ref, I_o_ref,
R_sh_ref, R_sh_0, R_s,
cells_in_series,
R_sh_exp=5.5,
EgRef=1.121,
irrad_ref=1000, temp_ref=25):
'''
Calculates five parameter values for the single diode equation at
effective irradiance and cell temperature using the PVsyst v6
model described in [1,2,3]. The five values returned by calcparams_pvsyst
can be used by singlediode to calculate an IV curve.

Parameters
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the difference between numeric and float?

Should a user NOT expect this function to be vectorized over the "float" parameters?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm pretty sure the function will work on array or Series inputs. Is the issue the type description float implies a singleton?

We aren't using numeric or float consistently in the comments. For me to fix that, requires that I learn the distinction. New issue, I think.

Copy link
Member

Choose a reason for hiding this comment

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

I've used numeric for input that can be scalar, np.array, or pd.Series. float is specific to scalar. Whether or not this is a good idea could be discussed in a separate issue, if necessary.

Copy link
Member

Choose a reason for hiding this comment

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

for more context, note that some functions require "array-like" (np.array or pd.Series) and others require pd.Series.

Copy link
Member Author

Choose a reason for hiding this comment

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

So, numeric unless there's a reason to specify a scalar float?

Copy link
Member

Choose a reason for hiding this comment

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

or unless there's a reason to specify array-like or Series.

Copy link
Member Author

Choose a reason for hiding this comment

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

I looked at both calcparams_desoto and calcparams_pvsyst and I'm going to leave the docstrings as is for now. The typical use of either function is to pass model parameters for one module and a list of conditions (irradiance and temperature). That use is reflected in the docstring's use of numeric and float, where each model parameter is a single value for a module. The functions are more flexible that this use case.

If we want to overhaul the docstrings, let's open a new issue for it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think an issue is wise in order to define what we mean to ourselves/users and make everything consistent.

----------
effective_irradiance : numeric
The irradiance (W/m2) that is converted to photocurrent.

temp_cell : numeric
The average cell temperature of cells within a module in C.

alpha_sc : float
The short-circuit current temperature coefficient of the
module in units of A/C.

gamma_ref : float
The diode ideality factor

Copy link
Member

Choose a reason for hiding this comment

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

missing mugamma documentation here. mugamma or mu_gamma?

Copy link
Member Author

Choose a reason for hiding this comment

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

added. changed to mu_gamma

Copy link
Member

Choose a reason for hiding this comment

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

it appears that the change was not applied here.

mugamma : float
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps mu_gamma?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes

The temperature coefficient for the diode ideality factor, 1/K

I_L_ref : float
The light-generated current (or photocurrent) at reference conditions,
in amperes.

I_o_ref : float
The dark or diode reverse saturation current at reference conditions,
in amperes.

R_sh_ref : float
The shunt resistance at reference conditions, in ohms.

R_sh_0 : float
The shunt resistance at zero irradiance conditions, in ohms.

R_s : float
Copy link
Contributor

Choose a reason for hiding this comment

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

R_s_ref seems more consistent with other variables at reference conditions.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree. But here we run into an unfortunate linkage to the SAM database for the CEC model parameters. R_s is the heading in that file, which is used to define the keys for the module parameter dict. Although in my opinion R_s for PVsyst is a different parameter than R_s for De Soto or CEC (because parameter values are determined jointly, they are intrinsically linked and can't be regarded as correct for a different model) I do think it's useful to maintain a common nomenclature.

I think changing R_s to R_s_ref will involve some work in pvsystem.retrieve_sam so I'm not going to take that on in this PR.

The series resistance at reference conditions, in ohms.

cells_in_series : integer
Copy link
Contributor

Choose a reason for hiding this comment

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

Ns would seem more consistent with the desoto model and the nNsVth variable.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree but we use cells_in_series as the key for the SAPM parameters, and some have a preference for descriptive variable names.

The number of cells connected in series.

R_sh_exp : float
The exponent in the equation for shunt resistance, unitless. Defaults
to 5.5.

EgRef : float
The energy bandgap at reference temperature in units of eV.
1.121 eV for crystalline silicon. EgRef must be >0.

irrad_ref : float (optional, default=1000)
Reference irradiance in W/m^2.

temp_ref : float (optional, default=25)
Reference cell temperature in C.

Returns
-------
Tuple of the following results:

photocurrent : numeric
Light-generated current in amperes

Copy link
Member

Choose a reason for hiding this comment

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

temp_cell

Copy link
Member Author

Choose a reason for hiding this comment

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

I edited the output description (and in pvsystem.calcparams_desoto) to remove the phrase 'at irradiance....=Tcell' I think the context is clear.

saturation_current : numeric
Diode saturation current in amperes

Copy link
Member

Choose a reason for hiding this comment

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

temp_cell

resistance_series : float
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like this should be "numeric" too.

Series resistance in ohms

resistance_shunt : numeric
Shunt resistance in ohms

Copy link
Member

Choose a reason for hiding this comment

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

temp_cell

nNsVth : numeric
The product of the usual diode ideality factor (n, unitless),
number of cells in series (Ns), and cell thermal voltage at
specified effective irradiance and cell temperature.

References
----------
[1] K. Sauer, T. Roessler, C. W. Hansen, Modeling the Irradiance and
Temperature Dependence of Photovoltaic Modules in PVsyst,
IEEE Journal of Photovoltaics v5(1), January 2015.

[2] A. Mermoud, PV modules modelling, Presentation at the 2nd PV
Performance Modeling Workshop, Santa Clara, CA, May 2013

[3] A. Mermoud, T. Lejeune, Performance Assessment of a Simulation Model
for PV modules of any available technology, 25th European Photovoltaic
Solar Energy Conference, Valencia, Spain, Sept. 2010

See Also
--------
calcparams_desoto
singlediode

'''

# Boltzmann constant in J/K
k = 1.3806488e-23
Copy link
Contributor

Choose a reason for hiding this comment

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

Appears to differ a bit from scipy.constants.Boltzmann, which is 1.38064852e-23

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch. That's a typo.

Copy link
Member

Choose a reason for hiding this comment

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

ref #483.


# elementary charge in coulomb
q = 1.6021766e-19

# reference temperature
Tref_K = temp_ref + 273.15
Tcell_K = temp_cell + 273.15

Copy link
Member

Choose a reason for hiding this comment

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

temp_ref_K ?
temp_cell_K ?

Copy link
Member Author

Choose a reason for hiding this comment

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

these are local variables, so I think its OK that they are different in style.

Copy link
Member Author

Choose a reason for hiding this comment

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

So I changed them as you suggested :)

gamma = gamma_ref + mugamma * (Tcell_K - Tref_K)
nNsVth = gamma * k / q * cells_in_series * Tcell_K

IL = effective_irradiance / irrad_ref * \
(I_L_ref + alpha_sc * (Tcell_K - Tref_K))
Copy link
Contributor

Choose a reason for hiding this comment

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

Optimization possibility: Tcell_K - Tref_K is computed twice.


I0 = I_o_ref * ((Tcell_K / Tref_K) ** 3) * \
(np.exp((q * EgRef) / (k * gamma) * (1 / Tref_K - 1 / Tcell_K)))

Rsh_tmp = (R_sh_ref - R_sh_0 * np.exp(-R_sh_exp)) / (1.0 - np.exp(-R_sh_exp))
Rsh_base = np.maximum(0.0, Rsh_tmp)
Copy link
Contributor

Choose a reason for hiding this comment

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

In looking over http://files.pvsyst.com/help/pvmodule_rshexp.htm, it is not clear to me why Rsh_base is not simply R_sh_ref. What am I missing?

Copy link
Member Author

Choose a reason for hiding this comment

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

You're not, PVsyst documentation is missing that definition of Rsh_base. Ken Sauer and Thomas Roessler are my sources, they got it verbally from PVsyst.


Rsh = Rsh_base + (R_sh_0 - Rsh_base) * \
np.exp(-R_sh_exp * effective_irradiance / irrad_ref)
Copy link
Contributor

Choose a reason for hiding this comment

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

Optimization possibility: effective_irradiance / irrad_ref is computed twice.


Rs = R_s

return IL, I0, Rs, Rsh, nNsVth


def retrieve_sam(name=None, path=None):
'''
Retrieve latest module and inverter info from a local file or the
Expand Down
76 changes: 76 additions & 0 deletions pvlib/test/test_pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,23 @@ def cec_module_params(sam_data):
return module_parameters


@pytest.fixture(scope="session")
Copy link
Member

Choose a reason for hiding this comment

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

not a big deal, but if we're still making changes this should not have scope="session"

Copy link
Member Author

Choose a reason for hiding this comment

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

I copied that. scope="module"?

Copy link
Member

@wholmgren wholmgren Jul 6, 2018

Choose a reason for hiding this comment

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

no scope argument. @pytest.fixture and nothing else

Copy link
Member

Choose a reason for hiding this comment

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

The reasoning for this is that this function returns a mutable dictionary. In principle, one test could inadvertently modify the dictionary, and a second test could pass or fail because of that modification. @pytest.fixture will call the function and generate a new dictionary each time the data is needed. Adding scope="module" or scope="session" instructs pytest to recreate the data only once per test module (the fixture could be imported into a new module and data would be regenerated) or once per test session (this defeats a large part of the purpose of fixtures).

Copy link
Member Author

@cwhanse cwhanse Jul 6, 2018

Choose a reason for hiding this comment

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

OK. I'll remove this instance of scope=session from the pvsyst module parameter fixture. Thanks for the explanation.

def pvsyst_module_params():
module_parameters = {}
module_parameters['gamma_ref'] = 1.05
module_parameters['mugamma'] = 0.001
Copy link
Member

Choose a reason for hiding this comment

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

test fixture and the two tests below need mugamma --> mu_gamma

module_parameters['I_L_ref'] = 6.0
module_parameters['I_o_ref'] = 5e-9
module_parameters['EgRef'] = 1.121
module_parameters['R_sh_ref'] = 300
module_parameters['R_sh_0'] = 1000
module_parameters['R_s'] = 0.5
module_parameters['R_sh_exp'] = 5.5
module_parameters['cells_in_series'] = 60
module_parameters['alpha_sc'] = 0.001
return module_parameters


def test_sapm(sapm_module_params):

times = pd.DatetimeIndex(start='2015-01-01', periods=5, freq='12H')
Expand Down Expand Up @@ -386,6 +403,35 @@ def test_calcparams_desoto(cec_module_params):
assert_allclose(nNsVth, 0.473)


def test_calcparams_pvsyst(pvsyst_module_params):
Copy link
Contributor

Choose a reason for hiding this comment

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

Not critical, but you might consider a simple "API" test that passes the five computed coefficients into singlediode just to make sure the computation runs. This makes sure that pvsyst gets updated if the API changes.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fair point. I'm not going to put a new test in here because I don't clearly see how to separate issues being tested, and to avoid redundant tests. The current test_calcparams_pvsyst verifies correct output of pvsystem.calcparams_pvsyst. Adding a call to singlediode within test_calcparams_pvsyst adds a new purpose (API check) and I prefer to keep tests to a single purpose. Adding a new test that calls calcparams_pvsyst and then singlediode would duplicate the test of PVSystem.calcparams_pvsyst method.

Do you have an example in the campanelli pull request?

Copy link
Contributor

Choose a reason for hiding this comment

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

No, I haven't written any tests yet and I admit that I am not familiar with this particular mocker setup. If a mock call can verify the API, then that would be great. I don't mean to hold up this PR and I'll look into it further with the Campanelli model.

times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H')
effective_irradiance = pd.Series([0.0, 800.0], index=times)
temp_cell = pd.Series([25, 50], index=times)

IL, I0, Rs, Rsh, nNsVth = pvsystem.calcparams_pvsyst(
effective_irradiance,
temp_cell,
alpha_sc=pvsyst_module_params['alpha_sc'],
gamma_ref=pvsyst_module_params['gamma_ref'],
mugamma=pvsyst_module_params['mugamma'],
I_L_ref=pvsyst_module_params['I_L_ref'],
I_o_ref=pvsyst_module_params['I_o_ref'],
R_sh_ref=pvsyst_module_params['R_sh_ref'],
R_sh_0=pvsyst_module_params['R_sh_0'],
R_s=pvsyst_module_params['R_s'],
cells_in_series=pvsyst_module_params['cells_in_series'],
EgRef=pvsyst_module_params['EgRef'])

assert_series_equal(np.round(IL, 3), pd.Series([0.0, 4.8200], index=times))
assert_series_equal(np.round(I0, 3),
pd.Series([0.0, 1.47e-7], index=times))
assert_allclose(Rs, 0.500)
assert_series_equal(np.round(Rsh, 3),
pd.Series([1000.0, 305.757], index=times))
assert_series_equal(np.round(nNsVth, 4),
pd.Series([1.6186, 1.7961], index=times))
Copy link
Contributor

Choose a reason for hiding this comment

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

My preference is to compute the expected values directly in the test. I'm curious if you have particular reasons not to do that?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't other than it was easy (for me) to compute the values in MATLAB and hardcode them here. My way is certainly less transparent.

Copy link
Member

Choose a reason for hiding this comment

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

I'm confused. I don't understand how general test values can be computed inside a test without duplicating the function itself.

Copy link
Contributor

Choose a reason for hiding this comment

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

Some view including a separate (re)computation as redundant, others view it as transparent. In this case I don't think any of the computations require an implicit solver, so where the values come from should not leave too much to question.



def test_PVSystem_calcparams_desoto(cec_module_params, mocker):
mocker.spy(pvsystem, 'calcparams_desoto')
module_parameters = cec_module_params.copy()
Expand Down Expand Up @@ -414,6 +460,36 @@ def test_PVSystem_calcparams_desoto(cec_module_params, mocker):
assert_allclose(nNsVth, 0.5, atol=0.1)


def test_PVSystem_calcparams_pvsyst(pvsyst_module_params, mocker):
mocker.spy(pvsystem, 'calcparams_pvsyst')
module_parameters = pvsyst_module_params.copy()
system = pvsystem.PVSystem(module_parameters=module_parameters)
effective_irradiance = np.array([0, 800])
temp_cell = np.array([25, 50])
IL, I0, Rs, Rsh, nNsVth = system.calcparams_pvsyst(effective_irradiance,
temp_cell)
pvsystem.calcparams_pvsyst.assert_called_once_with(
effective_irradiance,
temp_cell,
alpha_sc=pvsyst_module_params['alpha_sc'],
gamma_ref=pvsyst_module_params['gamma_ref'],
mugamma=pvsyst_module_params['mugamma'],
I_L_ref=pvsyst_module_params['I_L_ref'],
I_o_ref=pvsyst_module_params['I_o_ref'],
R_sh_ref=pvsyst_module_params['R_sh_ref'],
R_sh_0=pvsyst_module_params['R_sh_0'],
R_s=pvsyst_module_params['R_s'],
cells_in_series=pvsyst_module_params['cells_in_series'],
EgRef=pvsyst_module_params['EgRef'],
R_sh_exp=pvsyst_module_params['R_sh_exp'])

assert_allclose(IL, np.array([0.0, 4.8200]), atol=1)
assert_allclose(I0, np.array([0.0, 1.47e-7]), atol=1.0e-5)
assert_allclose(Rs, 0.5, atol=0.1)
assert_allclose(Rsh, np.array([1000, 305.757]), atol=50)
assert_allclose(nNsVth, np.array([1.6186, 1.7961]), atol=0.1)


@pytest.fixture(params=[
{ # Can handle all python scalar inputs
'Rsh': 20.,
Expand Down