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

Vectorize geometry calculations #60

Merged
merged 38 commits into from
Aug 2, 2019
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f1dd2fd
Make calculation of PV row coords vectorized
Jul 25, 2019
d584454
Now can create PV rows and ground based on vectorized calculations
Jul 25, 2019
6cccc4c
Make sure not trying to transform with index out of range
Jul 25, 2019
310cac5
Added test for 2D solar vector calculation
Jul 25, 2019
201423b
Vectorized ground shadow coords calculation
Jul 25, 2019
82a035a
Updated shadow coords calc, now makes more sense
Jul 25, 2019
4464674
Order the shadow coords, and calculate when direct shading happens
Jul 26, 2019
7719186
Leverage numpy indexing for pv row coordinates
Jul 26, 2019
6780246
Finally able to create ground shadows from vectorization
Jul 26, 2019
b4e7d8c
Can calculate cut points vectorized
Jul 26, 2019
1900588
Recurse to create ground surfaces with cut points and shadows
Jul 26, 2019
0ae919e
Fix calculation of cut point coordinates: needed radians for rotatin
Jul 26, 2019
834a1b2
Add missing pieces from other class: interrow shad, neighbrs, views
Jul 26, 2019
303ffbf
Started formatting OrderedPVArray to the fit/transform paradigm
Jul 29, 2019
474b6f1
Fix class method: all tests in test_pvarray.py pass
Jul 29, 2019
053a1d5
Now casting shadows and cutting grnd in transform + private methods
Jul 29, 2019
5ed70df
Now all pvarray properties will be rebuilt everytime called
Jul 29, 2019
6a4557c
Now reflectivities passed to irradiance models at init
Jul 29, 2019
8926855
New pv array class methods and use private methods as well
Jul 29, 2019
0737fc7
Update engine to run pv arrays with new fit/transform paradigm
Jul 29, 2019
36cd963
fix pvarray vfcalculator and models tests
Jul 29, 2019
58fec1e
Fix edge point calculation issue and run fn tests
Jul 29, 2019
6a2b6f8
Quick fix for projection approximations errors in fast class
Jul 29, 2019
1bc1a74
Make direct shading condition consistent with previous class
Jul 30, 2019
63bc548
Replace old class with new one: shaded len infinites smaller, reidxd
Jul 30, 2019
bffff14
Add TODO in test
Jul 30, 2019
5ea1bba
Start cleaning up
Jul 30, 2019
ee9173f
Clean up engine.py docstrings and PVEngine class
Jul 31, 2019
b6964e1
Clean up geometry/base.py and udpate docstrings
Jul 31, 2019
f1d232a
Updated docstring in pvground.py
Jul 31, 2019
252ffaf
Update docstrings of OrderedPVArray
Jul 31, 2019
43a1fd8
Remove old redundant and slower class
Jul 31, 2019
934deaf
Updated 4/6 tutorials
Aug 1, 2019
023e304
Implement fix in util function to create ground + add test
Aug 1, 2019
43e06b1
Updated tutorials on running timeseries simulations
Aug 1, 2019
c8dd679
Update readme and tutorials from sphinx docs section
Aug 1, 2019
6832f12
Clean up and add suggestions
Aug 2, 2019
9e8fddb
Do not use mutable objects like [] as fn input default values
Aug 2, 2019
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
118 changes: 53 additions & 65 deletions pvfactors/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
timeseries simulations."""

import numpy as np
from pvfactors.geometry import OrderedPVArray
from pvfactors.viewfactors import VFCalculator
from pvfactors.irradiance import HybridPerezOrdered
from scipy import linalg
Expand All @@ -14,16 +13,15 @@ class PVEngine(object):
as a timeseries when the pvarrays can be build from dictionary parameters
"""

def __init__(self, params, vf_calculator=VFCalculator(),
def __init__(self, pvarray, vf_calculator=VFCalculator(),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's meaningless to have pvarray=OrderedPVArray(), because it will make something fail later.
It's better to initialize the PV array outside of the engine and then pass it in

irradiance_model=HybridPerezOrdered(),
cls_pvarray=OrderedPVArray,
fast_mode_pvrow_index=None):
"""Create pv engine class, and initialize timeseries parameters.

Parameters
----------
params : dict
The parameters defining the PV array
pvarray : BasePVArray (or child) object
The initialized PV array object that will be used for calculations
vf_calculator : vf calculator object, optional
Calculator that will be used to calculate the view factor matrices
(Default =
Expand All @@ -33,61 +31,53 @@ def __init__(self, params, vf_calculator=VFCalculator(),
(Default =
:py:class:`~pvfactors.irradiance.models.HybridPerezOrdered`
object)
cls_pvarray : class of PV array, optional
Class that will be used to build the PV array
(Default =
:py:class:`~pvfactors.geometry.pvarray.OrderedPVArray` class)
fast_mode_pvrow_index : int, optional
If a valid pvrow index is passed, then the PVEngine fast mode
If a pvrow index is passed, then the PVEngine fast mode
will be activated and the engine calculation will be done only
for the back surface of the selected pvrow (Default = None)
for the back surface of the pvrow with the corresponding
index (Default = None)

"""
self.params = params
self.vf_calculator = vf_calculator
self.irradiance = irradiance_model
self.cls_pvarray = cls_pvarray
self.is_fast_mode = isinstance(fast_mode_pvrow_index, int) \
and fast_mode_pvrow_index < params['n_pvrows']
Copy link
Contributor Author

Choose a reason for hiding this comment

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

since params is not passed anymore to the engine, we can't do this check for the user anymore

self.pvarray = pvarray
self.is_fast_mode = isinstance(fast_mode_pvrow_index, int)
Copy link
Contributor

Choose a reason for hiding this comment

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

Would personally do this to avoid the type check:

self.is_fast_mode = False if fast_mode_pvrow_index is None else True

self.fast_mode_pvrow_index = fast_mode_pvrow_index

# Required timeseries values
self.solar_zenith = None
self.solar_azimuth = None
self.surface_tilt = None
self.surface_azimuth = None
# These values will be updated at fitting time
self.n_points = None
self.skip_step = None

def fit(self, timestamps, DNI, DHI, solar_zenith, solar_azimuth,
surface_tilt, surface_azimuth, albedo):
"""Fit the timeseries data to the engine. More specifically,
save all the parameters that needs to be saved, and perform the
irradiance transformations required by the irradiance model.
save all the parameters that needs to be saved, and fit the PV array
and irradiance models to the data (i.e. perform all the intermediate
vector-based calculations).
Note that all angles follow the pvlib-python angle convention: North -
0 deg, East - 90 deg, etc.

Parameters
----------
timestamps : array-like
timestamps : array-like or timestamp-like
List of timestamps of the simulation.
DNI : array-like
DNI : array-like or float
Direct normal irradiance values [W/m2]
DHI : array-like
DHI : array-like or float
Diffuse horizontal irradiance values [W/m2]
solar_zenith : array-like
solar_zenith : array-like or float
Solar zenith angles [deg]
solar_azimuth : array-like
solar_azimuth : array-like or float
Solar azimuth angles [deg]
surface_tilt : array-like
surface_tilt : array-like or float
Surface tilt angles, from 0 to 180 [deg]
surface_azimuth : array-like
surface_azimuth : array-like or float
Surface azimuth angles [deg]
albedo : array-like
Albedo values (or ground reflectivity)
albedo : array-like or float
Albedo values (ground reflectivity)

"""
# Save
# Format inputs to numpy arrays if it looks like floats where inputted
if np.isscalar(DNI):
timestamps = [timestamps]
DNI = np.array([DNI])
Expand All @@ -96,23 +86,21 @@ def fit(self, timestamps, DNI, DHI, solar_zenith, solar_azimuth,
solar_azimuth = np.array([solar_azimuth])
surface_tilt = np.array([surface_tilt])
surface_azimuth = np.array([surface_azimuth])

# Format albedo
self.n_points = len(DNI)
if np.isscalar(albedo):
albedo = albedo * np.ones(self.n_points)

# Save timeseries values
self.solar_zenith = solar_zenith
self.solar_azimuth = solar_azimuth
self.surface_tilt = surface_tilt
self.surface_azimuth = surface_azimuth

# Fit irradiance model
self.irradiance.fit(timestamps, DNI, DHI, solar_zenith, solar_azimuth,
surface_tilt, surface_azimuth,
self.params['rho_front_pvrow'],
self.params['rho_back_pvrow'], albedo)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

now the rho reflectivity values are passed to the irradiance models at initialization: it kind of makes more sense since they are not timeseries values, and bc the fit() function is more or less for timeseries vectorized calculations

surface_tilt, surface_azimuth, albedo)

# Determine timesteps to skip when:
# Fit PV array
self.pvarray.fit(solar_zenith, solar_azimuth, surface_tilt,
surface_azimuth)

# Skip timesteps when:
# - solar zenith > 90, ie the sun is down
# - DNI or DHI is negative, which does not make sense
# - DNI and DHI are both zero
Expand All @@ -122,6 +110,11 @@ def fit(self, timestamps, DNI, DHI, solar_zenith, solar_azimuth,
def run_timestep(self, idx):
"""Run simulation for a single timestep index.

Timestep will be skipped when:
- solar zenith > 90, ie the sun is down
- DNI or DHI is negative, which does not make sense
- DNI and DHI are both zero

Copy link
Contributor

Choose a reason for hiding this comment

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

A little below in the Returns: It looks like None can be returned as well.

Parameters
----------
idx : int
Expand All @@ -138,26 +131,20 @@ def run_timestep(self, idx):
if self.skip_step[idx]:
pvarray = None
else:
# Update parameters
self.params.update(
{'solar_zenith': self.solar_zenith[idx],
'solar_azimuth': self.solar_azimuth[idx],
'surface_tilt': self.surface_tilt[idx],
'surface_azimuth': self.surface_azimuth[idx]})

# Create pv array
pvarray = self.cls_pvarray.from_dict(
self.params, surface_params=self.irradiance.params)
pvarray.cast_shadows()
pvarray.cuts_for_pvrow_view()

# Prepare inputs
geom_dict = pvarray.dict_surfaces
# To be returned at the end
pvarray = self.pvarray

# Transform pvarray to time step
pvarray.transform(idx)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

now pvarray is also using the "fit()/transform()" paradigm, just like the irradiance models


# Apply irradiance terms to pvarray
irradiance_vec, rho_vec, invrho_vec, total_perez_vec = \
self.irradiance.transform(pvarray, idx=idx)

# Prepare inputs to view factor calculator
geom_dict = pvarray.dict_surfaces
view_matrix, obstr_matrix = pvarray.view_obstr_matrices

if self.is_fast_mode:
# Indices of the surfaces of the back of the selected pvrows
list_surface_indices = pvarray.pvrows[
Expand All @@ -166,8 +153,8 @@ def run_timestep(self, idx):
# Calculate view factors using a subset of view_matrix to
# gain in calculation speed
vf_matrix_subset = self.vf_calculator.get_vf_matrix_subset(
geom_dict, pvarray.view_matrix, pvarray.obstr_matrix,
pvarray.pvrows, list_surface_indices)
geom_dict, view_matrix, obstr_matrix, pvarray.pvrows,
list_surface_indices)
pvarray.vf_matrix = vf_matrix_subset

irradiance_vec_subset = irradiance_vec[list_surface_indices]
Expand All @@ -190,8 +177,7 @@ def run_timestep(self, idx):
else:
# Calculate view factors
vf_matrix = self.vf_calculator.get_vf_matrix(
geom_dict, pvarray.view_matrix, pvarray.obstr_matrix,
pvarray.pvrows)
geom_dict, view_matrix, obstr_matrix, pvarray.pvrows)
pvarray.vf_matrix = vf_matrix

# Calculate radiosities
Expand All @@ -206,10 +192,12 @@ def run_timestep(self, idx):
- irradiance_vec[:-1] - isotropic_vec

# Update surfaces with values
for idx, surface in geom_dict.items():
surface.update_params({'q0': q0[idx], 'qinc': qinc[idx],
'isotropic': isotropic_vec[idx],
'reflection': reflection_vec[idx]})
Copy link
Contributor Author

Choose a reason for hiding this comment

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

there was a non-impactful conflict between the idx passed to run_timestep() and the index of this for loop

for idx_surf, surface in geom_dict.items():
surface.update_params(
{'q0': q0[idx_surf],
'qinc': qinc[idx_surf],
'isotropic': isotropic_vec[idx_surf],
'reflection': reflection_vec[idx_surf]})

return pvarray

Expand Down
Loading