From 17a42e4c9ae18b660eac93de5bd070d30a6f55ac Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 6 Aug 2024 13:48:19 +0200 Subject: [PATCH 01/13] Provide option to directly zip the output files --- CHANGELOG.md | 1 + docs/readthedocs/config.md | 4 +++ input/samples/vn_simona/vn_simona.conf | 1 + .../resources/config/config-template.conf | 1 + .../edu/ie3/simona/config/SimonaConfig.scala | 2 ++ .../ie3/simona/io/result/ResultSinkType.scala | 8 ++++- .../ie3/simona/main/RunSimonaStandalone.scala | 32 ++++++++++++++++++- .../sim/setup/SimonaStandaloneSetup.scala | 2 +- .../simona/config/ConfigFailFastSpec.scala | 2 +- .../simona/io/result/ResultSinkTypeSpec.scala | 5 ++- 10 files changed, 53 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 468de1d410..04c1f95aa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implementation of StorageAgent [#309](https://github.com/ie3-institute/simona/issues/309) - Enhanced Newton-Raphson-PowerFlow failures with more information [#815](https://github.com/ie3-institute/simona/issues/815) - Update RTD references and bibliography [#868](https://github.com/ie3-institute/simona/issues/868) +- Added option to directly zip the output files [#793](https://github.com/ie3-institute/simona/issues/793) ### Changed - Adapted to changed data source in PSDM [#435](https://github.com/ie3-institute/simona/issues/435) diff --git a/docs/readthedocs/config.md b/docs/readthedocs/config.md index ec8244a7e9..7b6e0927b2 100644 --- a/docs/readthedocs/config.md +++ b/docs/readthedocs/config.md @@ -94,9 +94,13 @@ simona.output.sink.csv { fileFormat = ".csv" filePrefix = "" fileSuffix = "" + zipFiles = false } ``` +While using a csv sink, the raw data output files can be zipped directly when `zipFiles = true` is used. + + #### Output configuration of the grid The grid output configuration defines for which grid components simulation values are to be output. diff --git a/input/samples/vn_simona/vn_simona.conf b/input/samples/vn_simona/vn_simona.conf index 9d7b4e4c39..5c3d5fc43d 100644 --- a/input/samples/vn_simona/vn_simona.conf +++ b/input/samples/vn_simona/vn_simona.conf @@ -51,6 +51,7 @@ simona.output.sink.csv { fileFormat = ".csv" filePrefix = "" fileSuffix = "" + zipFiles = false } simona.output.grid = { diff --git a/src/main/resources/config/config-template.conf b/src/main/resources/config/config-template.conf index 0b636a5d50..97deefbd79 100644 --- a/src/main/resources/config/config-template.conf +++ b/src/main/resources/config/config-template.conf @@ -269,6 +269,7 @@ simona.output.sink.csv { isHierarchic = Boolean | false filePrefix = "" fileSuffix = "" + zipFiles = "Boolean" | false } #@optional simona.output.sink.influxDb1x { diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index d8ec6729ef..947570eb4c 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -2054,6 +2054,7 @@ object SimonaConfig { filePrefix: java.lang.String, fileSuffix: java.lang.String, isHierarchic: scala.Boolean, + zipFiles: scala.Boolean, ) object Csv { def apply( @@ -2073,6 +2074,7 @@ object SimonaConfig { else "", isHierarchic = c.hasPathOrNull("isHierarchic") && c.getBoolean("isHierarchic"), + zipFiles = c.hasPathOrNull("zipFiles") && c.getBoolean("zipFiles"), ) } } diff --git a/src/main/scala/edu/ie3/simona/io/result/ResultSinkType.scala b/src/main/scala/edu/ie3/simona/io/result/ResultSinkType.scala index 3510810062..16f76a84f0 100644 --- a/src/main/scala/edu/ie3/simona/io/result/ResultSinkType.scala +++ b/src/main/scala/edu/ie3/simona/io/result/ResultSinkType.scala @@ -21,6 +21,7 @@ object ResultSinkType { fileFormat: String = ".csv", filePrefix: String = "", fileSuffix: String = "", + zipFiles: Boolean = false, ) extends ResultSinkType final case class InfluxDb1x(url: String, database: String, scenario: String) @@ -48,7 +49,12 @@ object ResultSinkType { sink.headOption match { case Some(params: SimonaConfig.Simona.Output.Sink.Csv) => - Csv(params.fileFormat, params.filePrefix, params.fileSuffix) + Csv( + params.fileFormat, + params.filePrefix, + params.fileSuffix, + params.zipFiles, + ) case Some(params: SimonaConfig.Simona.Output.Sink.InfluxDb1x) => InfluxDb1x(buildInfluxDb1xUrl(params), params.database, runName) case Some(params: SimonaConfig.ResultKafkaParams) => diff --git a/src/main/scala/edu/ie3/simona/main/RunSimonaStandalone.scala b/src/main/scala/edu/ie3/simona/main/RunSimonaStandalone.scala index d0a50802b8..3a75dce7fe 100644 --- a/src/main/scala/edu/ie3/simona/main/RunSimonaStandalone.scala +++ b/src/main/scala/edu/ie3/simona/main/RunSimonaStandalone.scala @@ -10,12 +10,17 @@ import edu.ie3.simona.config.{ArgsParser, ConfigFailFast, SimonaConfig} import edu.ie3.simona.main.RunSimona._ import edu.ie3.simona.sim.SimonaSim import edu.ie3.simona.sim.setup.SimonaStandaloneSetup +import edu.ie3.util.io.FileIOUtils import org.apache.pekko.actor.typed.scaladsl.AskPattern._ import org.apache.pekko.actor.typed.{ActorSystem, Scheduler} import org.apache.pekko.util.Timeout +import java.nio.file.Path import scala.concurrent.Await +import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration.DurationInt +import scala.jdk.FutureConverters.CompletionStageOps +import scala.util.{Failure, Success} /** Run a standalone simulation of simona * @@ -56,9 +61,34 @@ object RunSimonaStandalone extends RunSimona[SimonaStandaloneSetup] { case SimonaEnded(successful) => simonaSim.terminate() + val config = SimonaConfig(simonaSetup.typeSafeConfig).simona.output + + config.sink.csv.map(_.zipFiles).map { zipFiles => + if (zipFiles) { + val rawOutputPath = + Path.of(simonaSetup.resultFileHierarchy.rawOutputDataDir) + val archiveName = "rawOutputData.tar.gz" + val archivePath = rawOutputPath.getParent.resolve(archiveName) + + logger.info(s"Compressing raw output data to: `$archiveName`.") + + val compressFuture = + FileIOUtils.compressDir(rawOutputPath, archivePath).asScala + compressFuture.onComplete { + case Success(_) => + FileIOUtils.deleteRecursively(rawOutputPath) + case Failure(exception) => + logger.error( + s"Compression of output files to '$archivePath' has failed. Keep raw data.", + exception, + ) + } + Await.ready(compressFuture, 5.minutes) + } + } + successful } - } } diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index 32711a4a3d..abc64f5ee3 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -63,7 +63,7 @@ import scala.jdk.CollectionConverters._ class SimonaStandaloneSetup( val typeSafeConfig: Config, simonaConfig: SimonaConfig, - resultFileHierarchy: ResultFileHierarchy, + val resultFileHierarchy: ResultFileHierarchy, runtimeEventQueue: Option[LinkedBlockingQueue[RuntimeEvent]] = None, override val args: Array[String], ) extends SimonaSetup { diff --git a/src/test/scala/edu/ie3/simona/config/ConfigFailFastSpec.scala b/src/test/scala/edu/ie3/simona/config/ConfigFailFastSpec.scala index c38ef668f1..9a90f8f053 100644 --- a/src/test/scala/edu/ie3/simona/config/ConfigFailFastSpec.scala +++ b/src/test/scala/edu/ie3/simona/config/ConfigFailFastSpec.scala @@ -856,7 +856,7 @@ class ConfigFailFastSpec extends UnitSpec with ConfigTestData { intercept[InvalidConfigParameterException] { ConfigFailFast invokePrivate checkDataSink( Sink( - Some(Csv("", "", "", isHierarchic = false)), + Some(Csv("", "", "", isHierarchic = false, zipFiles = false)), Some(InfluxDb1x("", 0, "")), None, ) diff --git a/src/test/scala/edu/ie3/simona/io/result/ResultSinkTypeSpec.scala b/src/test/scala/edu/ie3/simona/io/result/ResultSinkTypeSpec.scala index 2c93aced13..389eba443f 100644 --- a/src/test/scala/edu/ie3/simona/io/result/ResultSinkTypeSpec.scala +++ b/src/test/scala/edu/ie3/simona/io/result/ResultSinkTypeSpec.scala @@ -23,6 +23,7 @@ class ResultSinkTypeSpec extends UnitSpec { filePrefix = "", fileSuffix = "", isHierarchic = false, + zipFiles = false, ) ), influxDb1x = None, @@ -30,10 +31,11 @@ class ResultSinkTypeSpec extends UnitSpec { ) inside(ResultSinkType(conf, "testRun")) { - case Csv(fileFormat, filePrefix, fileSuffix) => + case Csv(fileFormat, filePrefix, fileSuffix, zipFiles) => fileFormat shouldBe conf.csv.value.fileFormat filePrefix shouldBe conf.csv.value.filePrefix fileSuffix shouldBe conf.csv.value.fileSuffix + zipFiles shouldBe conf.csv.value.zipFiles case _ => fail("Wrong ResultSinkType got instantiated.") } @@ -105,6 +107,7 @@ class ResultSinkTypeSpec extends UnitSpec { filePrefix = "", fileSuffix = "", isHierarchic = false, + zipFiles = false, ) ), influxDb1x = Some( From 3275ac3c01f66ed90c94e6ffaae5a215d0bf893a Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 7 Aug 2024 15:47:24 +0200 Subject: [PATCH 02/13] Compressing single files instead of folder. --- .../ie3/simona/main/RunSimonaStandalone.scala | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/main/RunSimonaStandalone.scala b/src/main/scala/edu/ie3/simona/main/RunSimonaStandalone.scala index 3a75dce7fe..2a0d3d83af 100644 --- a/src/main/scala/edu/ie3/simona/main/RunSimonaStandalone.scala +++ b/src/main/scala/edu/ie3/simona/main/RunSimonaStandalone.scala @@ -63,27 +63,31 @@ object RunSimonaStandalone extends RunSimona[SimonaStandaloneSetup] { val config = SimonaConfig(simonaSetup.typeSafeConfig).simona.output - config.sink.csv.map(_.zipFiles).map { zipFiles => + config.sink.csv.map(_.zipFiles).foreach { zipFiles => if (zipFiles) { val rawOutputPath = Path.of(simonaSetup.resultFileHierarchy.rawOutputDataDir) - val archiveName = "rawOutputData.tar.gz" - val archivePath = rawOutputPath.getParent.resolve(archiveName) - - logger.info(s"Compressing raw output data to: `$archiveName`.") - - val compressFuture = - FileIOUtils.compressDir(rawOutputPath, archivePath).asScala - compressFuture.onComplete { - case Success(_) => - FileIOUtils.deleteRecursively(rawOutputPath) - case Failure(exception) => - logger.error( - s"Compression of output files to '$archivePath' has failed. Keep raw data.", - exception, - ) + + rawOutputPath.toFile.listFiles().foreach { file => + val fileName = file.getName + val archiveName = fileName.replace(".csv", "") + val filePath = rawOutputPath.resolve(fileName) + + val compressFuture = + FileIOUtils + .compressFile(filePath, rawOutputPath.resolve(archiveName)) + .asScala + compressFuture.onComplete { + case Success(_) => + FileIOUtils.deleteRecursively(filePath) + case Failure(exception) => + logger.error( + s"Compression of output file to '$archiveName' has failed. Keep raw data.", + exception, + ) + } + Await.ready(compressFuture, 5.minutes) } - Await.ready(compressFuture, 5.minutes) } } From 7a49690eb95a81a56bd6b5fbfbebc7e3f0d3cac4 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier Date: Mon, 16 Sep 2024 13:23:06 +0200 Subject: [PATCH 03/13] Improve code quality in fixedloadmodelspec and other tests --- CHANGELOG.md | 1 + .../simona/model/grid/SystemComponentSpec.scala | 5 +---- .../simona/model/participant/WecModelSpec.scala | 15 ++++++++------- .../participant/load/FixedLoadModelSpec.scala | 10 ++++------ .../simona/model/thermal/ThermalHouseSpec.scala | 2 +- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ff86ccca2..f305e7ee61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed FixedFeedModelSpec [#861](https://github.com/ie3-institute/simona/issues/861) - Fixing duration calculation in result events [#801](https://github.com/ie3-institute/simona/issues/801) - Handle MobSim requests for current prices [#892](https://github.com/ie3-institute/simona/issues/892) +- Improve code quality in fixedloadmodelspec and other tests [#919](https://github.com/ie3-institute/simona/issues/919) ## [3.0.0] - 2023-08-07 diff --git a/src/test/scala/edu/ie3/simona/model/grid/SystemComponentSpec.scala b/src/test/scala/edu/ie3/simona/model/grid/SystemComponentSpec.scala index dce98587ad..6149b6d014 100644 --- a/src/test/scala/edu/ie3/simona/model/grid/SystemComponentSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/grid/SystemComponentSpec.scala @@ -103,12 +103,10 @@ class SystemComponentSpec extends UnitSpec with DefaultTestData { ), ) - for ((operationStart, operationEnd, expected) <- testCases) { + testCases.foreach { case (operationStart, operationEnd, expected) => val operationTimeBuilder = setup() - operationStart.foreach(operationTimeBuilder.withStart) operationEnd.foreach(operationTimeBuilder.withEnd) - val operationTime: OperationTime = operationTimeBuilder.build() val interval: OperationInterval = @@ -117,7 +115,6 @@ class SystemComponentSpec extends UnitSpec with DefaultTestData { simulationEnd, operationTime, ) - interval should be(expected) } } diff --git a/src/test/scala/edu/ie3/simona/model/participant/WecModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/WecModelSpec.scala index 8933819773..4e7ab8e76f 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/WecModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/WecModelSpec.scala @@ -126,7 +126,7 @@ class WecModelSpec extends UnitSpec with DefaultTestData { -24573.39638823692, 0) velocities.zip(expectedPowers).foreach { case (velocity, power) => - val wecData = new WecRelevantData( + val wecData = WecRelevantData( MetersPerSecond(velocity), Celsius(20), Some(Pascals(101325d)), @@ -174,17 +174,18 @@ class WecModelSpec extends UnitSpec with DefaultTestData { val expectedPowers = Seq(-23377.23862017266, -24573.41320418286, -29029.60338829823) - temperatures.zip(expectedPowers).foreach { case (temperature, power) => - val wecData = new WecRelevantData( + for (i <- temperatures.indices) { + val temperature = temperatures(i) + val expectedPower = expectedPowers(i) + val wecData = WecRelevantData( MetersPerSecond(3.0), Celsius(temperature), Some(Pascals(101325d)), ) - val result = { + val result = wecModel.calculateActivePower(ModelState.ConstantState, wecData) - } - val expectedPower = Watts(power) - result shouldBe expectedPower + val expectedPowerInWatts = Watts(expectedPower) + result shouldBe expectedPowerInWatts } } } diff --git a/src/test/scala/edu/ie3/simona/model/participant/load/FixedLoadModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/load/FixedLoadModelSpec.scala index 98fd066841..d3492ea6f2 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/load/FixedLoadModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/load/FixedLoadModelSpec.scala @@ -94,13 +94,12 @@ class FixedLoadModelSpec reference, ) - for (_ <- 0 until 10000) { + (1 to 10000).foreach { _ => val calculatedPower = dut .calculateActivePower( ModelState.ConstantState, FixedLoadModel.FixedLoadRelevantData, ) - calculatedPower should approximate(expectedPower) } } @@ -116,8 +115,9 @@ class FixedLoadModelSpec forAll(testData) { (reference, expectedPower: Power) => val relevantData = FixedLoadModel.FixedLoadRelevantData - var scale = 0.0 - while (scale <= 2) { + val scales = (0 to 20).map(_ * 0.1) + + scales.foreach { scale => val scaledSRated = Kilowatts( loadInput.getsRated .to(PowerSystemUnits.KILOWATT) @@ -142,8 +142,6 @@ class FixedLoadModelSpec val expectedScaledPower = expectedPower * scale calculatedPower should approximate(expectedScaledPower) - - scale += 0.1 } } } diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala index 56e1cac144..b084257b83 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala @@ -36,7 +36,7 @@ class ThermalHouseSpec extends UnitSpec with HpInputTestData { (23d, true, false), ) - testCases.foreach { case (innerTemperature, isTooHigh, isTooLow) => + forAll(testCases) { (innerTemperature: Double, isTooHigh: Boolean, isTooLow: Boolean) => val innerTemp = Temperature(innerTemperature, Celsius) val isHigher = thermalHouseTest.isInnerTemperatureTooHigh(innerTemp) val isLower = thermalHouseTest.isInnerTemperatureTooLow(innerTemp) From e3bce9ad1eddced7ceae5ad736be5ca88df59675 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier Date: Mon, 16 Sep 2024 13:23:59 +0200 Subject: [PATCH 04/13] fmt --- .../simona/model/thermal/ThermalHouseSpec.scala | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala index b084257b83..944889abe2 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalHouseSpec.scala @@ -36,13 +36,14 @@ class ThermalHouseSpec extends UnitSpec with HpInputTestData { (23d, true, false), ) - forAll(testCases) { (innerTemperature: Double, isTooHigh: Boolean, isTooLow: Boolean) => - val innerTemp = Temperature(innerTemperature, Celsius) - val isHigher = thermalHouseTest.isInnerTemperatureTooHigh(innerTemp) - val isLower = thermalHouseTest.isInnerTemperatureTooLow(innerTemp) - - isHigher shouldBe isTooHigh - isLower shouldBe isTooLow + forAll(testCases) { + (innerTemperature: Double, isTooHigh: Boolean, isTooLow: Boolean) => + val innerTemp = Temperature(innerTemperature, Celsius) + val isHigher = thermalHouseTest.isInnerTemperatureTooHigh(innerTemp) + val isLower = thermalHouseTest.isInnerTemperatureTooLow(innerTemp) + + isHigher shouldBe isTooHigh + isLower shouldBe isTooLow } } From 1021c287ade7972244651139bfff044910c35d94 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier Date: Tue, 17 Sep 2024 17:40:14 +0200 Subject: [PATCH 05/13] Code improved --- .../model/grid/SystemComponentSpec.scala | 6 +-- .../model/participant/WecModelSpec.scala | 42 ++++++++++++------- .../participant/load/FixedLoadModelSpec.scala | 3 +- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/model/grid/SystemComponentSpec.scala b/src/test/scala/edu/ie3/simona/model/grid/SystemComponentSpec.scala index 6149b6d014..99505d928e 100644 --- a/src/test/scala/edu/ie3/simona/model/grid/SystemComponentSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/grid/SystemComponentSpec.scala @@ -103,7 +103,7 @@ class SystemComponentSpec extends UnitSpec with DefaultTestData { ), ) - testCases.foreach { case (operationStart, operationEnd, expected) => + testCases.forall { case (operationStart, operationEnd, expected) => val operationTimeBuilder = setup() operationStart.foreach(operationTimeBuilder.withStart) operationEnd.foreach(operationTimeBuilder.withEnd) @@ -115,8 +115,8 @@ class SystemComponentSpec extends UnitSpec with DefaultTestData { simulationEnd, operationTime, ) - interval should be(expected) - } + interval == expected + } shouldBe true } "reject an operation end that is before the operation start" in { diff --git a/src/test/scala/edu/ie3/simona/model/participant/WecModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/WecModelSpec.scala index 4e7ab8e76f..04efdbec59 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/WecModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/WecModelSpec.scala @@ -118,14 +118,23 @@ class WecModelSpec extends UnitSpec with DefaultTestData { "calculate active power output depending on velocity" in { val wecModel = buildWecModel() - val velocities = - Seq(1.0, 2.0, 3.0, 7.0, 9.0, 13.0, 15.0, 19.0, 23.0, 27.0, 34.0, 40.0) - val expectedPowers = - Seq(0, -2948.8095851378266, -24573.41320418286, -522922.2325710509, - -1140000, -1140000, -1140000, -1140000, -1140000, -1140000, - -24573.39638823692, 0) - - velocities.zip(expectedPowers).foreach { case (velocity, power) => + val testCases = Table( + ("velocity", "expectedPower"), + (1.0, 0.0), + (2.0, -2948.8095851378266), + (3.0, -24573.41320418286), + (7.0, -522922.2325710509), + (9.0, -1140000.0), + (13.0, -1140000.0), + (15.0, -1140000.0), + (19.0, -1140000.0), + (23.0, -1140000.0), + (27.0, -1140000.0), + (34.0, -24573.39638823692), + (40.0, 0.0), + ) + + forAll(testCases) { (velocity: Double, expectedPower: Double) => val wecData = WecRelevantData( MetersPerSecond(velocity), Celsius(20), @@ -133,9 +142,9 @@ class WecModelSpec extends UnitSpec with DefaultTestData { ) val result = wecModel.calculateActivePower(ModelState.ConstantState, wecData) - val expectedPower = Watts(power) + val expectedPowerInWatts = Watts(expectedPower) - result should be(expectedPower) + result should be(expectedPowerInWatts) } } @@ -170,13 +179,14 @@ class WecModelSpec extends UnitSpec with DefaultTestData { "calculate active power output depending on temperature" in { val wecModel = buildWecModel() - val temperatures = Seq(35.0, 20.0, -25.0) - val expectedPowers = - Seq(-23377.23862017266, -24573.41320418286, -29029.60338829823) + val testCases = Table( + ("temperature", "expectedPower"), + (35.0, -23377.23862017266), + (20.0, -24573.41320418286), + (-25.0, -29029.60338829823), + ) - for (i <- temperatures.indices) { - val temperature = temperatures(i) - val expectedPower = expectedPowers(i) + forAll(testCases) { (temperature: Double, expectedPower: Double) => val wecData = WecRelevantData( MetersPerSecond(3.0), Celsius(temperature), diff --git a/src/test/scala/edu/ie3/simona/model/participant/load/FixedLoadModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/load/FixedLoadModelSpec.scala index d3492ea6f2..1682d21b0c 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/load/FixedLoadModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/load/FixedLoadModelSpec.scala @@ -115,7 +115,8 @@ class FixedLoadModelSpec forAll(testData) { (reference, expectedPower: Power) => val relevantData = FixedLoadModel.FixedLoadRelevantData - val scales = (0 to 20).map(_ * 0.1) + val scales: LazyList[Double] = + LazyList.iterate(0.0)(_ + 0.1).takeWhile(_ <= 2.0) scales.foreach { scale => val scaledSRated = Kilowatts( From 0d6b21655b7668cbf927c8249322ba8c57864cc1 Mon Sep 17 00:00:00 2001 From: pierrepetersmeier Date: Thu, 19 Sep 2024 00:02:34 +0200 Subject: [PATCH 06/13] Use forall(testCases) --- .../model/grid/SystemComponentSpec.scala | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/model/grid/SystemComponentSpec.scala b/src/test/scala/edu/ie3/simona/model/grid/SystemComponentSpec.scala index 99505d928e..098282e388 100644 --- a/src/test/scala/edu/ie3/simona/model/grid/SystemComponentSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/grid/SystemComponentSpec.scala @@ -75,7 +75,8 @@ class SystemComponentSpec extends UnitSpec with DefaultTestData { val simulationEnd: ZonedDateTime = TimeUtil.withDefaults.toZonedDateTime("2019-01-02T00:00:00Z") - val testCases = Seq( + val testCases = Table( + ("operationStart", "operationEnd", "expectedInterval"), ( Some(TimeUtil.withDefaults.toZonedDateTime("2019-01-01T00:00:00Z")), Some(TimeUtil.withDefaults.toZonedDateTime("2019-01-02T00:00:00Z")), @@ -103,20 +104,24 @@ class SystemComponentSpec extends UnitSpec with DefaultTestData { ), ) - testCases.forall { case (operationStart, operationEnd, expected) => - val operationTimeBuilder = setup() - operationStart.foreach(operationTimeBuilder.withStart) - operationEnd.foreach(operationTimeBuilder.withEnd) - val operationTime: OperationTime = operationTimeBuilder.build() - - val interval: OperationInterval = - SystemComponent.determineOperationInterval( - defaultSimulationStart, - simulationEnd, - operationTime, - ) - interval == expected - } shouldBe true + forAll(testCases) { + ( + operationStart: Option[ZonedDateTime], + operationEnd: Option[ZonedDateTime], + expected: OperationInterval, + ) => + val operationTimeBuilder = setup() + operationStart.foreach(operationTimeBuilder.withStart) + operationEnd.foreach(operationTimeBuilder.withEnd) + val operationTime: OperationTime = operationTimeBuilder.build() + val interval: OperationInterval = + SystemComponent.determineOperationInterval( + defaultSimulationStart, + simulationEnd, + operationTime, + ) + interval should be(expected) + } } "reject an operation end that is before the operation start" in { From f73b11f325bb793167059e0001acfb7395dcdb31 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Thu, 19 Sep 2024 10:27:09 +0200 Subject: [PATCH 07/13] Adapted one more test in WecModelSpec Signed-off-by: Sebastian Peter --- .../model/participant/WecModelSpec.scala | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/model/participant/WecModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/WecModelSpec.scala index 04efdbec59..82f69d3b0d 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/WecModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/WecModelSpec.scala @@ -104,15 +104,22 @@ class WecModelSpec extends UnitSpec with DefaultTestData { "determine Betz coefficient correctly" in { val wecModel = buildWecModel() - val velocities = Seq(2.0, 2.5, 18.0, 27.0, 34.0, 40.0) - val expectedBetzResults = Seq(0.115933516, 0.2010945555, 0.108671106, - 0.032198846, 0.000196644, 0.0) - velocities.zip(expectedBetzResults).foreach { - case (velocity, betzResult) => - val windVel = MetersPerSecond(velocity) - val betzFactor = wecModel.determineBetzCoefficient(windVel) - val expected = Each(betzResult) - betzFactor shouldEqual expected + + val testCases = Table( + ("velocity", "expectedBetzResult"), + (2.0, 0.115933516), + (2.5, 0.2010945555), + (18.0, 0.108671106), + (27.0, 0.032198846), + (34.0, 0.000196644), + (40.0, 0.0), + ) + + forAll(testCases) { case (velocity: Double, expectedBetzResult: Double) => + val windVel = MetersPerSecond(velocity) + val betzFactor = wecModel.determineBetzCoefficient(windVel) + + betzFactor shouldEqual Each(expectedBetzResult) } } From 20ee6e35d2043ad531de371711d73a26630ddbdf Mon Sep 17 00:00:00 2001 From: Johannes Bao Date: Thu, 19 Sep 2024 14:26:34 +0200 Subject: [PATCH 08/13] fix power flow --- .../simona/agent/participant/ParticipantAgentFundamentals.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala index 0b0ad206e0..984815a846 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala @@ -808,6 +808,7 @@ protected trait ParticipantAgentFundamentals[ nextActivation, ) + unstashAll() stay() using stateDataFinal } From f7dc403b198ca38b25f59009fb7dae1f384d3032 Mon Sep 17 00:00:00 2001 From: Johannes Bao Date: Thu, 19 Sep 2024 14:36:00 +0200 Subject: [PATCH 09/13] added changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e5d0b8a71..5faecc0281 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,6 +111,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix activation of Hp when not under control of an EM [#922](https://github.com/ie3-institute/simona/issues/922) - Fix expected secondaryData in baseStateData [#955](https://github.com/ie3-institute/simona/issues/955) - Improve code quality in fixedloadmodelspec and other tests [#919](https://github.com/ie3-institute/simona/issues/919) +- Fix power flow calculation with em agents [#962](https://github.com/ie3-institute/simona/issues/962) ## [3.0.0] - 2023-08-07 From ff01e18e21a1d31ca54249b97a974ac959e899d9 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 20 Sep 2024 09:38:40 +0200 Subject: [PATCH 10/13] Increasing the compression timeout duration to 15 minutes. --- src/main/scala/edu/ie3/simona/main/RunSimonaStandalone.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/main/RunSimonaStandalone.scala b/src/main/scala/edu/ie3/simona/main/RunSimonaStandalone.scala index 2a0d3d83af..971d403aaf 100644 --- a/src/main/scala/edu/ie3/simona/main/RunSimonaStandalone.scala +++ b/src/main/scala/edu/ie3/simona/main/RunSimonaStandalone.scala @@ -18,7 +18,7 @@ import org.apache.pekko.util.Timeout import java.nio.file.Path import scala.concurrent.Await import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration.DurationInt +import scala.concurrent.duration.{Duration, DurationInt} import scala.jdk.FutureConverters.CompletionStageOps import scala.util.{Failure, Success} @@ -29,6 +29,7 @@ import scala.util.{Failure, Success} object RunSimonaStandalone extends RunSimona[SimonaStandaloneSetup] { override implicit val timeout: Timeout = Timeout(12.hours) + implicit val compressTimeoutDuration: Duration = 15.minutes override def setup(args: Array[String]): SimonaStandaloneSetup = { // get the config and prepare it with the provided args @@ -86,7 +87,7 @@ object RunSimonaStandalone extends RunSimona[SimonaStandaloneSetup] { exception, ) } - Await.ready(compressFuture, 5.minutes) + Await.ready(compressFuture, compressTimeoutDuration) } } } From 6f5d6ac5ff8c40b511a52e2c39580930413f34be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 06:59:14 +0000 Subject: [PATCH 11/13] Bump org.apache.commons:commons-csv from 1.11.0 to 1.12.0 (#970) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b47bc21820..c95d8e7366 100644 --- a/build.gradle +++ b/build.gradle @@ -149,7 +149,7 @@ dependencies { implementation 'javax.measure:unit-api:2.2' implementation 'tech.units:indriya:2.2' // quantities implementation "org.typelevel:squants_${scalaVersion}:1.8.3" - implementation 'org.apache.commons:commons-csv:1.11.0' + implementation 'org.apache.commons:commons-csv:1.12.0' implementation 'org.scalanlp:breeze_2.13:2.1.0' // scientific calculations (http://www.scalanlp.org/) implementation 'de.lmu.ifi.dbs.elki:elki:0.7.5' // Statistics (for random load model) implementation 'org.jgrapht:jgrapht-core:1.5.2' From c6e546f3ba3e72c169249215872bf5da87c26eba Mon Sep 17 00:00:00 2001 From: Daniel Feismann <98817556+danielfeismann@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:14:02 +0200 Subject: [PATCH 12/13] WeatherData HowTo for SIMONA with Copernicus ERA5 data at SIMONA RTD (#968) WeatherData HowTo for SIMONA with Copernicus ERA5 data at SIMONA RTD --- CHANGELOG.md | 1 + .../_static/bibliography/bibtexAll.bib | 6 ++++ .../howto/weatherDataHowToCopernicusERA5.md | 34 +++++++++++++++++++ docs/readthedocs/usersguide.md | 7 ++++ 4 files changed, 48 insertions(+) create mode 100644 docs/readthedocs/howto/weatherDataHowToCopernicusERA5.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f51755ec61..eb39c77589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Additional tests to check flexibility options of thermal house and storage [#729](https://github.com/ie3-institute/simona/issues/729) - EmAgents should be able to handle initialization [#945](https://github.com/ie3-institute/simona/issues/945) - Added option to directly zip the output files [#793](https://github.com/ie3-institute/simona/issues/793) +- Added weatherData HowTo for Copernicus ERA5 data [#967](https://github.com/ie3-institute/simona/issues/967) ### Changed - Adapted to changed data source in PSDM [#435](https://github.com/ie3-institute/simona/issues/435) diff --git a/docs/readthedocs/_static/bibliography/bibtexAll.bib b/docs/readthedocs/_static/bibliography/bibtexAll.bib index e3a6a3f045..23ecc48185 100644 --- a/docs/readthedocs/_static/bibliography/bibtexAll.bib +++ b/docs/readthedocs/_static/bibliography/bibtexAll.bib @@ -189,4 +189,10 @@ @Book{Kittl_2022 address = {Düren}, year = {2022}, doi = {10.17877/DE290R-22548} +} + +@MISC{Radiation_ECMWF, + author = {Robin Hogan}, + title = {Radiation Quantities in the ECMWF model and MARS}, + howpublished = {\url{https://www.ecmwf.int/sites/default/files/elibrary/2015/18490-radiation-quantities-ecmwf-model-and-mars.pdf}} } \ No newline at end of file diff --git a/docs/readthedocs/howto/weatherDataHowToCopernicusERA5.md b/docs/readthedocs/howto/weatherDataHowToCopernicusERA5.md new file mode 100644 index 0000000000..18594a0bba --- /dev/null +++ b/docs/readthedocs/howto/weatherDataHowToCopernicusERA5.md @@ -0,0 +1,34 @@ +(weatherDataHowToCopernicusERA5)= + +# How To use Copernicus ERA5 weather data in SIMONA + +To use weather data from the past within SIMONA we recommend to use the dataset [ERA5 hourly data on single levels from 1940 to present](https://cds-beta.climate.copernicus.eu/datasets/reanalysis-era5-single-levels?tab=download) of [Copernicus Climate Data Store](https://cds-beta.climate.copernicus.eu/). + +The following data parameter should be used + +- Wind + - 100m u-component of wind + - 100m v-component of wind +- Radiation + - Total sky direct solar radiation at surface (FDIR) + - Surface solar radiation downwards (SSRD) +- Temperature + - 2m temperature + +Since SIMONAs [PV Model](pv_model) requires direct and diffuse solar radiation, the diffuse solar radiation need to be determined from the ERA5 data. + +## Pre-Processing solar radiation weather data + +To obtain diffuse solar radiation data from ERA5 weather data, the necessary diffuse solar radiation (FDIFF) at surface can be calculated by + +$$ + FDIFF = SSRD - FDIR +$$ + +*with*\ +**SSRD** = Surface solar radiation downwards\ +**FDIR** = Total sky direct solar radiation at surface + + +**References:** +* {cite:cts}`Radiation_ECMWF` diff --git a/docs/readthedocs/usersguide.md b/docs/readthedocs/usersguide.md index 9bd66ac3c9..5b75b15ac7 100644 --- a/docs/readthedocs/usersguide.md +++ b/docs/readthedocs/usersguide.md @@ -119,6 +119,13 @@ Besides a configuration and the actual grid and grid participants, SIMONA also e There is an option to use sample weather data, but if you want sensible results, definitely consider supplying suitable data. Information on the expected data format and different supported sources are given in the input parameters section of the {doc}`config` file. +The following How-To's are available: +```{toctree} +--- +maxdepth: 1 +--- +howto/weatherDataHowToCopernicusERA5 +``` ## Simulation Outputs From a8be401b3438b2b67c8b5b898cf9672896271ba2 Mon Sep 17 00:00:00 2001 From: Daniel Feismann <98817556+danielfeismann@users.noreply.github.com> Date: Thu, 26 Sep 2024 12:16:26 +0200 Subject: [PATCH 13/13] Fix scheduling at evcs with more than one ev at a time without em (#788) * fix exception message * determine evcs results also in case of arriving evs to ensure correct result entries * add additionalActivationTick for the end of simulation to get result entries for evcs * changelog * add test case for charging three evs at same time and deliver proper results for it * remove unnecessary change * fix results for evs * fmt * groupby * newActiveEntries at EvcsModel * update stayingSchedules at EvcsAgentFundamentals * update stayingSchedules at EvcsAgentFundamentals * add tickStart to filter condition * fix test condition * remove unused method * revert change * reintroduce explaining comment * rollback startingSchedules * reintroduce new determination of schedules * test for provide correct results for three evs charging at same time at EvcsAgent * fix naming to comply with codacy * fmt * revert test case result * fmt * fmt * fix comment * explanatory comments * sync tests at EvcsAgentModelCalculationSpec * some more test changes * fix test cases * fmt * rollback changes of EvcsModelSpec * little refactoring * fmt * fmt * Adapting to changes in simonaAPI Signed-off-by: Sebastian Peter * Assigning recognizable UUIDs for test EVs Signed-off-by: Sebastian Peter * remove unnecessary EvcsModel in EvcsInputTestData * rollback changes of entriesByStartTick at EvcsModel * fmt * fmt * rollback unnecessary change of filtering schedules for tick * fmt * entriesByStartTick as sortedMap * only handle results at handleArrivingEvs when there is no state for the currentTick * rearrange EvcsAgentModelCalculationSpec results * Suggestion for result matching Signed-off-by: Sebastian Peter * refactor EvcsResult testing * refactor EvResult testing * Undo line deletion Signed-off-by: Sebastian Peter * rollback filter of results of evcs model * make EvcsModel.chargeEv private again * update exception message * fmt * remove blank cases when testing for expected messages of resultListener --------- Signed-off-by: Sebastian Peter Co-authored-by: Sebastian Peter --- CHANGELOG.md | 1 + .../evcs/EvcsAgentFundamentals.scala | 14 +- .../model/participant/evcs/EvcsModel.scala | 12 +- .../EvcsAgentModelCalculationSpec.scala | 389 +++++++++++++++++- .../ie3/simona/test/common/EvTestData.scala | 12 +- 5 files changed, 418 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb39c77589..7854a5e918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -114,6 +114,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix expected secondaryData in baseStateData [#955](https://github.com/ie3-institute/simona/issues/955) - Improve code quality in fixedloadmodelspec and other tests [#919](https://github.com/ie3-institute/simona/issues/919) - Fix power flow calculation with em agents [#962](https://github.com/ie3-institute/simona/issues/962) +- Fix scheduling at Evcs with more than one Ev at a time without Em [#787](https://github.com/ie3-institute/simona/issues/787) ## [3.0.0] - 2023-08-07 diff --git a/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgentFundamentals.scala index d98e837e48..69b072e55b 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgentFundamentals.scala @@ -65,7 +65,7 @@ import squants.{Dimensionless, Each, Power} import java.time.ZonedDateTime import java.util.UUID -import scala.collection.SortedSet +import scala.collection.immutable.SortedSet import scala.reflect.{ClassTag, classTag} protected trait EvcsAgentFundamentals @@ -494,9 +494,10 @@ protected trait EvcsAgentFundamentals val relevantData = createCalcRelevantData(modelBaseStateData, tick) + val lastState = getLastOrInitialStateData(modelBaseStateData, tick) + val updatedBaseStateData = { if (relevantData.arrivals.nonEmpty) { - val lastState = getLastOrInitialStateData(modelBaseStateData, tick) val currentEvs = modelBaseStateData.model.determineCurrentEvs( relevantData, @@ -528,6 +529,15 @@ protected trait EvcsAgentFundamentals modelBaseStateData } + // if the lastState's tick is the same as the actual tick the results have already been determined and announced when we handled the departedEvs + if (lastState.tick != tick) { + determineResultsAnnounceUpdateValueStore( + lastState, + currentTick, + modelBaseStateData, + ) + } + // We're only here if we're not flex-controlled, thus sending a Completion is always right goToIdleReplyCompletionAndScheduleTriggerForNextAction( updatedBaseStateData, diff --git a/src/main/scala/edu/ie3/simona/model/participant/evcs/EvcsModel.scala b/src/main/scala/edu/ie3/simona/model/participant/evcs/EvcsModel.scala index b00ec5e5e3..a1d21252ec 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/evcs/EvcsModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/evcs/EvcsModel.scala @@ -19,7 +19,6 @@ import edu.ie3.simona.model.participant.evcs.uncontrolled.{ ConstantPowerCharging, MaximumPowerCharging, } -import edu.ie3.util.scala.quantities.DefaultQuantities._ import edu.ie3.simona.model.participant.{ CalcRelevantData, FlexChangeIndicator, @@ -32,7 +31,8 @@ import edu.ie3.simona.util.TickUtil.TickLong import edu.ie3.util.quantities.PowerSystemUnits._ import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import edu.ie3.util.scala.OperationInterval -import squants.energy.{KilowattHours, Kilowatts} +import edu.ie3.util.scala.quantities.DefaultQuantities._ +import squants.energy.Kilowatts import squants.time.Seconds import squants.{Dimensionless, Energy, Power} import tech.units.indriya.unit.Units.PERCENT @@ -514,13 +514,17 @@ final case class EvcsModel( modelState: EvcsState, data: EvcsRelevantData, ): ApparentPower = - throw new NotImplementedError("Use calculatePowerAndEvSoc() instead.") + throw new NotImplementedError( + "Use calculateNewScheduling() or chargeEv() instead." + ) override protected def calculateActivePower( modelState: EvcsState, data: EvcsRelevantData, ): Power = - throw new NotImplementedError("Use calculatePowerAndEvSoc() instead.") + throw new NotImplementedError( + "Use calculateNewScheduling() or chargeEv() instead." + ) override def determineFlexOptions( data: EvcsRelevantData, diff --git a/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala index e4e9f25ff1..9b1502bce4 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala @@ -7,8 +7,12 @@ package edu.ie3.simona.agent.participant import com.typesafe.config.ConfigFactory +import edu.ie3.datamodel.models.OperationTime +import edu.ie3.datamodel.models.input.OperatorInput import edu.ie3.datamodel.models.input.system.EvcsInput -import edu.ie3.datamodel.models.input.system.characteristic.QV +import edu.ie3.datamodel.models.input.system.`type`.chargingpoint.ChargingPointTypeUtils +import edu.ie3.datamodel.models.input.system.`type`.evcslocation.EvcsLocationType +import edu.ie3.datamodel.models.input.system.characteristic.{CosPhiFixed, QV} import edu.ie3.datamodel.models.result.system.{EvResult, EvcsResult} import edu.ie3.simona.agent.ValueStore import edu.ie3.simona.agent.grid.GridAgentMessages.{ @@ -61,6 +65,7 @@ import squants.energy._ import squants.{Each, Energy, Power} import java.time.ZonedDateTime +import java.util.UUID import scala.collection.immutable.{SortedMap, SortedSet} class EvcsAgentModelCalculationSpec @@ -1987,6 +1992,386 @@ class EvcsAgentModelCalculationSpec } - } + "provide correct results for three evs charging at same time without Em" in { + val evService = TestProbe("evService") + val resultListener = TestProbe("ResultListener") + + val inputModelUuid = + UUID.fromString("3278d111-b6ce-438c-8b1a-d060be93e520") + val evcsInputModel = new EvcsInput( + inputModelUuid, + "Dummy_EvcsModel", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + nodeInputNoSlackNs04KvA, + CosPhiFixed.CONSTANT_CHARACTERISTIC, + null, + ChargingPointTypeUtils.ChargingStationType2, + 4, + 0.95, + EvcsLocationType.HOME, + true, + ) + + val initStateData = ParticipantInitializeStateData[ + EvcsInput, + EvcsRuntimeConfig, + ApparentPower, + ]( + evcsInputModel, + modelConfig = modelConfig, + secondaryDataServices = Iterable( + ActorExtEvDataService(evService.ref) + ), + simulationStartDate = simulationStartDate, + simulationEndDate = simulationEndDate, + resolution = 900L, + requestVoltageDeviationThreshold = requestVoltageDeviationThreshold, + outputConfig = NotifierConfig( + simulationResultInfo = true, + powerRequestReply = false, + flexResult = true, + ), + primaryServiceProxy = primaryServiceProxy.ref, + ) + val evcsAgent = TestFSMRef( + new EvcsAgent( + scheduler = scheduler.ref, + initStateData = initStateData, + listener = Iterable(resultListener.ref), + ) + ) + scheduler.send(evcsAgent, Activation(INIT_SIM_TICK)) + + /* Actor should ask for registration with primary service */ + primaryServiceProxy.expectMsg( + PrimaryServiceRegistrationMessage(inputModelUuid) + ) + /* State should be information handling and having correct state data */ + evcsAgent.stateName shouldBe HandleInformation + evcsAgent.stateData match { + case ParticipantInitializingStateData( + inputModel, + modelConfig, + secondaryDataServices, + simulationStartDate, + simulationEndDate, + resolution, + requestVoltageDeviationThreshold, + outputConfig, + maybeEmAgent, + ) => + inputModel shouldBe SimpleInputContainer(evcsInputModel) + modelConfig shouldBe modelConfig + secondaryDataServices shouldBe Iterable( + ActorExtEvDataService(evService.ref) + ) + simulationStartDate shouldBe simulationStartDate + simulationEndDate shouldBe simulationEndDate + resolution shouldBe resolution + requestVoltageDeviationThreshold shouldBe requestVoltageDeviationThreshold + outputConfig shouldBe NotifierConfig( + simulationResultInfo = true, + powerRequestReply = false, + flexResult = true, + ) + maybeEmAgent shouldBe None + case unsuitableStateData => + fail(s"Agent has unsuitable state data '$unsuitableStateData'.") + } + + /* Refuse registration */ + primaryServiceProxy.send( + evcsAgent, + RegistrationFailedMessage(primaryServiceProxy.ref), + ) + + evService.expectMsg(RegisterForEvDataMessage(evcsInputModel.getUuid)) + evService.send( + evcsAgent, + RegistrationSuccessfulMessage(evService.ref, Some(0)), + ) + + scheduler.expectMsg(Completion(evcsAgent.toTyped, Some(0))) + + /* TICK 0 (expected activation) + - currently no cars + */ + scheduler.send(evcsAgent, Activation(0)) + + evService.send( + evcsAgent, + ProvideEvDataMessage( + 0, + evService.ref, + ArrivingEvs(Seq.empty), + Some(900), + ), + ) + + scheduler.expectMsg(Completion(evcsAgent.toTyped, Some(900))) + + /* TICK 900 + * - ev900 arrives + * - charging with 11 kW + */ + scheduler.send(evcsAgent, Activation(900)) + + val ev900 = EvModelWrapper(evA.copyWithDeparture(3600)) + + evService.send( + evcsAgent, + ProvideEvDataMessage( + 900, + evService.ref, + ArrivingEvs(Seq(ev900)), + Some(1800), + ), + ) + + scheduler.expectMsg(Completion(evcsAgent.toTyped, Some(1800))) + + resultListener.expectMsgType[ParticipantResultEvent] match { + case ParticipantResultEvent(result: EvcsResult) => + result.getInputModel shouldBe evcsInputModel.getUuid + result.getTime shouldBe 0.toDateTime + result.getP should beEquivalentTo(0d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + } + + /* TICK 1800 + * - ev1800 arrives + * - charging with 11 kW + */ + scheduler.send(evcsAgent, Activation(1800)) + + val ev1800 = EvModelWrapper(evB.copyWithDeparture(4500)) + + evService.send( + evcsAgent, + ProvideEvDataMessage( + 1800, + evService.ref, + ArrivingEvs(Seq(ev1800)), + Some(2700), + ), + ) + + scheduler.expectMsg(Completion(evcsAgent.toTyped, Some(2700))) + + resultListener.expectMsgType[ParticipantResultEvent] match { + case ParticipantResultEvent(result: EvResult) => + result.getInputModel match { + case model if model == ev900.uuid => + result.getTime shouldBe 900.toDateTime + result.getP should beEquivalentTo(11d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + result.getSoc should beEquivalentTo(0.asPercent, 1e-2) + } + } + + resultListener.expectMsgType[ParticipantResultEvent] match { + case ParticipantResultEvent(result: EvcsResult) => + result.getInputModel shouldBe evcsInputModel.getUuid + result.getTime shouldBe 900.toDateTime + result.getP should beEquivalentTo(11d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + } + + /* TICK 2700 + * - ev2700 arrives + * - charging with 22 kW + */ + scheduler.send(evcsAgent, Activation(2700)) + + val ev2700 = EvModelWrapper(evC.copyWithDeparture(5400)) + + evService.send( + evcsAgent, + ProvideEvDataMessage( + 2700, + evService.ref, + ArrivingEvs(Seq(ev2700)), + None, + ), + ) + + scheduler.expectMsg(Completion(evcsAgent.toTyped, None)) + + resultListener.receiveN(2).foreach { + case ParticipantResultEvent(result: EvResult) => + result.getInputModel match { + case model if model == ev900.uuid => + result.getTime shouldBe 1800.toDateTime + result.getP should beEquivalentTo(11d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + result.getSoc should beEquivalentTo(4.74d.asPercent, 1e-2) + case model if model == ev1800.uuid => + result.getTime shouldBe 1800.toDateTime + result.getP should beEquivalentTo(11d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + result.getSoc should beEquivalentTo(0.asPercent, 1e-2) + } + } + + resultListener.expectMsgType[ParticipantResultEvent] match { + case ParticipantResultEvent(result: EvcsResult) => + result.getInputModel shouldBe evcsInputModel.getUuid + result.getTime shouldBe 1800.toDateTime + result.getP should beEquivalentTo(22d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + } + + // TICK 3600: ev900 leaves + evService.send( + evcsAgent, + DepartingEvsRequest(3600, Seq(ev900.uuid)), + ) + + evService.expectMsgType[DepartingEvsResponse] match { + case DepartingEvsResponse(evcs, evModels) => + evcs shouldBe evcsInputModel.getUuid + evModels should have size 1 + evModels.headOption match { + case Some(evModel) => + evModel.uuid shouldBe ev900.uuid + evModel.storedEnergy should approximate(KilowattHours(8.25)) + case None => fail("Expected to get at least one ev.") + } + } + + resultListener.receiveN(4).foreach { + case ParticipantResultEvent(result: EvResult) => + result.getInputModel match { + case model if model == ev900.uuid => + result.getTime match { + case time if time == 2700.toDateTime => + result.getP should beEquivalentTo(11d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + result.getSoc should beEquivalentTo(9.48d.asPercent, 1e-2) + case time if time == 3600.toDateTime => + result.getP should beEquivalentTo(0d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + result.getSoc should beEquivalentTo(14.22d.asPercent, 1e-2) + } + case model if model == ev1800.uuid => + result.getTime shouldBe 2700.toDateTime + result.getP should beEquivalentTo(11d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + result.getSoc should beEquivalentTo(3.44d.asPercent, 1e-2) + case model if model == ev2700.uuid => + result.getTime shouldBe 2700.toDateTime + result.getP should beEquivalentTo(22d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + result.getSoc should beEquivalentTo(0.asPercent, 1e-2) + } + } + + resultListener.expectMsgType[ParticipantResultEvent] match { + case ParticipantResultEvent(result: EvcsResult) => + result.getInputModel shouldBe evcsInputModel.getUuid + result.getTime shouldBe 2700.toDateTime + result.getP should beEquivalentTo(44d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + } + + // TICK 4500: ev1800 leaves + + evService.send( + evcsAgent, + DepartingEvsRequest(4500, Seq(ev1800.uuid)), + ) + + evService.expectMsgType[DepartingEvsResponse] match { + case DepartingEvsResponse(evcs, evModels) => + evcs shouldBe evcsInputModel.getUuid + evModels should have size 1 + evModels.headOption match { + case Some(evModel) => + evModel.uuid shouldBe ev1800.uuid + evModel.storedEnergy should approximate(KilowattHours(8.25)) + case None => fail("Expected to get at least one ev.") + } + } + + resultListener.receiveN(3).foreach { + case ParticipantResultEvent(result: EvResult) => + result.getInputModel match { + case model if model == ev1800.uuid => + result.getTime match { + case time if time == 3600.toDateTime => + result.getP should beEquivalentTo(11d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + result.getSoc should beEquivalentTo(6.88.asPercent, 1e-2) + case time if time == 4500.toDateTime => + result.getP should beEquivalentTo(0d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + result.getSoc should beEquivalentTo(10.31.asPercent, 1e-2) + } + case model if model == ev2700.uuid => + result.getTime shouldBe 3600.toDateTime + result.getP should beEquivalentTo(22d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + result.getSoc should beEquivalentTo(4.58d.asPercent, 1e-2) + } + } + + resultListener.expectMsgType[ParticipantResultEvent] match { + case ParticipantResultEvent(result: EvcsResult) => + result.getInputModel shouldBe evcsInputModel.getUuid + result.getTime shouldBe 3600.toDateTime + result.getP should beEquivalentTo(33d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + } + + // TICK 5400: ev2700 leaves + + evService.send( + evcsAgent, + DepartingEvsRequest(5400, Seq(ev2700.uuid)), + ) + + evService.expectMsgType[DepartingEvsResponse] match { + case DepartingEvsResponse(evcs, evModels) => + evcs shouldBe evcsInputModel.getUuid + evModels should have size 1 + evModels.headOption match { + case Some(evModel) => + evModel.uuid shouldBe ev2700.uuid + evModel.storedEnergy should approximate(KilowattHours(16.5)) + case None => fail("Expected to get at least one ev.") + } + } + + resultListener.receiveN(2).foreach { + case ParticipantResultEvent(result: EvResult) => + result.getInputModel match { + case model if model == ev2700.uuid => + result.getTime match { + case time if time == 4500.toDateTime => + result.getP should beEquivalentTo(22d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + result.getSoc should beEquivalentTo(9.17.asPercent, 1e-2) + case time if time == 5400.toDateTime => + result.getP should beEquivalentTo(0d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + result.getSoc should beEquivalentTo(13.75.asPercent, 1e-2) + } + } + } + + resultListener.expectMsgType[ParticipantResultEvent] match { + case ParticipantResultEvent(result: EvcsResult) => + result.getInputModel shouldBe evcsInputModel.getUuid + result.getTime shouldBe 4500.toDateTime + result.getP should beEquivalentTo(22d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + } + /* FixMe: We would expect another Evcs Result for the lastTick of 5400 here. + But this can't be calculated since there is no nextTick. + For simulation it is as well necessary to fix this e.g. by writing the lastResults when finishing simulation. + */ + } + } } diff --git a/src/test/scala/edu/ie3/simona/test/common/EvTestData.scala b/src/test/scala/edu/ie3/simona/test/common/EvTestData.scala index ca77f35553..56c90c8e41 100644 --- a/src/test/scala/edu/ie3/simona/test/common/EvTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/EvTestData.scala @@ -14,7 +14,7 @@ import java.util.UUID trait EvTestData { protected val evA: MockEvModel = new MockEvModel( - UUID.fromString("73c041c7-68e9-470e-8ca2-21fd7dbd1797"), + UUID.fromString("0-0-0-0-a"), "evA", Quantities.getQuantity(11d, PowerSystemUnits.KILOWATT), Quantities.getQuantity(11d, PowerSystemUnits.KILOWATT), @@ -22,11 +22,19 @@ trait EvTestData { 200, ) protected val evB: MockEvModel = new MockEvModel( - UUID.fromString("6d7d27a1-5cbb-4b73-aecb-dfcc5a6fb22e"), + UUID.fromString("0-0-0-0-b"), "evB", Quantities.getQuantity(11d, PowerSystemUnits.KILOWATT), Quantities.getQuantity(11d, PowerSystemUnits.KILOWATT), Quantities.getQuantity(80d, PowerSystemUnits.KILOWATTHOUR), 200, ) + protected val evC: MockEvModel = new MockEvModel( + UUID.fromString("0-0-0-0-c"), + "evC", + Quantities.getQuantity(22d, PowerSystemUnits.KILOWATT), + Quantities.getQuantity(22d, PowerSystemUnits.KILOWATT), + Quantities.getQuantity(120d, PowerSystemUnits.KILOWATTHOUR), + 200, + ) }