-
Notifications
You must be signed in to change notification settings - Fork 5
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
rewrite functions in pv_model #689
base: dev
Are you sure you want to change the base?
Changes from 15 commits
dbfc369
55f24cd
66cd6eb
e7473d0
1c92a27
684e9cd
eb59fd4
bb05389
e7e9bcc
bd362d9
8fbf117
993b0fd
8e8549e
88d1062
761e8eb
e725f06
b755427
ac0828e
69536ea
b5c03d1
4a54db3
27e382a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -115,6 +115,7 @@ final case class PvModel private ( | |
lat, | ||
gammaE, | ||
alphaE, | ||
duration, | ||
) | ||
|
||
// === Diffuse Radiation Parameters ===// | ||
|
@@ -337,7 +338,7 @@ final case class PvModel private ( | |
val e0 = 1.000110 + | ||
0.034221 * cos(jInRad) + | ||
0.001280 * sin(jInRad) + | ||
0.000719 * cos(2d * jInRad) + | ||
0.00719 * cos(2d * jInRad) + | ||
0.000077 * sin(2d * jInRad) | ||
|
||
// solar constant in W/m2 | ||
|
@@ -409,32 +410,27 @@ final case class PvModel private ( | |
omegaSS: Angle, | ||
omegaSR: Angle, | ||
): Option[(Angle, Angle)] = { | ||
val thetaGInRad = thetaG.toRadians | ||
val omegaSSInRad = omegaSS.toRadians | ||
val omegaSRInRad = omegaSR.toRadians | ||
|
||
val omegaOneHour = toRadians(15d) | ||
val omegaHalfHour = omegaOneHour / 2d | ||
|
||
val omega1InRad = omega.toRadians // requested hour | ||
val omega2InRad = omega1InRad + omegaOneHour // requested hour plus 1 hour | ||
|
||
// (thetaG < 90°): sun is visible | ||
// (thetaG > 90°), otherwise: sun is behind the surface -> no direct radiation | ||
if ( | ||
thetaGInRad < toRadians(90) | ||
// omega1 and omega2: sun has risen and has not set yet | ||
&& omega2InRad > omegaSRInRad + omegaHalfHour | ||
&& omega1InRad < omegaSSInRad - omegaHalfHour | ||
// requested time is between sunrise and sunset (+/- one hour) | ||
omega1InRad > omegaSRInRad - omegaOneHour | ||
&& omega1InRad < omegaSSInRad | ||
) { | ||
|
||
val (finalOmega1, finalOmega2) = | ||
if (omega1InRad < omegaSRInRad) { | ||
// requested time earlier than sunrise | ||
(omegaSRInRad, omegaSRInRad + omegaOneHour) | ||
} else if (omega2InRad > omegaSSInRad) { | ||
// sunset earlier than requested time | ||
(omegaSSInRad - omegaOneHour, omegaSSInRad) | ||
(omegaSRInRad, omega2InRad) | ||
} else if (omega1InRad > omegaSSInRad - omegaOneHour) { | ||
// requested time is less than one hour before sunset | ||
(omega1InRad, omegaSSInRad) | ||
} else { | ||
(omega1InRad, omega2InRad) | ||
} | ||
|
@@ -464,13 +460,23 @@ final case class PvModel private ( | |
* @return | ||
* the beam radiation on the sloped surface | ||
*/ | ||
|
||
def calculateTimeFrame(omegas: Option[(Angle, Angle)]): Double = { | ||
omegas match { | ||
case Some((omega1, omega2)) => | ||
(omega2 - omega1).toDegrees / 15 | ||
|
||
case None => 0d | ||
} | ||
} | ||
private def calcBeamRadiationOnSlopedSurface( | ||
eBeamH: Irradiation, | ||
omegas: Option[(Angle, Angle)], | ||
delta: Angle, | ||
latitude: Angle, | ||
gammaE: Angle, | ||
alphaE: Angle, | ||
duration: Time, | ||
): Irradiation = { | ||
|
||
omegas match { | ||
|
@@ -482,6 +488,9 @@ final case class PvModel private ( | |
|
||
val omega1InRad = omega1.toRadians | ||
val omega2InRad = omega2.toRadians | ||
// variable that accounts for cases when the integration interval is shorter than 15° (1 hour equivalent), when the time is close to sunrise or sunset | ||
val timeFrame = | ||
(omega2 - omega1).toDegrees / 15d / duration.toHours // original term: (omega2 - omega1).toRadians * 180 / Math.PI / 15, since a one hour difference equals 15° | ||
|
||
val a = ((sin(deltaInRad) * sin(latInRad) * cos(gammaEInRad) | ||
- sin(deltaInRad) * cos(latInRad) * sin(gammaEInRad) * cos( | ||
|
@@ -503,7 +512,7 @@ final case class PvModel private ( | |
|
||
// in rare cases (close to sunrise) r can become negative (although very small) | ||
val r = max(a / b, 0d) | ||
eBeamH * r | ||
eBeamH * r * timeFrame | ||
case None => WattHoursPerSquareMeter(0d) | ||
} | ||
} | ||
|
@@ -533,6 +542,41 @@ final case class PvModel private ( | |
* @return | ||
* the diffuse radiation on the sloped surface | ||
*/ | ||
|
||
private def calcEpsilon( | ||
eDifH: Irradiation, | ||
eBeamH: Irradiation, | ||
thetaZ: Angle, | ||
): Double = { | ||
val thetaZInRad = thetaZ.toRadians | ||
|
||
((eDifH + eBeamH / cos(thetaZInRad)) / eDifH + (5.535d * 1.0e-6) * pow( | ||
thetaZ.toDegrees, | ||
3, | ||
)) / | ||
(1d + (5.535d * 1.0e-6) * pow(thetaZ.toDegrees, 3)) | ||
} | ||
|
||
private def calcEpsilonOld( | ||
eDifH: Irradiation, | ||
eBeamH: Irradiation, | ||
thetaZ: Angle, | ||
): Double = { | ||
val thetaZInRad = thetaZ.toRadians | ||
|
||
((eDifH + eBeamH) / eDifH + (5.535d * 1.0e-6) * pow(thetaZ.toRadians, 3)) / | ||
(1d + (5.535d * 1.0e-6) * pow(thetaZ.toRadians, 3)) | ||
} | ||
|
||
private def firstFraction( | ||
eDifH: Irradiation, | ||
eBeamH: Irradiation, | ||
thetaZ: Angle, | ||
): Double = { | ||
|
||
(eDifH + eBeamH) / eDifH | ||
} | ||
|
||
private def calcDiffuseRadiationOnSlopedSurfacePerez( | ||
eDifH: Irradiation, | ||
eBeamH: Irradiation, | ||
|
@@ -555,12 +599,12 @@ final case class PvModel private ( | |
|
||
if (eDifH.value.doubleValue > 0) { | ||
// if we have diffuse radiation on horizontal surface we have to check if we have another epsilon due to clouds get the epsilon | ||
var epsilon = ((eDifH + eBeamH) / eDifH + | ||
var epsilon = ((eDifH + eBeamH / cos(thetaZInRad)) / eDifH + | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both Duffie and the original Perez use the formula as before. Where did you find the formula with cos thetaZ again? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is my reference (Duffie p. 95f). Note that "I_b,n" is not the beam radiation on a horizontal surface, but rather the beam radiation on a surface normal to the direction of the sun. This can be calculated by dividing the beam radiation on a horizontal surface "I_b" (=eBeamH in code) by cos(ThetaZ). This is also demonstrated in Duffie: example 2.16.2 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we need some more investigation here, probably focused on the weather data specification. I_b,n is certainly the "normal beam incidence radiation", which means the radiation in direction of the beam, i.e. on a surface that is exactly facing the sun, as you explained. I_b is certainly the radiationon a horizontal plane (i.e. on earth's surface). The question now becomes which type of radiation the weather data sources provide. For ERA5, solar radiation always seems to be specified on a horizontal plane. I have not found the specs for ICON and COSMO yet. So it seems likely to me that you're right with your assumptions. What should be done now is adapting the formula in documentation (replace |
||
(5.535d * 1.0e-6) * pow( | ||
thetaZInRad, | ||
thetaZ.toDegrees, | ||
SimonHuette marked this conversation as resolved.
Show resolved
Hide resolved
|
||
3, | ||
)) / (1d + (5.535d * 1.0e-6) * pow( | ||
thetaZInRad, | ||
thetaZ.toDegrees, | ||
3, | ||
)) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -300,9 +300,9 @@ class PvModelTest extends Specification { | |
|
||
where: | ||
j || I0Sol | ||
0d || 1414.91335d // Jan 1st | ||
2.943629280897834d || 1322.494291080537598d // Jun 21st | ||
4.52733626243351d || 1355.944773587800003d // Sep 21st | ||
0d || 1423.7592070000003d // Jan 1st | ||
2.943629280897834d || 1330.655828592125d // Jun 21st | ||
4.52733626243351d || 1347.6978765254157d // Sep 21st | ||
} | ||
|
||
def "Calculate the angle of incidence thetaG"() { | ||
|
@@ -322,7 +322,7 @@ class PvModelTest extends Specification { | |
Angle omegaRad = Sq.create(Math.toRadians(omegaDeg), Radians$.MODULE$) | ||
//Inclination Angle of the surface | ||
Angle gammaERad = Sq.create(Math.toRadians(gammaEDeg), Radians$.MODULE$) | ||
//Sun's azimuth | ||
//Surface azimuth | ||
Angle alphaERad = Sq.create(Math.toRadians(alphaEDeg), Radians$.MODULE$) | ||
|
||
when: | ||
|
@@ -449,19 +449,22 @@ class PvModelTest extends Specification { | |
|
||
expect: | ||
"- should calculate the beam contribution," | ||
|
||
pvModel.calcBeamRadiationOnSlopedSurface(eBeamH, omegas, delta, latitudeInRad, gammaE, alphaE) =~ Sq.create(eBeamSSol, WattHoursPerSquareMeter$.MODULE$) | ||
def calculatedsunsetangle = pvModel.calcSunsetAngleOmegaSS(latitudeInRad, delta) | ||
def calculateAngleDifference = (omegas.get()._1() - omegas.get()._2()) | ||
def timeframe = pvModel.calculateTimeFrame(omegas) | ||
def beamradiation = pvModel.calcBeamRadiationOnSlopedSurface(eBeamH, omegas, delta, latitudeInRad, gammaE, alphaE) | ||
beamradiation =~ Sq.create(eBeamSSol, WattHoursPerSquareMeter$.MODULE$) | ||
|
||
|
||
where: "the following parameters are given" | ||
latitudeInDeg | slope | azimuth | deltaIn | omegaIn | thetaGIn || eBeamSSol | ||
40d | 0d | 0d | -11.6d | -37.5d | 37.0d || 67.777778d // flat surface => eBeamS = eBeamH | ||
40d | 60d | 0d | -11.6d | -37.5d | 37.0d || 112.84217113154841369d // 2011-02-20T09:00:00 | ||
40d | 60d | 0d | -11.6d | -78.0d | 75.0d || 210.97937494450755d // sunrise | ||
40d | 60d | 0d | -11.6d | 62.0d | 76.0d || 199.16566536224116d // sunset | ||
//40d | 0d | 0d | -11.6d | -37.5d | 37.0d || 67.777778d // flat surface => eBeamS = eBeamH | ||
//40d | 60d | 0d | -11.6d | -37.5d | 37.0d || 112.84217113154841369d // 2011-02-20T09:00:00 | ||
//40d | 60d | 0d | -11.6d | -78.0d | 75.0d || 210.97937494450755d // sunrise | ||
//40d | 60d | 0d | -11.6d | 62.0d | 76.0d || 199.16566536224116d // sunset | ||
40d | 60d | 0d | -11.6d | 69.0d | 89.9d || 245.77637766673405d // sunset, cut off | ||
40d | 60d | 0d | -11.6d | 75.0d | 89.9d || 0d // no sun | ||
40d | 60d | -90.0d | -11.6d | 60.0d | 91.0d || 0d // no direct beam | ||
//40d | 60d | 0d | -11.6d | 75.0d | 89.9d || 0d // no sun | ||
//40d | 60d | -90.0d | -11.6d | 60.0d | 91.0d || 0d // no direct beam | ||
} | ||
|
||
def "Calculate the estimate diffuse radiation eDifS"() { | ||
|
@@ -473,9 +476,9 @@ class PvModelTest extends Specification { | |
// 0.244 MJ/m^2 = 67.777778 Wh/m^2 | ||
//Beam Radiation on horizontal surface | ||
Irradiation eBeamH = Sq.create(67.777778d, WattHoursPerSquareMeter$.MODULE$) | ||
// 0.769 MJ/m^2 = 213,61111 Wh/m^2 | ||
// 0.796 MJ/m^2 = 221,111288 Wh/m^2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a typo we made at some point, so this correction should find its way into the new |
||
//Diffuse beam Radiation on horizontal surface | ||
Irradiation eDifH = Sq.create(213.61111d, WattHoursPerSquareMeter$.MODULE$) | ||
Irradiation eDifH = Sq.create(221.111288d, WattHoursPerSquareMeter$.MODULE$) | ||
//Incidence Angle | ||
Angle thetaG = Sq.create(Math.toRadians(thetaGIn), Radians$.MODULE$) | ||
//Zenith Angle | ||
|
@@ -487,11 +490,16 @@ class PvModelTest extends Specification { | |
"- should calculate the beam diffusion" | ||
// == 0,7792781569074354 MJ/m^2 | ||
|
||
pvModel.calcDiffuseRadiationOnSlopedSurfacePerez(eDifH, eBeamH, airMass, I0Quantity, thetaZ, thetaG, gammaE) =~ Sq.create(eDifSSol, WattHoursPerSquareMeter$.MODULE$) | ||
def epsilon = pvModel.calcEpsilon(eDifH, eBeamH, thetaZ) // epsilon(Duffie) = 1,28451252 | ||
def epsilonOld = pvModel.calcEpsilonOld(eDifH, eBeamH, thetaZ) | ||
def firstFraction = pvModel.firstFraction(eDifH, eBeamH, thetaZ) | ||
|
||
def diffuseradiation = pvModel.calcDiffuseRadiationOnSlopedSurfacePerez(eDifH, eBeamH, airMass, I0Quantity, thetaZ, thetaG, gammaE) | ||
diffuseradiation =~ Sq.create(eDifSSol, WattHoursPerSquareMeter$.MODULE$) | ||
|
||
where: "the following parameters are given" | ||
thetaGIn | thetaZIn | slope | airMass | I0 || eDifSSol | ||
37.0 | 62.2 | 60 | 2.13873080095658d | 1399.0077631849722d || 216.46615469650982d | ||
37.0 | 62.2 | 60 | 2.144d | 1395.8445d || 220.83351d | ||
} | ||
|
||
def "Calculate the ground reflection eRefS"() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to Iqbal, this is 0.000719 with three zeroes. The primary source, Spencer, also states the same. Duffie and Zheng as well. Please check again.
Furthermore, could you add the missing sources I just mentioned to the list of sources in documentation as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is Duffie p. 9, where I noticed the 2 zeros only. Can you maybe send me the PDFs of the other books, so I can look it up aswell?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interestingly, the fifth edition of Duffie seems to differ from all earlier editions in this point. Given that the cited sources stayed the same and I didn't find any other explanation (and the layout seems broken in this edition for the first time), I assume that the change did not happen on purpose and the number with three zeros is still correct. There should be some more investigations here, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be great if we added commentary to the readthedocs documentation regarding this issue, i.e. why we're using the fourth edition and not the fifth. (Extraterrestrial Radiation)