Skip to content

Commit 55104d2

Browse files
Transferring aggregation tests
Signed-off-by: Sebastian Peter <[email protected]>
1 parent 0f1127d commit 55104d2

File tree

2 files changed

+163
-2
lines changed

2 files changed

+163
-2
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* © 2024. TU Dortmund University,
3+
* Institute of Energy Systems, Energy Efficiency and Energy Economics,
4+
* Research group Distribution grid planning and operation
5+
*/
6+
7+
package edu.ie3.simona.model.participant2.load
8+
9+
import edu.ie3.simona.model.participant2.ParticipantModel.{
10+
ActivePowerOperatingPoint,
11+
DateTimeData,
12+
FixedState,
13+
}
14+
import squants.{Dimensionless, Each, Energy, Power, Quantity}
15+
import squants.energy.KilowattHours
16+
import squants.time.Minutes
17+
18+
import java.time.ZonedDateTime
19+
import java.time.temporal.ChronoUnit
20+
21+
trait LoadModelTestHelper {
22+
23+
protected def calculateEnergyDiffForYear(
24+
model: LoadModel[DateTimeData],
25+
simulationStartDate: ZonedDateTime,
26+
expectedEnergy: Energy,
27+
): Dimensionless = {
28+
val duration = Minutes(15d)
29+
30+
val avgEnergy = calculatePowerForYear(
31+
model,
32+
simulationStartDate,
33+
).foldLeft(KilowattHours(0)) { case (energySum, power) =>
34+
energySum + (power * duration)
35+
}
36+
37+
getRelativeDifference(
38+
avgEnergy,
39+
expectedEnergy,
40+
)
41+
}
42+
43+
protected def calculatePowerForYear(
44+
model: LoadModel[DateTimeData],
45+
simulationStartDate: ZonedDateTime,
46+
): Iterable[Power] = {
47+
val quarterHoursInYear = 365L * 96L
48+
49+
(0L until quarterHoursInYear)
50+
.map { quarterHour =>
51+
val tick = quarterHour * 15 * 60
52+
val relevantData = DateTimeData(
53+
tick,
54+
simulationStartDate.plus(quarterHour * 15, ChronoUnit.MINUTES),
55+
)
56+
57+
model
58+
.determineOperatingPoint(
59+
FixedState(tick),
60+
relevantData,
61+
) match {
62+
case (ActivePowerOperatingPoint(p), _) =>
63+
p
64+
}
65+
}
66+
}
67+
68+
protected def getRelativeDifference[Q <: Quantity[Q]](
69+
actualResult: Q,
70+
expectedResult: Q,
71+
): Dimensionless =
72+
Each((expectedResult - actualResult).abs / expectedResult)
73+
74+
}

src/test/scala/edu/ie3/simona/model/participant2/load/ProfileLoadModelSpec.scala

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,30 @@ import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils
1515
import edu.ie3.simona.config.SimonaConfig.LoadRuntimeConfig
1616
import edu.ie3.simona.test.common.UnitSpec
1717
import edu.ie3.simona.test.matchers.DoubleMatchers
18+
import edu.ie3.util.TimeUtil
1819
import edu.ie3.util.quantities.PowerSystemUnits
19-
import edu.ie3.util.scala.quantities.{ApparentPower, Voltamperes}
20+
import edu.ie3.util.scala.quantities.{
21+
ApparentPower,
22+
Kilovoltamperes,
23+
Voltamperes,
24+
}
25+
import squants.Percent
26+
import squants.energy.{KilowattHours, Power, Watts}
2027
import tech.units.indriya.quantity.Quantities
2128

2229
import java.util.UUID
2330

24-
class ProfileLoadModelSpec extends UnitSpec with DoubleMatchers {
31+
class ProfileLoadModelSpec
32+
extends UnitSpec
33+
with DoubleMatchers
34+
with LoadModelTestHelper {
2535

2636
private implicit val powerTolerance: ApparentPower = Voltamperes(1e-2)
2737
private implicit val doubleTolerance: Double = 1e-6
2838

39+
private val simulationStartDate =
40+
TimeUtil.withDefaults.toZonedDateTime("2022-01-01T00:00:00Z")
41+
2942
"A profile load model" should {
3043

3144
val loadInput = new LoadInput(
@@ -126,5 +139,79 @@ class ProfileLoadModelSpec extends UnitSpec with DoubleMatchers {
126139

127140
}
128141

142+
"reach the targeted annual energy consumption" in {
143+
forAll(
144+
Table("profile", H0, L0, G0)
145+
) { profile =>
146+
val input = loadInput.copy().loadprofile(profile).build()
147+
148+
val config = LoadRuntimeConfig(
149+
calculateMissingReactivePowerWithModel = false,
150+
scaling = 1.0,
151+
uuids = List.empty,
152+
modelBehaviour = "profile",
153+
reference = "energy",
154+
)
155+
156+
val targetEnergyConsumption = KilowattHours(
157+
loadInput
158+
.geteConsAnnual()
159+
.to(PowerSystemUnits.KILOWATTHOUR)
160+
.getValue
161+
.doubleValue
162+
)
163+
164+
val model = ProfileLoadModel(input, config)
165+
166+
/* Test against a permissible deviation of 2 %. As per official documentation of the bdew load profiles
167+
* [https://www.bdew.de/media/documents/2000131_Anwendung-repraesentativen_Lastprofile-Step-by-step.pdf], 1.5 %
168+
* are officially permissible. But, as we currently do not take (bank) holidays into account, we cannot reach
169+
* this accuracy. */
170+
171+
calculateEnergyDiffForYear(
172+
model,
173+
simulationStartDate,
174+
targetEnergyConsumption,
175+
) should be < Percent(2)
176+
}
177+
}
178+
179+
"approximately reach the maximum power in a simulated year" in {
180+
forAll(
181+
Table("profile", H0, L0, G0)
182+
) { profile =>
183+
val input = loadInput.copy().loadprofile(profile).build()
184+
185+
val config = LoadRuntimeConfig(
186+
calculateMissingReactivePowerWithModel = false,
187+
scaling = 1.0,
188+
uuids = List.empty,
189+
modelBehaviour = "profile",
190+
reference = "power",
191+
)
192+
193+
val model = ProfileLoadModel(input, config)
194+
195+
val targetMaximumPower = Kilovoltamperes(
196+
input
197+
.getsRated()
198+
.to(PowerSystemUnits.KILOVOLTAMPERE)
199+
.getValue
200+
.doubleValue
201+
).toActivePower(input.getCosPhiRated)
202+
203+
val maximumPower = calculatePowerForYear(
204+
model,
205+
simulationStartDate,
206+
).maxOption.value
207+
208+
// the maximum value depends on the year of the simulation,
209+
// since the maximum value for h0 will be reached on Saturdays in the winter
210+
// and since the dynamization function reaches its maximum on day 366 (leap year)
211+
implicit val tolerance: Power = Watts(1)
212+
maximumPower should approximate(targetMaximumPower)
213+
}
214+
}
215+
129216
}
130217
}

0 commit comments

Comments
 (0)