Skip to content

Commit 1872ae8

Browse files
cwhansewholmgren
authored andcommitted
Expose first_solar_spectral_correction in PVSystem and ModelChain (#466)
* Add first_solar_spectral_correction method, improve infer_spectral_model * edits: input testing, first_solar_spectral_coefficients name * rename first_solar_spectral_correction method to first_solar_spectral_loss * add test for pvsystem.first_solar_spectral_loss * Fix declarations of weather and airmass in test_spectral_models * Correct errors and expected value in test_spectral_models * Fix space error * Fix handling of weather in test_spectral_models * Change test_PVSystem_first_solar_spectral_loss and test_spectral_models * Fix error in first_solar_spectral_loss * Formatting and flake8 * Add constant_spectral_loss back to test_spectral_models arguments * Fix line continuation in pvsystem.py * whatsnew, docstring for pvsystem.first_solar_spectral_loss, remove not implemented in modelchain.py docstring * Fix typo in pvsystem.py
1 parent d2c9bcd commit 1872ae8

File tree

6 files changed

+135
-19
lines changed

6 files changed

+135
-19
lines changed

docs/sphinx/source/whatsnew/v0.6.0.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ API Changes
1111
Enhancements
1212
~~~~~~~~~~~~
1313
* Add sea surface albedo in irradiance.py (:issue:`458`)
14+
* Implement first_solar_spectral_loss in modelchain.py (:issue:'359')
15+
1416

1517
Bug fixes
1618
~~~~~~~~~
@@ -31,3 +33,5 @@ Contributors
3133
~~~~~~~~~~~~
3234
* Will Holmgren
3335
* Yu Cao
36+
* Cliff Hansen
37+

pvlib/atmosphere.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,8 +466,11 @@ def first_solar_spectral_correction(pw, airmass_absolute, module_type=None,
466466
coefficients = _coefficients[module_type.lower()]
467467
elif module_type is None and coefficients is not None:
468468
pass
469+
elif module_type is None and coefficients is None:
470+
raise TypeError('No valid input provided, both module_type and ' +
471+
'coefficients are None')
469472
else:
470-
raise TypeError('ambiguous input, must supply only 1 of ' +
473+
raise TypeError('Cannot resolve input, must supply only one of ' +
471474
'module_type and coefficients')
472475

473476
# Evaluate Spectral Shift

pvlib/modelchain.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,8 @@ class ModelChain(object):
273273
spectral_model: None, str, or function, default None
274274
If None, the model will be inferred from the contents of
275275
system.module_parameters. Valid strings are 'sapm',
276-
'first_solar' (not implemented), 'no_loss'. The ModelChain
277-
instance will be passed as the first argument to a user-defined
276+
'first_solar', 'no_loss'. The ModelChain instance will be passed
277+
as the first argument to a user-defined
278278
function.
279279
280280
temp_model: str or function, default 'sapm'
@@ -518,7 +518,7 @@ def sapm_aoi_loss(self):
518518
return self
519519

520520
def no_aoi_loss(self):
521-
self.aoi_modifier = 1
521+
self.aoi_modifier = 1.0
522522
return self
523523

524524
@property
@@ -532,7 +532,7 @@ def spectral_model(self, model):
532532
elif isinstance(model, str):
533533
model = model.lower()
534534
if model == 'first_solar':
535-
raise NotImplementedError
535+
self._spectral_model = self.first_solar_spectral_loss
536536
elif model == 'sapm':
537537
self._spectral_model = self.sapm_spectral_loss
538538
elif model == 'no_loss':
@@ -546,12 +546,23 @@ def infer_spectral_model(self):
546546
params = set(self.system.module_parameters.keys())
547547
if set(['A4', 'A3', 'A2', 'A1', 'A0']) <= params:
548548
return self.sapm_spectral_loss
549+
elif ((('Technology' in params or
550+
'Material' in params) and
551+
(pvsystem._infer_cell_type() is not None)) or
552+
'first_solar_spectral_coefficients' in params):
553+
return self.first_solar_spectral_loss
549554
else:
550555
raise ValueError('could not infer spectral model from '
551-
'system.module_parameters')
556+
'system.module_parameters. Check that the '
557+
'parameters contain valid '
558+
'first_solar_spectral_coefficients or a valid '
559+
'Material or Technology value')
552560

553561
def first_solar_spectral_loss(self):
554-
raise NotImplementedError
562+
self.spectral_modifier = self.system.first_solar_spectral_loss(
563+
self.weather['precipitable_water'],
564+
self.airmass['airmass_absolute'])
565+
return self
555566

556567
def sapm_spectral_loss(self):
557568
self.spectral_modifier = self.system.sapm_spectral_loss(

pvlib/pvsystem.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,94 @@ def sapm_effective_irradiance(self, poa_direct, poa_diffuse,
421421
poa_direct, poa_diffuse, airmass_absolute, aoi,
422422
self.module_parameters, reference_irradiance=reference_irradiance)
423423

424+
def first_solar_spectral_loss(self, pw, airmass_absolute):
425+
426+
"""
427+
Use the :py:func:`first_solar_spectral_correction` function to
428+
calculate the spectral loss modifier. The model coefficients are
429+
specific to the module's cell type, and are determined by searching
430+
for one of the following keys in self.module_parameters (in order):
431+
'first_solar_spectral_coefficients' (user-supplied coefficients)
432+
'Technology' - a string describing the cell type, can be read from
433+
the CEC module parameter database
434+
'Material' - a string describing the cell type, can be read from
435+
the Sandia module database.
436+
437+
Parameters
438+
----------
439+
pw : array-like
440+
atmospheric precipitable water (cm).
441+
442+
airmass_absolute : array-like
443+
absolute (pressure corrected) airmass.
444+
445+
Returns
446+
-------
447+
modifier: array-like
448+
spectral mismatch factor (unitless) which can be multiplied
449+
with broadband irradiance reaching a module's cells to estimate
450+
effective irradiance, i.e., the irradiance that is converted to
451+
electrical current.
452+
"""
453+
454+
if 'first_solar_spectral_coefficients' in \
455+
self.module_parameters.keys():
456+
coefficients = \
457+
self.module_parameters['first_solar_spectral_coefficients']
458+
module_type = None
459+
else:
460+
module_type = self._infer_cell_type()
461+
coefficients = None
462+
463+
return atmosphere.first_solar_spectral_correction(pw,
464+
airmass_absolute,
465+
module_type,
466+
coefficients)
467+
468+
def _infer_cell_type(self):
469+
470+
"""
471+
Examines module_parameters and maps the Technology key for the CEC
472+
database and the Material key for the Sandia database to a common
473+
list of strings for cell type.
474+
475+
Returns
476+
-------
477+
cell_type: str
478+
479+
"""
480+
481+
_cell_type_dict = {'Multi-c-Si': 'multisi',
482+
'Mono-c-Si': 'monosi',
483+
'Thin Film': 'cigs',
484+
'a-Si/nc': 'asi',
485+
'CIS': 'cigs',
486+
'CIGS': 'cigs',
487+
'1-a-Si': 'asi',
488+
'CdTe': 'cdte',
489+
'a-Si': 'asi',
490+
'2-a-Si': None,
491+
'3-a-Si': None,
492+
'HIT-Si': 'monosi',
493+
'mc-Si': 'multisi',
494+
'c-Si': 'multisi',
495+
'Si-Film': 'asi',
496+
'CdTe': 'cdte',
497+
'EFG mc-Si': 'multisi',
498+
'GaAs': None,
499+
'a-Si / mono-Si': 'monosi'}
500+
501+
if 'Technology' in self.module_parameters.keys():
502+
# CEC module parameter set
503+
cell_type = _cell_type_dict[self.module_parameters['Technology']]
504+
elif 'Material' in self.module_parameters.keys():
505+
# Sandia module parameter set
506+
cell_type = _cell_type_dict[self.module_parameters['Material']]
507+
else:
508+
cell_type = None
509+
510+
return cell_type
511+
424512
def singlediode(self, photocurrent, saturation_current,
425513
resistance_series, resistance_shunt, nNsVth,
426514
ivcurve_pnts=None):

pvlib/test/test_modelchain.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -263,28 +263,28 @@ def test_aoi_models(system, location, aoi_model, expected):
263263
def constant_spectral_loss(mc):
264264
mc.spectral_modifier = 0.9
265265

266+
266267
@requires_scipy
267-
@pytest.mark.parametrize('spectral_model, expected', [
268-
('sapm', [182.338436597, -2.00000000e-02]),
269-
pytest.mark.xfail(raises=NotImplementedError)
270-
(('first_solar', [179.371460714, -2.00000000e-02])),
271-
('no_loss', [181.604438144, -2.00000000e-02]),
272-
(constant_spectral_loss, [163.061569511, -2e-2])
268+
@pytest.mark.parametrize('spectral_model', [
269+
'sapm', 'first_solar', 'no_loss', constant_spectral_loss
273270
])
274-
def test_spectral_models(system, location, spectral_model, expected):
271+
def test_spectral_models(system, location, spectral_model):
272+
times = pd.date_range('20160101 1200-0700', periods=3, freq='6H')
273+
weather = pd.DataFrame(data=[0.3, 0.5, 1.0],
274+
index=times,
275+
columns=['precipitable_water'])
275276
mc = ModelChain(system, location, dc_model='sapm',
276277
aoi_model='no_loss', spectral_model=spectral_model)
277-
times = pd.date_range('20160101 1200-0700', periods=2, freq='6H')
278-
ac = mc.run_model(times).ac
279-
280-
expected = pd.Series(np.array(expected), index=times)
281-
assert_series_equal(ac, expected, check_less_precise=2)
278+
spectral_modifier = mc.run_model(times=times,
279+
weather=weather).spectral_modifier
280+
assert isinstance(spectral_modifier, (pd.Series, float, int))
282281

283282

284283
def constant_losses(mc):
285284
mc.losses = 0.9
286285
mc.ac *= mc.losses
287286

287+
288288
@requires_scipy
289289
@pytest.mark.parametrize('losses_model, expected', [
290290
('pvwatts', [163.280464174, 0]),

pvlib/test/test_pvsystem.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,16 @@ def test_PVSystem_sapm_spectral_loss(sapm_module_params):
266266
out = system.sapm_spectral_loss(airmass)
267267

268268

269+
@pytest.mark.parametrize("expected", [1.03173953])
270+
def test_PVSystem_first_solar_spectral_loss(sapm_module_params, expected):
271+
system = pvsystem.PVSystem(module_parameters=sapm_module_params)
272+
pw = 3
273+
airmass_absolute = 3
274+
out = system.first_solar_spectral_loss(pw, airmass_absolute)
275+
276+
assert_allclose(out, expected, atol=1e-4)
277+
278+
269279
@pytest.mark.parametrize('aoi,expected', [
270280
(45, 0.9975036250000002),
271281
(np.array([[-30, 30, 100, np.nan]]),

0 commit comments

Comments
 (0)