Skip to content

Commit 05b3209

Browse files
authored
Upgrade bifacial functions to work with pvfactors v1.4.1 (pvlib#934)
* Tests are now passing with pvfactors v1.4.1 * Update docstrings names for returned variables per pvlib convention * Bump pvfactors version in ci config for testing * Add deprecation warning for parallel mode inputs * Update whatsnew file and remove unused import * Typo * Remove deprecation warnings and unused inputs * Updated whatsnew file * Remove changes from old whatsnew file
1 parent e22c085 commit 05b3209

File tree

7 files changed

+66
-148
lines changed

7 files changed

+66
-148
lines changed

ci/requirements-py35.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@ dependencies:
2525
- siphon # conda-forge
2626
- pip:
2727
- nrel-pysam>=2.0
28-
- pvfactors==1.0.1
28+
- pvfactors==1.4.1
2929
- pytest-rerunfailures # conda version is >3.6
3030
- pytest-remotedata # needs > 0.3.1

ci/requirements-py36.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ dependencies:
2727
- siphon # conda-forge
2828
- pip:
2929
- nrel-pysam>=2.0
30-
- pvfactors==1.0.1
30+
- pvfactors==1.4.1

ci/requirements-py37.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ dependencies:
2727
- siphon # conda-forge
2828
- pip:
2929
- nrel-pysam>=2.0
30-
- pvfactors==1.0.1
30+
- pvfactors==1.4.1

ci/requirements-py38.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ dependencies:
2727
- siphon # conda-forge
2828
- pip:
2929
- nrel-pysam>=2.0
30-
- pvfactors==1.0.1
30+
- pvfactors==1.4.1

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ v0.8.0 (Month day, year)
55

66
API Changes
77
~~~~~~~~~~~
8+
* Removed ``run_parallel_calculations`` and ``n_workers_for_parallel_calcs``
9+
from :py:func:`pvlib.bifacial.pvfactors_timeseries` inputs (:issue:`902`)(:pull:`934`)
810

911
Enhancements
1012
~~~~~~~~~~~~
13+
* Update :func:`~pvlib.bifacial.pvfactors_timeseries` to run with ``pvfactors`` v1.4.1 (:issue:`902`)(:pull:`934`)
1114

1215
Bug fixes
1316
~~~~~~~~~
@@ -36,3 +39,4 @@ Contributors
3639
* Kevin Anderson (:ghuser:`kanderso-nrel`)
3740
* Mark Mikofski (:ghuser:`mikofski`)
3841
* Joshua S. Stein (:ghuser:`jsstein`)
42+
* Marc A. Anoma (:ghuser:`anomam`)

pvlib/bifacial.py

Lines changed: 45 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@
99

1010
def pvfactors_timeseries(
1111
solar_azimuth, solar_zenith, surface_azimuth, surface_tilt,
12-
axis_azimuth,
13-
timestamps, dni, dhi, gcr, pvrow_height, pvrow_width, albedo,
14-
n_pvrows=3, index_observed_pvrow=1,
12+
axis_azimuth, timestamps, dni, dhi, gcr, pvrow_height, pvrow_width,
13+
albedo, n_pvrows=3, index_observed_pvrow=1,
1514
rho_front_pvrow=0.03, rho_back_pvrow=0.05,
16-
horizon_band_angle=15.,
17-
run_parallel_calculations=True, n_workers_for_parallel_calcs=2):
15+
horizon_band_angle=15.):
1816
"""
1917
Calculate front and back surface plane-of-array irradiance on
2018
a fixed tilt or single-axis tracker PV array configuration, and using
@@ -62,127 +60,92 @@ def pvfactors_timeseries(
6260
Back surface reflectivity of PV rows
6361
horizon_band_angle: float, default 15
6462
Elevation angle of the sky dome's diffuse horizon band (deg)
65-
run_parallel_calculations: bool, default True
66-
pvfactors is capable of using multiprocessing. Use this flag to decide
67-
to run calculations in parallel (recommended) or not.
68-
n_workers_for_parallel_calcs: int, default 2
69-
Number of workers to use in the case of parallel calculations. The
70-
'-1' value will lead to using a value equal to the number
71-
of CPU's on the machine running the model.
7263
7364
Returns
7465
-------
75-
front_poa_irradiance: numeric
66+
poa_front: numeric
7667
Calculated incident irradiance on the front surface of the PV modules
7768
(W/m2)
78-
back_poa_irradiance: numeric
69+
poa_back: numeric
7970
Calculated incident irradiance on the back surface of the PV modules
8071
(W/m2)
81-
df_registries: pandas DataFrame
82-
DataFrame containing detailed outputs of the simulation; for
83-
instance the shapely geometries, the irradiance components incident on
84-
all surfaces of the PV array (for all timestamps), etc.
85-
In the pvfactors documentation, this is refered to as the "surface
86-
registry".
72+
poa_front_absorbed: numeric
73+
Calculated absorbed irradiance on the front surface of the PV modules
74+
(W/m2), after AOI losses
75+
poa_back_absorbed: numeric
76+
Calculated absorbed irradiance on the back surface of the PV modules
77+
(W/m2), after AOI losses
8778
8879
References
8980
----------
9081
.. [1] Anoma, Marc Abou, et al. "View Factor Model and Validation for
9182
Bifacial PV and Diffuse Shade on Single-Axis Trackers." 44th IEEE
9283
Photovoltaic Specialist Conference. 2017.
9384
"""
94-
9585
# Convert pandas Series inputs (and some lists) to numpy arrays
9686
if isinstance(solar_azimuth, pd.Series):
9787
solar_azimuth = solar_azimuth.values
9888
elif isinstance(solar_azimuth, list):
9989
solar_azimuth = np.array(solar_azimuth)
10090
if isinstance(solar_zenith, pd.Series):
10191
solar_zenith = solar_zenith.values
92+
elif isinstance(solar_zenith, list):
93+
solar_zenith = np.array(solar_zenith)
10294
if isinstance(surface_azimuth, pd.Series):
10395
surface_azimuth = surface_azimuth.values
10496
elif isinstance(surface_azimuth, list):
10597
surface_azimuth = np.array(surface_azimuth)
10698
if isinstance(surface_tilt, pd.Series):
10799
surface_tilt = surface_tilt.values
100+
elif isinstance(surface_tilt, list):
101+
surface_tilt = np.array(surface_tilt)
108102
if isinstance(dni, pd.Series):
109103
dni = dni.values
104+
elif isinstance(dni, list):
105+
dni = np.array(dni)
110106
if isinstance(dhi, pd.Series):
111107
dhi = dhi.values
112-
if isinstance(solar_azimuth, list):
113-
solar_azimuth = np.array(solar_azimuth)
108+
elif isinstance(dhi, list):
109+
dhi = np.array(dhi)
114110

115111
# Import pvfactors functions for timeseries calculations.
116-
from pvfactors.run import (run_timeseries_engine,
117-
run_parallel_engine)
112+
from pvfactors.run import run_timeseries_engine
118113

119114
# Build up pv array configuration parameters
120115
pvarray_parameters = {
121116
'n_pvrows': n_pvrows,
122117
'axis_azimuth': axis_azimuth,
123118
'pvrow_height': pvrow_height,
124119
'pvrow_width': pvrow_width,
125-
'gcr': gcr,
126-
'rho_front_pvrow': rho_front_pvrow,
127-
'rho_back_pvrow': rho_back_pvrow,
120+
'gcr': gcr
121+
}
122+
123+
irradiance_model_params = {
124+
'rho_front': rho_front_pvrow,
125+
'rho_back': rho_back_pvrow,
128126
'horizon_band_angle': horizon_band_angle
129127
}
130128

131-
# Run pvfactors calculations: either in parallel or serially
132-
if run_parallel_calculations:
133-
report = run_parallel_engine(
134-
PVFactorsReportBuilder, pvarray_parameters,
135-
timestamps, dni, dhi,
136-
solar_zenith, solar_azimuth,
137-
surface_tilt, surface_azimuth,
138-
albedo, n_processes=n_workers_for_parallel_calcs)
139-
else:
140-
report = run_timeseries_engine(
141-
PVFactorsReportBuilder.build, pvarray_parameters,
142-
timestamps, dni, dhi,
143-
solar_zenith, solar_azimuth,
144-
surface_tilt, surface_azimuth,
145-
albedo)
129+
# Create report function
130+
def fn_build_report(pvarray):
131+
return {'total_inc_back': pvarray.ts_pvrows[index_observed_pvrow]
132+
.back.get_param_weighted('qinc'),
133+
'total_inc_front': pvarray.ts_pvrows[index_observed_pvrow]
134+
.front.get_param_weighted('qinc'),
135+
'total_abs_back': pvarray.ts_pvrows[index_observed_pvrow]
136+
.back.get_param_weighted('qabs'),
137+
'total_abs_front': pvarray.ts_pvrows[index_observed_pvrow]
138+
.front.get_param_weighted('qabs')}
139+
140+
# Run pvfactors calculations
141+
report = run_timeseries_engine(
142+
fn_build_report, pvarray_parameters,
143+
timestamps, dni, dhi, solar_zenith, solar_azimuth,
144+
surface_tilt, surface_azimuth, albedo,
145+
irradiance_model_params=irradiance_model_params)
146146

147147
# Turn report into dataframe
148148
df_report = pd.DataFrame(report, index=timestamps)
149149

150-
return df_report.total_inc_front, df_report.total_inc_back
151-
152-
153-
class PVFactorsReportBuilder(object):
154-
"""In pvfactors, a class is required to build reports when running
155-
calculations with multiprocessing because of python constraints"""
156-
157-
@staticmethod
158-
def build(report, pvarray):
159-
"""Reports will have total incident irradiance on front and
160-
back surface of center pvrow (index=1)"""
161-
# Initialize the report as a dictionary
162-
if report is None:
163-
report = {'total_inc_back': [], 'total_inc_front': []}
164-
# Add elements to the report
165-
if pvarray is not None:
166-
pvrow = pvarray.pvrows[1] # use center pvrow
167-
report['total_inc_back'].append(
168-
pvrow.back.get_param_weighted('qinc'))
169-
report['total_inc_front'].append(
170-
pvrow.front.get_param_weighted('qinc'))
171-
else:
172-
# No calculation is performed when the sun is down
173-
report['total_inc_back'].append(np.nan)
174-
report['total_inc_front'].append(np.nan)
175-
176-
return report
177-
178-
@staticmethod
179-
def merge(reports):
180-
"""Works for dictionary reports. Merges the reports list of
181-
dictionaries in a single dictionary. The list of the first
182-
dictionary are extended by those of all subsequent lists."""
183-
report = reports[0]
184-
keys_report = list(report.keys())
185-
for other_report in reports[1:]: # loop won't run if len(reports) < 2
186-
for key in keys_report:
187-
report[key] += other_report[key]
188-
return report
150+
return (df_report.total_inc_front, df_report.total_inc_back,
151+
df_report.total_abs_front, df_report.total_abs_back)

pvlib/tests/test_bifacial.py

Lines changed: 13 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import pandas as pd
2-
import numpy as np
32
from datetime import datetime
4-
from pvlib.bifacial import pvfactors_timeseries, PVFactorsReportBuilder
3+
from pvlib.bifacial import pvfactors_timeseries
54
from conftest import requires_pvfactors
65
import pytest
76

87

98
@requires_pvfactors
10-
@pytest.mark.parametrize('run_parallel_calculations',
11-
[False, True])
12-
def test_pvfactors_timeseries(run_parallel_calculations):
9+
def test_pvfactors_timeseries():
1310
""" Test that pvfactors is functional, using the TLDR section inputs of the
1411
package github repo README.md file:
1512
https://github.com/SunPower/pvfactors/blob/master/README.md#tldr---quick-start"""
@@ -39,29 +36,25 @@ def test_pvfactors_timeseries(run_parallel_calculations):
3936
expected_ipoa_front = pd.Series([1034.95474708997, 795.4423259036623],
4037
index=timestamps,
4138
name=('total_inc_front'))
42-
expected_ipoa_back = pd.Series([91.88707460262768, 78.05831585685215],
39+
expected_ipoa_back = pd.Series([92.12563846416197, 78.05831585685098],
4340
index=timestamps,
4441
name=('total_inc_back'))
4542

4643
# Run calculation
47-
ipoa_front, ipoa_back = pvfactors_timeseries(
44+
ipoa_inc_front, ipoa_inc_back, _, _ = pvfactors_timeseries(
4845
solar_azimuth, solar_zenith, surface_azimuth, surface_tilt,
4946
axis_azimuth,
5047
timestamps, dni, dhi, gcr, pvrow_height, pvrow_width, albedo,
5148
n_pvrows=n_pvrows, index_observed_pvrow=index_observed_pvrow,
5249
rho_front_pvrow=rho_front_pvrow, rho_back_pvrow=rho_back_pvrow,
53-
horizon_band_angle=horizon_band_angle,
54-
run_parallel_calculations=run_parallel_calculations,
55-
n_workers_for_parallel_calcs=-1)
50+
horizon_band_angle=horizon_band_angle)
5651

57-
pd.testing.assert_series_equal(ipoa_front, expected_ipoa_front)
58-
pd.testing.assert_series_equal(ipoa_back, expected_ipoa_back)
52+
pd.testing.assert_series_equal(ipoa_inc_front, expected_ipoa_front)
53+
pd.testing.assert_series_equal(ipoa_inc_back, expected_ipoa_back)
5954

6055

6156
@requires_pvfactors
62-
@pytest.mark.parametrize('run_parallel_calculations',
63-
[False, True])
64-
def test_pvfactors_timeseries_pandas_inputs(run_parallel_calculations):
57+
def test_pvfactors_timeseries_pandas_inputs():
6558
""" Test that pvfactors is functional, using the TLDR section inputs of the
6659
package github repo README.md file, but converted to pandas Series:
6760
https://github.com/SunPower/pvfactors/blob/master/README.md#tldr---quick-start"""
@@ -91,60 +84,18 @@ def test_pvfactors_timeseries_pandas_inputs(run_parallel_calculations):
9184
expected_ipoa_front = pd.Series([1034.95474708997, 795.4423259036623],
9285
index=timestamps,
9386
name=('total_inc_front'))
94-
expected_ipoa_back = pd.Series([91.88707460262768, 78.05831585685215],
87+
expected_ipoa_back = pd.Series([92.12563846416197, 78.05831585685098],
9588
index=timestamps,
9689
name=('total_inc_back'))
9790

9891
# Run calculation
99-
ipoa_front, ipoa_back = pvfactors_timeseries(
92+
ipoa_inc_front, ipoa_inc_back, _, _ = pvfactors_timeseries(
10093
solar_azimuth, solar_zenith, surface_azimuth, surface_tilt,
10194
axis_azimuth,
10295
timestamps, dni, dhi, gcr, pvrow_height, pvrow_width, albedo,
10396
n_pvrows=n_pvrows, index_observed_pvrow=index_observed_pvrow,
10497
rho_front_pvrow=rho_front_pvrow, rho_back_pvrow=rho_back_pvrow,
105-
horizon_band_angle=horizon_band_angle,
106-
run_parallel_calculations=run_parallel_calculations,
107-
n_workers_for_parallel_calcs=-1)
98+
horizon_band_angle=horizon_band_angle)
10899

109-
pd.testing.assert_series_equal(ipoa_front, expected_ipoa_front)
110-
pd.testing.assert_series_equal(ipoa_back, expected_ipoa_back)
111-
112-
113-
def test_build_1():
114-
"""Test that build correctly instantiates a dictionary, when passed a Nones
115-
for the report and pvarray arguments.
116-
"""
117-
report = None
118-
pvarray = None
119-
expected = {'total_inc_back': [np.nan], 'total_inc_front': [np.nan]}
120-
assert expected == PVFactorsReportBuilder.build(report, pvarray)
121-
122-
123-
def test_merge_1():
124-
"""Test that merge correctly returns the first element of the reports
125-
argument when there is only dictionary in reports.
126-
"""
127-
test_dict = {'total_inc_back': [1, 2, 3], 'total_inc_front': [4, 5, 6]}
128-
reports = [test_dict]
129-
assert test_dict == PVFactorsReportBuilder.merge(reports)
130-
131-
132-
def test_merge_2():
133-
"""Test that merge correctly combines two dictionary reports.
134-
"""
135-
test_dict_1 = {'total_inc_back': [1, 2], 'total_inc_front': [4, 5]}
136-
test_dict_2 = {'total_inc_back': [3], 'total_inc_front': [6]}
137-
expected = {'total_inc_back': [1, 2, 3], 'total_inc_front': [4, 5, 6]}
138-
reports = [test_dict_1, test_dict_2]
139-
assert expected == PVFactorsReportBuilder.merge(reports)
140-
141-
142-
def test_merge_3():
143-
"""Test that merge correctly combines three dictionary reports.
144-
"""
145-
test_dict_1 = {'total_inc_back': [1], 'total_inc_front': [4]}
146-
test_dict_2 = {'total_inc_back': [2], 'total_inc_front': [5]}
147-
test_dict_3 = {'total_inc_back': [3], 'total_inc_front': [6]}
148-
expected = {'total_inc_back': [1, 2, 3], 'total_inc_front': [4, 5, 6]}
149-
reports = [test_dict_1, test_dict_2, test_dict_3]
150-
assert expected == PVFactorsReportBuilder.merge(reports)
100+
pd.testing.assert_series_equal(ipoa_inc_front, expected_ipoa_front)
101+
pd.testing.assert_series_equal(ipoa_inc_back, expected_ipoa_back)

0 commit comments

Comments
 (0)