pvlib / solarfactors Goto Github PK
View Code? Open in Web Editor NEWA community-maintained implementation of the pvfactors bifacial irradiance model
License: BSD 3-Clause "New" or "Revised" License
A community-maintained implementation of the pvfactors bifacial irradiance model
License: BSD 3-Clause "New" or "Revised" License
Originally posted in SunPower/pvfactors#139:
PVEngine creates a mask indicating values that need not be considered in the calculation:
solarfactors/pvfactors/engine.py
Lines 170 to 175 in 21d4865
However, that mask is not actually used anywhere today. It used to be, but it looks like it stopped being used in #84 during the switch from iterative to vectorized code.
I think the underlying concept may still be useful even in the current vectorized approach, so I think it's probably worth seeing if we can start using it again somehow, otherwise it should just be deleted IMHO.
Originally posted in SunPower/pvfactors#136:
It seems like there are two sets of equivalent notebooks:
On first glance it seems like the notebook contents are identical, with the exception of pvfactors_demo.ipynb
which is only in docs/tutorials
. What's the motivation to have two sets of notebooks?
Originally posted in SunPower/pvfactors#134:
Oftentimes it is desirable to neglect edge effects to model a row in the interior of a large array. Unfortunately, it seems that the runtime scaling with n_pvrows
is quite bad -- I'm having trouble pinning down the degree of the asymptotic polynomial complexity (if it even is polynomial, might be factorial?), but it's certainly not linear or even quadratic:
The good news is that going all the way to n_pvrows > 30
is overkill for making edge effects negligible -- n_pvrows=11
seems pretty good, at least for this array geometry:
The bad news is that n_pvrows=11
is still an order of magnitude slower than n_pvrows=3
, the current default in the pvlib wrapper function. The code for the above plots is available here: https://gist.github.com/kanderso-nrel/e88e3f7389b9d144a546dbe5651dfe1e
I've not looked into how we might go about improving this situation. If I had to guess, it would require a pretty substantial refactor, if it's possible at all. It would be a pleasant surprise if that guess is incorrect :) But even if it can't be fixed, I think it's useful to have an issue documenting this effect.
Originally posted in SunPower/pvfactors#143:
Here's are two things that would be nice to have in the docs, derived largely or entirely from conversation with @spaneja:
For example: PVEngine has its own page (link), but its fit()
method does not, making it hard to know what parameters the method takes (without referring to the tutorials, anyway). I think it should be straightforward to get this working just with sphinx configuration changes.
It's possible to extract not only total incident and absorbed irradiance but also various sub-components. The docs have an example of this:
solarfactors/docs/tutorials/Run_full_timeseries_simulations.ipynb
Lines 232 to 235 in 21d4865
It would be nice for the available component names (e.g. reflection
) to be listed somewhere, along with specific descriptions of what each name refers to (e.g. does reflection
include ground-reflected irradiance, or just row-row reflections?).
Originally posted in SunPower/pvfactors#142:
It would be a minor convenience if BasePVArray.plot_at_idx
could generate its own pyplot Axes object instead of forcing the user to pass one in. I don't mind keeping it as a required parameter in the plot_at_idx
methods of other classes, but since BasePVArray
is at the top of the tree and likely the one that people will be calling, it makes sense to me for it to be a special case.
It's also convenient that ax
is currently the last required parameter, so there's no need to reorder parameters, just tack =None
onto the parameter and do a quick check in the method body. However, since the method currently doesn't return anything, the user would have to use plt.gca()
or similar to access the plot object, so it might make sense to start returning ax
at the end of the method too.
Originally submitted as SunPower/pvfactors#146 by @pasquierjb:
When calling perez_diffuse_luminance irradiance model with a panel configuration where the solar vector is almost orthogonal to the surface normal, the computation falls into an infinite recursion loop.
from pvfactors.irradiance.models import perez_diffuse_luminance
timestamps = ['2020-01-02 15:00:00+00:00']
surface_tilt=[86.43564127244697]
surface_azimuth=[312.7]
solar_zenith=[78.53023197697105]
solar_azimuth=[221.9758276910072]
DNI=[100]
DHI=[50]
perez_diffuse_luminance(
timestamps, surface_tilt, surface_azimuth, solar_zenith,
solar_azimuth, DNI, DHI)
RecursionError: maximum recursion depth exceeded while calling a Python object
This is probably because the dot product of the solar vector and both surface normal (front and back) is computed as negative by pvlib.
from pvlib import irradiance
surface_tilt = 86.43564127244697
surface_azimuth = 312.7
solar_azimuth = 221.9758276910072
solar_zenith = 78.53023197697105
irradiance.aoi_projection(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth)
-3.642919299551295e-17
irradiance.aoi_projection(180-surface_tilt, surface_azimuth-180, solar_zenith, solar_azimuth)
-3.642919299551295e-17
Originally posted in SunPower/pvfactors#135:
By adjusting the gcr
, pvrow_width
, and n_pvrows
inputs it is possible to simulate arrays covering an arbitrarily large distance. For typical inputs (e.g. n_pvrows=3
, pvrow_width=4
, gcr=0.4
), the spanned distance is relatively small, but it's not wholly unreasonable to model a system with, for example, n_pvrows=15
, gcr=0.5
, pvrow_width=6
, in which case the simulated array spans over 100 meters between the first and last rows. The reason this matters is because the default value of MAX_X_GROUND
is 100, so the farthest segments in the scene get ignored entirely in the above extreme example. This leads to strange effects like the following, where legend entries indicate pvrow_height
and pvrow_width
:
Because the row width to height ratio is constant across all three variants, I'd expect the returned irradiance values to be identical. However, they are not, and it's due to an increasingly large portion of the array crossing that MAX_X_GROUND
boundary as the array scale increases. By setting MAX_X_GROUND
to a number large enough to capture the entire array, the difference disappears and the three curves overlap as expected. Here is some code to reproduce:
from pvfactors import config
# uncomment these lines to make the three lines overlap
#config.MAX_X_GROUND = 1e3
#config.MIN_X_GROUND = -config.MAX_X_GROUND
import pvfactors
from pvfactors import geometry
import pvlib
import pandas as pd
import matplotlib.pyplot as plt
times = pd.date_range('2019-12-21 11:30', '2019-12-21 13:05', freq='5T', tz='Etc/GMT+5')
loc = pvlib.location.Location(40, -80)
sp = loc.get_solarposition(times)
cs = loc.get_clearsky(times)
tr = pvlib.tracking.singleaxis(sp.zenith, sp.azimuth, gcr=0.5)
data = {}
for height, width in [(1.5, 2.0), (3.0, 4.0), (4.5, 6.0)]:
ret = pvlib.bifacial.pvfactors_timeseries(sp.azimuth, sp.zenith,
tr.surface_azimuth, tr.surface_tilt,
180, times, cs.dni, cs.dhi,
0.5, height, width, 0.2,
n_pvrows=15, index_observed_pvrow=7)
data[f'({height}, {width})'] = ret[1]
pd.DataFrame(data).plot(figsize=(7, 4))
plt.ylabel('Rear-Side Irradiance [W/m2]')
For reference, the above plot was generated with pvfactors 1.5.1 and pvlib 0.8.1.
The above scenario is certainly unusual, but I don't think it's out of the realm of reasonable usage. Here are some ideas:
MAX_X_GROUND
and MIN_X_GROUND
values. I'm not sure what consequences this would have, if any, for more typical simulations.pvfactors.config
is not mentioned anywhere in the sphinx docs, though maybe I've just missed it.cc @spaneja
Originally posted in SunPower/pvfactors#141:
Currently there is nothing stopping a user from specifying an array much wider than it is tall and proceeding to model it at steep tilts that cause the lower module edge to go to negative heights. When that happens, negative irradiance values are returned. For example, for static solar position and irradiance, here's how the returned rearside irradiance varies with surface tilt for an array with pvrow_width/pvrow_height=3
showing how irradiance goes negative when pvrow_width/2 * sin(surface_tilt) > pvrow_height
:
Code to reproduce above plot (v1.5.2):
from pvfactors.geometry import OrderedPVArray
from pvfactors.engine import PVEngine
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
ts_inputs = pd.DataFrame({
"timestamps": 0,
"DNI": 1000,
"DHI": 200,
"solar_zenith": 45,
"solar_azimuth": 90,
"surface_tilt": np.linspace(0, 90, num=1000),
"surface_azimuth": 90,
"albedo": 0.2,
})
pvarray_parameters = {
"n_pvrows": 3,
"pvrow_height": 1,
"pvrow_width": 3,
"axis_azimuth": 180,
"gcr": 0.5,
}
# tilt when part of the module goes underground
boundary_tilt = np.degrees(np.arcsin(
pvarray_parameters["pvrow_height"] / (pvarray_parameters["pvrow_width"] / 2)
))
pvarray = OrderedPVArray.init_from_dict(pvarray_parameters)
engine = PVEngine(pvarray)
engine.fit(**ts_inputs)
engine.run_full_mode()
grear = pvarray.ts_pvrows[0].back.get_param_weighted("qinc")
plt.plot(ts_inputs["surface_tilt"], grear)
plt.axvline(boundary_tilt, c='k', ls='--')
plt.xlabel("Surface Tilt [degrees]")
plt.ylabel("Rearside Irradiance [W/m2]")
This behavior has tripped people up at least twice:
I think it would be good to raise an error, or at least emit a warning, in this situation. Perhaps a check like this in OrderedPVArray.fit
?
# Check whether the lower module edge ever goes underground
lower_edge_height = self.height - 0.5 * self.width * np.sin(np.radians(surface_tilt))
if np.any(lower_edge_height < 0):
msg = ("Input pvrow_height is not large enough to prevent modules "
"from colliding with ground. Check pvrow_width, "
"pvrow_height, and surface_tilt.")
raise ValueError(msg)
Originally posted in SunPower/pvfactors#131:
Changing the horizon_band_angle
input to HybridPerezOrdered
doesn't seem to affect front-side irradiance nearly as much as it does rear-side. Code to reproduce:
from pvfactors.geometry import OrderedPVArray
from pvfactors.engine import PVEngine
from pvfactors.irradiance.models import HybridPerezOrdered
import pandas as pd
import numpy as np
times = pd.to_datetime(['2019-06-01 12:00'])
dni = np.array([600])
dhi = np.array([100])
solar_zenith = np.array([45])
solar_azimuth = np.array([270])
surface_tilt = np.array([45])
surface_azimuth = np.array([270])
albedo = np.array([0.25])
def build_report(pvarray):
row = pvarray.ts_pvrows[1]
return {
'front': row.front.get_param_weighted('qabs')[0],
'back': row.back.get_param_weighted('qabs')[0],
}
for band_angle in [5, 15]:
irradiance_model = HybridPerezOrdered(horizon_band_angle=band_angle)
pvarray = OrderedPVArray(n_pvrows=3, pvrow_height=1, pvrow_width=1, axis_azimuth=180, gcr=0.5)
engine = PVEngine(pvarray, irradiance_model=irradiance_model)
engine.fit(times, dni, dhi, solar_zenith, solar_azimuth, surface_tilt, surface_azimuth, albedo)
out = engine.run_full_mode(fn_build_report=build_report)
print(f'band_angle = {band_angle}\t-->', out)
band_angle = 5 --> {'front': 737.1206177322375, 'back': 43.08209190810574}
band_angle = 15 --> {'front': 737.1517229315606, 'back': 50.38605014260793}
Looking at HybridPerezOrdered.transform
, I guess the horizon band shading calculation for the front is done differently from the rear. In fact it's not clear to me that horizon band shading is calculated at all for the front side, but maybe I'm missing it. If it is, I would naively expect horizon band shading loss to be more or less the same for front and rear side.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.