8000 Add module transparency and spacing to irradiance models by anomam · Pull Request #72 · SunPower/pvfactors · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add module transparency and spacing to irradiance models #72

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 4 commits into from
Sep 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion docs/sphinx/developer/irradiance.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.. irradiance
.. _irradiance_classes:

.. currentmodule:: pvfactors

Expand Down
1 change: 1 addition & 0 deletions docs/sphinx/theory/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Contents:
intro
view_factors
problem_formulation
irradiance_models


.. rubric:: Footnotes
Expand Down
7 changes: 4 additions & 3 deletions docs/sphinx/theory/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ Introduction

| Due to new bifacial technologies and larger utility-scale photovoltaic (PV) arrays, there is a growing need for models that can more accurately account for the multiple diffuse light components and reflections incident on the front and back surfaces of a PV array.

.. figure:: /theory/intro_pictures/Irradiance_components.PNG
.. figure:: /theory/static/bifacial-solar-panels.jpg
:align: center
:width: 40%

Fig. 1: Schematic showing direct and diffuse irradiance incident on a PV system
Fig. 1: Example of bifacial modules on single-axis tracker


| Ray tracing models are often chosen for their high level of accuracy, but in order to reach such precision they often become computationally intensive and slower to run.
| The view factor model presented here is exploring the possibility of using a simplified method for the calculation of bifacial irradiance. The method presented here is an application of view factors for a 2D geometry of an array of single-axis trackers, invariant by translation along the tracker axis. It can be used for energy production calculation of large PV arrays thanks to its high computational speed, and also because edge effects occurring in large PV arrays are negligible.
| The view factor model presented here is exploring the possibility of using a simplified method for the calculation of bifacial irradiance. The method presented here is an application of view factors for a 2D geometry of a PV array of single-axis trackers or fixed tilt systems, invariant by translation along the tracker axis. It can be used for energy production calculation of large PV arrays thanks to its high computational speed, and also because edge effects occurring in large PV arrays are negligible.

| The goal of this view factor model is to provide fast and accurate irradiance calculations to understand diffuse shading and bifacial effects better.
27 changes: 27 additions & 0 deletions docs/sphinx/theory/irradiance_models.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.. _irradiance_models_theory:

Irradiance models
=================

As shown in the :ref:`Full mode theory <full_mode_theory>` and :ref:`Fast mode theory <fast_mode_theory>` sections, we always need to calculate a sky term for the different surfaces of the PV array.

The sky term is the sum of all the irradiance components (for each surface) that are not directly related to the view factors or to the reflection process, but which still contribute to the incident irradiance on the surfaces. For instance, the direct component of the light incident on the front surface of a PV row is not directly dependent on the view factors, but we still need to account for it in the mathematical model, so this component will go into the sky term.

A lot of different assumptions can be made, which will lead to more or less accurate results. But ``pvfactors`` was designed to make the implementation of these assumptions modular: all of these assumptions can be implemented inside a single Python class which can be used by the other parts of the model. This was done to make it easy for users to create their own irradiance modeling assumptions (inside a new class), and to then plug it into the ``pvfactors`` view-factor mathematical and geometry models.

``pvfactors`` currently provides two irradiance models that can be used interchangeably in the calculations, and which are described in more details in the :ref:`irradiance developer API <irradiance_classes>`.

- the isotropic model assumes that all of the diffuse light from the sky dome is isotropic.
- the (hybrid) perez model follows [#perez_paper]_ and assumes that the diffuse light can be broken down into circumsolar, isotropic, and horizon components (see Fig. 1 below). Validation work shows that this model is more accurate for calculating back-side irradiance with ``pvfactors``.

.. figure:: /theory/static/Irradiance_components.PNG
:align: center
:width: 40%

Fig. 1: Schematic showing direct and diffuse irradiance components on a PV system and according to the Perez diffuse light model [#perez_paper]_



.. rubric:: Footnotes

.. [#perez_paper] Perez, R., Seals, R., Ineichen, P., Stewart, R. and Menicucci, D., 1987. A new simplified version of the Perez diffuse irradiance model for tilted surfaces. Solar energy, 39(3), pp.221-231.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 1 addition & 5 deletions pvfactors/irradiance/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ def transform(self, *args, **kwargs):
"""Not implemented"""
raise NotImplementedError

def transform_ts(self, *args, **kwargs):
"""Not implemented"""
raise NotImplementedError

def get_full_modeling_vectors(self, *args, **kwargs):
"""Not implemented"""
raise NotImplementedError
Expand All @@ -49,7 +45,7 @@ def pvrow_illum(self):
raise NotImplementedError

@property
def sky(self):
def sky_luminance(self):
"""Not implemented"""
raise NotImplementedError

Expand Down
72 changes: 59 additions & 13 deletions pvfactors/irradiance/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class IsotropicOrdered(BaseModel):
cats = ['ground', 'front_pvrow', 'back_pvrow']
irradiance_comp = ['direct']

def __init__(self, rho_front=0.01, rho_back=0.03):
def __init__(self, rho_front=0.01, rho_back=0.03, module_transparency=0.,
module_spacing_ratio=0.):
"""Initialize irradiance model values that will be saved later on.

Parameters
Expand All @@ -32,12 +33,24 @@ def __init__(self, rho_front=0.01, rho_back=0.03):
Reflectivity of the front side of the PV rows (default = 0.01)
rho_back : float, optional
Reflectivity of the back side of the PV rows (default = 0.03)
module_transparency : float, optional
Module transparency (from 0 to 1), which will let some direct light
pass through the PV modules in the PV rows and reach the shaded
ground (Default = 0., fully opaque)
module_spacing_ratio : float, optional
Module spacing ratio (from 0 to 1), which is the ratio of the area
covered by the space between PV modules over the total area of the
PV rows, and which determines how much direct light will reach the
shaded ground through the PV rows
(Default = 0., no spacing at all)
"""
self.direct = dict.fromkeys(self.cats)
self.total_perez = dict.fromkeys(self.cats)
self.isotropic_luminance = None
self.rho_front = rho_front
self.rho_back = rho_back
self.module_transparency = module_transparency
self.module_spacing_ratio = module_spacing_ratio
self.albedo = None
self.GHI = None
self.DHI = None
Expand Down Expand Up @@ -99,7 +112,13 @@ def fit(self, timestamps, DNI, DHI, solar_zenith, solar_azimuth,
self.albedo = albedo

# DNI seen by ground illuminated surfaces
self.direct['ground'] = DNI * cosd(solar_zenith)
self.direct['ground_illum'] = DNI * cosd(solar_zenith)
self.direct['ground_shaded'] = (
# Direct light through PV modules spacing
self.direct['ground_illum'] * self.module_spacing_ratio
# Direct light through PV modules, by transparency
+ self.direct['ground_illum'] * (1. - self.module_spacing_ratio)
* self.module_transparency)

# Calculate AOI on front pvrow using pvlib implementation
aoi_front_pvrow = aoi_function(
Expand Down Expand Up @@ -133,12 +152,12 @@ def transform(self, pvarray):

# Transform timeseries ground
pvarray.ts_ground.illum_params.update(
{'direct': self.direct['ground'],
{'direct': self.direct['ground_illum'],
'rho': self.albedo,
'inv_rho': 1. / self.albedo,
'total_perez': self.gnd_illum})
pvarray.ts_ground.shaded_params.update(
{'direct': np.zeros(n_steps),
{'direct': self.direct['ground_shaded'],
'rho': self.albedo,
'inv_rho': 1. / self.albedo,
'total_perez': self.gnd_shaded})
Expand Down Expand Up @@ -209,7 +228,7 @@ def get_full_modeling_vectors(self, pvarray, idx):
@property
def gnd_shaded(self):
"""Total timeseries irradiance incident on ground shaded areas"""
return self.DHI
return self.DHI + self.direct['ground_shaded']

@property
def gnd_illum(self):
Expand Down Expand Up @@ -250,7 +269,8 @@ class HybridPerezOrdered(BaseModel):
def __init__(self, horizon_band_angle=DEFAULT_HORIZON_BAND_ANGLE,
circumsolar_angle=DEFAULT_CIRCUMSOLAR_ANGLE,
circumsolar_model='uniform_disk', rho_front=0.01,
rho_back=0.03):
< F438 span class='blob-code-inner blob-code-marker ' data-code-marker="+"> rho_back=0.03, module_transparency=0.,
module_spacing_ratio=0.):
"""Initialize irradiance model values that will be saved later on.

Parameters
Expand All @@ -267,6 +287,16 @@ def __init__(self, horizon_band_angle=DEFAULT_HORIZON_BAND_ANGLE,
Reflectivity of the front side of the PV rows (default = 0.01)
rho_back : float, optional
Reflectivity of the back side of the PV rows (default = 0.03)
module_transparency : float, optional
Module transparency (from 0 to 1), which will let some direct light
pass through the PV modules in the PV rows and reach the shaded
ground (Default = 0., fully opaque)
module_spacing_ratio : float, optional
Module spacing ratio (from 0 to 1), which is the ratio of the area
covered by the space between PV modules over the total area of the
PV rows, and which determines how much direct light will reach the
shaded ground through the PV rows
(Default = 0., no spacing at all)
"""
self.direct = dict.fromkeys(self.cats)
self.circumsolar = dict.fromkeys(self.cats)
Expand All @@ -278,6 +308,8 @@ def __init__(self, horizon_band_angle=DEFAULT_HORIZON_BAND_ANGLE,
self.circumsolar_model = circumsolar_model
self.rho_front = rho_front
self.rho_back = rho_back
self.module_transparency = module_transparency
self.module_spacing_ratio = module_spacing_ratio
self.albedo = None
self.GHI = None
self.DNI = None
Expand Down Expand Up @@ -349,8 +381,20 @@ def fit(self, timestamps, DNI, DHI, solar_zenith, solar_azimuth,
self.albedo = albedo

# Ground surfaces
self.direct['ground'] = DNI * cosd(solar_zenith)
self.circumsolar['ground'] = luminance_circumsolar
self.direct['ground_illum'] = DNI * cosd(solar_zenith)
self.direct['ground_shaded'] = (
# Direct light through PV modules spacing
self.direct['ground_illum'] * self.module_spacing_ratio
# Direct light through PV modules, by transparency
+ self.direct['ground_illum'] * (1. - self.module_spacing_ratio)
* self.module_transparency)
self.circumsolar['ground_illum'] = luminance_circumsolar
self.circumsolar['ground_shaded'] = (
# Circumsolar light through PV modules spacing
self.circumsolar['ground_illum'] * self.module_spacing_ratio
# Circumsolar light through PV modules, by transparency
+ self.circumsolar['ground_illum']
* (1. - self.module_spacing_ratio) * self.module_transparency)
self.horizon['ground'] = np.zeros(n)

# PV row surfaces
Expand Down Expand Up @@ -393,15 +437,15 @@ def transform(self, pvarray):

# Transform timeseries ground
pvarray.ts_ground.illum_params.update({
'direct': self.direct['ground'],
'circumsolar': self.circumsolar['ground'],
'direct': self.direct['ground_illum'],
'circumsolar': self.circumsolar['ground_illum'],
'horizon': np.zeros(n_steps),
'rho': self.albedo,
'inv_rho': 1. / self.albedo,
'total_perez': self.gnd_illum})
pvarray.ts_ground.shaded_params.update({
'direct': np.zeros(n_steps),
'circumsolar': np.zeros(n_steps),
'direct': self.direct['ground_shaded'],
'circumsolar': self.circumsolar['ground_shaded'],
'horizon': np.zeros(n_steps),
'rho': self.albedo,
'inv_rho': 1. / self.albedo,
Expand Down Expand Up @@ -499,7 +543,9 @@ def get_full_modeling_vectors(self, pvarray, idx):
@property
def gnd_shaded(self):
"""Total timeseries irradiance incident on ground shaded areas"""
return self.total_perez['ground_shaded']
return (self.total_perez['ground_shaded']
+ self.direct['ground_shaded']
+ self.circumsolar['ground_shaded'])

@property
def gnd_illum(self):
Expand Down
55 changes: 55 additions & 0 deletions pvfactors/tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,3 +488,58 @@ def fn_report(pvarray): return (pvarray.ts_pvrows[1]

np.testing.assert_allclose(qinc_fast, 123.75346216)
np.testing.assert_allclose(report['qinc_back'], 116.49050349491)


def test_pvengine_float_inputs_perez_transparency_spacing(params):
"""Test that module transparency and spacing are having the
expected effect to calculated PV back side irradiance"""

# Irradiance inputs
timestamps = dt.datetime(2019, 6, 11, 11)
DNI = 1000.
DHI = 100.

# --- with 0 transparency and spacing
# Create models
irr_params = {'module_transparency': 0.,
'module_spacing_ratio': 0.}
irradiance_model = HybridPerezOrdered(**irr_params)
pvarray = OrderedPVArray.init_from_dict(params)
eng = PVEngine(pvarray, irradiance_model=irradiance_model)

# Fit engine
eng.fit(timestamps, DNI, DHI,
params['solar_zenith'],
params['solar_azimuth'],
params['surface_tilt'],
params['surface_azimuth'],
params['rho_ground'])
# Run timestep
pvarray = eng.run_full_mode_timestep(0)
no_spacing_transparency_back_qinc = (
pvarray.pvrows[1].back.get_param_weighted('qinc'))

# --- with non-0 transparency and spacing
# Create models
irr_params = {'module_transparency': 0.1,
'module_spacing_ratio': 0.1}
irradiance_model = HybridPerezOrdered(**irr_params)
pvarray = OrderedPVArray.init_from_dict(params)
eng = PVEngine(pvarray, irradiance_model=irradiance_model)

# Fit engine
eng.fit(timestamps, DNI, DHI,
params['solar_zenith'],
params['solar_azimuth'],
params['surface_tilt'],
params['surface_azimuth'],
params['rho_ground'])
# Run timestep
pvarray = eng.run_full_mode_timestep(0)
# Checks
expected_back_qinc = 132.13881181118185 # higher than when params are 0
w_spacing_transparency_back_qinc = (
pvarray.pvrows[1].back.get_param_weighted('qinc'))
np.testing.assert_almost_equal(
w_spacing_transparency_back_qinc, expected_back_qinc)
assert no_spacing_transparency_back_qinc < w_spacing_transparency_back_qinc
Loading
0