Skip to content
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

Snow coverage model from SAM #764

Merged
merged 163 commits into from
Mar 28, 2020
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
163 commits
Select commit Hold shift + click to select a range
bea249f
added snow_model code. Needs formatting
JPalakapillyKWH Aug 8, 2019
a931096
starting numpy-ification
JPalakapillyKWH Aug 8, 2019
ca285d7
minor changes
JPalakapillyKWH Aug 9, 2019
f7acc74
added tests and made model faster
JPalakapillyKWH Aug 13, 2019
9a7964b
finished tests.
JPalakapillyKWH Aug 13, 2019
67cff8b
fixed lint errors.
JPalakapillyKWH Aug 13, 2019
97565bf
fixed minor lint error
JPalakapillyKWH Aug 14, 2019
78ac66a
fixed bug where model stopped prematurely
JPalakapillyKWH Aug 15, 2019
a2ccd92
docstring changes mostly. Changed the location of division by 10 for …
JPalakapillyKWH Aug 16, 2019
4d65294
fixed tests to account for prev change
JPalakapillyKWH Aug 16, 2019
bcd8c37
fixed docstring for snow slide amount
JPalakapillyKWH Aug 16, 2019
8edfffe
docstring edits, move some functions to private
cwhanse Feb 7, 2020
cf71e2a
Merge branch 'master' of https://github.com/pvlib/pvlib-python into s…
cwhanse Feb 7, 2020
43d21cd
rewrite, reduce helpers, remove while loop
cwhanse Feb 13, 2020
1b5dcb1
rename functions, review responses, add subhourly test
cwhanse Feb 14, 2020
7b7edfe
fixes
cwhanse Feb 14, 2020
c5ac03b
temperature -> temp_air, test correction, fix default for m
cwhanse Feb 14, 2020
b9f1988
put m=-80 back
cwhanse Feb 14, 2020
04a0b15
edits for threshold, use first data point
cwhanse Mar 2, 2020
109fa7d
add initial snow, better logic for coverage events
cwhanse Mar 4, 2020
436c8d1
lint, text fix
cwhanse Mar 4, 2020
92d693c
workaround for py35/pandas 0.23.4
cwhanse Mar 5, 2020
b5a9fb9
move line
cwhanse Mar 5, 2020
7116cd2
improve comments
cwhanse Mar 6, 2020
54f7a5e
improvements from review
cwhanse Mar 6, 2020
63c2f58
improve docstring
cwhanse Mar 6, 2020
c766e9a
reorder comparison, add missing inplace
cwhanse Mar 6, 2020
4c25f2a
correct test, cut/paste error
cwhanse Mar 6, 2020
6c6c663
vectorize
cwhanse Mar 9, 2020
7689ef6
correct .clip
cwhanse Mar 9, 2020
47c26da
function and file renaming
cwhanse Mar 10, 2020
ff2ca66
Merge branch 'master' of https://github.com/pvlib/pvlib-python into s…
cwhanse Mar 10, 2020
3a04ccd
docstring corrections, replace hack with pandas offset
cwhanse Mar 13, 2020
10cd8f2
refactor bifacial merge, improve merge tests (#747)
alexandermorgan Jul 20, 2019
733e489
Implement IEC 61853 IAM calculations (#752)
adriesse Jul 26, 2019
2f6376e
remove python 2 lines (#757)
alexandermorgan Jul 26, 2019
16bdde9
fix rounding issue in linke turbidity lookup function (#755)
alexandermorgan Aug 1, 2019
c4091cc
edit DIRINDEX docstring (#760)
cwhanse Aug 6, 2019
1ac666d
added snow_model code. Needs formatting
JPalakapillyKWH Aug 8, 2019
0b6236a
starting numpy-ification
JPalakapillyKWH Aug 8, 2019
ad48714
minor changes
JPalakapillyKWH Aug 9, 2019
edb1b1e
added tests and made model faster
JPalakapillyKWH Aug 13, 2019
8f59d8b
finished tests.
JPalakapillyKWH Aug 13, 2019
0352814
fixed lint errors.
JPalakapillyKWH Aug 13, 2019
d970d20
fixed minor lint error
JPalakapillyKWH Aug 14, 2019
6fa5c95
fixed bug where model stopped prematurely
JPalakapillyKWH Aug 15, 2019
81f2c85
docstring changes mostly. Changed the location of division by 10 for …
JPalakapillyKWH Aug 16, 2019
2e13c1b
fixed tests to account for prev change
JPalakapillyKWH Aug 16, 2019
62f5df6
fixed docstring for snow slide amount
JPalakapillyKWH Aug 16, 2019
2858799
Add recombination current params to all bishop88 functions (#763)
adriesse Aug 28, 2019
539402c
Raise if no parameters provided to retrieve_sam() (#770) (#775)
Peque Aug 28, 2019
04083bf
refactor cec test data into fixtures (#774)
Peque Aug 29, 2019
3052442
remove functions marked for 0.7 removal (#772)
wholmgren Aug 29, 2019
bf79511
move celltemp functions to celltemp.py, expose celltemp.pvsyst in Mod…
cwhanse Sep 9, 2019
f5f776a
Update module and inverter files (#761) (#767)
Peque Sep 9, 2019
81f78d5
refactor repeated code in ModelChain singlediode and LocalizedPVSyste…
Peque Sep 9, 2019
f1b0c5a
Create ivtools (#718)
cwhanse Sep 11, 2019
b75909c
clean up whatsnew after #718 merge (#778)
cwhanse Sep 11, 2019
724ac68
ModelChain 0.7 deprecations, remove times kwarg (#773)
wholmgren Sep 16, 2019
cabc6c8
add timeout to get_psm3 (#741)
cwhanse Sep 16, 2019
fc179a4
Update forecast.py comment typo
aperetti Sep 23, 2019
468e42d
Get rid of `re` deprecation warnings (#787)
Peque Oct 10, 2019
ee7ef7a
Fix documentation references to inverter/module data (#791)
veronicaguo Oct 18, 2019
53d4654
Create iam.py, consistent naming for IAM functions (#783)
cwhanse Oct 18, 2019
743a4fb
add bare environment CI tests (#790)
wholmgren Oct 21, 2019
9d7683e
handle warnings from temperature model tests (#796)
cwhanse Oct 23, 2019
bf29887
replace Pandas item() implementation with numpy's using .values (#797)
CameronTStark Oct 24, 2019
6bfc18b
mark xfail of test_get_psm3 (#803)
CameronTStark Oct 29, 2019
029cf20
coefficient estimation method following DeSoto(2006) (#784)
tylunel Oct 31, 2019
dac54de
change tools._scalar_out/_array_out arg name to avoid collision with …
CameronTStark Oct 31, 2019
1b6e62d
Implement IEC 61853 IAM calculations for diffuse irradiance (#793)
adriesse Nov 2, 2019
e6c33dd
add macOS 10.14 Mojave to Azure Pipelines CI (#812)
cwhanse Nov 5, 2019
2305603
update readme docs to stable
wholmgren Nov 5, 2019
c0fda4f
Add note clearksky Ineichen term b (#814)
cedricleroy Nov 7, 2019
4250d71
Drop DataFrame as option for `module` input to pvsystem.sapm (#811)
cwhanse Nov 7, 2019
4f6584a
Don't expose model parameter dictionaries to users (#805)
veronicaguo Nov 13, 2019
f6b0e41
* Fix for issue #782 (#816)
tylunel Nov 14, 2019
bcbd29f
Add shield organization table with download shields (#820)
CameronTStark Nov 15, 2019
bef5866
fix reading MIDC files with mismatching header/data columns (#822)
alorenzo175 Nov 19, 2019
8792b60
Change units on SAPM effective irradiance from suns to W/m2 (#815)
cwhanse Nov 26, 2019
69e340f
fix handful of documentation warnings (#819)
wholmgren Nov 26, 2019
6a48f94
update contributing documentation, pr template (#818)
wholmgren Nov 26, 2019
54d535b
import bifacial module in __init__.py (#826)
wholmgren Nov 27, 2019
b5bdcce
Put SAM product renaming code in a separate function, simplify, add w…
adriesse Nov 27, 2019
ad12c56
Create scaling.py and implement WVM model (#807)
jranalli Dec 2, 2019
35daed1
Fix typo in TMY total sky cover uncertainty column name (#831)
hamiltonkibbe Dec 6, 2019
673490c
Location object creation from epw metadata (#821)
tylunel Dec 12, 2019
c52bc77
Implement IEC 61853 module temperature model (#834)
adriesse Dec 13, 2019
dd72dcd
update to numpy-1.12.0 (#830)
mikofski Dec 13, 2019
06de0aa
Docstring formatting (#833)
kevinsa5 Dec 16, 2019
2be01f0
*– Formatting of ModelChain.diode_params in pandas.DataFrame (#832)
tylunel Dec 17, 2019
3b6b7db
update whatsnew.rst, add contributors, v0.7.0 release date, address #…
CameronTStark Dec 18, 2019
7b9f922
change overlooked irradiance.total_irrad references to irradiance.get…
kandersolar Dec 23, 2019
92adcfc
refactor get_psm3 code into parse_psm3, read_psm3 (#842)
kandersolar Jan 3, 2020
cb131d6
Fix error in the irradiance unit checker. (#844)
dzimmanck Jan 9, 2020
02a604d
Add gallery of examples using sphinx-gallery (#846)
kandersolar Jan 10, 2020
ae18625
add pvgis to iotools (#845)
mikofski Jan 10, 2020
0b0f097
fix table line lengtsh, remove #noqa, use grid (#852)
mikofski Jan 10, 2020
dd504f6
Improves sapm deprecation warning checker (#854)
cwhanse Jan 12, 2020
c20d443
improve solpos tz requirements documentation (#853)
wholmgren Jan 13, 2020
618fe26
DOC: remove superscript formatting for citation callouts (#855)
kandersolar Jan 13, 2020
b5ab200
reorganize tests into subfolders and use pathlib for conftest DATA_DI…
mikofski Jan 16, 2020
e981839
add Boyle/Coello (Humboldt State Univ) soiling model (#850)
nappaillav Jan 17, 2020
e124175
update whats new for #844 and other contributors (#851)
wholmgren Jan 18, 2020
8da7354
add numfocus affiliation to sphinx docs (#862)
mikofski Jan 22, 2020
18c475f
fix tmy3 leapyear in February handling, coerce_year raises exception …
mikofski Jan 29, 2020
13500d2
Update for PySAM 2.0 (#874)
cwhanse Feb 6, 2020
08f5f19
docstring edits, move some functions to private
cwhanse Feb 7, 2020
1200266
make test_psm3.py robust to API overuse errors (#873)
CameronTStark Feb 10, 2020
578a70f
Fix backwards path sep (#876)
mikofski Feb 11, 2020
1c12459
forecast compat with pandas 1.0, fix bug in Location tz handling (#879)
wholmgren Feb 12, 2020
6c2372f
remove needs_pandas decorator (#885)
CameronTStark Feb 13, 2020
551331a
rewrite, reduce helpers, remove while loop
cwhanse Feb 13, 2020
0c9f848
Add calcparams_desoto+singlediode example to gallery (#872)
kandersolar Feb 14, 2020
8343212
fix documentation home page title (#890)
CameronTStark Feb 14, 2020
3661d86
rename functions, review responses, add subhourly test
cwhanse Feb 14, 2020
f3a2eec
fixes
cwhanse Feb 14, 2020
e80763c
temperature -> temp_air, test correction, fix default for m
cwhanse Feb 14, 2020
b92efee
put m=-80 back
cwhanse Feb 14, 2020
da22506
add Kimber soiling model (#860)
mikofski Feb 15, 2020
238acc6
move contents of 0.6.4 whatsnew into 0.7.0 whatsnew (#899)
wholmgren Feb 21, 2020
7ee7cc6
compatibility for cftime==1.1 (#900)
wholmgren Feb 21, 2020
76309cc
Use pytest remotedata (#896)
CameronTStark Feb 21, 2020
ea286cb
Include Python3.8 into Azure Pipelines (#904)
CameronTStark Feb 22, 2020
4104062
eliminate some of the test suite warnings (#906)
wholmgren Feb 24, 2020
1441d51
Add Contributing section about gallery examples (#905)
kandersolar Feb 24, 2020
23e9ef7
Expose temperature.faiman in PVSystem and ModelChain (#897)
kandersolar Feb 25, 2020
02926a5
Rename test_modelchain system fixture (#915)
veronicaguo Feb 26, 2020
f3d286c
BLD: build docs on Azure Pipelines (#909)
CameronTStark Feb 26, 2020
f70377b
fix read_tmy3 with year coerced not monotonic, breaks soiling (#910)
mikofski Feb 29, 2020
5788223
add read pvgis tmy (#907)
mikofski Mar 1, 2020
a341fd2
TST: make iotools tests robust to API downtime (#919)
CameronTStark Mar 2, 2020
891158a
Link to code of conduct (#922)
mikofski Mar 2, 2020
67f6537
edits for threshold, use first data point
cwhanse Mar 2, 2020
00bda7a
Fix most sphinx warnings (#912)
kandersolar Mar 3, 2020
7524ba8
Fancy "view on github" links in documentation (#913)
kandersolar Mar 3, 2020
52ff419
add initial snow, better logic for coverage events
cwhanse Mar 4, 2020
649a6a5
lint, text fix
cwhanse Mar 4, 2020
d183fae
TST: Use templates in Azure Pipelines config file (#926)
wholmgren Mar 4, 2020
06a3ae7
workaround for py35/pandas 0.23.4
cwhanse Mar 5, 2020
380a576
move line
cwhanse Mar 5, 2020
29e1772
improve comments
cwhanse Mar 6, 2020
1c2ad4f
improvements from review
cwhanse Mar 6, 2020
87288cf
improve docstring
cwhanse Mar 6, 2020
9eb70a9
reorder comparison, add missing inplace
cwhanse Mar 6, 2020
bf42c8f
correct test, cut/paste error
cwhanse Mar 6, 2020
82e94a4
vectorize
cwhanse Mar 9, 2020
b6919b2
correct .clip
cwhanse Mar 9, 2020
14e1390
function and file renaming
cwhanse Mar 10, 2020
21e1086
docstring corrections, replace hack with pandas offset
cwhanse Mar 13, 2020
04ded05
Merge branch 'snow_coverage_model' of https://github.com/JPalakapilly…
cwhanse Mar 18, 2020
4aaa6f1
update api.rst, whatsnew
cwhanse Mar 18, 2020
fc68b91
Merge branch 'master' of https://github.com/pvlib/pvlib-python into s…
cwhanse Mar 18, 2020
af1a2ac
fix headings
cwhanse Mar 18, 2020
aa6bbba
change to use pandas to_offset method
cwhanse Mar 19, 2020
4989c02
Update docs/sphinx/source/api.rst
cwhanse Mar 19, 2020
a41561b
Update docs/sphinx/source/whatsnew/v0.7.2.rst
cwhanse Mar 19, 2020
31ed477
Update pvlib/snow.py
cwhanse Mar 19, 2020
e37eda0
review comments
cwhanse Mar 19, 2020
86599f4
Merge branch 'snow_coverage_model' of https://github.com/JPalakapilly…
cwhanse Mar 19, 2020
21c6813
add snow to __init__.py
cwhanse Mar 19, 2020
bf66c37
add test for irregular times
cwhanse Mar 19, 2020
ae476ed
correct user name
cwhanse Mar 19, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions pvlib/snowcoverage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
"""
The ``snow`` module contains functions that model the effect of snow on
solar modules.
"""

import numpy as np
import pandas as pd
from pvlib.tools import sind


def _time_delta_in_hours(times):
delta = times.to_series().diff()
return delta.dt.seconds.div(3600)


def fully_covered(snowfall, threshold=1.):
'''
Calculates the timesteps when the row's slant height is fully covered
by snow.

Parameters
----------
snowfall : Series
Accumulated snowfall in each time period [cm]

threshold : float, default 1.0
Minimum hourly snowfall to cover a row's slant height. [cm/hr]

Returns
----------
boolean: Series
True where the snowfall exceeds the defined threshold to fully cover
the panel.

Notes
-----
Implements the model described in [1]_ with minor improvements in [2]_.

References
----------
.. [1] Marion, B.; Schaefer, R.; Caine, H.; Sanchez, G. (2013).
“Measured and modeled photovoltaic system energy losses from snow for
Colorado and Wisconsin locations.” Solar Energy 97; pp.112-121.
.. [2] Ryberg, D; Freeman, J. "Integration, Validation, and Application
of a PV Snow Coverage Model in SAM" (2017) NREL Technical Report
'''
delta = snowfall.index.to_series().diff() # [0] will be NaT
timestep = delta.dt.seconds.div(3600) # convert to hours
time_adjusted = snowfall / timestep
time_adjusted.iloc[0] = 0 # replace NaN from NaT / timestep
return time_adjusted >= threshold


def snow_coverage_nrel(snowfall, poa_irradiance, temperature, surface_tilt,
threshold_snowfall=1., m=-80,
sliding_coefficient=0.197):
'''
Calculates the fraction of the slant height of a row of modules covered by
snow at every time step.

Parameters
----------
snowfall : Series
Accumulated snowfall at the end of each time period. [cm]
poa_irradiance : Series
Total in-plane irradiance [W/m^2]
temperature : Series
Ambient air temperature at the surface [C]
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Ambient air temperature at the surface [C]
Ambient air temperature [C]

Copy link
Member

Choose a reason for hiding this comment

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

I think "at the surface" will cause more confusion that clarity. I don't think we use this phrase anywhere else in the library.

surface_tilt : numeric
Tilt of module's from horizontal, e.g. surface facing up = 0,
surface facing horizon = 90. Must be between 0 and 180. [degrees]
threshold_snowfall : float, default 1.0
Copy link
Member

Choose a reason for hiding this comment

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

same thing as threshold in snow_nrel_fully_covered?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, both threshold_snowfall now

Minimum hourly snowfall to cover a row's slant height. [cm/hr]
m : numeric
A coefficient used in the model described in [1]_. [W/(m^2 C)]
sliding coefficient : numeric
Empirically determined coefficient used in [1]_ to determine how much
snow slides off in each time period. [unitless]

Returns
-------
snow_coverage : numeric
The fraction of a the slant height of a row of modules that is covered
by snow at each time step.

Notes
-----
Initial snow coverage is assumed to be zero. Implements the model described
in [1]_ with minor improvements in [2]_. Validated for fixed tilt systems.

References
----------
.. [1] Marion, B.; Schaefer, R.; Caine, H.; Sanchez, G. (2013).
“Measured and modeled photovoltaic system energy losses from snow for
Colorado and Wisconsin locations.” Solar Energy 97; pp.112-121.
.. [2] Ryberg, D; Freeman, J. (2017). "Integration, Validation, and
Application of a PV Snow Coverage Model in SAM" NREL Technical Report
NREL/TP-6A20-68705
'''

# set up output Series
snow_coverage = pd.Series(index=poa_irradiance.index, data=np.nan)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
snow_coverage = pd.Series(index=poa_irradiance.index, data=np.nan)
snow_coverage = pd.Series(np.nan, index=poa_irradiance.index)

snow_events = snowfall[fully_covered(snowfall, threshold_snowfall)]

can_slide = temperature > poa_irradiance / m
slide_amt = sliding_coefficient * sind(surface_tilt) * \
_time_delta_in_hours(poa_irradiance.index)

uncovered = pd.Series(0.0, index=poa_irradiance.index)
uncovered[can_slide] = slide_amt[can_slide]
Copy link
Member

Choose a reason for hiding this comment

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

It's not immediately obvious to me what's happening here and why the variable is called uncovered.


windows = [(ev, ne) for (ev, ne) in
zip(snow_events.index[:-1], snow_events.index[1:])]
# add last time window
windows.append((snow_events.index[-1], snowfall.index[-1]))

for (ev, ne) in windows:
filt = (snow_coverage.index > ev) & (snow_coverage.index <= ne)
snow_coverage[ev] = 1.0
snow_coverage[filt] = 1.0 - uncovered[filt].cumsum()

# clean up periods where row is completely uncovered
snow_coverage[snow_coverage < 0] = 0
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
snow_coverage[snow_coverage < 0] = 0
snow_coverage = snow_coverage.clip(lower=0)

snow_coverage = snow_coverage.fillna(value=0.)
return snow_coverage


def snow_loss_factor(snow_coverage, num_strings):
'''
Calculates the DC loss due to snow coverage. Assumes that if a string is
partially covered by snow, it produces 0W.

Parameters
----------
snow_coverage : numeric
The fraction of row slant height covered by snow at each time step.

num_strings: int
The number of parallel-connected strings along a row slant height.

Returns
-------
loss : numeric
fraction of DC capacity loss due to snow coverage at each time step.
'''
return np.ceil(snow_coverage * num_strings) / num_strings
51 changes: 51 additions & 0 deletions pvlib/tests/test_snowcoverage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import numpy as np
import pandas as pd

from pandas.util.testing import assert_series_equal

from pvlib import snowcoverage
from pvlib.tools import sind


def test_fully_covered():
dt = pd.date_range(start="2019-1-1 12:00:00", end="2019-1-1 18:00:00",
freq='1h')
snowfall_data = pd.Series([1, 5, .6, 4, .23, -5, 19], index=dt)
expected = pd.Series([False, True, False, True, False, False, True],
index=dt)
actual_snowfall = snowcoverage.fully_covered(snowfall_data)
assert_series_equal(actual_snowfall, expected)


def test_snow_coverage_nrel():
surface_tilt = 45
sliding_coefficient = 0.197
dt = pd.date_range(start="2019-1-1 10:00:00", end="2019-1-1 17:00:00",
freq='1h')
poa_irradiance = pd.Series([400, 200, 100, 1234, 134, 982, 100, 100], index=dt)
temperature = pd.Series([10, 2, 10, 1234, 34, 982, 10, 10], index=dt)
slide_amt = sliding_coefficient * sind(surface_tilt)
snowfall_data = pd.Series([1, .5, .6, .4, .23, -5, .1, .1], index=dt)
snow_coverage = snowcoverage.snow_coverage_nrel(
snowfall_data, poa_irradiance, temperature, surface_tilt,
threshold_snowfall=0.0)
# covered every hour except when snowfall = -5
expected = pd.Series([1., 1., 1., 1., 1., 1. - slide_amt, 1., 1.], index=dt)
assert_series_equal(snow_coverage, expected)
# snowfall_threshold = 0.6
snow_coverage = snowcoverage.snow_coverage_nrel(
snowfall_data, poa_irradiance, temperature, surface_tilt,
threshold_snowfall=0.6)
# covered every hour except when snowfall = -5
covered = np.append(np.array([0., 0.]),
1.0 - slide_amt * np.array([0, 1, 2, 3, 4, 5]))
expected = pd.Series(covered, index=dt)
assert_series_equal(snow_coverage, expected)


def test_snow_loss_factor():
num_strings = 8
snow_coverage = pd.Series([1, 1, .5, .6, .2, .4, 0])
expected = pd.Series([1, 1, .5, .625, .25, .5, 0])
actual = snowcoverage.snow_loss_factor(snow_coverage, num_strings)
assert_series_equal(expected, actual)