Skip to content

Commit b4916e1

Browse files
echedey-lscwhanse
andauthored
[BUG]: don't infer spectral model in ModelChain (#2253)
* Fix #2017 * rm TODOs * cwhanse review Co-Authored-By: Cliff Hansen <[email protected]> * typo * pep8 compliance * ups * Update modelchain.py * Infer a string, key for setter * pep8 linting * Remove delayed-inference code - codecov warning * default to no_loss * infer_spectral_model tweak -> return list if possible * remove infer_spectral_model * lint * review * remove reference to infer_spectral_model method --------- Co-authored-by: Cliff Hansen <[email protected]> Co-authored-by: Cliff Hansen <[email protected]>
1 parent 4b113ab commit b4916e1

File tree

4 files changed

+46
-64
lines changed

4 files changed

+46
-64
lines changed

docs/sphinx/source/reference/modelchain.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ on the information in the associated :py:class:`~pvsystem.PVSystem` object.
112112
modelchain.ModelChain.infer_dc_model
113113
modelchain.ModelChain.infer_ac_model
114114
modelchain.ModelChain.infer_aoi_model
115-
modelchain.ModelChain.infer_spectral_model
116115
modelchain.ModelChain.infer_temperature_model
117116
modelchain.ModelChain.infer_losses_model
118117

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
v0.11.3 (Anticipated March, 2025)
55
---------------------------------
66

7+
Breaking Changes
8+
~~~~~~~~~~~~~~~~
9+
* The pvlib.location.Location.pytz attribute is now read only. The
10+
pytz attribute is now set internally to be consistent with the
11+
pvlib.location.Location.tz attribute. (:issue:`2340`, :pull:`2341`)
12+
* Users must now provide ModelChain.spectral_model, or the 'no_loss' spectral
13+
model is assumed. pvlib.modelchain.ModelChain no longer attempts to infer
14+
the spectral model from PVSystem attributes. (:issue:`2017`, :pull:`2253`)
15+
716
Bug fixes
817
~~~~~~~~~
918
* Fix a bug in :py:func:`pvlib.bifacial.get_irradiance_poa` which may have yielded non-zero
@@ -62,11 +71,6 @@ Maintenance
6271
* asv 0.4.2 upgraded to asv 0.6.4 to fix CI failure due to pinned older conda.
6372
(:pull:`2352`)
6473

65-
Breaking Changes
66-
~~~~~~~~~~~~~~~~
67-
* The pvlib.location.Location.pytz attribute is now read only. The
68-
pytz attribute is now set internally to be consistent with the
69-
pvlib.location.Location.tz attribute. (:issue:`2340`, :pull:`2341`)
7074

7175
Contributors
7276
~~~~~~~~~~~~

pvlib/modelchain.py

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
# Optional keys to communicate temperature data. If provided,
3131
# 'cell_temperature' overrides ModelChain.temperature_model and sets
32-
# ModelChain.cell_temperature to the data. If 'module_temperature' is provdied,
32+
# ModelChain.cell_temperature to the data. If 'module_temperature' is provided,
3333
# overrides ModelChain.temperature_model with
3434
# pvlib.temperature.sapm_celL_from_module
3535
TEMPERATURE_KEYS = ('module_temperature', 'cell_temperature')
@@ -253,7 +253,7 @@ def __repr__(self):
253253
def _head(obj):
254254
try:
255255
return obj[:3]
256-
except:
256+
except Exception:
257257
return obj
258258

259259
if type(self.dc) is tuple:
@@ -269,7 +269,7 @@ def _head(obj):
269269
'\n')
270270
lines = []
271271
for attr in mc_attrs:
272-
if not (attr.startswith('_') or attr=='times'):
272+
if not (attr.startswith('_') or attr == 'times'):
273273
lines.append(f' {attr}: ' + _mcr_repr(getattr(self, attr)))
274274
desc4 = '\n'.join(lines)
275275
return (desc1 + desc2 + desc3 + desc4)
@@ -330,12 +330,15 @@ class ModelChain:
330330
'interp' and 'no_loss'. The ModelChain instance will be passed as the
331331
first argument to a user-defined function.
332332
333-
spectral_model : str, or function, optional
334-
If not specified, the model will be inferred from the parameters that
335-
are common to all of system.arrays[i].module_parameters.
336-
Valid strings are 'sapm', 'first_solar', 'no_loss'.
333+
spectral_model : str or function, optional
334+
Valid strings are:
335+
336+
- ``'sapm'``
337+
- ``'first_solar'``
338+
- ``'no_loss'``
339+
337340
The ModelChain instance will be passed as the first argument to
338-
a user-defined function.
341+
a user-defined function. If not specified, ``'no_loss'`` is assumed.
339342
340343
temperature_model : str or function, optional
341344
Valid strings are: 'sapm', 'pvsyst', 'faiman', 'fuentes', 'noct_sam'.
@@ -386,7 +389,6 @@ def __init__(self, system, location,
386389

387390
self.results = ModelChainResult()
388391

389-
390392
@classmethod
391393
def with_pvwatts(cls, system, location,
392394
clearsky_model='ineichen',
@@ -855,9 +857,7 @@ def spectral_model(self):
855857

856858
@spectral_model.setter
857859
def spectral_model(self, model):
858-
if model is None:
859-
self._spectral_model = self.infer_spectral_model()
860-
elif isinstance(model, str):
860+
if isinstance(model, str):
861861
model = model.lower()
862862
if model == 'first_solar':
863863
self._spectral_model = self.first_solar_spectral_loss
@@ -867,30 +867,12 @@ def spectral_model(self, model):
867867
self._spectral_model = self.no_spectral_loss
868868
else:
869869
raise ValueError(model + ' is not a valid spectral loss model')
870-
else:
870+
elif model is None:
871+
# not setting a model is equivalent to setting no_loss
872+
self._spectral_model = self.no_spectral_loss
873+
else: # assume model is callable with 1st argument = the MC instance
871874
self._spectral_model = partial(model, self)
872875

873-
def infer_spectral_model(self):
874-
"""Infer spectral model from system attributes."""
875-
module_parameters = tuple(
876-
array.module_parameters for array in self.system.arrays)
877-
params = _common_keys(module_parameters)
878-
if {'A4', 'A3', 'A2', 'A1', 'A0'} <= params:
879-
return self.sapm_spectral_loss
880-
elif ((('Technology' in params or
881-
'Material' in params) and
882-
(self.system._infer_cell_type() is not None)) or
883-
'first_solar_spectral_coefficients' in params):
884-
return self.first_solar_spectral_loss
885-
else:
886-
raise ValueError('could not infer spectral model from '
887-
'system.arrays[i].module_parameters. Check that '
888-
'the module_parameters for all Arrays in '
889-
'system.arrays contain valid '
890-
'first_solar_spectral_coefficients, a valid '
891-
'Material or Technology value, or set '
892-
'spectral_model="no_loss".')
893-
894876
def first_solar_spectral_loss(self):
895877
self.results.spectral_modifier = self.system.first_solar_spectral_loss(
896878
_tuple_from_dfs(self.results.weather, 'precipitable_water'),
@@ -1570,7 +1552,7 @@ def _prepare_temperature(self, data):
15701552
----------
15711553
data : DataFrame
15721554
May contain columns ``'cell_temperature'`` or
1573-
``'module_temperaure'``.
1555+
``'module_temperature'``.
15741556
15751557
Returns
15761558
-------
@@ -1679,6 +1661,7 @@ def run_model(self, weather):
16791661
self.prepare_inputs(weather)
16801662
self.aoi_model()
16811663
self.spectral_model()
1664+
16821665
self.effective_irradiance_model()
16831666

16841667
self._run_from_effective_irrad(weather)

tests/test_modelchain.py

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ def test_with_pvwatts(pvwatts_dc_pvwatts_ac_system, location, weather):
346346

347347

348348
def test_run_model_with_irradiance(sapm_dc_snl_ac_system, location):
349-
mc = ModelChain(sapm_dc_snl_ac_system, location)
349+
mc = ModelChain(sapm_dc_snl_ac_system, location, spectral_model='sapm')
350350
times = pd.date_range('20160101 1200-0700', periods=2, freq='6h')
351351
irradiance = pd.DataFrame({'dni': 900, 'ghi': 600, 'dhi': 150},
352352
index=times)
@@ -629,9 +629,13 @@ def test_run_model_arrays_weather(sapm_dc_snl_ac_system_same_arrays,
629629

630630

631631
def test_run_model_perez(sapm_dc_snl_ac_system, location):
632-
mc = ModelChain(sapm_dc_snl_ac_system, location,
633-
transposition_model='perez')
634-
times = pd.date_range('20160101 1200-0700', periods=2, freq='6h')
632+
mc = ModelChain(
633+
sapm_dc_snl_ac_system,
634+
location,
635+
transposition_model="perez",
636+
spectral_model="sapm",
637+
)
638+
times = pd.date_range("20160101 1200-0700", periods=2, freq="6h")
635639
irradiance = pd.DataFrame({'dni': 900, 'ghi': 600, 'dhi': 150},
636640
index=times)
637641
ac = mc.run_model(irradiance).results.ac
@@ -642,10 +646,14 @@ def test_run_model_perez(sapm_dc_snl_ac_system, location):
642646

643647

644648
def test_run_model_gueymard_perez(sapm_dc_snl_ac_system, location):
645-
mc = ModelChain(sapm_dc_snl_ac_system, location,
646-
airmass_model='gueymard1993',
647-
transposition_model='perez')
648-
times = pd.date_range('20160101 1200-0700', periods=2, freq='6h')
649+
mc = ModelChain(
650+
sapm_dc_snl_ac_system,
651+
location,
652+
airmass_model="gueymard1993",
653+
transposition_model="perez",
654+
spectral_model="sapm",
655+
)
656+
times = pd.date_range("20160101 1200-0700", periods=2, freq="6h")
649657
irradiance = pd.DataFrame({'dni': 900, 'ghi': 600, 'dhi': 150},
650658
index=times)
651659
ac = mc.run_model(irradiance).results.ac
@@ -1272,18 +1280,6 @@ def test_singlediode_dc_arrays(location, dc_model,
12721280
assert isinstance(dc, (pd.Series, pd.DataFrame))
12731281

12741282

1275-
@pytest.mark.parametrize('dc_model', ['sapm', 'cec', 'cec_native'])
1276-
def test_infer_spectral_model(location, sapm_dc_snl_ac_system,
1277-
cec_dc_snl_ac_system,
1278-
cec_dc_native_snl_ac_system, dc_model):
1279-
dc_systems = {'sapm': sapm_dc_snl_ac_system,
1280-
'cec': cec_dc_snl_ac_system,
1281-
'cec_native': cec_dc_native_snl_ac_system}
1282-
system = dc_systems[dc_model]
1283-
mc = ModelChain(system, location, aoi_model='physical')
1284-
assert isinstance(mc, ModelChain)
1285-
1286-
12871283
@pytest.mark.parametrize('temp_model', [
12881284
'sapm_temp', 'faiman_temp', 'pvsyst_temp', 'fuentes_temp',
12891285
'noct_sam_temp'])
@@ -2002,9 +1998,9 @@ def test__irrad_for_celltemp():
20021998

20031999

20042000
def test_ModelChain___repr__(sapm_dc_snl_ac_system, location):
2005-
2006-
mc = ModelChain(sapm_dc_snl_ac_system, location,
2007-
name='my mc')
2001+
mc = ModelChain(
2002+
sapm_dc_snl_ac_system, location, name="my mc", spectral_model="sapm"
2003+
)
20082004

20092005
expected = '\n'.join([
20102006
'ModelChain: ',

0 commit comments

Comments
 (0)