From 543ee86421c4dc3b852a83e43f5e24b835efa0d4 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier Date: Mon, 5 Aug 2024 16:00:32 +0200 Subject: [PATCH 1/7] test --- .../simona/model/participant/BMModel.scala | 14 +- .../model/participant/BMModelSpec.scala | 210 ++++++++++++++++++ 2 files changed, 217 insertions(+), 7 deletions(-) create mode 100644 src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala diff --git a/src/main/scala/edu/ie3/simona/model/participant/BMModel.scala b/src/main/scala/edu/ie3/simona/model/participant/BMModel.scala index 418af563d5..556fc67d64 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/BMModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/BMModel.scala @@ -52,7 +52,7 @@ final case class BMModel( /** Saves power output of last cycle. Needed for load gradient */ - private var _lastPower: Option[Power] = None + var _lastPower: Option[Power] = None override def calculatePower( tick: Long, @@ -100,7 +100,7 @@ final case class BMModel( * @return * factor k1 */ - private def calculateK1(time: ZonedDateTime): Double = { + def calculateK1(time: ZonedDateTime): Double = { val weekendCorr = Vector(0.98, 0.985, 0.982, 0.982, 0.97, 0.96, 0.95, 0.93, 0.925, 0.95, 0.98, 1.01, 1.018, 1.01, 1.01, 0.995, 1, 0.995, 0.99, 0.985, 0.99, 0.98, 0.975, 0.99) @@ -120,7 +120,7 @@ final case class BMModel( * @return * factor k2 */ - private def calculateK2(time: ZonedDateTime): Double = { + def calculateK2(time: ZonedDateTime): Double = { time.getDayOfYear match { case x if x < 150 || x > 243 => 1.03 // correction factor in heating season @@ -138,7 +138,7 @@ final case class BMModel( * @return * heat demand in Megawatt */ - private def calculatePTh( + def calculatePTh( temp: Temperature, k1: Double, k2: Double, @@ -158,7 +158,7 @@ final case class BMModel( * @return * usage */ - private def calculateUsage(pTh: Power): Double = { + def calculateUsage(pTh: Power): Double = { // if demand exceeds capacity -> activate peak load boiler (no effect on electrical output) val maxHeat = Megawatts(43.14) val usageUnchecked = pTh / maxHeat @@ -173,7 +173,7 @@ final case class BMModel( * @return * efficiency */ - private def calculateEff(usage: Double): Double = + def calculateEff(usage: Double): Double = min(0.18 * pow(usage, 3) - 0.595 * pow(usage, 2) + 0.692 * usage + 0.724, 1) /** Calculates electrical output from usage and efficiency @@ -206,7 +206,7 @@ final case class BMModel( * @return * electrical output after load gradient has been applied */ - private def applyLoadGradient( + def applyLoadGradient( pEl: Power ): Power = { _lastPower match { diff --git a/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala new file mode 100644 index 0000000000..c6a4578352 --- /dev/null +++ b/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala @@ -0,0 +1,210 @@ +/* + * © 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 edu.ie3.datamodel.models.input.NodeInput +import edu.ie3.datamodel.models.input.system.`type`.BmTypeInput +import edu.ie3.datamodel.models.input.system.characteristic.CosPhiFixed +import edu.ie3.simona.model.participant.control.QControl +import edu.ie3.util.scala.OperationInterval +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import squants.energy.{Kilowatts, Megawatts} +import squants.market.EUR +import squants.thermal.Celsius +import tech.units.indriya.quantity.Quantities + +import java.time.ZonedDateTime +import java.util.UUID + +/** + * Test class that tries to cover all special cases of the current implementation of the {@link BMModel} + * + * Test results have been calculated on paper using equations from wiki: https://wiki.ie3.e-technik.tu-dortmund.de/!simona/model:bm_model + */ +class BMModelSpec extends AnyFlatSpec with Matchers { + + val nodeInput: NodeInput = _ + val bmType: BmTypeInput = new BmTypeInput( + UUID.fromString("bc06e089-03cd-481e-9e28-228266a148a4"), + "BM Model Test Type 1", + Quantities.getQuantity(0, EUR), + Quantities.getQuantity(0.05, EuroPerKilowattHour), + Quantities.getQuantity(5, edu.ie3.util.quantities.PowerSystemUnits.PERCENT_PER_HOUR), + Quantities.getQuantity(190, edu.ie3.util.quantities.PowerSystemUnits.KILOVOLTAMPERE), + 1d, + Quantities.getQuantity(100d, tech.units.indriya.unit.Units.PERCENT) + ) + + def buildBmModel(): BMModel = { + new BMModel( + UUID.fromString("1b332f94-03e4-4abe-b142-8fceca689c53"), + "BM Model Test", + OperationInterval(0L, 86400L), + QControl(new CosPhiFixed("cosPhiFixed:{(0.0,1.0)}")), + Kilowatts(190), + bmType.getCosPhiRated, + "MockNode", + isCostControlled = true, + EUR(bmType.getOpex.getValue.doubleValue()), + EuroPerKilowattHour(0.051d), + 0.05 + ) + } + + "BMModel" should "calculate K1 correctly" in { + val bmModel = buildBmModel() + + val testCases = Seq( + ("2019-01-04T05:15:00+01:00[Europe/Berlin]", 1d), // Friday + ("2019-01-07T05:15:00+01:00[Europe/Berlin]", 1d), // Monday + ("2019-01-05T05:15:00+01:00[Europe/Berlin]", 0.96d), // Saturday, 5:15AM + ("2019-01-05T15:59:00+01:00[Europe/Berlin]", 0.995d) // Sunday, 3:59PM + ) + + testCases.foreach { case (time, k1Sol) => + val k1Calc = bmModel.calculateK1(ZonedDateTime.parse(time)) + k1Calc shouldBe k1Sol + } + } + + it should "calculate K2 correctly" in { + val bmModel = buildBmModel() + + val testCases = Seq( + ("2019-05-29T05:15:00+02:00[Europe/Berlin]", 1.03d), // Day 149 of the year + ("2019-05-30T05:15:00+02:00[Europe/Berlin]", 0.61d), // Day 150 of the year + ("2019-08-31T05:15:00+02:00[Europe/Berlin]", 0.61d), // Day 243 of the year + ("2019-09-01T05:15:00+02:00[Europe/Berlin]", 1.03d) // Day 244 of the year + ) + + testCases.foreach { case (time, k2Sol) => + val k2Calc = bmModel.calculateK2(ZonedDateTime.parse(time)) + k2Calc shouldBe k2Sol + } + } + + it should "calculate PTh correctly" in { + val bmModel = buildBmModel() + + val testCases = Seq( + (19.28, 1d, 1d, 5.62d), // independent of temp + (30d, 2d, 3d, 33.72d), + (19.2799999d, 1d, 1d, 5.6147201076d), // dependent on temp + (15d, 1.01d, 0.61d, 6.296542d) // somewhat realistic + ) + + testCases.foreach { case (temp, k1, k2, pThSol) => + val pThCalc = bmModel.calculatePTh(Celsius(temp), k1, k2) + pThCalc should be (Megawatts(pThSol) +- Megawatts(0.0001)) + } + } + + it should "calculate usage correctly" in { + val bmModel = buildBmModel() + + val testCases = Seq( + (43.14d, 1d), // exactly maximum heat + (50d, 1d), // more than maximum, cap to 1 + (20d, 0.463606861382d), // less than max + (0d, 0d) // zero + ) + + testCases.foreach { case (pTh, usageSol) => + val usageCalc = bmModel.calculateUsage(Megawatts(pTh)) + usageCalc should be (usageSol +- 0.00000001) + } + } + + it should "calculate efficiency correctly" in { + val bmModel = buildBmModel() + + val testCases = Seq( + (1d, 1d), + (0d, 0.724d), + (0.75d, 0.98425d), + (0.86446317d, 0.993848918615d) + ) + + testCases.foreach { case (usage, effSol) => + val effCalc = bmModel.calculateEff(usage) + effCalc should be (effSol +- 0.000000001) + } + } + + it should "calculate electrical output correctly" in { + val bmModel = new BMModel( + UUID.fromString("8fbaf82d-5170-4636-bd7a-790eccbea880"), + "BM Model Test", + OperationInterval(0L, 86400L), + QControl(new CosPhiFixed("cosPhiFixed:{(0.0,1.0)}")), + Kilowatts(190), + bmType.getCosPhiRated, + "MockNode", + isCostControlled = true, + EUR(bmType.getOpex.getValue.doubleValue()), + EuroPerKilowattHour(feedInTariff), + 0.05 + ) + + val testCases = Seq( + (0.051d, 1d, 1d, -190d), // tariff greater than opex => full power + (0.04d, 0.75d, 0.98425d, -140.255625d), // tariff too little, only serve heat demand + (0.04d, 1d, 1d, -190d) // tariff too little, but max heat demand + ) + + testCases.foreach { case (feedInTariff, usage, eff, pElSol) => + val pElCalc = bmModel.calculateElOutput(usage, eff) + pElCalc should be (Kilowatts(pElSol) +- Kilowatts(0.0001)) + } + } + + it should "apply load gradient correctly" in { + val bmModel = buildBmModel() + + val testCases = Seq( + (-100d, -120d, -109.5d), // increase of power, more than load gradient allows + (-50d, -55d, -55d), // increase, within load gradient + (-50d, -41d, -41d), // decrease, within load gradient + (-30d, -15d, -20.5d) // decrease, more than load gradient + ) + + testCases.foreach { case (lastPower, pEl, pElSol) => + bmModel._lastPower = Some(Kilowatts(lastPower)) + val pElCalc = bmModel.applyLoadGradient(Kilowatts(pEl)) + pElCalc should be (Kilowatts(pElSol)) + } + } + + it should "calculate P correctly" in { + val bmModel = new BMModel( + UUID.fromString("08e44067-5d1e-4a6e-97ef-d8bc9c8c256a"), + "BM Model Test", + OperationInterval(0L, 86400L), + QControl(new CosPhiFixed("cosPhiFixed:{(0.0,1.0)}")), + Kilowatts(190), + bmType.getCosPhiRated, + "MockNode", + isCostControlled = true, + EUR(bmType.getOpex.getValue.doubleValue()), + EuroPerKilowattHour(feedInTariff), + 0.05 + ) + + val testCases = Seq( + (Megawatts(20), Megawatts(5), Megawatts(-15)), + (Megawatts(40), Megawatts(10), Megawatts(-30)), + (Megawatts(60), Megawatts(20), Megawatts(-40)), + (Megawatts(80), Megawatts(30), Megawatts(-50)) + ) + + testCases.foreach { case (heatDemand, power, pSol) => + val pCalc = bmModel.calculateP(heatDemand, power) + pCalc should be (pSol) + } + } +} \ No newline at end of file From b84cca15b09b16ef42768623467ae23cd44abb34 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier Date: Fri, 16 Aug 2024 15:05:37 +0200 Subject: [PATCH 2/7] Rewrote BMModelTest from groovy to scala. --- CHANGELOG.md | 1 + .../simona/model/participant/BMModel.scala | 4 +- .../model/participant/BMModelTest.groovy | 253 -------------- .../model/participant/BMModelSpec.scala | 322 ++++++++++++------ 4 files changed, 228 insertions(+), 352 deletions(-) delete mode 100644 src/test/groovy/edu/ie3/simona/model/participant/BMModelTest.groovy diff --git a/CHANGELOG.md b/CHANGELOG.md index 468de1d410..f26cd5a720 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Rewrote SystemComponentTest from groovy to scala [#646](https://github.com/ie3-institute/simona/issues/646) - Converting remaining rst files to markdown [#838](https://github.com/ie3-institute/simona/issues/838) - Merging both `FixedFeedInModelSpec` tests [#870](https://github.com/ie3-institute/simona/issues/870) +- Rewrote BMModelTest from groovy to scala [#646](https://github.com/ie3-institute/simona/issues/646) ### Fixed - Removed a repeated line in the documentation of vn_simona config [#658](https://github.com/ie3-institute/simona/issues/658) diff --git a/src/main/scala/edu/ie3/simona/model/participant/BMModel.scala b/src/main/scala/edu/ie3/simona/model/participant/BMModel.scala index 556fc67d64..19fbe3e601 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/BMModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/BMModel.scala @@ -73,7 +73,7 @@ final case class BMModel( * @return * Active power */ - override protected def calculateActivePower( + override def calculateActivePower( modelState: ConstantState.type, data: BMCalcRelevantData, ): Power = { @@ -184,7 +184,7 @@ final case class BMModel( * @return * electrical output as Power */ - private def calculateElOutput( + def calculateElOutput( usage: Double, eff: Double, ): Power = { diff --git a/src/test/groovy/edu/ie3/simona/model/participant/BMModelTest.groovy b/src/test/groovy/edu/ie3/simona/model/participant/BMModelTest.groovy deleted file mode 100644 index f6d83d770b..0000000000 --- a/src/test/groovy/edu/ie3/simona/model/participant/BMModelTest.groovy +++ /dev/null @@ -1,253 +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.unit.Units.PERCENT - -import edu.ie3.datamodel.models.input.NodeInput -import edu.ie3.datamodel.models.input.system.characteristic.CosPhiFixed -import edu.ie3.datamodel.models.input.system.type.BmTypeInput -import edu.ie3.simona.model.participant.ModelState.ConstantState$ -import edu.ie3.simona.model.participant.control.QControl -import edu.ie3.util.scala.OperationInterval -import edu.ie3.util.scala.quantities.EuroPerKilowatthour$ -import edu.ie3.util.scala.quantities.Sq -import scala.Some -import spock.lang.Shared -import spock.lang.Specification -import squants.energy.Kilowatts$ -import squants.energy.Megawatts$ -import squants.market.EUR$ -import squants.thermal.Celsius$ -import tech.units.indriya.quantity.Quantities - -import java.time.ZonedDateTime - -/** - * Test class that tries to cover all special cases of the current implementation of the {@link BMModel} - * - * Test results have been calculated on paper using equations from wiki: https://wiki.ie3.e-technik.tu-dortmund.de/!simona/model:bm_model - */ -class BMModelTest extends Specification { - - @Shared - NodeInput nodeInput - @Shared - BmTypeInput bmType - - def setupSpec() { - // build the NodeInputModel - nodeInput = Mock(NodeInput) - - // build the BMTypesInputModel - bmType = new BmTypeInput( - UUID.fromString("bc06e089-03cd-481e-9e28-228266a148a4"), - "BM Model Test Type 1", - Quantities.getQuantity(0, EURO), - Quantities.getQuantity(0.05d, EURO_PER_KILOWATTHOUR), - Quantities.getQuantity(5d, PERCENT_PER_HOUR), - Quantities.getQuantity(190, KILOVOLTAMPERE), - 1d, - Quantities.getQuantity(100d, PERCENT) - ) - } - - def getStandardModel() { - return new BMModel( - UUID.fromString("1b332f94-03e4-4abe-b142-8fceca689c53"), - "BM Model Test", - OperationInterval.apply(0L, 86400L), - QControl.apply(new CosPhiFixed("cosPhiFixed:{(0.0,1.0)}")), - Sq.create(190, Kilowatts$.MODULE$), - bmType.getCosPhiRated(), - "MockNode", - true, - Sq.create(bmType.opex.value.doubleValue(), EUR$.MODULE$), - Sq.create(0.051d, EuroPerKilowatthour$.MODULE$), - 0.05) - } - - def "Test calculateK1"() { - given: - BMModel bmModel = getStandardModel() - - when: - def k1Calc = bmModel.calculateK1(ZonedDateTime.parse(time)) - - then: - k1Calc == k1Sol - - where: - time || k1Sol - '2019-01-04T05:15:00+01:00[Europe/Berlin]' || 1d // Friday - '2019-01-07T05:15:00+01:00[Europe/Berlin]' || 1d // Monday - '2019-01-05T05:15:00+01:00[Europe/Berlin]' || 0.96d // Saturday, 5:15AM - '2019-01-05T15:59:00+01:00[Europe/Berlin]' || 0.995d // Sunday, 3:59PM - } - - def "Test calculateK2"() { - given: - BMModel bmModel = getStandardModel() - - when: - def k2Calc = bmModel.calculateK2(ZonedDateTime.parse(time)) - - then: - k2Calc == k2Sol - - where: - time || k2Sol - '2019-05-29T05:15:00+02:00[Europe/Berlin]' || 1.03d // Day 149 of the year - '2019-05-30T05:15:00+02:00[Europe/Berlin]' || 0.61d // Day 150 of the year - '2019-08-31T05:15:00+02:00[Europe/Berlin]' || 0.61d // Day 243 of the year - '2019-09-01T05:15:00+02:00[Europe/Berlin]' || 1.03d // Day 244 of the year - } - - def "Test calculatePTh"() { - given: - BMModel bmModel = getStandardModel() - - when: - def pThCalc = bmModel.calculatePTh(Sq.create(temp, Celsius$.MODULE$), k1, k2) - - then: "compare in watts" - pThCalc - Sq.create(pThSol, Megawatts$.MODULE$) < Sq.create(0.0001d, Megawatts$.MODULE$) - - where: - temp | k1 | k2 || pThSol - 19.28d | 1d | 1d || 5.62d // independent of temp - 30d | 2d | 3d || 33.72d - 19.2799999d | 1d | 1d || 5.6147201076d // dependent on temp - 15d | 1.01d | 0.61d || 6.296542d // somewhat realistic - } - - def "Test calculateUsage"() { - given: - BMModel bmModel = getStandardModel() - - when: - def usageCalc = bmModel.calculateUsage(Sq.create(pTh, Megawatts$.MODULE$)) - - then: - Math.abs(usageCalc - usageSol) < 0.00000001 - - where: - pTh || usageSol - 43.14d || 1d // exactly maximum heat - 50d || 1d // more than maximum, cap to 1 - 20d || 0.463606861382d // less than max - 0d || 0d // zero - } - - def "Test calculateEff"() { - given: - BMModel bmModel = getStandardModel() - - when: - def effCalc = bmModel.calculateEff(usage) - - then: - Math.abs(effCalc - effSol) < 0.000000001 - - where: - usage || effSol - 1d || 1d - 0d || 0.724d - 0.75d || 0.98425d - 0.86446317d || 0.993848918615d - } - - def "Test calculateElOutput"() { - when: - BMModel bmModel = new BMModel( - UUID.fromString("8fbaf82d-5170-4636-bd7a-790eccbea880"), - "BM Model Test", - OperationInterval.apply(0L, 86400L), - QControl.apply(new CosPhiFixed("cosPhiFixed:{(0.0,1.0)}")), - Sq.create(190, Kilowatts$.MODULE$), - bmType.getCosPhiRated(), - "MockNode", - true, - Sq.create(bmType.opex.value.doubleValue(), EUR$.MODULE$), - Sq.create(feedInTariff, EuroPerKilowatthour$.MODULE$), - 0.05) - - def pElCalc = bmModel.calculateElOutput(usage, eff) - - then: "compare in watts" - pElCalc - Sq.create(pElSol, Kilowatts$.MODULE$) < Sq.create(0.0001d, Kilowatts$.MODULE$) - - where: - feedInTariff | usage | eff || pElSol - 0.051d | 1d | 1d || -190d // tariff greater than opex => full power - 0.04d | 0.75d | 0.98425d || -140.255625d // tariff too little, only serve heat demand - 0.04d | 1d | 1d || -190d // tariff too little, but max heat demand - } - - def "Test applyLoadGradient"() { - given: - BMModel bmModel = getStandardModel() - bmModel._lastPower = new Some(Sq.create(lastPower, Kilowatts$.MODULE$)) - - when: - def pElCalc = bmModel.applyLoadGradient(Sq.create(pEl, Kilowatts$.MODULE$)) - - then: - pElCalc == Sq.create(pElSol, Kilowatts$.MODULE$) - - where: - lastPower | pEl || pElSol - -100d | -120d || -109.5d // increase of power, more than load gradient allows - -50d | -55d || -55d // increase, within load gradient - -50d | -41d || -41d // decrease, within load gradient - -30d | -15 || -20.5d // decrease, more than load gradient - } - - def "Test calculateP"() { - given: "date, time, a temperature and last power output and the built model" - // construct date and time from string - ZonedDateTime dateTime = ZonedDateTime.parse(time) - - /* Prepare the calculation relevant data */ - BMModel.BMCalcRelevantData relevantData = new BMModel.BMCalcRelevantData(dateTime, Sq.create(temp, Celsius$.MODULE$)) - - BMModel bmModel = new BMModel( - UUID.fromString("08a8134d-04b7-45de-a937-9a55fab4e1af"), - "BM Model Test", - OperationInterval.apply(0L, 86400L), - QControl.apply(new CosPhiFixed("cosPhiFixed:{(0.0,1.0)}")), - Sq.create(190, Kilowatts$.MODULE$), - bmType.getCosPhiRated(), - "MockNode", - costControlled, - Sq.create(bmType.opex.value.doubleValue(), EUR$.MODULE$), - Sq.create(0.051d, EuroPerKilowatthour$.MODULE$), - 0.05) - - // modify data store: add last output power, one hour in the past - bmModel._lastPower = new Some(Sq.create(lastPower, Kilowatts$.MODULE$)) - - when: "the power from the grid is calculated" - def powerCalc = bmModel.calculateActivePower(ConstantState$.MODULE$, relevantData) - - then: "compare in kilowatts" - powerCalc - Sq.create(powerSol, Kilowatts$.MODULE$) < Sq.create(1e-12d, Kilowatts$.MODULE$) - - where: - time | temp | costControlled | lastPower || powerSol - '2019-01-05T05:15:00+01:00[Europe/Berlin]' | 10 | true | -40.0d || -49.5d // weekend day in heating season, power increase capped by load gradient - '2019-01-04T05:15:00+01:00[Europe/Berlin]' | 10 | true | -80.0d || -70.5d // working day in heating season, power decrease capped by load gradient - '2019-01-04T05:15:00+01:00[Europe/Berlin]' | -20 | true | -182.0d || -190d // peek load boiler activated, max output because cost < revenues - '2019-01-04T05:15:00+01:00[Europe/Berlin]' | -7 | true | -182.0d || -190d // close to peak load, max output because cost < revenues - '2019-01-04T05:15:00+01:00[Europe/Berlin]' | -7 | false | -150.0d || -152.16900643778735d // close to peak load, not cost controlled but just serving heat demand - '2019-07-07T10:15:00+02:00[Europe/Berlin]' | 19 | true | -10.0d || -12.099949463243976d // weekend day outside heating season, increase not capped - '2019-07-05T05:15:00+02:00[Europe/Berlin]' | 20 | true | -20.0d || -11.70638561892377d // working day outside heating season, decrease not capped - '2019-07-06T10:15:00+02:00[Europe/Berlin]' | 20 | true | -0.0d || -9.5d // weekend day outside heating season, increase capped - '2019-07-05T05:15:00+02:00[Europe/Berlin]' | 22 | true | -22.0d || -12.5d // working day outside heating season, decrease capped - } -} \ No newline at end of file diff --git a/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala index c6a4578352..a7a021952e 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala @@ -6,38 +6,53 @@ package edu.ie3.simona.model.participant -import edu.ie3.datamodel.models.input.NodeInput import edu.ie3.datamodel.models.input.system.`type`.BmTypeInput import edu.ie3.datamodel.models.input.system.characteristic.CosPhiFixed +import edu.ie3.simona.model.participant.ModelState.ConstantState import edu.ie3.simona.model.participant.control.QControl +import edu.ie3.simona.test.common.UnitSpec import edu.ie3.util.scala.OperationInterval -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers +import edu.ie3.util.scala.quantities.EuroPerKilowatthour import squants.energy.{Kilowatts, Megawatts} import squants.market.EUR import squants.thermal.Celsius +import squants.{Power, Temperature} import tech.units.indriya.quantity.Quantities import java.time.ZonedDateTime import java.util.UUID -/** - * Test class that tries to cover all special cases of the current implementation of the {@link BMModel} - * - * Test results have been calculated on paper using equations from wiki: https://wiki.ie3.e-technik.tu-dortmund.de/!simona/model:bm_model - */ -class BMModelSpec extends AnyFlatSpec with Matchers { +/** Test class that tries to cover all special cases of the current + * implementation of the {@link BMModel} + * + * Test results have been calculated on paper using equations from wiki: + * https://wiki.ie3.e-technik.tu-dortmund.de/!simona/model:bm_model + */ + +class BMModelSpec extends UnitSpec { + + implicit val powerTolerance: Power = Megawatts(1e-4) + implicit val power2Tolerance: Power = Kilowatts(1e-4) + implicit val usageTolerance: Double = 1e-12 - val nodeInput: NodeInput = _ val bmType: BmTypeInput = new BmTypeInput( UUID.fromString("bc06e089-03cd-481e-9e28-228266a148a4"), "BM Model Test Type 1", - Quantities.getQuantity(0, EUR), - Quantities.getQuantity(0.05, EuroPerKilowattHour), - Quantities.getQuantity(5, edu.ie3.util.quantities.PowerSystemUnits.PERCENT_PER_HOUR), - Quantities.getQuantity(190, edu.ie3.util.quantities.PowerSystemUnits.KILOVOLTAMPERE), + Quantities.getQuantity(0, edu.ie3.util.quantities.PowerSystemUnits.EURO), + Quantities.getQuantity( + 0.05d, + edu.ie3.util.quantities.PowerSystemUnits.EURO_PER_KILOWATTHOUR, + ), + Quantities.getQuantity( + 5, + edu.ie3.util.quantities.PowerSystemUnits.PERCENT_PER_HOUR, + ), + Quantities.getQuantity( + 190, + edu.ie3.util.quantities.PowerSystemUnits.KILOVOLTAMPERE, + ), 1d, - Quantities.getQuantity(100d, tech.units.indriya.unit.Units.PERCENT) + Quantities.getQuantity(100d, tech.units.indriya.unit.Units.PERCENT), ) def buildBmModel(): BMModel = { @@ -51,160 +66,273 @@ class BMModelSpec extends AnyFlatSpec with Matchers { "MockNode", isCostControlled = true, EUR(bmType.getOpex.getValue.doubleValue()), - EuroPerKilowattHour(0.051d), - 0.05 + EuroPerKilowatthour(0.51d), + 0.05, ) } - "BMModel" should "calculate K1 correctly" in { - val bmModel = buildBmModel() + "A BMModel" should { + "calculate K1 correctly" in { + val bmModel = buildBmModel() - val testCases = Seq( - ("2019-01-04T05:15:00+01:00[Europe/Berlin]", 1d), // Friday - ("2019-01-07T05:15:00+01:00[Europe/Berlin]", 1d), // Monday - ("2019-01-05T05:15:00+01:00[Europe/Berlin]", 0.96d), // Saturday, 5:15AM - ("2019-01-05T15:59:00+01:00[Europe/Berlin]", 0.995d) // Sunday, 3:59PM - ) + val testCases = Table( + ("Time", "K1 Solution"), + ("2019-01-04T05:15:00+01:00[Europe/Berlin]", 1d), // Friday + ("2019-01-07T05:15:00+01:00[Europe/Berlin]", 1d), // Monday + ("2019-01-05T05:15:00+01:00[Europe/Berlin]", 0.96d), // Saturday, 5:15AM + ("2019-01-05T15:59:00+01:00[Europe/Berlin]", 0.995d), // Sunday, 3:59PM + ) - testCases.foreach { case (time, k1Sol) => - val k1Calc = bmModel.calculateK1(ZonedDateTime.parse(time)) - k1Calc shouldBe k1Sol + testCases.foreach { case (time, k1Sol) => + val k1Calc = bmModel.calculateK1(ZonedDateTime.parse(time)) + k1Calc should be(k1Sol) + } } } - it should "calculate K2 correctly" in { + "calculate K2 correctly" in { val bmModel = buildBmModel() - val testCases = Seq( - ("2019-05-29T05:15:00+02:00[Europe/Berlin]", 1.03d), // Day 149 of the year - ("2019-05-30T05:15:00+02:00[Europe/Berlin]", 0.61d), // Day 150 of the year - ("2019-08-31T05:15:00+02:00[Europe/Berlin]", 0.61d), // Day 243 of the year - ("2019-09-01T05:15:00+02:00[Europe/Berlin]", 1.03d) // Day 244 of the year + val testCases = Table( + ("Time", "K2 Solution"), + ( + "2019-05-29T05:15:00+02:00[Europe/Berlin]", + 1.03d, + ), // Day 149 of the year + ( + "2019-05-30T05:15:00+02:00[Europe/Berlin]", + 0.61d, + ), // Day 150 of the year + ( + "2019-08-31T05:15:00+02:00[Europe/Berlin]", + 0.61d, + ), // Day 243 of the year + ("2019-09-01T05:15:00+02:00[Europe/Berlin]", 1.03d), // Day 244 of the year ) testCases.foreach { case (time, k2Sol) => val k2Calc = bmModel.calculateK2(ZonedDateTime.parse(time)) - k2Calc shouldBe k2Sol + k2Calc should be(k2Sol) } } - it should "calculate PTh correctly" in { + "calculate PTh correctly" in { val bmModel = buildBmModel() - val testCases = Seq( + val testCases = Table( + ("Temperature", "K1", "K2", "PTh Solution"), (19.28, 1d, 1d, 5.62d), // independent of temp (30d, 2d, 3d, 33.72d), (19.2799999d, 1d, 1d, 5.6147201076d), // dependent on temp - (15d, 1.01d, 0.61d, 6.296542d) // somewhat realistic + (15d, 1.01d, 0.61d, 6.296542d), // somewhat realistic ) testCases.foreach { case (temp, k1, k2, pThSol) => val pThCalc = bmModel.calculatePTh(Celsius(temp), k1, k2) - pThCalc should be (Megawatts(pThSol) +- Megawatts(0.0001)) + pThCalc should approximate(Megawatts(pThSol))(powerTolerance) } } - it should "calculate usage correctly" in { + "calculate usage correctly" in { val bmModel = buildBmModel() - val testCases = Seq( + val testCases = Table( + ("PTh", "Usage Solution"), (43.14d, 1d), // exactly maximum heat (50d, 1d), // more than maximum, cap to 1 (20d, 0.463606861382d), // less than max - (0d, 0d) // zero + (0d, 0d), // zero ) testCases.foreach { case (pTh, usageSol) => val usageCalc = bmModel.calculateUsage(Megawatts(pTh)) - usageCalc should be (usageSol +- 0.00000001) + usageCalc should be(usageSol +- usageTolerance) } } - it should "calculate efficiency correctly" in { + "calculate efficiency correctly" in { val bmModel = buildBmModel() - val testCases = Seq( + val testCases = Table( + ("Usage", "Efficiency Solution"), (1d, 1d), (0d, 0.724d), (0.75d, 0.98425d), - (0.86446317d, 0.993848918615d) + (0.86446317d, 0.993848918615d), ) testCases.foreach { case (usage, effSol) => val effCalc = bmModel.calculateEff(usage) - effCalc should be (effSol +- 0.000000001) + effCalc should be(effSol +- 0.000000001) } } - it should "calculate electrical output correctly" in { - val bmModel = new BMModel( - UUID.fromString("8fbaf82d-5170-4636-bd7a-790eccbea880"), - "BM Model Test", - OperationInterval(0L, 86400L), - QControl(new CosPhiFixed("cosPhiFixed:{(0.0,1.0)}")), - Kilowatts(190), - bmType.getCosPhiRated, - "MockNode", - isCostControlled = true, - EUR(bmType.getOpex.getValue.doubleValue()), - EuroPerKilowattHour(feedInTariff), - 0.05 - ) + "calculate electrical output correctly" in { - val testCases = Seq( + val testCases = Table( + ("FeedInTariff", "Usage", "Efficiency", "PEl Solution"), (0.051d, 1d, 1d, -190d), // tariff greater than opex => full power - (0.04d, 0.75d, 0.98425d, -140.255625d), // tariff too little, only serve heat demand - (0.04d, 1d, 1d, -190d) // tariff too little, but max heat demand + ( + 0.04d, + 0.75d, + 0.98425d, + -140.255625d, + ), // tariff too little, only serve heat demand + (0.04d, 1d, 1d, -190d), // tariff too little, but max heat demand ) testCases.foreach { case (feedInTariff, usage, eff, pElSol) => + val bmModel = new BMModel( + UUID.fromString("8fbaf82d-5170-4636-bd7a-790eccbea880"), + "BM Model Test", + OperationInterval(0L, 86400L), + QControl(new CosPhiFixed("cosPhiFixed:{(0.0,1.0)}")), + Kilowatts(190), + bmType.getCosPhiRated, + "MockNode", + isCostControlled = true, + EUR(bmType.getOpex.getValue.doubleValue()), + EuroPerKilowatthour(feedInTariff), + 0.05, + ) + val pElCalc = bmModel.calculateElOutput(usage, eff) - pElCalc should be (Kilowatts(pElSol) +- Kilowatts(0.0001)) + pElCalc.value should be(Kilowatts(pElSol).value +- 1e-4) } } - it should "apply load gradient correctly" in { + "apply load gradient correctly" in { val bmModel = buildBmModel() - val testCases = Seq( - (-100d, -120d, -109.5d), // increase of power, more than load gradient allows - (-50d, -55d, -55d), // increase, within load gradient - (-50d, -41d, -41d), // decrease, within load gradient - (-30d, -15d, -20.5d) // decrease, more than load gradient + val testCases = Table( + ("Last Power", "PEl", "PEl Solution"), + ( + Kilowatts(-100d), // Last Power + Kilowatts(-120d), // PEl + Kilowatts(-109.5d), // PEl Solution + ), // increase of power, more than load gradient allows + ( + Kilowatts(-50d), // Last Power + Kilowatts(-55d), // PEl + Kilowatts(-55d), // PEl Solution + ), // increase, within load gradient + ( + Kilowatts(-50d), // Last Power + Kilowatts(-41d), // PEl + Kilowatts(-41d), // PEl Solution + ), // decrease, within load gradient + ( + Kilowatts(-30d), // Last Power + Kilowatts(-15d), // PEl + Kilowatts(-20.5d), // PEl Solution + ), // decrease, more than load gradient ) testCases.foreach { case (lastPower, pEl, pElSol) => - bmModel._lastPower = Some(Kilowatts(lastPower)) - val pElCalc = bmModel.applyLoadGradient(Kilowatts(pEl)) - pElCalc should be (Kilowatts(pElSol)) + bmModel._lastPower = Some(lastPower) + val pElCalc = bmModel.applyLoadGradient(pEl) + pElCalc should approximate(pElSol)(power2Tolerance) } } - it should "calculate P correctly" in { - val bmModel = new BMModel( - UUID.fromString("08e44067-5d1e-4a6e-97ef-d8bc9c8c256a"), - "BM Model Test", - OperationInterval(0L, 86400L), - QControl(new CosPhiFixed("cosPhiFixed:{(0.0,1.0)}")), - Kilowatts(190), - bmType.getCosPhiRated, - "MockNode", - isCostControlled = true, - EUR(bmType.getOpex.getValue.doubleValue()), - EuroPerKilowattHour(feedInTariff), - 0.05 - ) + "calculate P correctly" in { - val testCases = Seq( - (Megawatts(20), Megawatts(5), Megawatts(-15)), - (Megawatts(40), Megawatts(10), Megawatts(-30)), - (Megawatts(60), Megawatts(20), Megawatts(-40)), - (Megawatts(80), Megawatts(30), Megawatts(-50)) + val testCases = Table( + ("time", "temp", "costControlled", "lastPower", "powerSol"), + ( + "2019-01-05T05:15:00+01:00[Europe/Berlin]", + Celsius(10), + true, + Kilowatts(-40.0), + Kilowatts(-49.5), + ), + ( + "2019-01-04T05:15:00+01:00[Europe/Berlin]", + Celsius(10), + true, + Kilowatts(-80.0), + Kilowatts(-89.5), + ), + ( + "2019-01-04T05:15:00+01:00[Europe/Berlin]", + Celsius(-20), + true, + Kilowatts(-182.0), + Kilowatts(-190), + ), + ( + "2019-01-04T05:15:00+01:00[Europe/Berlin]", + Celsius(-7), + true, + Kilowatts(-182.0), + Kilowatts(-190), + ), + ( + "2019-01-04T05:15:00+01:00[Europe/Berlin]", + Celsius(-7), + false, + Kilowatts(-150.0), + Kilowatts(-152.16900643778735), + ), + ( + "2019-07-07T10:15:00+02:00[Europe/Berlin]", + Celsius(19), + true, + Kilowatts(-10.0), + Kilowatts(-19.5), + ), + ( + "2019-07-05T05:15:00+02:00[Europe/Berlin]", + Celsius(20), + true, + Kilowatts(-20.0), + Kilowatts(-29.5), + ), + ( + "2019-07-06T10:15:00+02:00[Europe/Berlin]", + Celsius(20), + true, + Kilowatts(0.0), + Kilowatts(-9.5), + ), + ( + "2019-07-05T05:15:00+02:00[Europe/Berlin]", + Celsius(22), + true, + Kilowatts(-22.0), + Kilowatts(-31.5), + ), ) - testCases.foreach { case (heatDemand, power, pSol) => - val pCalc = bmModel.calculateP(heatDemand, power) - pCalc should be (pSol) + forAll(testCases) { + ( + time: String, + temp: Temperature, + costControlled: Boolean, + lastPower: Power, + powerSol: Power, + ) => + val dateTime = ZonedDateTime.parse(time) + val relevantData = new BMModel.BMCalcRelevantData(dateTime, temp) + + val bmModel = new BMModel( + UUID.fromString("08a8134d-04b7-45de-a937-9a55fab4e1af"), + "BM Model Test", + OperationInterval(0L, 86400L), + QControl(new CosPhiFixed("cosPhiFixed:{(0.0,1.0)}")), + Kilowatts(190), + bmType.getCosPhiRated(), + "MockNode", + costControlled, + EUR(bmType.getOpex.getValue.doubleValue()), + EuroPerKilowatthour(0.51d), + 0.05, + ) + + bmModel._lastPower = Some(lastPower) + val powerCalc = + bmModel.calculateActivePower(ConstantState, relevantData) + powerCalc should be(powerSol) } } -} \ No newline at end of file +} From 35350da91a2a5ccdb58d3cf39b6b69c3e7752c79 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier Date: Fri, 16 Aug 2024 15:23:36 +0200 Subject: [PATCH 3/7] fmt --- .../edu/ie3/simona/model/participant/BMModelSpec.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala index a7a021952e..12d3906425 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala @@ -120,7 +120,7 @@ class BMModelSpec extends UnitSpec { val bmModel = buildBmModel() val testCases = Table( - ("Temperature", "K1", "K2", "PTh Solution"), + ("Temperature", "K1", "K2", "PTh Sol"), (19.28, 1d, 1d, 5.62d), // independent of temp (30d, 2d, 3d, 33.72d), (19.2799999d, 1d, 1d, 5.6147201076d), // dependent on temp @@ -154,7 +154,7 @@ class BMModelSpec extends UnitSpec { val bmModel = buildBmModel() val testCases = Table( - ("Usage", "Efficiency Solution"), + ("Usage", "Efficiency Sol"), (1d, 1d), (0d, 0.724d), (0.75d, 0.98425d), @@ -170,7 +170,7 @@ class BMModelSpec extends UnitSpec { "calculate electrical output correctly" in { val testCases = Table( - ("FeedInTariff", "Usage", "Efficiency", "PEl Solution"), + ("FeedInTariff", "Usage", "Efficiency", "PEl Sol"), (0.051d, 1d, 1d, -190d), // tariff greater than opex => full power ( 0.04d, @@ -205,7 +205,7 @@ class BMModelSpec extends UnitSpec { val bmModel = buildBmModel() val testCases = Table( - ("Last Power", "PEl", "PEl Solution"), + ("Last Power", "PEl", "PEl Sol"), ( Kilowatts(-100d), // Last Power Kilowatts(-120d), // PEl From fd08c69c9a257f9ea674368e7e8dcb8f64e06496 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier <155652256+pierrepetersmeier@users.noreply.github.com> Date: Thu, 22 Aug 2024 19:28:19 +0200 Subject: [PATCH 4/7] Update src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala Co-authored-by: Daniel Feismann <98817556+danielfeismann@users.noreply.github.com> --- .../scala/edu/ie3/simona/model/participant/BMModelSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala index 12d3906425..0ad4858497 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala @@ -313,7 +313,7 @@ class BMModelSpec extends UnitSpec { powerSol: Power, ) => val dateTime = ZonedDateTime.parse(time) - val relevantData = new BMModel.BMCalcRelevantData(dateTime, temp) + val relevantData = BMCalcRelevantData(dateTime, temp) val bmModel = new BMModel( UUID.fromString("08a8134d-04b7-45de-a937-9a55fab4e1af"), From 1236ebc6f42d272c90d323fe180de996d3fd1f96 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier Date: Thu, 22 Aug 2024 21:02:13 +0200 Subject: [PATCH 5/7] Improve Code Quality and fix test "calculate P correctly" --- .../model/participant/BMModelSpec.scala | 69 +++++++------------ 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala index 0ad4858497..f4efa251d6 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala @@ -6,7 +6,6 @@ package edu.ie3.simona.model.participant -import edu.ie3.datamodel.models.input.system.`type`.BmTypeInput import edu.ie3.datamodel.models.input.system.characteristic.CosPhiFixed import edu.ie3.simona.model.participant.ModelState.ConstantState import edu.ie3.simona.model.participant.control.QControl @@ -17,7 +16,6 @@ import squants.energy.{Kilowatts, Megawatts} import squants.market.EUR import squants.thermal.Celsius import squants.{Power, Temperature} -import tech.units.indriya.quantity.Quantities import java.time.ZonedDateTime import java.util.UUID @@ -32,29 +30,8 @@ import java.util.UUID class BMModelSpec extends UnitSpec { implicit val powerTolerance: Power = Megawatts(1e-4) - implicit val power2Tolerance: Power = Kilowatts(1e-4) implicit val usageTolerance: Double = 1e-12 - val bmType: BmTypeInput = new BmTypeInput( - UUID.fromString("bc06e089-03cd-481e-9e28-228266a148a4"), - "BM Model Test Type 1", - Quantities.getQuantity(0, edu.ie3.util.quantities.PowerSystemUnits.EURO), - Quantities.getQuantity( - 0.05d, - edu.ie3.util.quantities.PowerSystemUnits.EURO_PER_KILOWATTHOUR, - ), - Quantities.getQuantity( - 5, - edu.ie3.util.quantities.PowerSystemUnits.PERCENT_PER_HOUR, - ), - Quantities.getQuantity( - 190, - edu.ie3.util.quantities.PowerSystemUnits.KILOVOLTAMPERE, - ), - 1d, - Quantities.getQuantity(100d, tech.units.indriya.unit.Units.PERCENT), - ) - def buildBmModel(): BMModel = { new BMModel( UUID.fromString("1b332f94-03e4-4abe-b142-8fceca689c53"), @@ -62,10 +39,10 @@ class BMModelSpec extends UnitSpec { OperationInterval(0L, 86400L), QControl(new CosPhiFixed("cosPhiFixed:{(0.0,1.0)}")), Kilowatts(190), - bmType.getCosPhiRated, + 1d, "MockNode", isCostControlled = true, - EUR(bmType.getOpex.getValue.doubleValue()), + EUR(0.05), EuroPerKilowatthour(0.51d), 0.05, ) @@ -129,7 +106,7 @@ class BMModelSpec extends UnitSpec { testCases.foreach { case (temp, k1, k2, pThSol) => val pThCalc = bmModel.calculatePTh(Celsius(temp), k1, k2) - pThCalc should approximate(Megawatts(pThSol))(powerTolerance) + pThCalc should approximate(Megawatts(pThSol)) } } @@ -176,7 +153,7 @@ class BMModelSpec extends UnitSpec { 0.04d, 0.75d, 0.98425d, - -140.255625d, + -140.25562499999998d, ), // tariff too little, only serve heat demand (0.04d, 1d, 1d, -190d), // tariff too little, but max heat demand ) @@ -188,10 +165,10 @@ class BMModelSpec extends UnitSpec { OperationInterval(0L, 86400L), QControl(new CosPhiFixed("cosPhiFixed:{(0.0,1.0)}")), Kilowatts(190), - bmType.getCosPhiRated, + 1d, "MockNode", isCostControlled = true, - EUR(bmType.getOpex.getValue.doubleValue()), + EUR(0.05), EuroPerKilowatthour(feedInTariff), 0.05, ) @@ -207,31 +184,31 @@ class BMModelSpec extends UnitSpec { val testCases = Table( ("Last Power", "PEl", "PEl Sol"), ( - Kilowatts(-100d), // Last Power - Kilowatts(-120d), // PEl - Kilowatts(-109.5d), // PEl Solution + Kilowatts(-100d), + Kilowatts(-120d), + Kilowatts(-109.5d), ), // increase of power, more than load gradient allows ( - Kilowatts(-50d), // Last Power - Kilowatts(-55d), // PEl - Kilowatts(-55d), // PEl Solution + Kilowatts(-50d), + Kilowatts(-55d), + Kilowatts(-55d), ), // increase, within load gradient ( - Kilowatts(-50d), // Last Power - Kilowatts(-41d), // PEl - Kilowatts(-41d), // PEl Solution + Kilowatts(-50d), + Kilowatts(-41d), + Kilowatts(-41d), ), // decrease, within load gradient ( - Kilowatts(-30d), // Last Power - Kilowatts(-15d), // PEl - Kilowatts(-20.5d), // PEl Solution + Kilowatts(-30d), + Kilowatts(-15d), + Kilowatts(-20.5d), ), // decrease, more than load gradient ) testCases.foreach { case (lastPower, pEl, pElSol) => bmModel._lastPower = Some(lastPower) val pElCalc = bmModel.applyLoadGradient(pEl) - pElCalc should approximate(pElSol)(power2Tolerance) + pElCalc should approximate(pElSol) } } @@ -313,7 +290,7 @@ class BMModelSpec extends UnitSpec { powerSol: Power, ) => val dateTime = ZonedDateTime.parse(time) - val relevantData = BMCalcRelevantData(dateTime, temp) + val relevantData = BMModel.BMCalcRelevantData(dateTime, Celsius(temp)) val bmModel = new BMModel( UUID.fromString("08a8134d-04b7-45de-a937-9a55fab4e1af"), @@ -321,10 +298,10 @@ class BMModelSpec extends UnitSpec { OperationInterval(0L, 86400L), QControl(new CosPhiFixed("cosPhiFixed:{(0.0,1.0)}")), Kilowatts(190), - bmType.getCosPhiRated(), + 1d, "MockNode", costControlled, - EUR(bmType.getOpex.getValue.doubleValue()), + EUR(0.05), EuroPerKilowatthour(0.51d), 0.05, ) @@ -332,7 +309,7 @@ class BMModelSpec extends UnitSpec { bmModel._lastPower = Some(lastPower) val powerCalc = bmModel.calculateActivePower(ConstantState, relevantData) - powerCalc should be(powerSol) + powerCalc.value should be(powerSol.value) } } } From df05751d7bc62ccdfdcee52324eb6f4c6af58279 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Fri, 23 Aug 2024 15:18:34 +0200 Subject: [PATCH 6/7] Removing table type specification Signed-off-by: Sebastian Peter --- .../model/participant/HpModelSpec.scala | 248 +++++++++--------- 1 file changed, 123 insertions(+), 125 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala index 3d5f97f29e..e99a5c8c26 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala @@ -18,7 +18,7 @@ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMin import edu.ie3.simona.test.common.UnitSpec import edu.ie3.simona.test.common.input.HpInputTestData import edu.ie3.util.scala.quantities.WattsPerKelvin -import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor3} +import org.scalatest.prop.TableDrivenPropertyChecks import squants.energy.{KilowattHours, Kilowatts, Watts} import squants.thermal.Celsius import squants.{Kelvin, Power, Temperature} @@ -234,168 +234,166 @@ class HpModelSpec "determining the flexibility options for different states" should { "deliver correct flexibility options" in { - val testCases - : TableFor3[ThermalGridState, HpState, (Double, Double, Double)] = - Table( - ("thermalState", "lastState", "expectedValues"), - // House is below lower temperature boundary - ( + val testCases = Table( + ("thermalState", "lastState", "expectedValues"), + // House is below lower temperature boundary + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(15), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), + ), + HpState( + isRunning = false, + 0, + Some(hpData.ambientTemperature), + Kilowatts(0.0), + Kilowatts(0.0), ThermalGridState( Some(ThermalHouseState(0L, Celsius(15), Kilowatts(0))), - Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), - ), - HpState( - isRunning = false, - 0, - Some(hpData.ambientTemperature), - Kilowatts(0.0), - Kilowatts(0.0), - ThermalGridState( - Some(ThermalHouseState(0L, Celsius(15), Kilowatts(0))), - Some( - ThermalStorageState(0L, KilowattHours(20), Kilowatts(0)) - ), + Some( + ThermalStorageState(0L, KilowattHours(20), Kilowatts(0)) ), - None, ), - (95.0, 95.0, 95.0), + None, ), - // House is between target temperature and lower temperature boundary, Hp actually running - ( + (95.0, 95.0, 95.0), + ), + // House is between target temperature and lower temperature boundary, Hp actually running + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(19), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), + ), + HpState( + isRunning = true, + 0, + Some(hpData.ambientTemperature), + Kilowatts(95.0), + Kilowatts(80.0), ThermalGridState( - Some(ThermalHouseState(0L, Celsius(19), Kilowatts(0))), - Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), - ), - HpState( - isRunning = true, - 0, - Some(hpData.ambientTemperature), - Kilowatts(95.0), - Kilowatts(80.0), - ThermalGridState( - Some(ThermalHouseState(0L, Celsius(19), Kilowatts(80))), - Some( - ThermalStorageState(0L, KilowattHours(20), Kilowatts(0)) - ), + Some(ThermalHouseState(0L, Celsius(19), Kilowatts(80))), + Some( + ThermalStorageState(0L, KilowattHours(20), Kilowatts(0)) ), - None, ), - (95.0, 0.0, 95.0), + None, ), + (95.0, 0.0, 95.0), + ), - // House is between target temperature and lower temperature boundary, Hp actually not running - ( + // House is between target temperature and lower temperature boundary, Hp actually not running + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(19), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), + ), + HpState( + isRunning = false, + 0, + Some(hpData.ambientTemperature), + Kilowatts(0.0), + Kilowatts(0.0), ThermalGridState( Some(ThermalHouseState(0L, Celsius(19), Kilowatts(0))), - Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), - ), - HpState( - isRunning = false, - 0, - Some(hpData.ambientTemperature), - Kilowatts(0.0), - Kilowatts(0.0), - ThermalGridState( - Some(ThermalHouseState(0L, Celsius(19), Kilowatts(0))), - Some( - ThermalStorageState(0L, KilowattHours(20), Kilowatts(0)) - ), + Some( + ThermalStorageState(0L, KilowattHours(20), Kilowatts(0)) ), - None, ), - (0.0, 0.0, 95.0), + None, + ), + (0.0, 0.0, 95.0), + ), + // Storage and house have remaining capacity + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(80))), + Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), ), - // Storage and house have remaining capacity - ( + HpState( + isRunning = true, + 0, + Some(hpData.ambientTemperature), + Kilowatts(95.0), + Kilowatts(80.0), ThermalGridState( Some(ThermalHouseState(0L, Celsius(21), Kilowatts(80))), Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), ), - HpState( - isRunning = true, - 0, - Some(hpData.ambientTemperature), - Kilowatts(95.0), - Kilowatts(80.0), - ThermalGridState( - Some(ThermalHouseState(0L, Celsius(21), Kilowatts(80))), - Some(ThermalStorageState(0L, KilowattHours(20), Kilowatts(0))), - ), - Some(HouseTemperatureUpperBoundaryReached(0L)), - ), - (95.0, 0.0, 95.0), + Some(HouseTemperatureUpperBoundaryReached(0L)), ), + (95.0, 0.0, 95.0), + ), - // Storage is full, House has capacity till upper boundary - ( + // Storage is full, House has capacity till upper boundary + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(21), Kilowatts(80))), + Some(ThermalStorageState(0L, KilowattHours(500), Kilowatts(0))), + ), + HpState( + isRunning = false, + 0, + Some(hpData.ambientTemperature), + Kilowatts(0.0), + Kilowatts(0.0), ThermalGridState( Some(ThermalHouseState(0L, Celsius(21), Kilowatts(80))), - Some(ThermalStorageState(0L, KilowattHours(500), Kilowatts(0))), - ), - HpState( - isRunning = false, - 0, - Some(hpData.ambientTemperature), - Kilowatts(0.0), - Kilowatts(0.0), - ThermalGridState( - Some(ThermalHouseState(0L, Celsius(21), Kilowatts(80))), - Some( - ThermalStorageState(0L, KilowattHours(500), Kilowatts(0)) - ), + Some( + ThermalStorageState(0L, KilowattHours(500), Kilowatts(0)) ), - Some(HouseTemperatureUpperBoundaryReached(0L)), ), - (0.0, 0.0, 95.0), + Some(HouseTemperatureUpperBoundaryReached(0L)), ), + (0.0, 0.0, 95.0), + ), - // No capacity for flexibility at all because house is - // at upperTempBoundary and storage is at max capacity - ( + // No capacity for flexibility at all because house is + // at upperTempBoundary and storage is at max capacity + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(22), Kilowatts(80))), + Some(ThermalStorageState(0L, KilowattHours(500), Kilowatts(0))), + ), + HpState( + isRunning = true, + 0, + Some(hpData.ambientTemperature), + Kilowatts(95.0), + Kilowatts(80.0), ThermalGridState( Some(ThermalHouseState(0L, Celsius(22), Kilowatts(80))), - Some(ThermalStorageState(0L, KilowattHours(500), Kilowatts(0))), - ), - HpState( - isRunning = true, - 0, - Some(hpData.ambientTemperature), - Kilowatts(95.0), - Kilowatts(80.0), - ThermalGridState( - Some(ThermalHouseState(0L, Celsius(22), Kilowatts(80))), - Some( - ThermalStorageState(0L, KilowattHours(500), Kilowatts(0)) - ), + Some( + ThermalStorageState(0L, KilowattHours(500), Kilowatts(0)) ), - Some(HouseTemperatureUpperBoundaryReached(0L)), ), - (0.0, 0.0, 0.0), + Some(HouseTemperatureUpperBoundaryReached(0L)), ), + (0.0, 0.0, 0.0), + ), - // No capacity for flexibility at all when storage is full and house has been (externally) heated up above upperTemperatureBoundary - ( + // No capacity for flexibility at all when storage is full and house has been (externally) heated up above upperTemperatureBoundary + ( + ThermalGridState( + Some(ThermalHouseState(0L, Celsius(25), Kilowatts(0))), + Some(ThermalStorageState(0L, KilowattHours(500), Kilowatts(0))), + ), + HpState( + isRunning = false, + 0, + Some(hpData.ambientTemperature), + Kilowatts(95.0), + Kilowatts(80.0), ThermalGridState( Some(ThermalHouseState(0L, Celsius(25), Kilowatts(0))), - Some(ThermalStorageState(0L, KilowattHours(500), Kilowatts(0))), - ), - HpState( - isRunning = false, - 0, - Some(hpData.ambientTemperature), - Kilowatts(95.0), - Kilowatts(80.0), - ThermalGridState( - Some(ThermalHouseState(0L, Celsius(25), Kilowatts(0))), - Some( - ThermalStorageState(0L, KilowattHours(500), Kilowatts(0)) - ), + Some( + ThermalStorageState(0L, KilowattHours(500), Kilowatts(0)) ), - None, ), - (0.0, 0.0, 0.0), + None, ), - ) + (0.0, 0.0, 0.0), + ), + ) // Run the test cases forAll(testCases) { From dfa3f2de96eed2de0e1c0e2a0dfaeb9149ef6134 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier Date: Sun, 25 Aug 2024 19:09:48 +0200 Subject: [PATCH 7/7] Fix test "calculate P correctly" --- .../model/participant/BMModelSpec.scala | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala index f4efa251d6..011dbb9567 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/BMModelSpec.scala @@ -29,7 +29,7 @@ import java.util.UUID class BMModelSpec extends UnitSpec { - implicit val powerTolerance: Power = Megawatts(1e-4) + implicit val powerTolerance: Power = Kilowatts(1e-4) implicit val usageTolerance: Double = 1e-12 def buildBmModel(): BMModel = { @@ -216,68 +216,77 @@ class BMModelSpec extends UnitSpec { val testCases = Table( ("time", "temp", "costControlled", "lastPower", "powerSol"), + // weekend day in heating season, power increase capped by load gradient ( "2019-01-05T05:15:00+01:00[Europe/Berlin]", - Celsius(10), + Celsius(10.0), true, Kilowatts(-40.0), Kilowatts(-49.5), ), + // working day in heating season, power decrease capped by load gradient ( "2019-01-04T05:15:00+01:00[Europe/Berlin]", - Celsius(10), + Celsius(10.0), true, Kilowatts(-80.0), - Kilowatts(-89.5), + Kilowatts(-70.5), ), + // peak load boiler activated, max output because cost < revenues ( "2019-01-04T05:15:00+01:00[Europe/Berlin]", - Celsius(-20), + Celsius(-20.0), true, Kilowatts(-182.0), - Kilowatts(-190), + Kilowatts(-190.0), ), + // close to peak load, max output because cost < revenues ( "2019-01-04T05:15:00+01:00[Europe/Berlin]", - Celsius(-7), + Celsius(-7.0), true, Kilowatts(-182.0), - Kilowatts(-190), + Kilowatts(-190.0), ), + // close to peak load, not cost controlled but just serving heat demand ( "2019-01-04T05:15:00+01:00[Europe/Berlin]", - Celsius(-7), + Celsius(-7.0), false, Kilowatts(-150.0), Kilowatts(-152.16900643778735), ), + // weekend day outside heating season, increase not capped ( "2019-07-07T10:15:00+02:00[Europe/Berlin]", - Celsius(19), + Celsius(19.0), true, Kilowatts(-10.0), - Kilowatts(-19.5), + Kilowatts(-12.099949463243976), ), + // working day outside heating season, decrease not capped ( "2019-07-05T05:15:00+02:00[Europe/Berlin]", - Celsius(20), + Celsius(20.0), true, Kilowatts(-20.0), - Kilowatts(-29.5), + Kilowatts(-11.70638561892377), ), + // weekend day outside heating season, increase capped ( "2019-07-06T10:15:00+02:00[Europe/Berlin]", - Celsius(20), + Celsius(20.0), true, Kilowatts(0.0), Kilowatts(-9.5), ), + // working day outside heating season, decrease capped ( "2019-07-05T05:15:00+02:00[Europe/Berlin]", - Celsius(22), + Celsius(22.0), true, Kilowatts(-22.0), - Kilowatts(-31.5), + Kilowatts(-12.5), ), ) @@ -302,14 +311,16 @@ class BMModelSpec extends UnitSpec { "MockNode", costControlled, EUR(0.05), - EuroPerKilowatthour(0.51d), + EuroPerKilowatthour(0.051d), 0.05, ) bmModel._lastPower = Some(lastPower) + val powerCalc = bmModel.calculateActivePower(ConstantState, relevantData) - powerCalc.value should be(powerSol.value) + + powerCalc should approximate(powerSol) } } }