From dbfc3691505c47986f80d8994a96927414d47cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Wed, 20 Dec 2023 09:30:26 +0100 Subject: [PATCH 01/36] initial implementation of new functions --- .../simona/model/participant/PvModel.scala | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index 847d45e5a1..4bfb74cd24 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -405,32 +405,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) } @@ -478,6 +473,7 @@ final case class PvModel private ( val omega1InRad = omega1.toRadians val omega2InRad = omega2.toRadians + val timeFrame = (omega2 - omega1) / 15d val a = ((sin(deltaInRad) * sin(latInRad) * cos(gammaEInRad) - sin(deltaInRad) * cos(latInRad) * sin(gammaEInRad) * cos( @@ -499,7 +495,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) } } From 66cd6eb76040274dca43c9d034b5cd17684388b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Wed, 20 Dec 2023 09:49:01 +0100 Subject: [PATCH 02/36] noticed an error in Test --- .../groovy/edu/ie3/simona/model/participant/PvModelTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy index ade1dc2fef..7cd6ff31cd 100644 --- a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy +++ b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy @@ -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: From 1c92a27ed9c19b3820289f95af8e6a1305c8ebd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Sun, 7 Jan 2024 13:00:33 +0100 Subject: [PATCH 03/36] attempt to fix error with wrong unit in the variable timeFrame --- src/main/scala/edu/ie3/simona/model/participant/PvModel.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index 4bfb74cd24..e39a2d3743 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -473,7 +473,7 @@ final case class PvModel private ( val omega1InRad = omega1.toRadians val omega2InRad = omega2.toRadians - val timeFrame = (omega2 - omega1) / 15d + val timeFrame = (omega2 - omega1).toRadians * 12 / Math.PI val a = ((sin(deltaInRad) * sin(latInRad) * cos(gammaEInRad) - sin(deltaInRad) * cos(latInRad) * sin(gammaEInRad) * cos( From 684e9cd5ebc2e96800918c12550433d980ae0fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Wed, 17 Jan 2024 08:21:47 +0100 Subject: [PATCH 04/36] added comment --- src/main/scala/edu/ie3/simona/model/participant/PvModel.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index e39a2d3743..87b16091aa 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -473,7 +473,7 @@ final case class PvModel private ( val omega1InRad = omega1.toRadians val omega2InRad = omega2.toRadians - val timeFrame = (omega2 - omega1).toRadians * 12 / Math.PI + val timeFrame = (omega2 - omega1).toRadians * 12 / Math.PI // 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( From eb59fd41bc0dc7707b7c82959f0a4e071d7fc68f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Mon, 22 Jan 2024 21:43:35 +0100 Subject: [PATCH 05/36] fixed epsilon + testing beam radiation --- .../simona/model/participant/PvModel.scala | 7 +++--- .../model/participant/PvModelTest.groovy | 22 +++++++++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index 87b16091aa..538ac42da9 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -473,6 +473,7 @@ 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).toRadians * 12 / Math.PI // original term: (omega2 - omega1).toRadians * 180 / Math.PI / 15, since a one hour difference equals 15° val a = ((sin(deltaInRad) * sin(latInRad) * cos(gammaEInRad) @@ -547,12 +548,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 + (5.535d * 1.0e-6) * pow( - thetaZInRad, + thetaZ.toDegrees, 3 )) / (1d + (5.535d * 1.0e-6) * pow( - thetaZInRad, + thetaZ.toDegrees, 3 )) diff --git a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy index 7cd6ff31cd..5c278f4726 100644 --- a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy +++ b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy @@ -449,18 +449,26 @@ class PvModelTest extends Specification { expect: "- should calculate the beam contribution," + def calculatedsunsetangle = pvModel.calcSunsetAngleOmegaSS(latitudeInRad, delta) + def calculateAngleDifference(Tuple2 angles) { + def firstAngle = angles._1 + def secondAngle = angles._2 - pvModel.calcBeamRadiationOnSlopedSurface(eBeamH, omegas, delta, latitudeInRad, gammaE, alphaE) =~ Sq.create(eBeamSSol, WattHoursPerSquareMeter$.MODULE$) + return firstAngle.minus(secondAngle) + } + def timeframe = (calculateAngleDifference(omegas)).toRadians * 12 / Math.PI + 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 | 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 | 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 } From bb053895f9dd020d6b8a7f317513c4353d05e1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Thu, 25 Jan 2024 10:08:35 +0100 Subject: [PATCH 06/36] testing beam and diffuse radiation --- .../simona/model/participant/PvModel.scala | 28 +++++++++++++++++-- .../model/participant/PvModelTest.groovy | 20 ++++++------- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index 538ac42da9..d799d112c7 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -455,6 +455,16 @@ final case class PvModel private ( * @return * the beam radiation on the sloped surface */ + + def calculateTimeFrame(omegas: Option[(Angle, Angle)]): Unit = { + omegas match { + case Some((omega1, omega2)) => + + (omega2 - omega1).toRadians * 12 / Math.PI + + case None => 0d + } + } private def calcBeamRadiationOnSlopedSurface( eBeamH: Irradiation, omegas: Option[(Angle, Angle)], @@ -526,6 +536,18 @@ final case class PvModel private ( * @return * the diffuse radiation on the sloped surface */ + + private def calcEpsilon(eDifH: Irradiation, eBeamH: Irradiation, thetaZ: Angle): Unit = { + ((eDifH + eBeamH) / eDifH + + (5.535d * 1.0e-6) * pow( + thetaZ.toRadians, + 3 + )) / (1d + (5.535d * 1.0e-6) * pow( + thetaZ.toRadians, + 3 + )) + } + private def calcDiffuseRadiationOnSlopedSurfacePerez( eDifH: Irradiation, eBeamH: Irradiation, @@ -548,12 +570,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 / cos (thetaZInRad)) / eDifH + + var epsilon = ((eDifH + eBeamH) / eDifH + (5.535d * 1.0e-6) * pow( - thetaZ.toDegrees, + thetaZ.toRadians, 3 )) / (1d + (5.535d * 1.0e-6) * pow( - thetaZ.toDegrees, + thetaZ.toRadians, 3 )) diff --git a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy index 5c278f4726..bdb50484b4 100644 --- a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy +++ b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy @@ -450,13 +450,8 @@ class PvModelTest extends Specification { expect: "- should calculate the beam contribution," def calculatedsunsetangle = pvModel.calcSunsetAngleOmegaSS(latitudeInRad, delta) - def calculateAngleDifference(Tuple2 angles) { - def firstAngle = angles._1 - def secondAngle = angles._2 - - return firstAngle.minus(secondAngle) - } - def timeframe = (calculateAngleDifference(omegas)).toRadians * 12 / Math.PI + //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$) @@ -468,8 +463,8 @@ class PvModelTest extends Specification { //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"() { @@ -495,7 +490,12 @@ 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) + + + + 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 From e7e9bccb69e006e1d1d3cd7815a651aa332e38ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Fri, 26 Jan 2024 09:29:40 +0100 Subject: [PATCH 07/36] further testing --- .../simona/model/participant/PvModel.scala | 19 +++++++++++-------- .../model/participant/PvModelTest.groovy | 6 +++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index d799d112c7..0afa4f1d8f 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -456,11 +456,11 @@ final case class PvModel private ( * the beam radiation on the sloped surface */ - def calculateTimeFrame(omegas: Option[(Angle, Angle)]): Unit = { + def calculateTimeFrame(omegas: Option[(Angle, Angle)]): Double = { omegas match { case Some((omega1, omega2)) => - (omega2 - omega1).toRadians * 12 / Math.PI + (omega2 - omega1).toDegrees / 15 case None => 0d } @@ -471,7 +471,8 @@ final case class PvModel private ( delta: Angle, latitude: Angle, gammaE: Angle, - alphaE: Angle + alphaE: Angle, + duration: Time ): Irradiation = { omegas match { @@ -484,7 +485,7 @@ 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).toRadians * 12 / Math.PI // original term: (omega2 - omega1).toRadians * 180 / Math.PI / 15, since a one hour difference equals 15° + val timeFrame = (omega2 - omega1).toDegrees * 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( @@ -537,13 +538,15 @@ final case class PvModel private ( * the diffuse radiation on the sloped surface */ - private def calcEpsilon(eDifH: Irradiation, eBeamH: Irradiation, thetaZ: Angle): Unit = { - ((eDifH + eBeamH) / eDifH + + 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.toRadians, + thetaZ.toDegrees, 3 )) / (1d + (5.535d * 1.0e-6) * pow( - thetaZ.toRadians, + thetaZ.toDegrees, 3 )) } diff --git a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy index bdb50484b4..3551298f9e 100644 --- a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy +++ b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy @@ -450,7 +450,7 @@ class PvModelTest extends Specification { expect: "- should calculate the beam contribution," def calculatedsunsetangle = pvModel.calcSunsetAngleOmegaSS(latitudeInRad, delta) - //def calculateAngleDifference = (omegas.get(1) - omegas.get(2)) + 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$) @@ -462,8 +462,8 @@ class PvModelTest extends Specification { //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 | 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 } From bd362d9e9ebd25f90372f07db3c41632714aa80b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Mon, 29 Jan 2024 08:05:34 +0100 Subject: [PATCH 08/36] testing --- .../simona/model/participant/PvModel.scala | 24 +++++++++++++++++-- .../model/participant/PvModelTest.groovy | 4 ++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index 0afa4f1d8f..75e446ea70 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -110,7 +110,8 @@ final case class PvModel private ( delta, lat, gammaE, - alphaE + alphaE, + duration ) // === Diffuse Radiation Parameters ===// @@ -485,7 +486,7 @@ 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 * duration.toHours // original term: (omega2 - omega1).toRadians * 180 / Math.PI / 15, since a one hour difference equals 15° + 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( @@ -551,6 +552,25 @@ final case class PvModel private ( )) } + 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, diff --git a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy index 3551298f9e..c8b8a1779a 100644 --- a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy +++ b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy @@ -491,8 +491,8 @@ class PvModelTest extends Specification { // == 0,7792781569074354 MJ/m^2 def epsilon = pvModel.calcEpsilon(eDifH, eBeamH, thetaZ) - - + 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$) From 8fbf117a4582abf148763ada5dc215f7ee5e0409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Mon, 29 Jan 2024 08:12:40 +0100 Subject: [PATCH 09/36] noticed error in CalcExtraterrestrialRadiation --- src/main/scala/edu/ie3/simona/model/participant/PvModel.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index 75e446ea70..136c9e69f2 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -334,7 +334,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 From 993b0fd190b9187a994e59f50896573fa2f2343b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Mon, 29 Jan 2024 09:21:20 +0100 Subject: [PATCH 10/36] adjusted values for extraterrestrial radiation in test function --- .../edu/ie3/simona/model/participant/PvModelTest.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy index c8b8a1779a..5b394809e7 100644 --- a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy +++ b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy @@ -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"() { From 8e8549ea5d2edfc813ec6c1cbe7aa2cd6ff0b811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Wed, 7 Feb 2024 15:19:04 +0100 Subject: [PATCH 11/36] found error in GroovyTest (wrong data) --- .../simona/model/participant/PvModel.scala | 31 +++++-------------- .../model/participant/PvModelTest.groovy | 8 ++--- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index 136c9e69f2..34ca829923 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -542,27 +542,15 @@ final case class PvModel private ( 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 - )) + ((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 - )) + ((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 = { @@ -593,12 +581,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 + (5.535d * 1.0e-6) * pow( - thetaZ.toRadians, + thetaZ.toDegrees, 3 )) / (1d + (5.535d * 1.0e-6) * pow( - thetaZ.toRadians, + thetaZ.toDegrees, 3 )) @@ -635,10 +623,7 @@ final case class PvModel private ( // calculate the f_ij components based on the epsilon bin val f11 = -0.0161 * pow(x, 3) + 0.1840 * pow(x, 2) - 0.3806 * x + 0.2324 - val f12 = 0.0134 * pow(x, 4) - 0.1938 * pow(x, 3) + 0.8410 * pow( - x, - 2 - ) - 1.4018 * x + 1.3579 + val f12 = 0.0134 * pow(x, 4) - 0.1938 * pow(x, 3) + 0.8410 * pow(x, 2) - 1.4018 * x + 1.3579 val f13 = 0.0032 * pow(x, 3) - 0.0280 * pow(x, 2) - 0.0056 * x - 0.0385 val f21 = -0.0048 * pow(x, 3) + 0.0536 * pow(x, 2) - 0.1049 * x + 0.0034 val f22 = 0.0012 * pow(x, 3) - 0.0067 * pow(x, 2) + 0.0091 * x - 0.0269 diff --git a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy index 5b394809e7..71f8afb7fb 100644 --- a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy +++ b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy @@ -476,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 //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 @@ -490,7 +490,7 @@ class PvModelTest extends Specification { "- should calculate the beam diffusion" // == 0,7792781569074354 MJ/m^2 - def epsilon = pvModel.calcEpsilon(eDifH, eBeamH, thetaZ) + 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) @@ -499,7 +499,7 @@ class PvModelTest extends Specification { 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"() { From 761e8eb22879b9055f62337e12403967354d1598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Tue, 5 Mar 2024 17:26:23 +0100 Subject: [PATCH 12/36] spotless apply --- .../simona/model/participant/PvModel.scala | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index fb8023b40a..fe2a830d68 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -464,7 +464,6 @@ final case class PvModel private ( def calculateTimeFrame(omegas: Option[(Angle, Angle)]): Double = { omegas match { case Some((omega1, omega2)) => - (omega2 - omega1).toDegrees / 15 case None => 0d @@ -490,7 +489,8 @@ 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 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( @@ -543,26 +543,40 @@ final case class PvModel private ( * the diffuse radiation on the sloped surface */ - private def calcEpsilon(eDifH: Irradiation, eBeamH: Irradiation, thetaZ: Angle): Double = { + 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)) + ((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 = { + 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)) + ((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 = { + private def firstFraction( + eDifH: Irradiation, + eBeamH: Irradiation, + thetaZ: Angle, + ): Double = { (eDifH + eBeamH) / eDifH } - private def calcDiffuseRadiationOnSlopedSurfacePerez( eDifH: Irradiation, eBeamH: Irradiation, @@ -627,7 +641,10 @@ final case class PvModel private ( // calculate the f_ij components based on the epsilon bin val f11 = -0.0161 * pow(x, 3) + 0.1840 * pow(x, 2) - 0.3806 * x + 0.2324 - val f12 = 0.0134 * pow(x, 4) - 0.1938 * pow(x, 3) + 0.8410 * pow(x, 2) - 1.4018 * x + 1.3579 + val f12 = 0.0134 * pow(x, 4) - 0.1938 * pow(x, 3) + 0.8410 * pow( + x, + 2, + ) - 1.4018 * x + 1.3579 val f13 = 0.0032 * pow(x, 3) - 0.0280 * pow(x, 2) - 0.0056 * x - 0.0385 val f21 = -0.0048 * pow(x, 3) + 0.0536 * pow(x, 2) - 0.1049 * x + 0.0034 val f22 = 0.0012 * pow(x, 3) - 0.0067 * pow(x, 2) + 0.0091 * x - 0.0269 From e725f06bf049d47fb26bacbb4058c67d2221b1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Sat, 30 Mar 2024 09:01:30 +0100 Subject: [PATCH 13/36] added sources in documentation --- docs/readthedocs/models/pv_model.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/readthedocs/models/pv_model.md b/docs/readthedocs/models/pv_model.md index 8ddb822c0b..6ddd75fd7e 100644 --- a/docs/readthedocs/models/pv_model.md +++ b/docs/readthedocs/models/pv_model.md @@ -240,6 +240,8 @@ $$ ```{eval-rst} * :cite:ts:`Zheng.2017` p. 53, formula 2.3b * :cite:ts:`Iqbal.1983` +* :cite:ts:`Spencer.1971` +* :cite:ts:`Duffie.2013` ``` ### Beam Radiation on Sloped Surface From b755427b0159a11fdd0e8664bcc71a69ea33d9ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Sat, 30 Mar 2024 09:09:55 +0100 Subject: [PATCH 14/36] angle in degrees documentation --- docs/readthedocs/models/pv_model.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/readthedocs/models/pv_model.md b/docs/readthedocs/models/pv_model.md index 6ddd75fd7e..8eb4ce2da2 100644 --- a/docs/readthedocs/models/pv_model.md +++ b/docs/readthedocs/models/pv_model.md @@ -312,6 +312,8 @@ $$ \epsilon = \frac{\frac{E_{dif,H} + E_{beam,H}}{E_{dif,H}} + 5.535 \cdot 10^{-6} \cdot \theta_{z}^3}{1 + 5.535 \cdot 10^{-6} \cdot \theta_{z}^3} $$ +where the angle $theta_{z}$ is in **degrees**. + Calculating a brightness index $$ @@ -414,6 +416,7 @@ $$ * :cite:ts:`Perez.1987` * :cite:ts:`Perez.1990` * :cite:ts:`Myers.2017` p. 96f +* :cite:ts:`Duffie.2013` p. 95f ``` ### Reflected Radiation on Sloped Surface From ac0828e9db83b420c5c3d8a503763398f5e3f5b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=BCtte?= Date: Sat, 30 Mar 2024 11:11:03 +0100 Subject: [PATCH 15/36] updated links in library --- docs/readthedocs/_static/bibliography/bibtexAll.bib | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/readthedocs/_static/bibliography/bibtexAll.bib b/docs/readthedocs/_static/bibliography/bibtexAll.bib index e3a6a3f045..8899037ad0 100644 --- a/docs/readthedocs/_static/bibliography/bibtexAll.bib +++ b/docs/readthedocs/_static/bibliography/bibtexAll.bib @@ -15,7 +15,7 @@ @Article{Maleki.2017 @MISC{Itaca_Sun, author = {Itacanet}, title = {The Sun As A Source Of Energy}, -howpublished={\url{https://www.itacanet.org/the-sun-as-a-source-of-energy/part-3-calculating-solar-angles/}} +howpublished={\url{https://de.scribd.com/document/455342846/Part-3-Calculating-Solar-Angles-ITACA}} } @article{Spencer.1971, @@ -73,7 +73,7 @@ @book{Quaschning.2013 author = {Quaschning, Volker}, year = {2013}, title = {Regenerative Energiesysteme: Technologie; Berechnung; Simulation}, - url = {http://ebooks.ciando.com/book/index.cfm/bok_id/471802}, + url = {https://www.hanser-elibrary.com/doi/book/10.3139/9783446435711}, price = {39.99 EUR}, address = {M{\"u}nchen}, edition = {8., aktualisierte und erw. Aufl.}, @@ -92,7 +92,8 @@ @Inbook{Schoenberg.1929 pages={1--280}, abstract={Die ersten Versuche, Messungen der Lichtst{\"a}rke der Himmelsk{\"o}rper auszuf{\"u}hren, um auf diesem Wege Aufschl{\"u}sse {\"u}ber ihre Beschaffenheit zu erhalten, fallen in die Zeit jenes gewaltigen Aufschwungs, welchen die Optik durch die Arbeiten von Newton und Huygens am Ende des siebzehnten und zu Beginn des achtzehnten Jahrhunderts erfahren hatte. Schon Huygens1 selbst versuchte die Helligkeit des Sirius mit derjenigen der Sonne zu vergleichen, indem er das Sonnenlicht durch eine kleine {\"O}ffnung im verschlossenen Ende eines langen Rohres abschw{\"a}chte. Der schwedische Physiker Celsius2 suchte nach einem Gesetze der Lichtabnahme beleuchteter Fl{\"a}chen, doch waren seine Schlu{\ss}folgerungen, ebenso wie diejenigen von Huygens, infolge der Unzul{\"a}nglichkeit der angewandten Methoden nicht stichhaltig. Au{\ss}er der Formel der Lichtabnahme mit dem Quadrate der Entfernung besa{\ss} die Physik noch keinerlei R{\"u}stzeug an strengen Definitionen und Gesetzen und keinerlei Apparate zur Messung der Lichtst{\"a}rken. Buffon3 versuchte den Verlust zu bestimmen, den das Sonnenlicht bei Reflexion an gl{\"a}sernen Spiegeln erleidet. Aber erst die gro{\ss} angelegten Arbeiten von Pierre Bouguer4 5 (1698--1758) und Johann Heinrich Lambert (1728--1777) schufen die Grundlagen der Photometrie. In systematischer experimenteller Arbeit, die durch sinnreiche Theorien erl{\"a}utert wird, behandelt Bouguer ihre Hauptprobleme: die Absorption des Lichtes bei der Reflexion und beim Durchgang durch feste und fl{\"u}ssige K{\"o}rper sowie die diffuse Reflexion an matten Fl{\"a}chen und beim Durchgange durch tr{\"u}be Medien.}, isbn={978-3-642-90703-6}, -doi={10.1007/978-3} +doi={10.1007/978-3}, +url = {https://link.springer.com/chapter/10.1007/978-3-642-90703-6_1} } @MISC{WikiAirMass, @@ -122,7 +123,7 @@ @book{Iqbal.1983 author = {Iqbal, Muhammad}, year = {1983}, title = {An Introduction To Solar Radiation}, - url = {http://site.ebrary.com/lib/alltitles/docDetail.action?docID=10678925}, + url = {https://books.google.de/books/about/An_Introduction_To_Solar_Radiation.html?id=3_qWce_mbPsC&redir_esc=y}, address = {Oxford}, publisher = {{Elsevier Science}}, isbn = {9780123737502} From 69536ea43aefdff99756898eeacb0c03e8049a1f Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 16 Sep 2024 14:27:09 +0200 Subject: [PATCH 16/36] A tiny bit of cleanup Signed-off-by: Sebastian Peter --- .../simona/model/participant/PvModel.scala | 87 +++++++++---------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index fe2a830d68..a654a2cf3f 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -460,15 +460,6 @@ 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)], @@ -517,6 +508,15 @@ final case class PvModel private ( } } + def calculateTimeFrame(omegas: Option[(Angle, Angle)]): Double = { + omegas match { + case Some((omega1, omega2)) => + (omega2 - omega1).toDegrees / 15 + + case None => 0d + } + } + /** Calculates the diffuse radiation on a sloped surface based on the model of * Perez et al. * @@ -542,41 +542,6 @@ 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, @@ -666,6 +631,40 @@ final case class PvModel private ( ) } + 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 + } + /** Calculates the reflected radiation on a sloped surface * * @param eBeamH From b5c03d143a59c9bc19d6f7b233108db4a66de943 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 16 Sep 2024 15:03:33 +0200 Subject: [PATCH 17/36] Reverting different sloped surface workaround Signed-off-by: Sebastian Peter --- .../simona/model/participant/PvModel.scala | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index a654a2cf3f..5ce9651690 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -115,7 +115,6 @@ final case class PvModel private ( lat, gammaE, alphaE, - duration, ) // === Diffuse Radiation Parameters ===// @@ -410,27 +409,32 @@ 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 ( - // requested time is between sunrise and sunset (+/- one hour) - omega1InRad > omegaSRInRad - omegaOneHour - && omega1InRad < omegaSSInRad + thetaGInRad < toRadians(90) + // omega1 and omega2: sun has risen and has not set yet + && omega2InRad > omegaSRInRad + omegaHalfHour + && omega1InRad < omegaSSInRad - omegaHalfHour ) { val (finalOmega1, finalOmega2) = if (omega1InRad < omegaSRInRad) { // requested time earlier than sunrise - (omegaSRInRad, omega2InRad) - } else if (omega1InRad > omegaSSInRad - omegaOneHour) { - // requested time is less than one hour before sunset - (omega1InRad, omegaSSInRad) + (omegaSRInRad, omegaSRInRad + omegaOneHour) + } else if (omega2InRad > omegaSSInRad) { + // sunset earlier than requested time + (omegaSSInRad - omegaOneHour, omegaSSInRad) } else { (omega1InRad, omega2InRad) } @@ -467,7 +471,6 @@ final case class PvModel private ( latitude: Angle, gammaE: Angle, alphaE: Angle, - duration: Time, ): Irradiation = { omegas match { @@ -479,9 +482,6 @@ 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,20 +503,11 @@ 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 * timeFrame + eBeamH * r case None => WattHoursPerSquareMeter(0d) } } - def calculateTimeFrame(omegas: Option[(Angle, Angle)]): Double = { - omegas match { - case Some((omega1, omega2)) => - (omega2 - omega1).toDegrees / 15 - - case None => 0d - } - } - /** Calculates the diffuse radiation on a sloped surface based on the model of * Perez et al. * From 4a54db3a0ad9883430989637e7956137254bf7a3 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 16 Sep 2024 15:07:56 +0200 Subject: [PATCH 18/36] Reverting changed eccentricity correction factor Signed-off-by: Sebastian Peter --- src/main/scala/edu/ie3/simona/model/participant/PvModel.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index 5ce9651690..392e060ceb 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -337,7 +337,7 @@ final case class PvModel private ( val e0 = 1.000110 + 0.034221 * cos(jInRad) + 0.001280 * sin(jInRad) + - 0.00719 * cos(2d * jInRad) + + 0.000719 * cos(2d * jInRad) + 0.000077 * sin(2d * jInRad) // solar constant in W/m2 From 27e382ae6f9970e7c53df954f4021e4f76678b02 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 16 Sep 2024 15:12:52 +0200 Subject: [PATCH 19/36] Reverting changes in test Signed-off-by: Sebastian Peter --- .../model/participant/PvModelTest.groovy | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy index 71f8afb7fb..ea8d6f5016 100644 --- a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy +++ b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy @@ -300,9 +300,9 @@ class PvModelTest extends Specification { where: j || I0Sol - 0d || 1423.7592070000003d // Jan 1st - 2.943629280897834d || 1330.655828592125d // Jun 21st - 4.52733626243351d || 1347.6978765254157d // Sep 21st + 0d || 1414.91335d // Jan 1st + 2.943629280897834d || 1322.494291080537598d // Jun 21st + 4.52733626243351d || 1355.944773587800003d // Sep 21st } def "Calculate the angle of incidence thetaG"() { @@ -449,22 +449,19 @@ class PvModelTest extends Specification { expect: "- should calculate the beam contribution," - 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$) + + pvModel.calcBeamRadiationOnSlopedSurface(eBeamH, omegas, delta, latitudeInRad, gammaE, alphaE) =~ 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"() { From 1b78b8840a1f250b62dcf6964f2b3630d7fda5ce Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 27 Jan 2025 13:04:25 +0100 Subject: [PATCH 20/36] Applying corrections to PvModelSpec Signed-off-by: Sebastian Peter --- .../model/participant/PvModelSpec.scala | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala index 157ff0a041..7526eae894 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala @@ -13,18 +13,11 @@ import edu.ie3.datamodel.models.input.{NodeInput, OperatorInput} import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils import edu.ie3.simona.test.common.{DefaultTestData, UnitSpec} import edu.ie3.util.quantities.PowerSystemUnits._ -import edu.ie3.util.scala.quantities.{ - ApparentPower, - Irradiation, - Kilovoltamperes, - Megavars, - ReactivePower, - WattHoursPerSquareMeter, -} +import edu.ie3.util.scala.quantities._ import org.locationtech.jts.geom.{Coordinate, GeometryFactory, Point} import org.scalatest.GivenWhenThen import squants.Each -import squants.energy.{Kilowatts, Power} +import squants.energy.Kilowatts import squants.space.{Angle, Degrees, Radians} import tech.units.indriya.quantity.Quantities.getQuantity import tech.units.indriya.unit.Units._ @@ -97,7 +90,6 @@ class PvModelSpec extends UnitSpec with GivenWhenThen with DefaultTestData { private implicit val apparentPowerTolerance: ApparentPower = Kilovoltamperes( 1e-10 ) - private implicit val powerTolerance: Power = Kilowatts(1e-10) private implicit val reactivePowerTolerance: ReactivePower = Megavars(1e-10) "A PV Model" should { @@ -766,20 +758,22 @@ class PvModelSpec extends UnitSpec with GivenWhenThen with DefaultTestData { "calculate the estimate diffuse radiation eDifS correctly" in { val testCases = Table( ("thetaGDeg", "thetaZDeg", "gammaEDeg", "airMass", "I0", "eDifSSol"), - (37.0d, 62.2d, 60d, 2.13873080095658d, 1399.0077631849722d, - 216.46615469650982d), + // Reference Duffie 4th ed., p.95 + // I_0 = 5.025 MJ * 277.778 Wh/MJ = 1395.83445 Wh + // eDifSSol is 0.79607 MJ = 221.131 Wh if one only calculates the relevant terms + // from I_T on p. 96, but Duffie uses fixed f values, so the inaccuracy is fine + (37.0d, 62.2d, 60d, 2.144d, 1395.83445d, 225.5949007753d), ) forAll(testCases) { (thetaGDeg, thetaZDeg, gammaEDeg, airMass, I0, eDifSSol) => - // Reference p.95 - // https://www.sku.ac.ir/Datafiles/BookLibrary/45/John%20A.%20Duffie,%20William%20A.%20Beckman(auth.)-Solar%20Engineering%20of%20Thermal%20Processes,%20Fourth%20Edition%20(2013).pdf + // Reference Duffie 4th ed., p.95 Given("using the input data") // Beam Radiation on horizontal surface val eBeamH = - 67.777778d // 1 MJ/m^2 = 277,778 Wh/m^2 -> 0.244 MJ/m^2 = 67.777778 Wh/m^2 + 67.777778d // 1 MJ/m^2 = 277.778 Wh/m^2 -> 0.244 MJ/m^2 = 67.777778 Wh/m^2 // Diffuse Radiation on a horizontal surface - val eDifH = 213.61111d // 0.769 MJ/m^2 = 213,61111 Wh/m^2 + val eDifH = 221.111288d // 0.796 MJ/m^2 = 221.111288 Wh/m^2 When("the diffuse radiation is calculated") val eDifSCalc = pvModel.calcDiffuseRadiationOnSlopedSurfacePerez( From 0e850abdcaefea17d36971a85f6e948bba446796 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 27 Jan 2025 16:22:17 +0100 Subject: [PATCH 21/36] Removing testing methods, fixing formatting Signed-off-by: Sebastian Peter --- .../simona/model/participant/PvModel.scala | 51 +++---------------- 1 file changed, 7 insertions(+), 44 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index 9aa6e7d280..833894b34f 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -582,11 +582,10 @@ final case class PvModel private ( x = IntStream .range(0, discreteSkyClearnessCategories.length) .filter((i: Int) => - (finalEpsilon - discreteSkyClearnessCategories(i)( - 0 - ) >= 0) && (finalEpsilon - discreteSkyClearnessCategories( - i - )(1) < 0) + (finalEpsilon - + discreteSkyClearnessCategories(i)(0) >= 0) && + (finalEpsilon - + discreteSkyClearnessCategories(i)(1) < 0) ) .findFirst .getAsInt + 1 @@ -612,48 +611,12 @@ final case class PvModel private ( // finally calculate the diffuse radiation on an inclined surface eDifH * ( - ((1 + cos( - gammaEInRad - )) / 2) * (1 - f1) + (f1 * (aPerez / bPerez)) + (f2 * sin( - gammaEInRad - )) + ((1 + cos(gammaEInRad)) / 2) * (1 - f1) + + (f1 * (aPerez / bPerez)) + + (f2 * sin(gammaEInRad)) ) } - 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 - } - /** Calculates the reflected radiation on a sloped surface * * @param eBeamH From 6607121b11e7a1add0d70469fe0e8db3789f411e Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 27 Jan 2025 16:23:41 +0100 Subject: [PATCH 22/36] Removing old test Signed-off-by: Sebastian Peter --- .../model/participant/PvModelTest.groovy | 527 ------------------ 1 file changed, 527 deletions(-) delete mode 100644 src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy diff --git a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy b/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy deleted file mode 100644 index ea8d6f5016..0000000000 --- a/src/test/groovy/edu/ie3/simona/model/participant/PvModelTest.groovy +++ /dev/null @@ -1,527 +0,0 @@ -/* - * © 2020. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.model.participant - -import static edu.ie3.util.quantities.PowerSystemUnits.* -import static tech.units.indriya.quantity.Quantities.getQuantity -import squants.Dimensionless -import squants.Each$ -import squants.energy.Kilowatts$ -import squants.space.Degrees$ -import squants.space.Radians$ -import squants.space.SquareMeters$ -import edu.ie3.datamodel.models.OperationTime -import edu.ie3.datamodel.models.input.NodeInput -import edu.ie3.datamodel.models.input.OperatorInput -import edu.ie3.datamodel.models.input.system.PvInput -import edu.ie3.datamodel.models.input.system.characteristic.CosPhiFixed -import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils -import edu.ie3.simona.model.participant.control.QControl -import edu.ie3.util.scala.quantities.Irradiation -import edu.ie3.util.scala.OperationInterval -import edu.ie3.util.scala.quantities.Megavars$ -import edu.ie3.util.scala.quantities.Sq -import edu.ie3.util.scala.quantities.WattHoursPerSquareMeter$ -import org.locationtech.jts.geom.Coordinate -import org.locationtech.jts.geom.GeometryFactory -import org.locationtech.jts.geom.Point -import scala.Option -import spock.lang.Shared -import spock.lang.Specification -import squants.space.Angle -import java.time.ZonedDateTime - -/** - * Test class that tries to cover all special cases of the current implementation of the PvModel - * - * Some of these test cases are taken from the examples of - * Duffie, J. A., & Beckman, W. A. (2013). Solar engineering of thermal processes (4th ed.). Hoboken, N.J.: John Wiley & Sons. - * - * The page examples can be found using the page number provided in each test. Results may differ slightly from the - * book as we sometimes use different formulas. Furthermore, sometimes the example might be slightly adapted to fit our needs. - * - */ - -class PvModelTest extends Specification { - - @Shared - PvModel pvModel - - @Shared - GeometryFactory geometryFactory - - def setupSpec() { - - // build the NodeInputModel (which defines the location of the pv input model) - /// the NodeInputModel needs a GeoReference for the Pv to work - geometryFactory = new GeometryFactory() - Point p = geometryFactory.createPoint( - new Coordinate(13.2491, 53.457909)) - NodeInput nodeInput = new NodeInput( - UUID.fromString("85f8b517-8a2d-4c20-86c6-3ff3c5823e6d"), - "NodeInputModel for PvModel Test", - OperatorInput.NO_OPERATOR_ASSIGNED, - OperationTime.notLimited(), - getQuantity(1, PU), - false, - p, - GermanVoltageLevelUtils.MV_20KV, - 11 - ) - - - // build the PvInputModel - PvInput pvInput = new PvInput( - UUID.fromString("adb4eb23-1dd6-4406-a5e7-02e1e4c9dead"), - "Pv Model Test", - OperatorInput.NO_OPERATOR_ASSIGNED, - OperationTime.notLimited(), - nodeInput, - new CosPhiFixed("cosPhiFixed:{(0.0,0.9)}"), - 0.20000000298023224, - getQuantity(-8.926613807678223, DEGREE_GEOM), - getQuantity(97, PERCENT), - getQuantity(41.01871871948242, DEGREE_GEOM), - 0.8999999761581421, - 1, - false, - getQuantity(10, KILOVOLTAMPERE), - 0.8999999761581421 - ) - - // build the PvModel - double scalingFactor = 1.0d - pvModel = PvModel.apply( - pvInput.uuid, - pvInput.id, - OperationInterval.apply(0L, 86400L), - scalingFactor, - QControl.apply(pvInput.qCharacteristics), - Sq.create(pvInput.sRated.to(KILOWATT).value.doubleValue(), Kilowatts$.MODULE$), - pvInput.cosPhiRated, - Sq.create(pvInput.node.geoPosition.y, Degrees$.MODULE$), - Sq.create(pvInput.node.geoPosition.x, Degrees$.MODULE$), - pvInput.albedo, - Sq.create(pvInput.etaConv.to(PU).value.doubleValue(), Each$.MODULE$), - Sq.create(pvInput.azimuth.to(RADIAN).value.doubleValue(), Radians$.MODULE$), - Sq.create(pvInput.elevationAngle.value.doubleValue(), Radians$.MODULE$), - Sq.create(1d, SquareMeters$.MODULE$) - ) - } - - def "A PvModel should have sMax set to be 10% higher than its sRated"() { - expect: - pvModel.sMax().toKilowatts() == ((pvModel.sRated().toKilowatts() * 1.1)) - } - - def "A PvModel should provide reactive power up to 110% of it's rated apparent power"() { - given: "default adjusted voltage" - Dimensionless adjustedVoltage = Sq.create(1, Each$.MODULE$) // needed for method call but not applicable for cosphi_p - - when: "the reactive power is calculated" - def qCalc = pvModel.calculateReactivePower(Sq.create(pVal, Kilowatts$.MODULE$), adjustedVoltage) - - then: - qCalc =~ Sq.create(qSoll, Megavars$.MODULE$) - - where: - pVal || qSoll - 9.5d || 0.004601059995959599d // above sRated (no q limitation) - 11d || 0d // above sMax (limit q becomes active) - } - - def "Calculate day angle J"() { - when: - Angle jCalc = pvModel.calcAngleJ(ZonedDateTime.parse(time)) - - then: - jCalc =~ Sq.create(jSol, Radians$.MODULE$) - - where: - time || jSol - '2019-01-05T05:15:00+01:00[Europe/Berlin]' || 0.06885682528415985d - '2016-10-31T12:15:00+01:00[Europe/Berlin]' || 5.23311872159614873d // leap year => day = 305 - '2017-10-31T12:15:00+01:00[Europe/Berlin]' || 5.21590451527510877d // regular year => day = 304 - '2011-06-21T13:00:00+02:00[Europe/Berlin]' || 2.9436292808978335d // regular year => day = 172 - '2011-04-05T16:00:00+02:00[Europe/Berlin]' || 1.6181353941777565d // regular year => day = 95 - '2011-09-21T00:00:00+02:00[Europe/Berlin]' || 4.5273362624335105d // regular year => day = 264 - '2011-03-21T00:00:00+01:00[Europe/Berlin]' || 1.359922299362157d // regular year => day = 80 - } - - def "Calculate declination angle delta"() { - when: - Angle dayAngleQuantity = Sq.create(j, Radians$.MODULE$) - Angle deltaCalc = pvModel.calcSunDeclinationDelta(dayAngleQuantity) - - then: - deltaCalc =~ Sq.create(deltaSol, Radians$.MODULE$) - - where: - j || deltaSol - 0d || -0.402449d // Jan 1st - 2.943629280897834d || 0.40931542032971796d // Jun 21 (maximum: 23.45°) - 6.024972212363987d || -0.4069636112528855d // Dec 21 (minimum: -23.45°) - 4.52733626243351d || 0.01790836361732633d // Sep 21 (0°) - 1.359922299362157d || -0.0011505915019577827d // Mar 21 (0°) - } - - def "Calculate hour angle omega"() { - when: - ZonedDateTime dateTime = ZonedDateTime.parse(time) - Angle dayAngleQuantity = Sq.create(j, Radians$.MODULE$) - Angle longitudeQuantity = Sq.create(longitude, Radians$.MODULE$) - - Angle omegaCalc = pvModel.calcHourAngleOmega(dateTime, dayAngleQuantity, longitudeQuantity) - - then: - omegaCalc =~ Sq.create(omegaSol, Radians$.MODULE$) - - where: - time | j | longitude || omegaSol - '2019-01-01T05:00:00+01:00[Europe/Berlin]' | 0d | 0.16d || -1.9465030168609223d // long: ~9.17°E - '2019-01-01T10:05:00+01:00[Europe/Berlin]' | 0d | 0.16d || -0.6156894622152458d // different time: 10:05 - '2019-01-01T12:00:00+01:00[Europe/Berlin]' | 0d | 0.16d || -0.11390730226687622d // 12:00 - '2019-01-01T14:00:00+01:00[Europe/Berlin]' | 0d | 0.16d || 0.40969147333142264d // 14:00 - '2019-01-01T17:30:00+01:00[Europe/Berlin]' | 0d | 0.16d || 1.3259893306284447d // 17:30 - '2019-03-21T05:00:00+01:00[Europe/Berlin]' | 1.359922299362157d | 0.16d || -1.9677750757840207d // different j (different date) - '2019-01-01T05:00:00+01:00[Europe/Berlin]' | 0d | 0.175d || -1.9315030168609224d // different long, ~10°E - '2011-06-21T11:00:00+02:00[Europe/Berlin]' | 2.9436292808978d | 0.2337d || -0.2960273936975511d // long of Berlin (13.39E), - '2011-06-21T12:00:00+02:00[Europe/Berlin]' | 2.9436292808978d | 0.2337d || -0.034228005898401644d // long of Berlin (13.39E), - '2011-06-21T13:00:00+02:00[Europe/Berlin]' | 2.9436292808978d | 0.2337d || 0.2275713819007478d // long of Berlin (13.39E), - '2011-06-21T14:00:00+02:00[Europe/Berlin]' | 2.9436292808978d | 0.2337d || 0.4893707696998972d // long of Berlin (13.39E), - '2011-06-21T15:00:00+02:00[Europe/Berlin]' | 2.9436292808978d | 0.2337d || 0.7511701574990467d // long of Berlin (13.39E), - '2011-04-05T16:00:00+02:00[Europe/Berlin]' | 1.6181353941777565d | 0.2337d || 1.0062695999127786d // long of Berlin (13.39E), - '2011-06-21T12:00:00+02:00[Europe/Berlin]' | 2.9436292808978d | 0.5449d || 0.2769719941015987d // long of Cairo (31.22E), - } - - def "Calculate sunset angle omegaSS"() { - when: - Angle latitudeQuantity = Sq.create(latitude, Radians$.MODULE$) - Angle deltaQuantity = Sq.create(delta, Radians$.MODULE$) - - Angle omegaSSCalc = pvModel.calcSunsetAngleOmegaSS(latitudeQuantity, deltaQuantity) - - then: - omegaSSCalc =~ Sq.create(omegaSSSol, Radians$.MODULE$) - - where: - latitude | delta || omegaSSSol - 0.9d | -0.402449d || 1.0045975406286176d // lat: ~51.57°N - 0.935d | -0.402449d || 0.956011693657339d // different lat: ~53.57°N - 0.9d | 0.017908d || 1.5933675693198284d // different delta - 0.157952297d | 0.384670567d || 1.635323424114512d // //Example 2.2 Goswami Priciples of Solar Engineering - } - - def "Calculate solar altitude angle alphaS"() { - when: - Angle omegaQuantity = Sq.create(omega, Radians$.MODULE$) - Angle deltaQuantity = Sq.create(delta, Radians$.MODULE$) - Angle latitudeQuantity = Sq.create(latitude, Radians$.MODULE$) - - Angle alphaSCalc = pvModel.calcSolarAltitudeAngleAlphaS(omegaQuantity, deltaQuantity, latitudeQuantity) - - then: - alphaSCalc =~ Sq.create(alphaSSol, Radians$.MODULE$) - - where: - omega | delta | latitude || alphaSSol - 1.946503016860923d | -0.402449d | 0.9d || -0.5429594681352444d // delta: Jan 1st, lat: ~51.57°N - 1.967775075784021d | -0.001150591501958d | 0.9d || -0.24363984335678648d // delta: March 21st - 1.946503016860923d | -0.402449d | 0.935d || -0.5417322854819461d // delta: Jan 1st, lat: ~53.57°N - 1.256637061d | -0.402449d | 0.698d || -0.033897520990303694d // omega: 82°, delta: Jan 1st, lat: ~39.99°N - 0.409691473331422d | -0.402449d | 0.9d || 0.21956610107293822d // omega: 14:00, delta: Jan 1st - -0.85019406d | -0.00720875d | 0.9128072d || 0.40911138927659646d // omega: -48.71° = 09:00, delta: March 21st, lat: Berlin - +0.22425484d | +0.40899596d | 0.9128072d || 1.0386092658376944d // omega: +12.84° = 14:00 MESZ = 13:00 MEZ, delta: June 21st, lat: Berlin - -0.81703281d | -0.00720875d | 0.54628806d || 0.619982384489836d // omega: -36.9809° = 09:00, delta: March 21st, lat: Cairo - -0.00438329d | +0.40899596d | 0.54628806d || 1.4334492081530734d // omega: -0.25° = 12:00, delta: June 21st, lat: Cairo - +0.0126074d | -0.40842934d | 0.54628806d || 0.6160025701438165d // omega: +0.7223° = 12:00, delta: Dez 21st, lat: Cairo - -0.78639785d | +0.1549651d | 0.54628806d || 0.7430566034615067d // omega: -45.05° = 09:00, delta: Sep 1st, lat: Cairo - +1.04619786d | 0.1549651d | 0.54628806d || 0.5270965151470974d // omega: +59.943° = 16:00, delta: Sep 1st, lat: Cairo - 0d | -0.305432619d | 0.518013722d || 0.7473499857948969d // omega: 0 = Solar Noon, delta: February 01st, lat/lon: Gainsville (29.68 N, 82.27 W) //Example 2.1a Goswami Priciples of Solar Engineering - -1.374970385d | +0.380755678d | 0.157952297d || 0.2391202791125743d // omega: -78.78° = 7:00 a.m., delta: June 01st, lat/lon: Tocumen Panama (9.05 N, 79.37 W) //Example 2.2a Goswami Priciples of Solar Engineering - 0d | -0.268780705d | -0.616101226d || 1.2234758057948967d // omega: 0° = Solar noon., delta: November 01st, lat/lon: Canberra Australia (35.3 S, 149.1 E) //Example 2.3b Goswami Priciples of Solar Engineering - Math.toRadians(-37.5d) | Math.toRadians(-14d) | Math.toRadians(43d) || Math.toRadians(23.4529893659531784299686037109330117049955654837550d) // '2011-02-13T09:30:00' from Duffie - Math.toRadians(97.5d) | Math.toRadians(23.1d) | Math.toRadians(43d) || Math.toRadians(10.356151317506402829742934977890382350725031728508d) // '2011-07-01T06:30:00' from Duffie - // Reference: Quaschning, Regenerative Energiesysteme figure 2.15 and figure 2.16 // gammaS@Quaschning = alphaS@SIMONA ! - Math.toRadians(-47.15114406) | Math.toRadians(23.4337425d) | Math.toRadians(52.3d) || Math.toRadians(44.12595614280154d) // Berlin (13.2E 52.3N) '2011-06-21T09:00:00' MEZ - Math.toRadians(-32.15114394d) | Math.toRadians(23.4337425d) | Math.toRadians(52.3d) || Math.toRadians(52.15790489243239d) // Berlin (13.2E 52.3N) '2011-06-21T10:00:00' MEZ - Math.toRadians(-17.15114381d) | Math.toRadians(23.4337425d) | Math.toRadians(52.3d) || Math.toRadians(58.29851278388936d) // Berlin (13.2E 52.3N) '2011-06-21T11:00:00' MEZ - Math.toRadians(-2.151143686d) | Math.toRadians(23.4337425d) | Math.toRadians(52.3d) || Math.toRadians(61.086849596117524d) // Berlin (13.2E 52.3N) '2011-06-21T12:00:00' MEZ - Math.toRadians(12.84885587d) | Math.toRadians(23.4337425d) | Math.toRadians(52.3d) || Math.toRadians(59.50792770681503d) // Berlin (13.2E 52.3N) '2011-06-21T13:00:00' MEZ - Math.toRadians(27.84885599d) | Math.toRadians(23.4337425d) | Math.toRadians(52.3d) || Math.toRadians(54.170777340509574d) // Berlin (13.2E 52.3N) '2011-06-21T14:00:00' MEZ - Math.toRadians(58.28178946d) | Math.toRadians(7.79402247d) | Math.toRadians(52.3d) || Math.toRadians(25.203526133755485d) // Berlin (13.2E 52.3N) '2011-09-04T16:00:00' MEZ - Math.toRadians(0.948855924d) | Math.toRadians(23.4337425d) | Math.toRadians(30.1d) || Math.toRadians(83.28023248078853d) // Cairo (31.3E 30.1N) '2011-06-21T12:00:00' MEZ+1h - } - - def "Calculate zenith angle thetaZ"() { - when: - Angle alphaSQuantity = Sq.create(alphaS, Radians$.MODULE$) - - Angle thetaZCalc = pvModel.calcZenithAngleThetaZ(alphaSQuantity) - - then: - thetaZCalc =~ Sq.create(thetaZSol, Radians$.MODULE$) - - where: - alphaS || thetaZSol - 0d || 1.5707963267948966d // 0° - 0.785398163397448d || 0.7853981633974486d // 45° - 1.570796326794897d || -4.440892098500626E-16d // 90° - } - - def "Calculate air mass"() { - when: - Angle thetaZQuantity = Sq.create(thetaZ, Radians$.MODULE$) - - double airMassCalc = pvModel.calcAirMass(thetaZQuantity) - - then: - airMassCalc =~ airMassSol - - where: - thetaZ || airMassSol - 0d || 1d // 0° - 0.785398163397448d || 1.41321748045965d // 45° - 1.570796326794897d || 37.640108631323025d // 90° - } - - def "Calculate extraterrestrial radiation IO"() { - when: - Angle dayAngleQuantity = Sq.create(j, Radians$.MODULE$) - - Irradiation I0Calc = pvModel.calcExtraterrestrialRadiationI0(dayAngleQuantity) - - then: - I0Calc =~ Sq.create(I0Sol, WattHoursPerSquareMeter$.MODULE$) - - where: - j || I0Sol - 0d || 1414.91335d // Jan 1st - 2.943629280897834d || 1322.494291080537598d // Jun 21st - 4.52733626243351d || 1355.944773587800003d // Sep 21st - } - - def "Calculate the angle of incidence thetaG"() { - - "Calculate the angle of incidence of beam radiation on a surface located at a Latitude" + - "at a certain hour angle (solar time) on a given declination (date) if the surface " + - "is tilted by a certain slope from the horizontal and pointed to a certain panel azimuth " + - "west of south." - - "== Calculate the angle of incidence thetaG ==" - given: - // Declination Angle delta of the sun at solar noon - Angle deltaRad = Sq.create(Math.toRadians(deltaIn), Radians$.MODULE$) - //Latitude in Radian - Angle latitudeInRad = Sq.create(Math.toRadians(latitudeInDeg), Radians$.MODULE$) - //Hour Angle - Angle omegaRad = Sq.create(Math.toRadians(omegaDeg), Radians$.MODULE$) - //Inclination Angle of the surface - Angle gammaERad = Sq.create(Math.toRadians(gammaEDeg), Radians$.MODULE$) - //Surface azimuth - Angle alphaERad = Sq.create(Math.toRadians(alphaEDeg), Radians$.MODULE$) - - when: - Angle thetaG = pvModel.calcAngleOfIncidenceThetaG(deltaRad, latitudeInRad, gammaERad, alphaERad, omegaRad) - - then: - "- should calculate the angle of incidence thetaG " - thetaG.toDegrees() =~ Sq.create(thetaGOut, Degrees$.MODULE$).toDegrees() - - where: "the following parameters are given" - latitudeInDeg | deltaIn | omegaDeg | gammaEDeg | alphaEDeg || thetaGOut - 43d | -14d | -22.5d | 45d | 15d || 35.176193345578606393727080835951995075234213360724d // Duffie - 51.516667d | +18.4557514d | -15.00225713d | 30d | +0d || 14.420271449960715d // Iqbal - 51.516667d | +18.4557514d | -15.00225713d | 90d | +0d || 58.65287310017624d // Iqbal - 35.0d | +23.2320597d | +30.00053311d | 45d | 10d || 39.62841449023577d // Kalogirou - Solar Energy Engineering Example 2.7 ISBN 978-0-12-374501-9; DOI https://doi.org/10.1016/B978-0-12-374501-9.X0001-5 - 35.0d | +23.2320597d | +30.00053311d | 45d | 90d || 18.946300807438607d // Kalogirou - Solar Energy Engineering Example 2.7 changed to 90° panel azimuth to WEST - 35.0d | +23.2320597d | +74.648850625d | 45d | 90d || 21.95480347380729d // Kalogirou - Solar Energy Engineering Example 2.7 90° panel azimuth to WEST at 17:00 - 35.0d | +23.2320597d | +74.648850625d | 45d | -90d || 109.00780288303966d // Kalogirou - Solar Energy Engineering Example 2.7 90° panel azimuth to EAST at 17:00 - 27.96d | -17.51d | -11.1d | 30d | +10d || 22.384603601536398d // Goswami Priciples of Solar Engineering Example 2.7a - -35.3d | -17.51d | -4.2d | 30d | +170d || 14.882390116876563d // Goswami Priciples of Solar Engineering Example 2.7b - } - - def "Testing the equality of zenith angle of a horizontal surface and thetaG of a sloped surface"() { - - "Iqbal Figure 1.6.2 - the angle of incidence of a surface sloped by angle beta at " + - "latitude phi should be same as the zenith angle of an unsloped surface" + - "positioned at latitude phi - beta " + - "" - - given: - "- using pre-calculated parameters" - //Latitude in Radian - Angle latitudeInRad = Sq.create(Math.toRadians(latitudeInDeg), Radians$.MODULE$) - // Declination Angle delta of the sun at solar noon - Angle delta = Sq.create(Math.toRadians(deltaIn), Radians$.MODULE$) - //Hour Angle - Angle omega = Sq.create(Math.toRadians(omegaIn), Radians$.MODULE$) - //Inclination Angle of the surface - Angle gammaE = Sq.create(Math.toRadians(slope), Radians$.MODULE$) - //Sun's azimuth - Angle alphaE = Sq.create(Math.toRadians(azimuth), Radians$.MODULE$) - - // should calculate the angle of incidence thetaG - when: - Angle thetaG = pvModel.calcAngleOfIncidenceThetaG(delta, latitudeInRad, gammaE, alphaE, omega) - - then: - thetaG.toDegrees() =~ thetaOut - - where: "the following parameters are given" - latitudeInDeg | deltaIn | omegaIn | slope | azimuth || thetaOut - 45d | -7.15 | -82.5d | 60d | 0 || 80.94904834048776d // thetaG - 15d | -7.15 | -82.5d | 30d | 0 || 80.94904834048776d // same test but 15° South with 15° less sloped surface - 0d | -7.15 | -82.5d | 15d | 0 || 80.94904834048776d // same test but 15° South with 15° less sloped surface - 52.3d | 23.4337425 | 2.15114395d | 0d | 0 || 28.91315041538251d // Berlin 21.06. 12:00 => thetaG = 90 - alphaS - 70.3d | 23.4337425 | 2.15114395d | 18d | 0 || 28.91315041538251d // same test but 18° North with 18° sloped surface - } - - def "Calculate Rb (cos(thetaG)/cos(thetaZ))"() { - - "On March 4 at a latitude of 45◦ and a surface slope of 60◦ determine Rb at 6:30 AM and Rb,ave " + - "for the hour 6 to 7 AM." - - given: - "- using pre-calculated parameters" - //Latitude in Radian - Angle latitudeInRad = Sq.create(Math.toRadians(latitudeInDeg), Radians$.MODULE$) - // Declination Angle delta of the sun at solar noon - Angle delta = Sq.create(Math.toRadians(deltaIn), Radians$.MODULE$) - //Hour Angle - Angle omega = Sq.create(Math.toRadians(omegaIn), Radians$.MODULE$) - //Inclination Angle of the surface - Angle gammaE = Sq.create(Math.toRadians(slope), Radians$.MODULE$) - //Sun's azimuth - Angle alphaE = Sq.create(Math.toRadians(azimuth), Radians$.MODULE$) - - expect: - "- should calculate the angle of incidence thetaG and zenith angle thetaZ of beam radiation on a surface correctly" - - pvModel.calcAngleOfIncidenceThetaG(delta, latitudeInRad, gammaE, alphaE, omega).toDegrees() =~ thetaOut - - - where: "the following parameters are given" - latitudeInDeg | deltaIn | omegaIn | slope | azimuth || thetaOut - 45 | -7.15 | -82.5 | 60 | 0 || 80.94904834048776 // thetaG - 45 | -7.15 | -82.5 | 0 | 0 || 89.79565474295107 // thetaZ - 40 | -11.6 | -82.5 | 60 | 0 || 79.11011928744357 - 40 | -11.6 | 82.5 | 60 | 0 || 79.11011928744357 - 40 | -11.6 | -78.0 | 60 | 0 || 74.92072065185143 - 40 | -11.6 | 78.0 | 60 | 0 || 74.92072065185143 - } - - def "Calculate the estimate beam radiation eBeamS"() { - - "Using the Perez diffuse model, estimate the beam, diffuse, and ground-reflected components of solar " + - "radiation and the total radiation on a surface sloped 60◦ toward the south at a latitude of 40◦ N " + - "for the hour 9 to 10 AM on February 20. Here I = 1.04 MJ/m2 and ρg = 0.60." - //Reference p.95 - //https://www.sku.ac.ir/Datafiles/BookLibrary/45/John%20A.%20Duffie,%20William%20A.%20Beckman(auth.)-Solar%20Engineering%20of%20Thermal%20Processes,%20Fourth%20Edition%20(2013).pdf - - given: - //Inclination of the Pv system in degrees (tilted from the horizontal) - Angle gammaE = Sq.create(Math.toRadians(slope), Radians$.MODULE$) - //Inclination of the Pv system in degrees (Inclined in a compass direction)(South 0◦; West 90◦; East -90◦) - Angle alphaE = Sq.create(Math.toRadians(azimuth), Radians$.MODULE$) - //Latitude in Radian - Angle latitudeInRad = Sq.create(Math.toRadians(latitudeInDeg), Radians$.MODULE$) - // 1 MJ/m^2 = 277,778 Wh/m^2 - // 0.244 MJ/m^2 = 67.777778 Wh/m^2 - //Beam Radiation on horizontal surface - Irradiation eBeamH = Sq.create(67.777778d, WattHoursPerSquareMeter$.MODULE$) - // Declination Angle delta of the sun at solar noon - Angle delta = Sq.create(Math.toRadians(deltaIn), Radians$.MODULE$) - //Hour Angle - Angle omega = Sq.create(Math.toRadians(omegaIn), Radians$.MODULE$) - //Incidence Angle - Angle thetaG = Sq.create(Math.toRadians(thetaGIn), Radians$.MODULE$) - //Sunset Angle - Angle omegaSS = pvModel.calcSunsetAngleOmegaSS(latitudeInRad, delta) - //Sunrise Angle (Sunset Angle * (-1)) - Angle omegaSR = omegaSS.$times(-1d) - //omega1 and omega2 - Option> omegas = pvModel.calculateBeamOmegas(thetaG, omega, omegaSS, omegaSR) - - expect: - "- should calculate the beam contribution," - - pvModel.calcBeamRadiationOnSlopedSurface(eBeamH, omegas, delta, latitudeInRad, gammaE, alphaE) =~ 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 | 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 - } - - def "Calculate the estimate diffuse radiation eDifS"() { - - given: - //Inclination of the Pv system in degrees (tilted from the horizontal) - Angle gammaE = Sq.create(Math.toRadians(slope), Radians$.MODULE$) - // 1 MJ/m^2 = 277,778 Wh/m^2 - // 0.244 MJ/m^2 = 67.777778 Wh/m^2 - //Beam Radiation on horizontal surface - Irradiation eBeamH = Sq.create(67.777778d, WattHoursPerSquareMeter$.MODULE$) - // 0.796 MJ/m^2 = 221,111288 Wh/m^2 - //Diffuse beam Radiation on horizontal surface - Irradiation eDifH = Sq.create(221.111288d, WattHoursPerSquareMeter$.MODULE$) - //Incidence Angle - Angle thetaG = Sq.create(Math.toRadians(thetaGIn), Radians$.MODULE$) - //Zenith Angle - Angle thetaZ = Sq.create(Math.toRadians(thetaZIn), Radians$.MODULE$) - // Extraterrestrial radiation - Irradiation I0Quantity = Sq.create(I0, WattHoursPerSquareMeter$.MODULE$) - - expect: - "- should calculate the beam diffusion" - // == 0,7792781569074354 MJ/m^2 - - 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.144d | 1395.8445d || 220.83351d - } - - def "Calculate the ground reflection eRefS"() { - - given: - "- Beam radiation and diffuse bean radiation on horizontal surface" - //Inclination of the Pv system in degrees (tilted from the horizontal) - Angle gammaE = Sq.create(Math.toRadians(slope), Radians$.MODULE$) - // 1 MJ/m^2 = 277,778 Wh/m^2 - // 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 - //Diffuse beam Radiation on horizontal surface - Irradiation eDifH = Sq.create(213.61111d, WattHoursPerSquareMeter$.MODULE$) - - expect: - "- should calculate the ground reflection correctly" - // == 0,15194999952 MJ/m^2 (Book: 0.156 MJ/m^2) - - pvModel.calcReflectedRadiationOnSlopedSurface(eBeamH, eDifH, gammaE, albedo) =~ Sq.create(eRefSSol, WattHoursPerSquareMeter$.MODULE$) - - - where: "the following parameters are given" - slope | albedo || eRefSSol - 60 | 0.60 || 42.20833319999999155833336d // '2011-02-20T09:00:00' - } -} From 866170bc273ee89d6575cb90b6de2ffb5bd6809f Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 27 Jan 2025 16:52:29 +0100 Subject: [PATCH 23/36] Replacing questionable source Signed-off-by: Sebastian Peter --- docs/readthedocs/_static/bibliography/bibtexAll.bib | 6 ------ docs/readthedocs/models/pv_model.md | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/docs/readthedocs/_static/bibliography/bibtexAll.bib b/docs/readthedocs/_static/bibliography/bibtexAll.bib index 21a22d47cb..8761d828b4 100644 --- a/docs/readthedocs/_static/bibliography/bibtexAll.bib +++ b/docs/readthedocs/_static/bibliography/bibtexAll.bib @@ -12,12 +12,6 @@ @Article{Maleki.2017 DOI = {10.3390/en10010134} } -@MISC{Itaca_Sun, -author = {Itacanet}, -title = {The Sun As A Source Of Energy}, -howpublished={\url{https://de.scribd.com/document/455342846/Part-3-Calculating-Solar-Angles-ITACA}} -} - @article{Spencer.1971, added-at = {2018-06-18T21:23:34.000+0200}, author = {Spencer, J. W.}, diff --git a/docs/readthedocs/models/pv_model.md b/docs/readthedocs/models/pv_model.md index 83358010fa..7ec0734614 100644 --- a/docs/readthedocs/models/pv_model.md +++ b/docs/readthedocs/models/pv_model.md @@ -127,7 +127,7 @@ $$ **References:** * {cite:cts}`Maleki.2017` -* {cite:cts}`Itaca_Sun` +* {cite:cts}`Duffie.2013` p. 17 (1.16.10) ### Solar Altitude Angle @@ -146,7 +146,7 @@ $$ **References:** * {cite:cts}`Maleki.2017` p. 5 -* {cite:cts}`Itaca_Sun` +* {cite:cts}`Duffie.2013` p. 15 (1.6.5) with $\sin (\alpha_s) = \cos (\theta_z)$ ### Zenith Angle From 72645c745f0dbd0cc4c25d1add5653382d5a20aa Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 27 Jan 2025 16:57:06 +0100 Subject: [PATCH 24/36] Adding hint about Duffie 5th ed Signed-off-by: Sebastian Peter --- docs/readthedocs/models/pv_model.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readthedocs/models/pv_model.md b/docs/readthedocs/models/pv_model.md index 7ec0734614..1baa451394 100644 --- a/docs/readthedocs/models/pv_model.md +++ b/docs/readthedocs/models/pv_model.md @@ -235,7 +235,7 @@ $$ * {cite:cts}`Zheng.2017` p. 53, formula 2.3b * {cite:cts}`Iqbal.1983` * {cite:cts}`Spencer.1971` -* {cite:cts}`Duffie.2013` +* {cite:cts}`Duffie.2013` (the fifth ed. seems to have a typo here) ### Beam Radiation on Sloped Surface From a7bd94fc159624dc6048c4d8ab22193c98371748 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Tue, 28 Jan 2025 14:34:30 +0100 Subject: [PATCH 25/36] renaming brightness parameter as delta --- .../scala/edu/ie3/simona/model/participant/PvModel.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index 833894b34f..a694f89fd5 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -545,7 +545,7 @@ final case class PvModel private ( val gammaEInRad = gammaE.toRadians // == brightness index beta ==// - val beta = eDifH * airMass / extraterrestrialRadiationI0 + val delta = eDifH * airMass / extraterrestrialRadiationI0 // == cloud index epsilon ==// // if we have no clouds, the epsilon bin is 8, as epsilon bin for an epsilon in [6.2, inf.[ = 8 @@ -604,8 +604,8 @@ final case class PvModel private ( val f23 = 0.0052 * pow(x, 3) - 0.0971 * pow(x, 2) + 0.2856 * x - 0.1389 // calculate circuumsolar brightness coefficient f1 and horizon brightness coefficient f2 - val f1 = max(0, f11 + f12 * beta + f13 * thetaZInRad) - val f2 = f21 + f22 * beta + f23 * thetaZInRad + val f1 = max(0, f11 + f12 * delta + f13 * thetaZInRad) + val f2 = f21 + f22 * delta + f23 * thetaZInRad val aPerez = max(0, cos(thetaGInRad)) val bPerez = max(cos(1.4835298641951802), cos(thetaZInRad)) From 6a0bdf087431dd37a0efbcbaeae842aff81f6e9e Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Tue, 28 Jan 2025 14:36:52 +0100 Subject: [PATCH 26/36] use Megajoule as input to increase comprehensibility of test --- .../ie3/simona/model/participant/PvModelSpec.scala | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala index 7526eae894..c102f5e3a4 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala @@ -756,13 +756,16 @@ class PvModelSpec extends UnitSpec with GivenWhenThen with DefaultTestData { } "calculate the estimate diffuse radiation eDifS correctly" in { + def megaJoule2WattHours(megajoule: Double): Double= + {megajoule / (3.6/1000)} + val testCases = Table( ("thetaGDeg", "thetaZDeg", "gammaEDeg", "airMass", "I0", "eDifSSol"), // Reference Duffie 4th ed., p.95 // I_0 = 5.025 MJ * 277.778 Wh/MJ = 1395.83445 Wh - // eDifSSol is 0.79607 MJ = 221.131 Wh if one only calculates the relevant terms - // from I_T on p. 96, but Duffie uses fixed f values, so the inaccuracy is fine - (37.0d, 62.2d, 60d, 2.144d, 1395.83445d, 225.5949007753d), + // eDifSSol is 0.79607 MJ (0.444 + 0.348 + 0.003) if one only calculates the relevant terms + // from I_T on p. 96, but Duffie uses fixed f values, so the inaccuracy is fine (approx. 4.5 Wh/m^2 or 0.016 MJ/m^2) + (37.0d, 62.2d, 60d, 2.144d, megaJoule2WattHours(5.025), megaJoule2WattHours(0.812140993078191252)), ) forAll(testCases) { @@ -771,9 +774,9 @@ class PvModelSpec extends UnitSpec with GivenWhenThen with DefaultTestData { Given("using the input data") // Beam Radiation on horizontal surface val eBeamH = - 67.777778d // 1 MJ/m^2 = 277.778 Wh/m^2 -> 0.244 MJ/m^2 = 67.777778 Wh/m^2 + megaJoule2WattHours(0.244) // 0.244 MJ/m^2 = 67.777778 Wh/m^2 // Diffuse Radiation on a horizontal surface - val eDifH = 221.111288d // 0.796 MJ/m^2 = 221.111288 Wh/m^2 + val eDifH = megaJoule2WattHours(0.796) // 0.796 MJ/m^2 = 221.111111 Wh/m^2 When("the diffuse radiation is calculated") val eDifSCalc = pvModel.calcDiffuseRadiationOnSlopedSurfacePerez( From 780f58154d766c4232b0bf459016371d7583ce73 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Tue, 28 Jan 2025 14:38:05 +0100 Subject: [PATCH 27/36] typo --- src/main/scala/edu/ie3/simona/model/participant/PvModel.scala | 2 +- .../scala/edu/ie3/simona/model/participant/PvModelSpec.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index a694f89fd5..693099a49a 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -603,7 +603,7 @@ final case class PvModel private ( val f22 = 0.0012 * pow(x, 3) - 0.0067 * pow(x, 2) + 0.0091 * x - 0.0269 val f23 = 0.0052 * pow(x, 3) - 0.0971 * pow(x, 2) + 0.2856 * x - 0.1389 - // calculate circuumsolar brightness coefficient f1 and horizon brightness coefficient f2 + // calculate circumsolar brightness coefficient f1 and horizon brightness coefficient f2 val f1 = max(0, f11 + f12 * delta + f13 * thetaZInRad) val f2 = f21 + f22 * delta + f23 * thetaZInRad val aPerez = max(0, cos(thetaGInRad)) diff --git a/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala index c102f5e3a4..48ed48af94 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala @@ -755,7 +755,7 @@ class PvModelSpec extends UnitSpec with GivenWhenThen with DefaultTestData { } } - "calculate the estimate diffuse radiation eDifS correctly" in { + "calculate the estimated diffuse radiation eDifS correctly" in { def megaJoule2WattHours(megajoule: Double): Double= {megajoule / (3.6/1000)} From 08828f0d72d47e9129fbc3a340b8399762834f3a Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Tue, 28 Jan 2025 15:37:33 +0100 Subject: [PATCH 28/36] Apply suggestions from code review Corrections in documentation Co-authored-by: Daniel Feismann <98817556+danielfeismann@users.noreply.github.com> --- docs/readthedocs/models/pv_model.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/readthedocs/models/pv_model.md b/docs/readthedocs/models/pv_model.md index 1baa451394..d80c4ab697 100644 --- a/docs/readthedocs/models/pv_model.md +++ b/docs/readthedocs/models/pv_model.md @@ -127,7 +127,7 @@ $$ **References:** * {cite:cts}`Maleki.2017` -* {cite:cts}`Duffie.2013` p. 17 (1.16.10) +* {cite:cts}`Duffie.2013` p. 17 (formula 1.6.10) ### Solar Altitude Angle @@ -146,7 +146,7 @@ $$ **References:** * {cite:cts}`Maleki.2017` p. 5 -* {cite:cts}`Duffie.2013` p. 15 (1.6.5) with $\sin (\alpha_s) = \cos (\theta_z)$ +* {cite:cts}`Duffie.2013` p. 15 (formula 1.6.5) with $\sin (\alpha_s) = \cos (\theta_z)$ ### Zenith Angle From dc73481839a5c02448811a4307f952de7557b62e Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Tue, 28 Jan 2025 15:48:21 +0100 Subject: [PATCH 29/36] Enhancing hint about typo in Duffie Signed-off-by: Sebastian Peter --- docs/readthedocs/models/pv_model.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readthedocs/models/pv_model.md b/docs/readthedocs/models/pv_model.md index d80c4ab697..1585165ddf 100644 --- a/docs/readthedocs/models/pv_model.md +++ b/docs/readthedocs/models/pv_model.md @@ -235,7 +235,7 @@ $$ * {cite:cts}`Zheng.2017` p. 53, formula 2.3b * {cite:cts}`Iqbal.1983` * {cite:cts}`Spencer.1971` -* {cite:cts}`Duffie.2013` (the fifth ed. seems to have a typo here) +* {cite:cts}`Duffie.2013` (the fifth ed. seems to have a typo in formula (1.4.1b): factor $0.000719$ is missing a zero) ### Beam Radiation on Sloped Surface From a0676094fc444a8661feed7100ba8b15d7309363 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Tue, 28 Jan 2025 16:00:58 +0100 Subject: [PATCH 30/36] Fixing math mode subscripts Signed-off-by: Sebastian Peter --- docs/readthedocs/models/pv_model.md | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/readthedocs/models/pv_model.md b/docs/readthedocs/models/pv_model.md index 1585165ddf..c28b30c42e 100644 --- a/docs/readthedocs/models/pv_model.md +++ b/docs/readthedocs/models/pv_model.md @@ -198,11 +198,11 @@ $$ Calculating the air mass ratio by dividing the radius of the earth with approx. effective height of the atmosphere (each in kilometer) $$ -airmassratio = (\frac{6371 km}{9 km}) = 707.8\overline{8} +\mathrm{airmassratio} = (\frac{6371 km}{9 km}) = 707.8\overline{8} $$ $$ -airmass = \sqrt{(707.8\overline{8} \cdot \cos({\theta_z}))^2 +2 \cdot 707.8\overline{8} +1)} - 707.8\overline{8} \cdot \cos{(\theta_z)}) +\mathrm{airmass} = \sqrt{(707.8\overline{8} \cdot \cos({\theta_z}))^2 +2 \cdot 707.8\overline{8} +1)} - 707.8\overline{8} \cdot \cos{(\theta_z)}) $$ **References:** @@ -276,7 +276,7 @@ b = (\cos(\phi) \cdot \cos(\delta)) \cdot (\sin(\omega_{2}) - \sin(\omega_{1})) $$ $$ -E_{beam,S} = E_{beam,H} \cdot \frac{a}{b} +E_{\mathrm{beam},S} = E_{\mathrm{beam},H} \cdot \frac{a}{b} $$ **Please note:** $\frac{1}{180}\pi$ is omitted from these formulas, as we are already working with data in *radians*. @@ -288,7 +288,7 @@ $$ **$\omega_1$** = hour angle $\omega$\ **$\omega_2$** = hour angle $\omega$ + 1 hour\ **$\alpha_e$** = surface azimuth angle\ -**$E_{beam,H}$** = beam radiation (horizontal surface) +**$E_{\mathrm{beam},H}$** = beam radiation (horizontal surface) **Reference:** @@ -302,7 +302,7 @@ The diffuse radiation is computed using the Perez model, which divides the radia A cloud index is defined by $$ -\epsilon = \frac{\frac{E_{dif,H} + E_{beam,H}}{E_{dif,H}} + 5.535 \cdot 10^{-6} \cdot \theta_{z}^3}{1 + 5.535 \cdot 10^{-6} \cdot \theta_{z}^3} +\epsilon = \frac{\frac{E_{\mathrm{dif},H} + E_{\mathrm{beam},H}}{E_{\mathrm{dif},H}} + 5.535 \cdot 10^{-6} \cdot \theta_{z}^3}{1 + 5.535 \cdot 10^{-6} \cdot \theta_{z}^3} $$ where the angle $theta_{z}$ is in **degrees**. @@ -310,7 +310,7 @@ where the angle $theta_{z}$ is in **degrees**. Calculating a brightness index $$ -\Delta = m \cdot \frac{E_{dif,H}}{I_{0}} +\Delta = m \cdot \frac{E_{\mathrm{dif},H}}{I_{0}} $$ **Perez Fij coefficients (Myers 2017):** @@ -388,7 +388,7 @@ $$ the diffuse radiation can be calculated: $$ -E_{dif,S} = E_{dif,H} \cdot (\frac{1}{2} \cdot (1 + +E_{\mathrm{dif},S} = E_{\mathrm{dif},H} \cdot (\frac{1}{2} \cdot (1 + cos(\gamma_{e})) \cdot (1- F_{1}) + \frac{a}{b} \cdot F_{1} + F_{2} \cdot \sin(\gamma_{e})) $$ @@ -400,8 +400,8 @@ $$ **$\gamma_{e}$** = slope angle of the surface\ **$I_{0}$** = Extraterrestrial Radiation\ **$m$** = air mass\ -**$E_{beam,H}$** = beam radiation (horizontal surface)\ -**$E_{dif,H}$** = diffuse radiation (horizontal surface) +**$E_{\mathrm{beam},H}$** = beam radiation (horizontal surface)\ +**$E_{\mathrm{dif},H}$** = diffuse radiation (horizontal surface) **References:** @@ -414,12 +414,12 @@ $$ ### Reflected Radiation on Sloped Surface $$ -E_{ref,S} = E_{Ges,H} \cdot \frac{\rho}{2} \cdot (1- +E_{\mathrm{ref},S} = E_{\mathrm{Ges},H} \cdot \frac{\rho}{2} \cdot (1- \cos(\gamma_{e})) $$ *with*\ -**$E_{Ges,H}$** = total horizontal radiation ($E_{beam,H} + E_{dif,H})$\ +**$E_{\mathrm{Ges},H}$** = total horizontal radiation ($E_{\mathrm{beam},H} + E_{\mathrm{dif},H})$\ **$\gamma_e$** = slope angle of the surface\ **$\rho$** = albedo @@ -433,13 +433,13 @@ $$ Received energy is calculated as the sum of all three types of irradiation. $$ -E_{total} = E_{beam,S} + E_{dif,S} + E_{ref,S} +E_{\mathrm{total}} = E_{\mathrm{beam},S} + E_{\mathrm{dif},S} + E_{\mathrm{ref},S} $$ *with*\ -**$E_{beam,S}$** = Beam radiation\ -**$E_{dif,S}$** = Diffuse radiation\ -**$E_{ref,S}$** = Reflected radiation +**$E_{\mathrm{beam},S}$** = Beam radiation\ +**$E_{\mathrm{dif},S}$** = Diffuse radiation\ +**$E_{\mathrm{ref},S}$** = Reflected radiation A generator correction factor (depending on month surface slope $\gamma_{e}$) and a temperature correction factor (depending on month) multiplied on top. From bda5e365d1363258099c015b1bd2dfdc7014bd2b Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Tue, 28 Jan 2025 16:48:28 +0100 Subject: [PATCH 31/36] Adding hints about normal incidence radiation and theta z in deg Signed-off-by: Sebastian Peter --- docs/readthedocs/models/pv_model.md | 5 +++-- .../scala/edu/ie3/simona/model/participant/PvModel.scala | 9 +++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/readthedocs/models/pv_model.md b/docs/readthedocs/models/pv_model.md index c28b30c42e..da64e2067a 100644 --- a/docs/readthedocs/models/pv_model.md +++ b/docs/readthedocs/models/pv_model.md @@ -302,10 +302,10 @@ The diffuse radiation is computed using the Perez model, which divides the radia A cloud index is defined by $$ -\epsilon = \frac{\frac{E_{\mathrm{dif},H} + E_{\mathrm{beam},H}}{E_{\mathrm{dif},H}} + 5.535 \cdot 10^{-6} \cdot \theta_{z}^3}{1 + 5.535 \cdot 10^{-6} \cdot \theta_{z}^3} +\epsilon = \frac{\frac{E_{\mathrm{dif},H} + E_{\mathrm{beam},N}}{E_{\mathrm{dif},H}} + 5.535 \cdot 10^{-6} \cdot \theta_{z}^3}{1 + 5.535 \cdot 10^{-6} \cdot \theta_{z}^3} $$ -where the angle $theta_{z}$ is in **degrees**. +with angle $\theta_z$ values in **degrees** ({cite:cts}`Duffie.2013` p. 94) and $E_{\mathrm{beam},N} = \frac{E_{\mathrm{beam},H}}{\cos (\theta_z)}$ ({cite:cts}`Duffie.2013` p. 95). Calculating a brightness index @@ -401,6 +401,7 @@ $$ **$I_{0}$** = Extraterrestrial Radiation\ **$m$** = air mass\ **$E_{\mathrm{beam},H}$** = beam radiation (horizontal surface)\ +**$E_{\mathrm{beam},N}$** = beam radiation (normal incidence, thus radiation on a plane normal to the direction of the beam)\ **$E_{\mathrm{dif},H}$** = diffuse radiation (horizontal surface) **References:** diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index 693099a49a..9642e59262 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -548,11 +548,16 @@ final case class PvModel private ( val delta = eDifH * airMass / extraterrestrialRadiationI0 // == cloud index epsilon ==// - // if we have no clouds, the epsilon bin is 8, as epsilon bin for an epsilon in [6.2, inf.[ = 8 + // if we have no clouds, the epsilon bin is 8, + // as the epsilon bin for an epsilon in [6.2, inf.[ is 8 var x = 8 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 + // if we have diffuse radiation on horizontal surface we have to consider + // the clearness parameter epsilon, which then gives us an epsilon bin x + + // Beam radiation is required on a plane normal to the beam direction (normal incidence), + // thus dividing by cos theta_z var epsilon = ((eDifH + eBeamH / cos(thetaZInRad)) / eDifH + (5.535d * 1.0e-6) * pow( thetaZ.toDegrees, From 8599bdd4f689172f8200608ccdf849e0a4093d1e Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Tue, 28 Jan 2025 16:57:40 +0100 Subject: [PATCH 32/36] fmt Signed-off-by: Sebastian Peter --- .../simona/model/participant/PvModelSpec.scala | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala index 48ed48af94..c7aef19b1c 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/PvModelSpec.scala @@ -756,8 +756,9 @@ class PvModelSpec extends UnitSpec with GivenWhenThen with DefaultTestData { } "calculate the estimated diffuse radiation eDifS correctly" in { - def megaJoule2WattHours(megajoule: Double): Double= - {megajoule / (3.6/1000)} + def megaJoule2WattHours(megajoule: Double): Double = { + megajoule / (3.6 / 1000) + } val testCases = Table( ("thetaGDeg", "thetaZDeg", "gammaEDeg", "airMass", "I0", "eDifSSol"), @@ -765,7 +766,14 @@ class PvModelSpec extends UnitSpec with GivenWhenThen with DefaultTestData { // I_0 = 5.025 MJ * 277.778 Wh/MJ = 1395.83445 Wh // eDifSSol is 0.79607 MJ (0.444 + 0.348 + 0.003) if one only calculates the relevant terms // from I_T on p. 96, but Duffie uses fixed f values, so the inaccuracy is fine (approx. 4.5 Wh/m^2 or 0.016 MJ/m^2) - (37.0d, 62.2d, 60d, 2.144d, megaJoule2WattHours(5.025), megaJoule2WattHours(0.812140993078191252)), + ( + 37.0d, + 62.2d, + 60d, + 2.144d, + megaJoule2WattHours(5.025), + megaJoule2WattHours(0.812140993078191252), + ), ) forAll(testCases) { @@ -776,7 +784,8 @@ class PvModelSpec extends UnitSpec with GivenWhenThen with DefaultTestData { val eBeamH = megaJoule2WattHours(0.244) // 0.244 MJ/m^2 = 67.777778 Wh/m^2 // Diffuse Radiation on a horizontal surface - val eDifH = megaJoule2WattHours(0.796) // 0.796 MJ/m^2 = 221.111111 Wh/m^2 + val eDifH = + megaJoule2WattHours(0.796) // 0.796 MJ/m^2 = 221.111111 Wh/m^2 When("the diffuse radiation is calculated") val eDifSCalc = pvModel.calcDiffuseRadiationOnSlopedSurfacePerez( From 770813cc813b0933b9c90088fe479ea097255675 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Tue, 28 Jan 2025 17:53:21 +0100 Subject: [PATCH 33/36] Getting rid of var and Java code Signed-off-by: Sebastian Peter --- .../simona/model/participant/PvModel.scala | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index 9642e59262..c66209437c 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -548,11 +548,7 @@ final case class PvModel private ( val delta = eDifH * airMass / extraterrestrialRadiationI0 // == cloud index epsilon ==// - // if we have no clouds, the epsilon bin is 8, - // as the epsilon bin for an epsilon in [6.2, inf.[ is 8 - var x = 8 - - if (eDifH.value.doubleValue > 0) { + val x = if (eDifH.value.doubleValue > 0) { // if we have diffuse radiation on horizontal surface we have to consider // the clearness parameter epsilon, which then gives us an epsilon bin x @@ -584,17 +580,23 @@ final case class PvModel private ( // get the corresponding bin val finalEpsilon = epsilon - x = IntStream - .range(0, discreteSkyClearnessCategories.length) - .filter((i: Int) => + discreteSkyClearnessCategories.indices + .find { i => (finalEpsilon - discreteSkyClearnessCategories(i)(0) >= 0) && - (finalEpsilon - - discreteSkyClearnessCategories(i)(1) < 0) - ) - .findFirst - .getAsInt + 1 + (finalEpsilon - + discreteSkyClearnessCategories(i)(1) < 0) + } + .map(_ + 1) + .getOrElse(8) + } else { + // epsilon in [6.2, inf.[ + 8 } + } else { + // if we have no clouds, the epsilon bin is 8, + // as the epsilon bin for an epsilon in [6.2, inf.[ is 8 + 8 } // calculate the f_ij components based on the epsilon bin From 29484a2d9770f732af90b3d6ba4fe191aa35c2e5 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Tue, 28 Jan 2025 18:03:08 +0100 Subject: [PATCH 34/36] Unused import Signed-off-by: Sebastian Peter --- src/main/scala/edu/ie3/simona/model/participant/PvModel.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala index c66209437c..98f2162ed2 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/PvModel.scala @@ -24,7 +24,6 @@ import tech.units.indriya.unit.Units._ import java.time.ZonedDateTime import java.util.UUID -import java.util.stream.IntStream import scala.math._ final case class PvModel private ( From c9a369b8330c8d775493aadca27d7c45d0a94fbb Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Tue, 28 Jan 2025 18:51:27 +0100 Subject: [PATCH 35/36] Fixing the first of two EmAgentIT tests Signed-off-by: Sebastian Peter --- .../edu/ie3/simona/agent/em/EmAgentIT.scala | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala index 8667aaa84e..e1d444d218 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -259,11 +259,11 @@ class EmAgentIT scheduler.expectMessage(Completion(storageAgent)) /* TICK 0 - LOAD: 0.000269 MW - PV: -0.005685 MW + LOAD: 0.269 kW + PV: -5.617 kW STORAGE: SOC 0 % -> charge with 5 kW - -> remaining -0.0004161 MW + -> remaining -0.348 kW */ emAgentActivation ! Activation(0) @@ -272,7 +272,7 @@ class EmAgentIT 0, weatherService.ref.toClassic, WeatherData( - WattsPerSquareMeter(400d), + WattsPerSquareMeter(540d), WattsPerSquareMeter(200d), Celsius(0d), MetersPerSecond(0d), @@ -285,19 +285,19 @@ class EmAgentIT emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 0L.toDateTime emResult.getP should equalWithTolerance( - -0.000416087825.asMegaWatt + -0.00034885012.asMegaWatt ) - emResult.getQ should equalWithTolerance(0.0000882855367.asMegaVar) + emResult.getQ should equalWithTolerance(0.00008828554.asMegaVar) } scheduler.expectMessage(Completion(emAgentActivation, Some(7200))) /* TICK 7200 - LOAD: 0.000269 MW (unchanged) - PV: -0.003797 MW + LOAD: 0.269 kW (unchanged) + PV: -3.651 kW STORAGE: SOC 63.3 % - -> charge with 3.5282 kW - -> remaining 0 MW + -> charge with 3.382 kW + -> remaining 0 kW */ emAgentActivation ! Activation(7200) @@ -322,24 +322,24 @@ class EmAgentIT emResult.getQ should equalWithTolerance(0.0000882855367.asMegaVar) } - scheduler.expectMessage(Completion(emAgentActivation, Some(13107))) + scheduler.expectMessage(Completion(emAgentActivation, Some(13362))) - /* TICK 13107 - LOAD: 0.000269 MW (unchanged) - PV: -0.003797 MW (unchanged) + /* TICK 13362 + LOAD: 0.269 kW (unchanged) + PV: -3.651 kW (unchanged) STORAGE: SOC 100 % -> charge with 0 kW - -> remaining -0.003528 MW + -> remaining -3.382 kW */ - emAgentActivation ! Activation(13107) + emAgentActivation ! Activation(13362) resultListener.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid - emResult.getTime shouldBe 13107L.toDateTime + emResult.getTime shouldBe 13362L.toDateTime emResult.getP should equalWithTolerance( - -0.0035281545552.asMegaWatt + -0.003382375474.asMegaWatt ) emResult.getQ should equalWithTolerance(0.0000882855367.asMegaVar) } @@ -347,11 +347,11 @@ class EmAgentIT scheduler.expectMessage(Completion(emAgentActivation, Some(14400))) /* TICK 14400 - LOAD: 0.000269 MW (unchanged) - PV: -0.000066 MW + LOAD: 0.269 kW (unchanged) + PV: -0.066 kW STORAGE: SOC 100 % -> charge with -0.202956 kW - -> remaining 0 MW + -> remaining 0 kW */ // send weather data before activation, which can happen From 62f0673805b273d5a10cedeb3862ee6d0841a748 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Wed, 29 Jan 2025 11:54:24 +0100 Subject: [PATCH 36/36] Fixing the second of two EmAgentIT tests Signed-off-by: Sebastian Peter --- .../edu/ie3/simona/agent/em/EmAgentIT.scala | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala index e1d444d218..630f17575d 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -558,11 +558,11 @@ class EmAgentIT val weatherDependentAgents = Seq(pvAgent, heatPumpAgent) /* TICK 0 - LOAD: 0.000269 MW - PV: -0.005685 MW + LOAD: 0.269 kW + PV: -5.617 kW Heat pump: off, can be turned on or stay off -> set point ~3.5 kW (bigger than 50 % rated apparent power): turned on - -> remaining -0.000566 MW + -> remaining -0.499 kW */ emAgentActivation ! Activation(0) @@ -572,7 +572,7 @@ class EmAgentIT 0, weatherService.ref.toClassic, WeatherData( - WattsPerSquareMeter(400d), + WattsPerSquareMeter(540d), WattsPerSquareMeter(200d), Celsius(0d), MetersPerSecond(0d), @@ -586,7 +586,7 @@ class EmAgentIT emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 0.toDateTime emResult.getP should equalWithTolerance( - -0.000566087824.asMegaWatt + -0.000498850118.asMegaWatt ) emResult.getQ should equalWithTolerance(0.001073120041.asMegaVar) } @@ -594,11 +594,11 @@ class EmAgentIT scheduler.expectMessage(Completion(emAgentActivation, Some(7200))) /* TICK 7200 - LOAD: 0.000269 MW (unchanged) - PV: -0.003797 MW + LOAD: 0.269 kW (unchanged) + PV: -3.651 kW Heat pump: running (turned on from last request), can also be turned off -> set point ~3.5 kW (bigger than 50 % rated apparent power): stays turned on with unchanged state - -> remaining 0 MW + -> remaining 1.468 kW */ emAgentActivation ! Activation(7200) @@ -621,17 +621,18 @@ class EmAgentIT case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 7200.toDateTime - emResult.getP should equalWithTolerance(0.00132184544484.asMegaWatt) + emResult.getP should equalWithTolerance(0.001467624526.asMegaWatt) emResult.getQ should equalWithTolerance(0.001073120041.asMegaVar) } scheduler.expectMessage(Completion(emAgentActivation, Some(14400))) /* TICK 14400 - LOAD: 0.000269 MW (unchanged) - PV: -0.000066 MW + LOAD: 0.269 kW (unchanged) + PV: -0.066 kW Heat pump: Is still running, can still be turned off -> flex signal is 0 MW: Heat pump is turned off + -> remaining 0.203 kW */ emAgentActivation ! Activation(14400) @@ -662,10 +663,11 @@ class EmAgentIT scheduler.expectMessage(Completion(emAgentActivation, Some(21600))) /* TICK 21600 - LOAD: 0.000269 MW (unchanged) - PV: -0.000032 MW + LOAD: 0.269 kW (unchanged) + PV: -0.026 kW Heat pump: Is not running, can run or stay off -> flex signal is 0 MW: Heat pump is turned off + -> remaining 0.242 kW */ emAgentActivation ! Activation(21600) @@ -675,6 +677,7 @@ class EmAgentIT 21600, weatherService.ref.toClassic, WeatherData( + // Same irradiation, but different angle of the sun WattsPerSquareMeter(5d), WattsPerSquareMeter(5d), Celsius(0d), @@ -688,17 +691,18 @@ class EmAgentIT case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 21600.toDateTime - emResult.getP should equalWithTolerance(0.0002367679996.asMegaWatt) + emResult.getP should equalWithTolerance(0.000242284024.asMegaWatt) emResult.getQ should equalWithTolerance(0.000088285537.asMegaVar) } scheduler.expectMessage(Completion(emAgentActivation, Some(28665))) /* TICK 28666 - LOAD: 0.000269 MW (unchanged) - PV: -0.000032 MW (unchanged) + LOAD: 0.269 kW (unchanged) + PV: -0.026 kW (unchanged) Heat pump: Is turned on again and cannot be turned off -> flex signal is no control -> 0.00485 MW + -> remaining 5.092 kW */ emAgentActivation ! Activation(28665) @@ -707,7 +711,7 @@ class EmAgentIT case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 28665.toDateTime - emResult.getP should equalWithTolerance(0.0050867679996.asMegaWatt) + emResult.getP should equalWithTolerance(0.005092284024.asMegaWatt) emResult.getQ should equalWithTolerance(0.001073120040.asMegaVar) }