Skip to content

Commit 6b92d21

Browse files
cwhansewholmgren
andauthored
Add scale_voltage_current_power to ModelChain.pvwatts_dc (#1138)
* add scale_voltage_current_power to ModelChain.pvwatts_dc, docstring edits, whatsnew * extend pvsystem.scale_voltage_current_power to not require all columns, and accept Series * handle tuples, use temporary DataFrame * improve scale_voltage_current_power, handle tuples * sort the new test * improve scale function, fix multiple string test * multiple string test runs * add PR number * Maintain column order in returned DataFrame Co-authored-by: Will Holmgren <[email protected]> * whatsnew entry formatting Co-authored-by: Will Holmgren <[email protected]> Co-authored-by: Will Holmgren <[email protected]>
1 parent 56971c6 commit 6b92d21

File tree

4 files changed

+68
-19
lines changed

4 files changed

+68
-19
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ Enhancements
8383
automatically switch to using ``'effective_irradiance'`` (if available) for
8484
cell temperature models, when ``'poa_global'`` is not provided in input
8585
weather or calculated from input weather data.
86+
* :py:meth:`~pvlib.modelchain.ModelChain.pvwatts_dc` now scales the DC power
87+
by ``pvsystem.PVSystem.modules_per_strings`` and
88+
``pvsystem.PVSystem.strings_per_inverter``. Note that both attributes still
89+
default to 1. (:pull:`1138`)
8690

8791
Bug fixes
8892
~~~~~~~~~

pvlib/modelchain.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,8 +730,33 @@ def pvsyst(self):
730730
return self._singlediode(self.system.calcparams_pvsyst)
731731

732732
def pvwatts_dc(self):
733+
"""Calculate DC power using the PVWatts model.
734+
735+
Results are stored in ModelChain.results.dc. DC power is computed
736+
from PVSystem.module_parameters['pdc0'] and then scaled by
737+
PVSystem.modules_per_string and PVSystem.strings_per_inverter.
738+
739+
Returns
740+
-------
741+
self
742+
743+
See also
744+
--------
745+
pvlib.pvsystem.PVSystem.pvwatts_dc
746+
pvlib.pvsystem.PVSystem.scale_voltage_current_power
747+
"""
733748
self.results.dc = self.system.pvwatts_dc(
734749
self.results.effective_irradiance, self.results.cell_temperature)
750+
if isinstance(self.results.dc, tuple):
751+
temp = tuple(
752+
pd.DataFrame(s, columns=['p_mp']) for s in self.results.dc)
753+
else:
754+
temp = pd.DataFrame(self.results.dc, columns=['p_mp'])
755+
scaled = self.system.scale_voltage_current_power(temp)
756+
if isinstance(scaled, tuple):
757+
self.results.dc = tuple(s['p_mp'] for s in scaled)
758+
else:
759+
self.results.dc = scaled['p_mp']
735760
return self
736761

737762
@property

pvlib/pvsystem.py

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,7 @@ def scale_voltage_current_power(self, data):
891891
Parameters
892892
----------
893893
data: DataFrame or tuple of DataFrame
894-
Must contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',
894+
May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',
895895
'i_sc', 'p_mp'`.
896896
897897
Returns
@@ -2626,13 +2626,13 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,
26262626

26272627
def scale_voltage_current_power(data, voltage=1, current=1):
26282628
"""
2629-
Scales the voltage, current, and power of the DataFrames
2630-
returned by :py:func:`singlediode` and :py:func:`sapm`.
2629+
Scales the voltage, current, and power in data by the voltage
2630+
and current factors.
26312631
26322632
Parameters
26332633
----------
26342634
data: DataFrame
2635-
Must contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',
2635+
May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',
26362636
'i_sc', 'p_mp'`.
26372637
voltage: numeric, default 1
26382638
The amount by which to multiply the voltages.
@@ -2648,14 +2648,15 @@ def scale_voltage_current_power(data, voltage=1, current=1):
26482648

26492649
# as written, only works with a DataFrame
26502650
# could make it work with a dict, but it would be more verbose
2651-
data = data.copy()
2652-
voltages = ['v_mp', 'v_oc']
2653-
currents = ['i_mp', 'i_x', 'i_xx', 'i_sc']
2654-
data[voltages] *= voltage
2655-
data[currents] *= current
2656-
data['p_mp'] *= voltage * current
2657-
2658-
return data
2651+
voltage_keys = ['v_mp', 'v_oc']
2652+
current_keys = ['i_mp', 'i_x', 'i_xx', 'i_sc']
2653+
power_keys = ['p_mp']
2654+
voltage_df = data.filter(voltage_keys, axis=1) * voltage
2655+
current_df = data.filter(current_keys, axis=1) * current
2656+
power_df = data.filter(power_keys, axis=1) * voltage * current
2657+
df = pd.concat([voltage_df, current_df, power_df], axis=1)
2658+
df_sorted = df[data.columns] # retain original column order
2659+
return df_sorted
26592660

26602661

26612662
def pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.):
@@ -2675,20 +2676,20 @@ def pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.):
26752676
Parameters
26762677
----------
26772678
g_poa_effective: numeric
2678-
Irradiance transmitted to the PV cells in units of W/m**2. To be
2679+
Irradiance transmitted to the PV cells. To be
26792680
fully consistent with PVWatts, the user must have already
26802681
applied angle of incidence losses, but not soiling, spectral,
2681-
etc.
2682+
etc. [W/m^2]
26822683
temp_cell: numeric
2683-
Cell temperature in degrees C.
2684+
Cell temperature [C].
26842685
pdc0: numeric
2685-
Power of the modules at 1000 W/m2 and cell reference temperature.
2686+
Power of the modules at 1000 W/m^2 and cell reference temperature. [W]
26862687
gamma_pdc: numeric
2687-
The temperature coefficient in units of 1/C. Typically -0.002 to
2688-
-0.005 per degree C.
2688+
The temperature coefficient of power. Typically -0.002 to
2689+
-0.005 per degree C. [1/C]
26892690
temp_ref: numeric, default 25.0
26902691
Cell reference temperature. PVWatts defines it to be 25 C and
2691-
is included here for flexibility.
2692+
is included here for flexibility. [C]
26922693
26932694
Returns
26942695
-------

pvlib/tests/test_modelchain.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,25 @@ def test_dc_model_user_func(pvwatts_dc_pvwatts_ac_system, location, weather,
11801180
assert not mc.results.ac.empty
11811181

11821182

1183+
def test_pvwatts_dc_multiple_strings(pvwatts_dc_pvwatts_ac_system, location,
1184+
weather, mocker):
1185+
system = pvwatts_dc_pvwatts_ac_system
1186+
m = mocker.spy(system, 'scale_voltage_current_power')
1187+
mc1 = ModelChain(system, location,
1188+
aoi_model='no_loss', spectral_model='no_loss')
1189+
mc1.run_model(weather)
1190+
assert m.call_count == 1
1191+
system.arrays[0].modules_per_string = 2
1192+
mc2 = ModelChain(system, location,
1193+
aoi_model='no_loss', spectral_model='no_loss')
1194+
mc2.run_model(weather)
1195+
assert isinstance(mc2.results.ac, (pd.Series, pd.DataFrame))
1196+
assert not mc2.results.ac.empty
1197+
expected = pd.Series(data=[2., np.nan], index=mc2.results.dc.index,
1198+
name='p_mp')
1199+
assert_series_equal(mc2.results.dc / mc1.results.dc, expected)
1200+
1201+
11831202
def acdc(mc):
11841203
mc.results.ac = mc.results.dc
11851204

0 commit comments

Comments
 (0)