Skip to content

Commit 933d0d8

Browse files
wholmgrencwhanse
andauthored
move ModelChain.weather and times to ModelChainResult (pvlib#1197)
* move ModelChain.weather and times to ModelChainResult * stickler * Update pvlib/tests/test_modelchain.py Co-authored-by: Cliff Hansen <cwhanse@sandia.gov> * update docs Co-authored-by: Cliff Hansen <cwhanse@sandia.gov>
1 parent 3269dc7 commit 933d0d8

File tree

4 files changed

+98
-73
lines changed

4 files changed

+98
-73
lines changed

docs/sphinx/source/modelchain.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,10 @@ function if you wanted to.
420420
421421
def pvusa_mc_wrapper(mc):
422422
# calculate the dc power and assign it to mc.dc
423-
mc.dc = pvusa(mc.total_irrad['poa_global'], mc.weather['wind_speed'], mc.weather['temp_air'],
423+
# in the future, need to explicitly iterate over system.arrays
424+
# https://github.com/pvlib/pvlib-python/issues/1115
425+
mc.dc = pvusa(mc.results.total_irrad['poa_global'],
426+
mc.results.weather['wind_speed'], mc.results.weather['temp_air'],
424427
mc.system.module_parameters['a'], mc.system.module_parameters['b'],
425428
mc.system.module_parameters['c'], mc.system.module_parameters['d'])
426429
@@ -436,7 +439,7 @@ function if you wanted to.
436439
437440
def no_loss_temperature(mc):
438441
# keep it simple
439-
mc.cell_temperature = mc.weather['temp_air']
442+
mc.cell_temperature = mc.results.weather['temp_air']
440443
return mc
441444
442445

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ Deprecations
6464
* ``ModelChain.spectral_modifier``
6565
* ``ModelChain.total_irrad``
6666
* ``ModelChain.tracking``
67+
* ``ModelChain.weather``
68+
* ``ModelChain.times``
6769

6870
Enhancements
6971
~~~~~~~~~~~~

pvlib/modelchain.py

Lines changed: 68 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ class ModelChainResult:
256256
_per_array_fields = {'total_irrad', 'aoi', 'aoi_modifier',
257257
'spectral_modifier', 'cell_temperature',
258258
'effective_irradiance', 'dc', 'diode_params',
259-
'dc_ohmic_losses'}
259+
'dc_ohmic_losses', 'weather'}
260260

261261
# system-level information
262262
solar_position: Optional[pd.DataFrame] = field(default=None)
@@ -278,6 +278,9 @@ class ModelChainResult:
278278
diode_params: Optional[PerArray[pd.DataFrame]] = field(default=None)
279279
dc_ohmic_losses: Optional[PerArray[pd.Series]] = field(default=None)
280280

281+
weather: Optional[PerArray[pd.DataFrame]] = None
282+
times: Optional[pd.DatetimeIndex] = None
283+
281284
def _result_type(self, value):
282285
"""Coerce `value` to the correct type according to
283286
``self._singleton_tuples``."""
@@ -375,7 +378,8 @@ class ModelChain:
375378
_deprecated_attrs = ['solar_position', 'airmass', 'total_irrad',
376379
'aoi', 'aoi_modifier', 'spectral_modifier',
377380
'cell_temperature', 'effective_irradiance',
378-
'dc', 'ac', 'diode_params', 'tracking']
381+
'dc', 'ac', 'diode_params', 'tracking',
382+
'weather', 'times']
379383

380384
def __init__(self, system, location,
381385
clearsky_model='ineichen',
@@ -406,9 +410,6 @@ def __init__(self, system, location,
406410
self.dc_ohmic_model = dc_ohmic_model
407411
self.losses_model = losses_model
408412

409-
self.weather = None
410-
self.times = None
411-
412413
self.results = ModelChainResult()
413414

414415
def __getattr__(self, key):
@@ -908,7 +909,7 @@ def infer_spectral_model(self):
908909

909910
def first_solar_spectral_loss(self):
910911
self.results.spectral_modifier = self.system.first_solar_spectral_loss(
911-
_tuple_from_dfs(self.weather, 'precipitable_water'),
912+
_tuple_from_dfs(self.results.weather, 'precipitable_water'),
912913
self.results.airmass['airmass_absolute']
913914
)
914915
return self
@@ -1005,8 +1006,8 @@ def _set_celltemp(self, model):
10051006

10061007
poa = _irrad_for_celltemp(self.results.total_irrad,
10071008
self.results.effective_irradiance)
1008-
temp_air = _tuple_from_dfs(self.weather, 'temp_air')
1009-
wind_speed = _tuple_from_dfs(self.weather, 'wind_speed')
1009+
temp_air = _tuple_from_dfs(self.results.weather, 'temp_air')
1010+
wind_speed = _tuple_from_dfs(self.results.weather, 'wind_speed')
10101011
kwargs = {}
10111012
if model == self.system.noct_sam_celltemp:
10121013
kwargs['effective_irradiance'] = self.results.effective_irradiance
@@ -1161,7 +1162,7 @@ def complete_irradiance(self, weather):
11611162
11621163
Notes
11631164
-----
1164-
Assigns attributes: ``weather``
1165+
Assigns attributes to ``results``: ``times``, ``weather``
11651166
11661167
Examples
11671168
--------
@@ -1174,7 +1175,7 @@ def complete_irradiance(self, weather):
11741175
>>> # my_weather containing 'dhi' and 'ghi'.
11751176
>>> mc = ModelChain(my_system, my_location) # doctest: +SKIP
11761177
>>> mc.complete_irradiance(my_weather) # doctest: +SKIP
1177-
>>> mc.run_model(mc.weather) # doctest: +SKIP
1178+
>>> mc.run_model(mc.results.weather) # doctest: +SKIP
11781179
11791180
>>> # my_weather containing 'dhi', 'ghi' and 'dni'.
11801181
>>> mc = ModelChain(my_system, my_location) # doctest: +SKIP
@@ -1184,16 +1185,16 @@ def complete_irradiance(self, weather):
11841185
self._check_multiple_input(weather)
11851186
# Don't use ModelChain._assign_weather() here because it adds
11861187
# temperature and wind-speed columns which we do not need here.
1187-
self.weather = _copy(weather)
1188+
self.results.weather = _copy(weather)
11881189
self._assign_times()
11891190
self.results.solar_position = self.location.get_solarposition(
1190-
self.times, method=self.solar_position_method)
1191+
self.results.times, method=self.solar_position_method)
11911192

11921193
if isinstance(weather, tuple):
1193-
for w in self.weather:
1194+
for w in self.results.weather:
11941195
self._complete_irradiance(w)
11951196
else:
1196-
self._complete_irradiance(self.weather)
1197+
self._complete_irradiance(self.results.weather)
11971198

11981199
return self
11991200

@@ -1238,7 +1239,7 @@ def _prep_inputs_solar_pos(self, weather):
12381239
pass
12391240

12401241
self.results.solar_position = self.location.get_solarposition(
1241-
self.times, method=self.solar_position_method,
1242+
self.results.times, method=self.solar_position_method,
12421243
**kwargs)
12431244
return self
12441245

@@ -1301,15 +1302,23 @@ def _verify(data, index=None):
13011302
for (i, array_data) in enumerate(data):
13021303
_verify(array_data, i)
13031304

1304-
def _configure_results(self):
1305-
"""Configure the type used for per-array fields in ModelChainResult.
1305+
def _configure_results(self, per_array_data):
1306+
"""Configure the type used for per-array fields in
1307+
ModelChainResult.
13061308
1307-
Must be called after ``self.weather`` has been assigned. If
1308-
``self.weather`` is a tuple and the number of arrays in the system
1309-
is 1, then per-array results are stored as length-1 tuples.
1309+
If ``per_array_data`` is True and the number of arrays in the
1310+
system is 1, then per-array results are stored as length-1
1311+
tuples. This overrides the PVSystem defaults of unpacking a 1
1312+
length tuple into a singleton.
1313+
1314+
Parameters
1315+
----------
1316+
per_array_data : bool
1317+
If input data is provided for each array, pass True. If a
1318+
single input data is provided for all arrays, pass False.
13101319
"""
13111320
self.results._singleton_tuples = (
1312-
self.system.num_arrays == 1 and isinstance(self.weather, tuple)
1321+
self.system.num_arrays == 1 and per_array_data
13131322
)
13141323

13151324
def _assign_weather(self, data):
@@ -1321,13 +1330,13 @@ def _build_weather(data):
13211330
if weather.get('temp_air') is None:
13221331
weather['temp_air'] = 20
13231332
return weather
1324-
if not isinstance(data, tuple):
1325-
self.weather = _build_weather(data)
1333+
if isinstance(data, tuple):
1334+
weather = tuple(_build_weather(wx) for wx in data)
1335+
self._configure_results(per_array_data=True)
13261336
else:
1327-
self.weather = tuple(
1328-
_build_weather(weather) for weather in data
1329-
)
1330-
self._configure_results()
1337+
weather = _build_weather(data)
1338+
self._configure_results(per_array_data=False)
1339+
self.results.weather = weather
13311340
self._assign_times()
13321341
return self
13331342

@@ -1344,18 +1353,20 @@ def _build_irrad(data):
13441353
return self
13451354

13461355
def _assign_times(self):
1347-
"""Assign self.times according the the index of self.weather.
1348-
1349-
If there are multiple DataFrames in self.weather then the index
1350-
of the first one is assigned. It is assumed that the indices of
1351-
each data frame in `weather` are the same. This can be verified
1352-
by calling :py:func:`_all_same_index` or
1353-
:py:meth:`self._check_multiple_weather` before calling this method.
1356+
"""Assign self.results.times according the the index of
1357+
self.results.weather.
1358+
1359+
If there are multiple DataFrames in self.results.weather then
1360+
the index of the first one is assigned. It is assumed that the
1361+
indices of each DataFrame in self.results.weather are the same.
1362+
This can be verified by calling :py:func:`_all_same_index` or
1363+
:py:meth:`self._check_multiple_weather` before calling this
1364+
method.
13541365
"""
1355-
if isinstance(self.weather, tuple):
1356-
self.times = self.weather[0].index
1366+
if isinstance(self.results.weather, tuple):
1367+
self.results.times = self.results.weather[0].index
13571368
else:
1358-
self.times = self.weather.index
1369+
self.results.times = self.results.weather.index
13591370

13601371
def prepare_inputs(self, weather):
13611372
"""
@@ -1386,8 +1397,8 @@ def prepare_inputs(self, weather):
13861397
13871398
Notes
13881399
-----
1389-
Assigns attributes: ``weather``, ``solar_position``, ``airmass``,
1390-
``total_irrad``, ``aoi``
1400+
Assigns attributes to ``results``: ``times``, ``weather``,
1401+
``solar_position``, ``airmass``, ``total_irrad``, ``aoi``
13911402
13921403
See also
13931404
--------
@@ -1421,9 +1432,9 @@ def prepare_inputs(self, weather):
14211432
self.results.solar_position['azimuth'])
14221433

14231434
self.results.total_irrad = get_irradiance(
1424-
_tuple_from_dfs(self.weather, 'dni'),
1425-
_tuple_from_dfs(self.weather, 'ghi'),
1426-
_tuple_from_dfs(self.weather, 'dhi'),
1435+
_tuple_from_dfs(self.results.weather, 'dni'),
1436+
_tuple_from_dfs(self.results.weather, 'ghi'),
1437+
_tuple_from_dfs(self.results.weather, 'dhi'),
14271438
airmass=self.results.airmass['airmass_relative'],
14281439
model=self.transposition_model
14291440
)
@@ -1482,8 +1493,8 @@ def prepare_inputs_from_poa(self, data):
14821493
14831494
Notes
14841495
-----
1485-
Assigns attributes: ``weather``, ``total_irrad``, ``solar_position``,
1486-
``airmass``, ``aoi``.
1496+
Assigns attributes to ``results``: ``times``, ``weather``,
1497+
``total_irrad``, ``solar_position``, ``airmass``, ``aoi``.
14871498
14881499
See also
14891500
--------
@@ -1642,10 +1653,12 @@ def run_model(self, weather):
16421653
16431654
Notes
16441655
-----
1645-
Assigns attributes: ``solar_position``, ``airmass``, ``weather``,
1646-
``total_irrad``, ``aoi``, ``aoi_modifier``, ``spectral_modifier``,
1647-
and ``effective_irradiance``, ``cell_temperature``, ``dc``, ``ac``,
1648-
``losses``, ``diode_params`` (if dc_model is a single diode model).
1656+
Assigns attributes to ``results``: ``times``, ``weather``,
1657+
``solar_position``, ``airmass``, ``total_irrad``, ``aoi``,
1658+
``aoi_modifier``, ``spectral_modifier``, and
1659+
``effective_irradiance``, ``cell_temperature``, ``dc``, ``ac``,
1660+
``losses``, ``diode_params`` (if dc_model is a single diode
1661+
model).
16491662
16501663
See also
16511664
--------
@@ -1701,10 +1714,12 @@ def run_model_from_poa(self, data):
17011714
17021715
Notes
17031716
-----
1704-
Assigns attributes: ``solar_position``, ``airmass``, ``weather``,
1705-
``total_irrad``, ``aoi``, ``aoi_modifier``, ``spectral_modifier``,
1706-
and ``effective_irradiance``, ``cell_temperature``, ``dc``, ``ac``,
1707-
``losses``, ``diode_params`` (if dc_model is a single diode model).
1717+
Assigns attributes to results: ``times``, ``weather``,
1718+
``solar_position``, ``airmass``, ``total_irrad``, ``aoi``,
1719+
``aoi_modifier``, ``spectral_modifier``, and
1720+
``effective_irradiance``, ``cell_temperature``, ``dc``, ``ac``,
1721+
``losses``, ``diode_params`` (if dc_model is a single diode
1722+
model).
17081723
17091724
See also
17101725
--------
@@ -1798,7 +1813,7 @@ def run_model_from_effective_irradiance(self, data=None):
17981813
If optional column ``'poa_global'`` is present, these data are used.
17991814
If ``'poa_global'`` is not present, ``'effective_irradiance'`` is used.
18001815
1801-
Assigns attributes: ``weather``, ``total_irrad``,
1816+
Assigns attributes to results: ``times``, ``weather``, ``total_irrad``,
18021817
``effective_irradiance``, ``cell_temperature``, ``dc``, ``ac``,
18031818
``losses``, ``diode_params`` (if dc_model is a single diode model).
18041819

0 commit comments

Comments
 (0)