8000 Add scale_voltage_current_power to ModelChain.pvwatts_dc by cwhanse · Pull Request #1138 · pvlib/pvlib-python · GitHub
[go: up one dir, main page]

Skip to content

Add scale_voltage_current_power to ModelChain.pvwatts_dc #1138

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 10 commits into from
Jan 21, 2021
4 changes: 4 additions & 0 deletions docs/sphinx/source/whatsnew/v0.9.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ Enhancements
automatically switch to using ``'effective_irradiance'`` (if available) for
cell temperature models, when ``'poa_global'`` is not provided in input
weather or calculated from input weather data.
* :py:meth:`~pvlib.modelchain.ModelChain.pvwatts_dc` now scales the DC power
by `pvsystem.PVSystem.modules_per_strings` and
`pvsystem.PVSystem.strings_per_inverter`. Note that both attributes still
default to 1. (:pull:`XXXX`)

Bug fixes
~~~~~~~~~
Expand Down
28 changes: 28 additions & 0 deletions pvlib/modelchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -730,8 +730,36 @@ def pvsyst(self):
return self._singlediode(self.system.calcparams_pvsyst)

def pvwatts_dc(self):
"""Calculate DC power using the PVWatts model.

Results are stored in ModelChain.results.dc. DC power is computed
from PVSystem.module_parameters['pdc0'] a 8000 nd then scaled by
PVSystem.modules_per_string and PVSystem.strings_per_inverter.

Returns
-------
self

See also
--------
pvlib.pvsystem.PVSystem.pvwatts_dc
pvlib.pvsystem.PVSystem.scale_voltage_current_power
"""
self.results.dc = self.system.pvwatts_dc(
self.results.effective_irradiance, self.results.cell_temperature)
if isinstance(self.results.dc, tuple):
temp = tuple(
pd.DataFrame(s, columns=['p_mp']) for s in self.results.dc)
else:
temp = pd.DataFrame(self.results.dc, columns=['p_mp'])
scaled = self.system.scale_voltage_current_power(
temp,
unwrap=False
)
if isinstance(scaled, tuple):
self.results.dc = tuple(pd.Series(s) for s in scaled)
else:
self.results.dc = pd.Series(scaled)
return self

@property
Expand Down
33 changes: 18 additions & 15 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,7 @@ def scale_voltage_current_power(self, data):
Parameters
----------
data: DataFrame or tuple of DataFrame
Must contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',
May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',
'i_sc', 'p_mp'`.

Returns
Expand Down Expand Up @@ -2626,13 +2626,13 @@ def i_from_v(resistance_shunt, resistance_series, nNsVth, voltage,

def scale_voltage_current_power(data, voltage=1, current=1):
"""
Scales the voltage, current, and power of the DataFrames
returned by :py:func:`singlediode` and :py:func:`sapm`.
Scales the voltage, current, and power in data by the voltage
and current factors.

Parameters
----------
data: DataFrame
Must contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',
May contain columns `'v_mp', 'v_oc', 'i_mp' ,'i_x', 'i_xx',
'i_sc', 'p_mp'`.
voltage: numeric, default 1
The amount by which to multiply the voltages.
Expand All @@ -2648,13 +2648,16 @@ def scale_voltage_current_power(data, voltage=1, current=1):

# as written, only works with a DataFrame
# could make it work with a dict, but it would be more verbose
voltage_keys = ['v_mp', 'v_oc']
current_keys = ['i_mp', 'i_x', 'i_xx', 'i_sc']
power_keys = ['p_mp']
data = data.copy()
voltages = ['v_mp', 'v_oc']
currents = ['i_mp', 'i_x', 'i_xx', 'i_sc']
voltages = voltage_keys and data.columns
currents = current_keys and data.columns
powers = power_keys and data.columns
Copy link
Member

Choose a reason for hiding this comment

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

I was thinking of something like this:

In [28]: voltage = 5

In [29]: current = 10

In [30]: df = pd.DataFrame([[1, 1, 1]], columns=['p_mp', 'v_mp', 'i_mp'])

In [31]: df
Out[31]:
   p_mp  v_mp  i_mp
0     1     1     1

In [32]:     voltage_keys = ['v_mp', 'v_oc']
    ...:     current_keys = ['i_mp', 'i_x', 'i_xx', 'i_sc']
    ...:     power_keys = ['p_mp']

In [33]: df.filter(voltage_keys, axis=1) * voltage
Out[33]:
   v_mp
0     5

In [34]: df.filter(current_keys, axis=1) * current
Out[34]:
   i_mp
0    10

Copy link
Member

Choose a reason for hiding this comment

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

In [35]: voltage_df = df.filter(voltage_keys, axis=1) * voltage

In [36]: current_df = df.filter(current_keys, axis=1) * current

In [37]: df = pd.concat([voltage_df, current_df], axis=1)

In [38]: df['p_mp'] = df['v_mp'] * df['i_mp']

In [39]: df
Out[39]:
   v_mp  i_mp  p_mp
0     5    10    50

Copy link
Member
@wholmgren wholmgren Jan 21, 2021

Choose a reason for hiding this comment

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

I don't know how to scale a power other than p_mp without hugely complicating the code. I also don't know that there's a need for it at this time.

Copy link
Member Author

Choose a reason for hiding this comment

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

The pvwatts methods don't produce voltage or current.

Copy link
Member
@wholmgren wholmgren Jan 21, 2021

Choose a reason for hiding this comment

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

I was still in the mindset that we could provide a DataFrame with p_mp=pdc, v_mp=pdc, and i_mp=1 as I suggested above. But yes it is better to just multiply the power keys by the voltage and current scale factors.

In [42]: df = pd.DataFrame([[1, 1, 1]], columns=['p_mp', 'v_mp', 'i_mp'])

In [43]: voltage_df = df.filter(voltage_keys, axis=1) * voltage

In [44]: current_df = df.filter(current_keys, axis=1) * current

In [45]: power_df = df.filter(power_keys, axis=1) * voltage * current

In [49]: concat_df = pd.concat([voltage_df, current_df, power_df], axis=1)

In [50]: concat_df = concat_df[df.columns]

In [51]: concat_df
Out[51]:
   p_mp  v_mp  i_mp
0    50     5    10

Copy link
Member Author

Choose a reason for hiding this comment

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

One issue with the temporary DataFrame: when converting back to Series, the column label 'p_mp' is assigned to the Series name, which affects testing but likely no user code. Any concern here?

Copy link
Member
@wholmgren wholmgren Jan 21, 2021

Choose a reason for hiding this comment

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

no concern. pandas assert_series_equal only recently started checking that attribute and it was a fair bit of work to fix all of the newly broken tests in the Arbiter.

data[voltages] *= voltage
data[currents] *= current
data['p_mp'] *= voltage * current

data[powers] *= voltage * current
return data


Expand All @@ -2675,20 +2678,20 @@ def pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.):
Parameters
----------
g_poa_effective: numeric
Irradiance transmitted to the PV cells in units of W/m**2. To be
Irradiance transmitted to the PV cells. To be
fully consistent with PVWatts, the user must have already
applied angle of incidence losses, but not soiling, spectral,
etc.
etc. [W/m^2]
temp_cell: numeric
Cell temperature in degrees C.
Cell temperature [C].
pdc0: numeric
Power of the modules at 1000 W/m2 and cell reference temperature.
Power of the modules at 1000 W/m^2 and cell reference temperature. [W]
gamma_pdc: numeric
The temperature coefficient in units of 1/C. Typically -0.002 to
-0.005 per degree C.
The temperature coefficient of power. Typically -0.002 to
-0.005 per degree C. [1/C]
temp_ref: numeric, default 25.0
Cell reference temperature. PVWatts defines it to be 25 C and
is included here for flexibility.
is included here for flexibility. [C]

Returns
-------
Expand Down
17 changes: 17 additions & 0 deletions pvlib/tests/test_modelchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,23 @@ def test_dc_model_user_func(pvwatts_dc_pvwatts_ac_system, location, weather,
assert not mc.results.ac.empty


def test_pvwatts_dc_multiple_strings(pvwatts_dc_pvwatts_ac_system, location,
weather, mocker):
system = pvwatts_dc_pvwatts_ac_system.copy()
system.modules_per_string = 2
m = mocker.spy(system, 'scale_voltage_current_power')
mc1 = ModelChain(pvwatts_dc_pvwatts_ac_system, location,
aoi_model='no_loss', spectral_model='no_loss')
mc1.run_model(weather)
mc2 = ModelChain(system, location,
aoi_model='no_loss', spectral_model='no_loss')
mc2.run_model(weather)
assert m.call_count == 1
assert isinstance(mc2.results.ac, (pd.Series, pd.DataFrame))
assert not mc2.results.ac.empty
assert np.isclose(mc2.results.dc / mc1.results.dc, 2.0)


def acdc(mc):
mc.results.ac = mc.results.dc

Expand Down
0