From eeb88dd8c73271e1abb892c5365388aad0335174 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Oct 2022 10:11:54 +0000 Subject: [PATCH 01/17] Bump org.sonarqube from 3.4.0.2513 to 3.5.0.2730 (#397) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 77ae459d99..a3143b771b 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { id "de.undercouch.download" version "5.3.0" // downloads plugin id "kr.motd.sphinx" version "2.10.1" // documentation generation id "com.github.johnrengelman.shadow" version "7.1.2" // fat jar - id "org.sonarqube" version "3.4.0.2513" // sonarqube + id "org.sonarqube" version "3.5.0.2730" // sonarqube id "org.scoverage" version "7.0.1" // scala code coverage scoverage id "com.github.maiflai.scalatest" version "0.32" // run scalatest without specific spec task id 'org.hidetake.ssh' version '2.10.1' From fea3cb905a38416482695f895729ef0a265ffe3e Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Sun, 30 Oct 2022 13:06:20 +0100 Subject: [PATCH 02/17] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ad98ddaf4..da9d97c2ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Adapted to changes of EvcsInput in PSDM [#377](https://github.com/ie3-institute/simona/pull/377) - Fix breaking SIMONA caused by changes in simonaAPI [#384] (https://github.com/ie3-institute/simona/issues/384) - Fixed awaiting departed EVs in ExtEvDataService [#392](https://github.com/ie3-institute/simona/issues/392) +- Fixed missing ModelBaseStateData generation for random load profiles [#399](https://github.com/ie3-institute/simona/issues/399) ### Removed - Remove workaround for tscfg tmp directory [#178](https://github.com/ie3-institute/simona/issues/178) From 6f2965a907ece452da28d551a9a9ea009ddd17fb Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Sun, 30 Oct 2022 15:16:30 +0100 Subject: [PATCH 03/17] add case for randomLoadModel --- .../participant/load/LoadAgentFundamentals.scala | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala index 9242bffa18..4862f4673d 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala @@ -127,7 +127,7 @@ protected trait LoadAgentFundamentals[LD <: LoadRelevantData, LM <: LoadModel[ * Also register for services, where needed. */ val lastTickInSimulation = simulationEndDate.toTick(simulationStartDate) val additionalActivationTicks = model match { - /* If no secondary data is needed (implicitly by fixed load model), add activation ticks for the simple model */ + /* If no secondary data is needed (implicitly by fixed load model and random load model), add activation ticks for the simple model */ case fixedLoadModel: FixedLoadModel => /* As participant agents always return their last known operation point on request, it is sufficient * to let a fixed load model determine it's operation point on: @@ -148,6 +148,14 @@ protected trait LoadAgentFundamentals[LD <: LoadRelevantData, LM <: LoadModel[ profileLoadModel.operationInterval.start, profileLoadModel.operationInterval.end ) + case randomLoadModel: RandomLoadModel => + activationTicksInOperationTime( + simulationStartDate, + // TODO: Check if parameter should given via Config + 900L, + randomLoadModel.operationInterval.start, + randomLoadModel.operationInterval.end + ) case _ => Array.emptyLongArray } @@ -364,9 +372,9 @@ case object LoadAgentFundamentals { this: LoadAgent.RandomLoadAgent => override def buildModel( - inputModel: LoadInput, - operationInterval: OperationInterval, - reference: LoadReference + inputModel: LoadInput, + operationInterval: OperationInterval, + reference: LoadReference ): RandomLoadModel = { val model = RandomLoadModel(inputModel, operationInterval, 1d, reference) model.enable() From 34053364cadb648da91077b9d4391fa7d2c28498 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Sun, 30 Oct 2022 16:24:35 +0100 Subject: [PATCH 04/17] give resolution via config --- input/samples/vn_simona/vn_simona.conf | 11 ++++++----- src/main/resources/config/config-template.conf | 1 + .../participant/load/LoadAgentFundamentals.scala | 9 ++++----- .../scala/edu/ie3/simona/config/SimonaConfig.scala | 9 ++++++++- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/input/samples/vn_simona/vn_simona.conf b/input/samples/vn_simona/vn_simona.conf index 666aeaf75c..549b09e176 100644 --- a/input/samples/vn_simona/vn_simona.conf +++ b/input/samples/vn_simona/vn_simona.conf @@ -17,7 +17,7 @@ simona.simulationName = "vn_simona" # Time Parameters ################################################################## simona.time.startDateTime = "2011-01-01 00:00:00" -simona.time.endDateTime = "2011-01-01 02:00:00" +simona.time.endDateTime = "2011-01-02 02:00:00" simona.time.schedulerReadyCheckWindow = 900 simona.time.stopOnFailedPowerFlow = true @@ -71,17 +71,17 @@ simona.output.participant.individualConfigs = [ { notifier = "pv" powerRequestReply = false - simulationResult = true + simulationResult = false }, { notifier = "wec" powerRequestReply = false - simulationResult = true + simulationResult = false }, { notifier = "evcs" powerRequestReply = false - simulationResult = true + simulationResult = false } ] @@ -98,6 +98,7 @@ simona.runtime.participant.load = { scaling = 1.0 modelBehaviour = "fix" reference = "power" + resolutionRandomLoadProfile = 900.0 } individualConfigs = [] } @@ -160,4 +161,4 @@ simona.gridConfig.refSystems = [ simona.powerflow.maxSweepPowerDeviation = 1E-5 // the maximum allowed deviation in power between two sweeps, before overall convergence is assumed simona.powerflow.newtonraphson.epsilon = [1E-12] simona.powerflow.newtonraphson.iterations = 50 -simona.powerflow.resolution = "3600s" +simona.powerflow.resolution = "3600d" diff --git a/src/main/resources/config/config-template.conf b/src/main/resources/config/config-template.conf index 10b293b1c2..fab62b8e7d 100644 --- a/src/main/resources/config/config-template.conf +++ b/src/main/resources/config/config-template.conf @@ -26,6 +26,7 @@ LoadRuntimeConfig { baseRuntimeConfig: BaseRuntimeConfig modelBehaviour: string # How the model behaves. Possible values: fix, profile, random reference: string # Scaling reference for the load model. Possible values: power, energy + resolutionRandomLoadProfile: double # Sets the time resolution of the generated random load profile } #@define extends BaseRuntimeConfig diff --git a/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala index 4862f4673d..703675ac64 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala @@ -151,8 +151,7 @@ protected trait LoadAgentFundamentals[LD <: LoadRelevantData, LM <: LoadModel[ case randomLoadModel: RandomLoadModel => activationTicksInOperationTime( simulationStartDate, - // TODO: Check if parameter should given via Config - 900L, + modelConfig.resolutionRandomLoadProfile.toLong, randomLoadModel.operationInterval.start, randomLoadModel.operationInterval.end ) @@ -372,9 +371,9 @@ case object LoadAgentFundamentals { this: LoadAgent.RandomLoadAgent => override def buildModel( - inputModel: LoadInput, - operationInterval: OperationInterval, - reference: LoadReference + inputModel: LoadInput, + operationInterval: OperationInterval, + reference: LoadReference ): RandomLoadModel = { val model = RandomLoadModel(inputModel, operationInterval, 1d, reference) model.enable() diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index 0a7b4fd2cc..10d6145f6c 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -302,7 +302,8 @@ object SimonaConfig { override val scaling: scala.Double, override val uuids: scala.List[java.lang.String], modelBehaviour: java.lang.String, - reference: java.lang.String + reference: java.lang.String, + resolutionRandomLoadProfile: scala.Double ) extends BaseRuntimeConfig( calculateMissingReactivePowerWithModel, scaling, @@ -318,6 +319,12 @@ object SimonaConfig { modelBehaviour = $_reqStr(parentPath, c, "modelBehaviour", $tsCfgValidator), reference = $_reqStr(parentPath, c, "reference", $tsCfgValidator), + resolutionRandomLoadProfile = $_reqDbl( + parentPath, + c, + "resolutionRandomLoadProfile", + $tsCfgValidator + ), calculateMissingReactivePowerWithModel = $_reqBln( parentPath, c, From b31cb08bdbf3bb632d2929b251c4cfb06ab2471f Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Sun, 30 Oct 2022 16:57:07 +0100 Subject: [PATCH 05/17] fix tests --- input/samples/vn_simona/vn_simona.conf | 2 +- .../resources/config/config-template.conf | 2 ++ .../simona/test/common/ConfigTestData.scala | 1 + .../edu/ie3/simona/util/ConfigUtilSpec.scala | 25 +++++++++++++++---- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/input/samples/vn_simona/vn_simona.conf b/input/samples/vn_simona/vn_simona.conf index 549b09e176..897330233c 100644 --- a/input/samples/vn_simona/vn_simona.conf +++ b/input/samples/vn_simona/vn_simona.conf @@ -98,7 +98,7 @@ simona.runtime.participant.load = { scaling = 1.0 modelBehaviour = "fix" reference = "power" - resolutionRandomLoadProfile = 900.0 + resolutionRandomLoadProfile = 900 } individualConfigs = [] } diff --git a/src/main/resources/config/config-template.conf b/src/main/resources/config/config-template.conf index fab62b8e7d..d0ec1f3fe2 100644 --- a/src/main/resources/config/config-template.conf +++ b/src/main/resources/config/config-template.conf @@ -24,8 +24,10 @@ BaseRuntimeConfig { #@define extends BaseRuntimeConfig LoadRuntimeConfig { baseRuntimeConfig: BaseRuntimeConfig + modelBehaviour: string # How the model behaves. Possible values: fix, profile, random reference: string # Scaling reference for the load model. Possible values: power, energy + #@Optional resolutionRandomLoadProfile: double # Sets the time resolution of the generated random load profile } diff --git a/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala b/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala index 24755d4fd1..7c69f576df 100644 --- a/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala @@ -66,6 +66,7 @@ trait ConfigTestData { | scaling = 1.0 | modelBehaviour = "fix" | reference = "power" + | resolutionRandomLoadProfile = 900 | } | individualConfigs = [] |} diff --git a/src/test/scala/edu/ie3/simona/util/ConfigUtilSpec.scala b/src/test/scala/edu/ie3/simona/util/ConfigUtilSpec.scala index 27f0595e69..c90fcf862b 100644 --- a/src/test/scala/edu/ie3/simona/util/ConfigUtilSpec.scala +++ b/src/test/scala/edu/ie3/simona/util/ConfigUtilSpec.scala @@ -44,6 +44,7 @@ class ConfigUtilSpec | scaling = 1.0 | modelBehaviour = "fix" | reference = "power" + | resolutionRandomLoadProfile = 900 | } | individualConfigs = [] |}""".stripMargin @@ -67,7 +68,8 @@ class ConfigUtilSpec scaling, uuids, modelBehaviour, - reference + reference, + 900L ) ) => calculateMissingReactivePowerWithModel shouldBe false @@ -100,6 +102,7 @@ class ConfigUtilSpec | scaling = 1.3 | modelBehaviour = "profile" | reference = "power" + | resolutionRandomLoadProfile = 900 | } | ] |}""".stripMargin @@ -126,7 +129,8 @@ class ConfigUtilSpec scaling, uuids, modelBehaviour, - reference + reference, + resolutionRandomLoadProfile ) ) => calculateMissingReactivePowerWithModel shouldBe false @@ -151,6 +155,7 @@ class ConfigUtilSpec | baseModelConfig.scaling = 1.3 | modelBehaviour = "profile" | reference = "power" + | resolutionRandomLoadProfile = 900 | } | individualConfigs = [ | { @@ -159,6 +164,7 @@ class ConfigUtilSpec | scaling = 1.3 | modelBehaviour = "profile" | reference = "power" + | resolutionRandomLoadProfile = 900 | } | ] |}""".stripMargin @@ -197,6 +203,7 @@ class ConfigUtilSpec | scaling = 1.3 | modelBehaviour = "profile" | reference = "power" + | resolutionRandomLoadProfile = 900 | } | individualConfigs = [ | { @@ -205,6 +212,7 @@ class ConfigUtilSpec | scaling = 1.3 | modelBehaviour = "profile" | reference = "power" + | resolutionRandomLoadProfile = 900 | }, | { | calculateMissingReactivePowerWithModel = false @@ -212,6 +220,7 @@ class ConfigUtilSpec | scaling = 1.5 | modelBehaviour = "random" | reference = "energy" + | resolutionRandomLoadProfile = 900 | } | ] |}""".stripMargin @@ -242,7 +251,8 @@ class ConfigUtilSpec 1.3, List("49f250fa-41ff-4434-a083-79c98d260a76"), "profile", - "power" + "power", + 900L ) actual.getOrDefault[LoadRuntimeConfig]( UUID.fromString("fb8f1443-1843-4ecd-a94a-59be8148397f") @@ -252,7 +262,8 @@ class ConfigUtilSpec 1.5, List("fb8f1443-1843-4ecd-a94a-59be8148397f"), "random", - "energy" + "energy", + 900L ) } } @@ -481,6 +492,7 @@ class ConfigUtilSpec | scaling = 1.0 | modelBehaviour = "profile" | reference = "power" + | resolutionRandomLoadProfile = 900 | } | individualConfigs = [ | { @@ -489,6 +501,7 @@ class ConfigUtilSpec | scaling = 1.3 | modelBehaviour = "profile" | reference = "power" + | resolutionRandomLoadProfile = 900 | }, | { | calculateMissingReactivePowerWithModel = false @@ -496,6 +509,7 @@ class ConfigUtilSpec | scaling = 1.5 | modelBehaviour = "profile" | reference = "power" + | resolutionRandomLoadProfile = 900 | } | ] |} @@ -550,7 +564,8 @@ class ConfigUtilSpec 1.0, List("default"), "profile", - "power" + "power", + 900L ) // return default if a request for pv is done, but fixed feed is found From cc17d32b51e319e248aa32dc50c110104da6fb5c Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Sun, 30 Oct 2022 17:02:21 +0100 Subject: [PATCH 06/17] rollback unnecessary changes --- input/samples/vn_simona/vn_simona.conf | 10 +++++----- .../agent/participant/load/LoadAgentFundamentals.scala | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/input/samples/vn_simona/vn_simona.conf b/input/samples/vn_simona/vn_simona.conf index 897330233c..f415027be6 100644 --- a/input/samples/vn_simona/vn_simona.conf +++ b/input/samples/vn_simona/vn_simona.conf @@ -17,7 +17,7 @@ simona.simulationName = "vn_simona" # Time Parameters ################################################################## simona.time.startDateTime = "2011-01-01 00:00:00" -simona.time.endDateTime = "2011-01-02 02:00:00" +simona.time.endDateTime = "2011-01-01 02:00:00" simona.time.schedulerReadyCheckWindow = 900 simona.time.stopOnFailedPowerFlow = true @@ -71,17 +71,17 @@ simona.output.participant.individualConfigs = [ { notifier = "pv" powerRequestReply = false - simulationResult = false + simulationResult = true }, { notifier = "wec" powerRequestReply = false - simulationResult = false + simulationResult = true }, { notifier = "evcs" powerRequestReply = false - simulationResult = false + simulationResult = true } ] @@ -161,4 +161,4 @@ simona.gridConfig.refSystems = [ simona.powerflow.maxSweepPowerDeviation = 1E-5 // the maximum allowed deviation in power between two sweeps, before overall convergence is assumed simona.powerflow.newtonraphson.epsilon = [1E-12] simona.powerflow.newtonraphson.iterations = 50 -simona.powerflow.resolution = "3600d" +simona.powerflow.resolution = "3600s" diff --git a/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala index 703675ac64..3e24d8d962 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala @@ -127,7 +127,7 @@ protected trait LoadAgentFundamentals[LD <: LoadRelevantData, LM <: LoadModel[ * Also register for services, where needed. */ val lastTickInSimulation = simulationEndDate.toTick(simulationStartDate) val additionalActivationTicks = model match { - /* If no secondary data is needed (implicitly by fixed load model and random load model), add activation ticks for the simple model */ + /* If no secondary data is needed (implicitly by fixed load model), add activation ticks for the simple model */ case fixedLoadModel: FixedLoadModel => /* As participant agents always return their last known operation point on request, it is sufficient * to let a fixed load model determine it's operation point on: From 79d728ecea5ebe9ebe4b547afa5569b57b603e71 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Tue, 1 Nov 2022 09:45:26 +0100 Subject: [PATCH 07/17] Comment for resolution of random load profile --- .../simona/agent/participant/load/LoadAgentFundamentals.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala index 3e24d8d962..cda15dc142 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala @@ -151,7 +151,8 @@ protected trait LoadAgentFundamentals[LD <: LoadRelevantData, LM <: LoadModel[ case randomLoadModel: RandomLoadModel => activationTicksInOperationTime( simulationStartDate, - modelConfig.resolutionRandomLoadProfile.toLong, + // Resolution for random load profile time steps. E.g. 900L for quarter hourly, 3600L for hourly ones. + 900L, randomLoadModel.operationInterval.start, randomLoadModel.operationInterval.end ) From 3a163a4b7a33fe52f3fa6996d6fce52c1bf2a1bf Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Tue, 1 Nov 2022 09:54:49 +0100 Subject: [PATCH 08/17] Rollback to dev --- input/samples/vn_simona/vn_simona.conf | 1 - .../resources/config/config-template.conf | 3 --- .../edu/ie3/simona/config/SimonaConfig.scala | 9 +------ .../simona/test/common/ConfigTestData.scala | 1 - .../edu/ie3/simona/util/ConfigUtilSpec.scala | 25 ++++--------------- 5 files changed, 6 insertions(+), 33 deletions(-) diff --git a/input/samples/vn_simona/vn_simona.conf b/input/samples/vn_simona/vn_simona.conf index f415027be6..666aeaf75c 100644 --- a/input/samples/vn_simona/vn_simona.conf +++ b/input/samples/vn_simona/vn_simona.conf @@ -98,7 +98,6 @@ simona.runtime.participant.load = { scaling = 1.0 modelBehaviour = "fix" reference = "power" - resolutionRandomLoadProfile = 900 } individualConfigs = [] } diff --git a/src/main/resources/config/config-template.conf b/src/main/resources/config/config-template.conf index d0ec1f3fe2..10b293b1c2 100644 --- a/src/main/resources/config/config-template.conf +++ b/src/main/resources/config/config-template.conf @@ -24,11 +24,8 @@ BaseRuntimeConfig { #@define extends BaseRuntimeConfig LoadRuntimeConfig { baseRuntimeConfig: BaseRuntimeConfig - modelBehaviour: string # How the model behaves. Possible values: fix, profile, random reference: string # Scaling reference for the load model. Possible values: power, energy - #@Optional - resolutionRandomLoadProfile: double # Sets the time resolution of the generated random load profile } #@define extends BaseRuntimeConfig diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index 10d6145f6c..0a7b4fd2cc 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -302,8 +302,7 @@ object SimonaConfig { override val scaling: scala.Double, override val uuids: scala.List[java.lang.String], modelBehaviour: java.lang.String, - reference: java.lang.String, - resolutionRandomLoadProfile: scala.Double + reference: java.lang.String ) extends BaseRuntimeConfig( calculateMissingReactivePowerWithModel, scaling, @@ -319,12 +318,6 @@ object SimonaConfig { modelBehaviour = $_reqStr(parentPath, c, "modelBehaviour", $tsCfgValidator), reference = $_reqStr(parentPath, c, "reference", $tsCfgValidator), - resolutionRandomLoadProfile = $_reqDbl( - parentPath, - c, - "resolutionRandomLoadProfile", - $tsCfgValidator - ), calculateMissingReactivePowerWithModel = $_reqBln( parentPath, c, diff --git a/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala b/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala index 7c69f576df..24755d4fd1 100644 --- a/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala @@ -66,7 +66,6 @@ trait ConfigTestData { | scaling = 1.0 | modelBehaviour = "fix" | reference = "power" - | resolutionRandomLoadProfile = 900 | } | individualConfigs = [] |} diff --git a/src/test/scala/edu/ie3/simona/util/ConfigUtilSpec.scala b/src/test/scala/edu/ie3/simona/util/ConfigUtilSpec.scala index c90fcf862b..27f0595e69 100644 --- a/src/test/scala/edu/ie3/simona/util/ConfigUtilSpec.scala +++ b/src/test/scala/edu/ie3/simona/util/ConfigUtilSpec.scala @@ -44,7 +44,6 @@ class ConfigUtilSpec | scaling = 1.0 | modelBehaviour = "fix" | reference = "power" - | resolutionRandomLoadProfile = 900 | } | individualConfigs = [] |}""".stripMargin @@ -68,8 +67,7 @@ class ConfigUtilSpec scaling, uuids, modelBehaviour, - reference, - 900L + reference ) ) => calculateMissingReactivePowerWithModel shouldBe false @@ -102,7 +100,6 @@ class ConfigUtilSpec | scaling = 1.3 | modelBehaviour = "profile" | reference = "power" - | resolutionRandomLoadProfile = 900 | } | ] |}""".stripMargin @@ -129,8 +126,7 @@ class ConfigUtilSpec scaling, uuids, modelBehaviour, - reference, - resolutionRandomLoadProfile + reference ) ) => calculateMissingReactivePowerWithModel shouldBe false @@ -155,7 +151,6 @@ class ConfigUtilSpec | baseModelConfig.scaling = 1.3 | modelBehaviour = "profile" | reference = "power" - | resolutionRandomLoadProfile = 900 | } | individualConfigs = [ | { @@ -164,7 +159,6 @@ class ConfigUtilSpec | scaling = 1.3 | modelBehaviour = "profile" | reference = "power" - | resolutionRandomLoadProfile = 900 | } | ] |}""".stripMargin @@ -203,7 +197,6 @@ class ConfigUtilSpec | scaling = 1.3 | modelBehaviour = "profile" | reference = "power" - | resolutionRandomLoadProfile = 900 | } | individualConfigs = [ | { @@ -212,7 +205,6 @@ class ConfigUtilSpec | scaling = 1.3 | modelBehaviour = "profile" | reference = "power" - | resolutionRandomLoadProfile = 900 | }, | { | calculateMissingReactivePowerWithModel = false @@ -220,7 +212,6 @@ class ConfigUtilSpec | scaling = 1.5 | modelBehaviour = "random" | reference = "energy" - | resolutionRandomLoadProfile = 900 | } | ] |}""".stripMargin @@ -251,8 +242,7 @@ class ConfigUtilSpec 1.3, List("49f250fa-41ff-4434-a083-79c98d260a76"), "profile", - "power", - 900L + "power" ) actual.getOrDefault[LoadRuntimeConfig]( UUID.fromString("fb8f1443-1843-4ecd-a94a-59be8148397f") @@ -262,8 +252,7 @@ class ConfigUtilSpec 1.5, List("fb8f1443-1843-4ecd-a94a-59be8148397f"), "random", - "energy", - 900L + "energy" ) } } @@ -492,7 +481,6 @@ class ConfigUtilSpec | scaling = 1.0 | modelBehaviour = "profile" | reference = "power" - | resolutionRandomLoadProfile = 900 | } | individualConfigs = [ | { @@ -501,7 +489,6 @@ class ConfigUtilSpec | scaling = 1.3 | modelBehaviour = "profile" | reference = "power" - | resolutionRandomLoadProfile = 900 | }, | { | calculateMissingReactivePowerWithModel = false @@ -509,7 +496,6 @@ class ConfigUtilSpec | scaling = 1.5 | modelBehaviour = "profile" | reference = "power" - | resolutionRandomLoadProfile = 900 | } | ] |} @@ -564,8 +550,7 @@ class ConfigUtilSpec 1.0, List("default"), "profile", - "power", - 900L + "power" ) // return default if a request for pv is done, but fixed feed is found From e72c07631fd093f20d8e750b4f879042c1d05917 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Tue, 1 Nov 2022 09:55:44 +0100 Subject: [PATCH 09/17] make RandomFactory random --- .../simona/model/participant/load/random/RandomLoadModel.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadModel.scala b/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadModel.scala index 1cd37b1b7d..fe050f568e 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadModel.scala @@ -79,7 +79,7 @@ final case class RandomLoadModel( ) } - private val randomFactory = RandomFactory.get(430431L) + private val randomLoadParamStore = RandomLoadParamStore() type GevKey = (DayType.Value, Int) @@ -146,6 +146,7 @@ final case class RandomLoadModel( case Some(foundIt) => foundIt case None => /* Instantiate new gev distribution, put it to storage and return it */ + val randomFactory = RandomFactory.get((Math.random()*1000000).toInt) val gevParameters = randomLoadParamStore.parameters(dateTime) val newGev = new GeneralizedExtremeValueDistribution( gevParameters.my, From 01f0f0596116ff5c5beb7bd0bb082a3b2f6db96d Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Tue, 1 Nov 2022 10:03:30 +0100 Subject: [PATCH 10/17] remove not used imports --- .../participant/load/LoadAgentFundamentals.scala | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala index cda15dc142..5bbfeb9d19 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala @@ -47,23 +47,15 @@ import edu.ie3.simona.model.participant.load.{ } import edu.ie3.simona.util.SimonaConstants import edu.ie3.simona.util.TickUtil._ -import edu.ie3.util.quantities.PowerSystemUnits.{ - KILOVARHOUR, - KILOWATTHOUR, - MEGAVAR, - MEGAWATT, - PU -} +import edu.ie3.util.quantities.PowerSystemUnits.PU import edu.ie3.util.scala.OperationInterval -import edu.ie3.util.scala.quantities.QuantityUtil + import tech.units.indriya.ComparableQuantity -import tech.units.indriya.quantity.Quantities import java.time.ZonedDateTime import java.util.UUID -import javax.measure.quantity.{Dimensionless, Energy, Power} +import javax.measure.quantity.{Dimensionless, Power} import scala.reflect.{ClassTag, classTag} -import scala.util.{Failure, Success} protected trait LoadAgentFundamentals[LD <: LoadRelevantData, LM <: LoadModel[ LD From e2de5fd0ec93f6ec7e0cc8bc16240f9d4ba94131 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Tue, 1 Nov 2022 10:03:54 +0100 Subject: [PATCH 11/17] format --- .../simona/model/participant/load/random/RandomLoadModel.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadModel.scala b/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadModel.scala index fe050f568e..5be84f680c 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadModel.scala @@ -79,7 +79,6 @@ final case class RandomLoadModel( ) } - private val randomLoadParamStore = RandomLoadParamStore() type GevKey = (DayType.Value, Int) @@ -146,7 +145,7 @@ final case class RandomLoadModel( case Some(foundIt) => foundIt case None => /* Instantiate new gev distribution, put it to storage and return it */ - val randomFactory = RandomFactory.get((Math.random()*1000000).toInt) + val randomFactory = RandomFactory.get((Math.random() * 1000000).toInt) val gevParameters = randomLoadParamStore.parameters(dateTime) val newGev = new GeneralizedExtremeValueDistribution( gevParameters.my, From ee7c4d0cec298ea29e640ccdc73bbe382b00f351 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Tue, 1 Nov 2022 10:09:14 +0100 Subject: [PATCH 12/17] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da9d97c2ce..7d25f5ce65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix breaking SIMONA caused by changes in simonaAPI [#384] (https://github.com/ie3-institute/simona/issues/384) - Fixed awaiting departed EVs in ExtEvDataService [#392](https://github.com/ie3-institute/simona/issues/392) - Fixed missing ModelBaseStateData generation for random load profiles [#399](https://github.com/ie3-institute/simona/issues/399) +- Fixed non-random first days of random load profiles [#401](https://github.com/ie3-institute/simona/issues/401) ### Removed - Remove workaround for tscfg tmp directory [#178](https://github.com/ie3-institute/simona/issues/178) From 41b94f7fb29ac049086d3e502104254c49948bbd Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Wed, 2 Nov 2022 15:39:12 +0100 Subject: [PATCH 13/17] Resolution of RandomLoadModel via RandomLoadParamStore --- .../agent/participant/load/LoadAgentFundamentals.scala | 9 +++++---- .../participant/load/random/RandomLoadParamStore.scala | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala index 5bbfeb9d19..58e6e9c365 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/load/LoadAgentFundamentals.scala @@ -38,7 +38,10 @@ import edu.ie3.simona.model.participant.load.profile.{ LoadProfileStore, ProfileLoadModel } -import edu.ie3.simona.model.participant.load.random.RandomLoadModel +import edu.ie3.simona.model.participant.load.random.{ + RandomLoadModel, + RandomLoadParamStore +} import edu.ie3.simona.model.participant.load.random.RandomLoadModel.RandomRelevantData import edu.ie3.simona.model.participant.load.{ FixedLoadModel, @@ -49,7 +52,6 @@ import edu.ie3.simona.util.SimonaConstants import edu.ie3.simona.util.TickUtil._ import edu.ie3.util.quantities.PowerSystemUnits.PU import edu.ie3.util.scala.OperationInterval - import tech.units.indriya.ComparableQuantity import java.time.ZonedDateTime @@ -143,8 +145,7 @@ protected trait LoadAgentFundamentals[LD <: LoadRelevantData, LM <: LoadModel[ case randomLoadModel: RandomLoadModel => activationTicksInOperationTime( simulationStartDate, - // Resolution for random load profile time steps. E.g. 900L for quarter hourly, 3600L for hourly ones. - 900L, + RandomLoadParamStore.resolution.getSeconds, randomLoadModel.operationInterval.start, randomLoadModel.operationInterval.end ) diff --git a/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadParamStore.scala b/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadParamStore.scala index af779e2439..44f481eafe 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadParamStore.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadParamStore.scala @@ -7,8 +7,7 @@ package edu.ie3.simona.model.participant.load.random import java.io.{InputStreamReader, Reader} -import java.time.ZonedDateTime - +import java.time.{Duration, ZonedDateTime} import com.typesafe.scalalogging.LazyLogging import edu.ie3.simona.exceptions.FileIOException import edu.ie3.simona.model.participant.load.DayType @@ -44,6 +43,7 @@ final case class RandomLoadParamStore private (reader: Reader) { } case object RandomLoadParamStore extends LazyLogging { + val resolution: Duration = Duration.ofMinutes(15) /** Default value store, that uses information from a file * 'random_load_parameters.csv' placed in the resources folder of the project From f1e139206b0d078da35cdfac1ab1d48eaea27ffe Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Wed, 2 Nov 2022 15:39:36 +0100 Subject: [PATCH 14/17] Using Random nextLong --- .../simona/model/participant/load/random/RandomLoadModel.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadModel.scala b/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadModel.scala index 5be84f680c..f7d260ec92 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/load/random/RandomLoadModel.scala @@ -26,6 +26,7 @@ import java.util.UUID import javax.measure.quantity.{Dimensionless, Power} import scala.annotation.tailrec import scala.collection.mutable +import scala.util.Random /** A load model consuming energy followed by time resolved probability. The * referencing to rated active power maps the output's 95 % quantile to this @@ -145,7 +146,7 @@ final case class RandomLoadModel( case Some(foundIt) => foundIt case None => /* Instantiate new gev distribution, put it to storage and return it */ - val randomFactory = RandomFactory.get((Math.random() * 1000000).toInt) + val randomFactory = RandomFactory.get(Random.nextLong()) val gevParameters = randomLoadParamStore.parameters(dateTime) val newGev = new GeneralizedExtremeValueDistribution( gevParameters.my, From 1b79f56f5df91e778e1a0321f31de46b40823a71 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Sun, 6 Nov 2022 18:10:55 +0100 Subject: [PATCH 15/17] Forced charging of EVs below minimal SOC Additional flex options change when reaching minimal SOC --- .../model/participant/evcs/EvcsModel.scala | 179 +++++++++++----- .../EvcsAgentModelCalculationSpec.scala | 191 ++++++------------ .../participant/evcs/EvcsModelSpec.scala | 48 +++-- 3 files changed, 231 insertions(+), 187 deletions(-) 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 761d660a09..409a296864 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 @@ -333,6 +333,7 @@ final case class EvcsModel( // the current tick excluded .incl(lastTick) .excl(currentTick) + // FIXME departing EVs don't get final results // in order to create 0kW entries for EVs that do not // start charging right away at lastTick, create mock @@ -700,9 +701,9 @@ final case class EvcsModel( sum.add(power) } - val (maxCharging, maxDischarging) = - preferredScheduling.foldLeft((zeroKW, zeroKW)) { - case ((chargingSum, dischargingSum), (ev, _)) => + val (maxCharging, forcedCharging, maxDischarging) = + preferredScheduling.foldLeft((zeroKW, zeroKW, zeroKW)) { + case ((chargingSum, forcedSum, dischargingSum), (ev, _)) => val maxPower = getMaxAvailableChargingPower(ev) val maxCharging = @@ -711,19 +712,36 @@ final case class EvcsModel( else zeroKW + val forcedCharging = + if (isEmpty(ev) && !isInLowerMargin(ev)) + maxPower // TODO maybe use preferred power instead + else + zeroKW + val maxDischarging = if (!isEmpty(ev) && vehicle2grid) maxPower.multiply(-1) else zeroKW - (chargingSum.add(maxCharging), dischargingSum.add(maxDischarging)) + ( + chargingSum.add(maxCharging), + forcedSum.add(forcedCharging), + dischargingSum.add(maxDischarging) + ) } + // if we need to charge at least one EV, we cannot discharge any other + val (adaptedMin, adaptedPreferred) = + if (forcedCharging.isGreaterThan(zeroKW)) + (forcedCharging, preferredPower.max(forcedCharging)) + else + (maxDischarging, preferredPower) + ProvideMinMaxFlexOptions( uuid, - preferredPower, - maxDischarging, + adaptedPreferred, + adaptedMin, maxCharging ) } @@ -755,22 +773,40 @@ final case class EvcsModel( !isEmpty(ev) } - val chargingSchedule = - createScheduleWithSetPower(data.tick, applicableEvs, setPower) + val (forcedChargingEvs, regularChargingEvs) = + if (setPower.isGreaterThan(zeroKW)) + // lower margin is excluded since charging is not required here anymore + applicableEvs.partition { ev => + isEmpty(ev) && !isInLowerMargin(ev) + } + else + (Set.empty[EvModel], applicableEvs) - val scheduleAtNextActivation = chargingSchedule - .map { case (_, _, _, scheduleAtNext) => - scheduleAtNext - } + val (forcedSchedules, remainingPower) = + createScheduleWithSetPower(data.tick, forcedChargingEvs, setPower) + + val (regularSchedules, _) = + createScheduleWithSetPower(data.tick, regularChargingEvs, remainingPower) + + val combinedSchedules = forcedSchedules ++ regularSchedules + + val schedulesOnly = combinedSchedules.flatMap { case (_, scheduleOpt) => + scheduleOpt + } + + val scheduleAtNextActivation = schedulesOnly + .map { case (_, _, scheduleAtNext) => scheduleAtNext } .reduceOption(_ || _) .getOrElse(false) - val nextScheduledTick = chargingSchedule.map { case (_, _, endTick, _) => + val nextScheduledTick = schedulesOnly.map { case (_, endTick, _) => endTick }.minOption - val allSchedules = chargingSchedule.map { case (ev, schedule, _, _) => - ev -> Some(schedule) + val allSchedules = combinedSchedules.map { + case (ev, Some((schedule, _, _))) => + ev -> Some(schedule) + case (ev, None) => ev -> None }.toMap ++ otherEvs.map(_ -> None).toMap ( @@ -786,13 +822,32 @@ final case class EvcsModel( ) } + /** @param currentTick + * The current tick + * @param evs + * The collection of EVs to assign charging power to + * @param setPower + * The remaining power to assign to given EVs + * @return + * A set of EV model and possibly charging schedule and activation + * indicators, as well as the remaining power that could not be assigned to + * given EVs + */ private def createScheduleWithSetPower( currentTick: Long, evs: Set[EvModel], setPower: ComparableQuantity[Power] - ): Set[(EvModel, ChargingSchedule, Long, Boolean)] = { + ): ( + Set[(EvModel, Option[(ChargingSchedule, Long, Boolean)])], + ComparableQuantity[Power] + ) = { - if (evs.isEmpty) return Set.empty + if (evs.isEmpty) return (Set.empty, setPower) + + if (QuantityUtil.isEquivalentAbs(setPower, zeroKW, 0d)) { + // No power left. Rest is not charging + return (evs.map { _ -> None }, setPower) + } val proposedPower = setPower.divide(evs.size) @@ -806,20 +861,24 @@ final case class EvcsModel( if (exceedingPowerEvs.isEmpty) { // end of recursion, rest of charging power fits to all - fittingPowerEvs.map { ev => + val results = fittingPowerEvs.map { ev => val chargingTicks = calculateChargingDuration(ev, proposedPower) val endTick = Math.min(currentTick + chargingTicks, ev.getDepartureTick) ( ev, - ChargingSchedule( - ev, - Seq(ChargingSchedule.Entry(currentTick, endTick, proposedPower)) - ), - endTick, - isFull(ev) || isEmpty(ev) + Some( + ChargingSchedule( + ev, + Seq(ChargingSchedule.Entry(currentTick, endTick, proposedPower)) + ), + endTick, + isFull(ev) || isEmpty(ev) || isInLowerMargin(ev) + ) ) - } + }: Set[(EvModel, Option[(ChargingSchedule, Long, Boolean)])] + + (results, zeroKW) } else { // not all evs can be charged with proposed power @@ -838,34 +897,37 @@ final case class EvcsModel( (ev, power, endTick) } - // if there's evs left whose max power has not been exceeded, go on with the recursion - val nextIterationResults = if (fittingPowerEvs.nonEmpty) { + // sum up allocated power + val chargingPowerSum = maxCharged.foldLeft(zeroKW) { + case (powerSum, (_, chargingPower, _)) => + powerSum.add(chargingPower) + } - // sum up allocated power - val chargingPowerSum = maxCharged.foldLeft(zeroKW) { - case (powerSum, (_, chargingPower, _)) => - powerSum.add(chargingPower) - } + val remainingAfterAllocation = setPower.subtract(chargingPowerSum) - // go into the next recursion step with the remaining power + // go into the next recursion step with the remaining power + val (nextIterationResults, remainingAfterRecursion) = createScheduleWithSetPower( currentTick, fittingPowerEvs, - setPower.subtract(chargingPowerSum) + remainingAfterAllocation ) - } else Set.empty - maxCharged.map { case (ev, power, endTick) => + val combinedResults = maxCharged.map { case (ev, power, endTick) => ( ev, - ChargingSchedule( - ev, - Seq(ChargingSchedule.Entry(currentTick, endTick, power)) - ), - endTick, - isFull(ev) || isEmpty(ev) + Some( + ChargingSchedule( + ev, + Seq(ChargingSchedule.Entry(currentTick, endTick, power)) + ), + endTick, + isFull(ev) || isEmpty(ev) || isInLowerMargin(ev) + ) ) } ++ nextIterationResults + + (combinedResults, remainingAfterRecursion) } } @@ -875,12 +937,20 @@ final case class EvcsModel( power: ComparableQuantity[Power] ): Long = { val timeUntilFullOrEmpty = - if (power.isGreaterThan(zeroKW)) - ev.getEStorage + if (power.isGreaterThan(zeroKW)) { + + // if we're below lowest SOC, flex options will change at that point + val targetEnergy = + if (isEmpty(ev) && !isInLowerMargin(ev)) + ev.getEStorage.multiply(lowestEvSoc) + else + ev.getEStorage + + targetEnergy .subtract(ev.getStoredEnergy) .divide(power) .asType(classOf[Time]) - else + } else ev.getStoredEnergy .subtract(ev.getEStorage.multiply(lowestEvSoc)) .divide(power.multiply(-1)) @@ -913,6 +983,23 @@ final case class EvcsModel( ev.getEStorage.multiply(lowestEvSoc).add(calcToleranceMargin(ev)) ) + /** @param ev + * the ev whose stored energy is to be checked + * @return + * whether the given ev's stored energy is within +- tolerance of the + * minimal charged energy allowed + */ + private def isInLowerMargin(ev: EvModel): Boolean = { + val toleranceMargin = calcToleranceMargin(ev) + val lowestSoc = ev.getEStorage.multiply(lowestEvSoc) + + ev.getStoredEnergy.isLessThanOrEqualTo( + lowestSoc.add(toleranceMargin) + ) && ev.getStoredEnergy.isGreaterThanOrEqualTo( + lowestSoc.subtract(toleranceMargin) + ) + } + private def calcToleranceMargin(ev: EvModel): ComparableQuantity[Energy] = getMaxAvailableChargingPower(ev) .multiply(Quantities.getQuantity(1, SECOND)) @@ -932,6 +1019,8 @@ final case class EvcsModel( data: EvcsRelevantData, lastState: EvcsState ): Set[EvModel] = { + // TODO use Seq instead of Set as return value + // if last state is from before current tick, determine current state val currentEVs = if (lastState.tick < data.tick) 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 a95abcd9c3..69c6919c56 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala @@ -1445,7 +1445,7 @@ class EvcsAgentModelCalculationSpec /* TICK 900 - ev 900 arrives - - charging with 9 kW + - charging with 11 kW */ val ev900 = evA.copyWithDeparture(4500L) @@ -1476,7 +1476,7 @@ class EvcsAgentModelCalculationSpec ) => modelUuid shouldBe evcsInputModel.getUuid referencePower shouldBe ev900.getSRatedAC - minPower shouldBe 0d.asKiloWatt // battery is empty + minPower shouldBe ev900.getSRatedAC // battery is empty maxPower shouldBe ev900.getSRatedAC } @@ -1484,11 +1484,11 @@ class EvcsAgentModelCalculationSpec flexResult.getInputModel shouldBe evcsInputModel.getUuid flexResult.getTime shouldBe 900L.toDateTime flexResult.getpRef should beEquivalentTo(ev900.getSRatedAC) - flexResult.getpMin should beEquivalentTo(0d.asKiloWatt) + flexResult.getpMin should beEquivalentTo(ev900.getSRatedAC) flexResult.getpMax should beEquivalentTo(ev900.getSRatedAC) } - emAgent.send(evcsAgent, IssuePowerCtrl(900L, 9.asKiloWatt)) + emAgent.send(evcsAgent, IssuePowerCtrl(900L, ev900.getSRatedAC)) // at 4500 ev is departing emAgent.expectMsg( @@ -1502,7 +1502,7 @@ class EvcsAgentModelCalculationSpec emAgent.expectMsgType[ParticipantResultEvent] match { case result => result.systemParticipantResult.getP should equalWithTolerance( - 9.asKiloWatt, + ev900.getSRatedAC, testingTolerance ) result.systemParticipantResult.getQ should equalWithTolerance( @@ -1543,7 +1543,7 @@ class EvcsAgentModelCalculationSpec evs.headOption.foreach { ev => ev.getUuid shouldBe ev900.getUuid ev.getStoredEnergy should equalWithTolerance( - 9.asKiloWattHour, + 11.asKiloWattHour, testingTolerance ) } @@ -1554,7 +1554,7 @@ class EvcsAgentModelCalculationSpec case ParticipantResultEvent(result: EvResult) => result.getInputModel shouldBe ev900.getUuid result.getTime shouldBe 900L.toDateTime - result.getP should beEquivalentTo(9d.asKiloWatt) + result.getP should beEquivalentTo(ev900.getSRatedAC) result.getQ should beEquivalentTo(0d.asMegaVar) result.getSoc should beEquivalentTo(0d.asPercent) } @@ -1563,7 +1563,7 @@ class EvcsAgentModelCalculationSpec case ParticipantResultEvent(result: EvcsResult) => result.getInputModel shouldBe evcsInputModel.getUuid result.getTime shouldBe 900L.toDateTime - result.getP should beEquivalentTo(9d.asKiloWatt) + result.getP should beEquivalentTo(ev900.getSRatedAC) result.getQ should beEquivalentTo(0d.asMegaVar) } @@ -1599,7 +1599,7 @@ class EvcsAgentModelCalculationSpec ) => modelUuid shouldBe evcsInputModel.getUuid referencePower shouldBe ev4500.getSRatedAC - minPower shouldBe 0d.asKiloWatt // battery is empty + minPower shouldBe ev900.getSRatedAC // battery is empty maxPower shouldBe ev4500.getSRatedAC } @@ -1607,20 +1607,20 @@ class EvcsAgentModelCalculationSpec flexResult.getInputModel shouldBe evcsInputModel.getUuid flexResult.getTime shouldBe 4500L.toDateTime flexResult.getpRef should beEquivalentTo(ev4500.getSRatedAC) - flexResult.getpMin should beEquivalentTo(0d.asKiloWatt) + flexResult.getpMin should beEquivalentTo(ev4500.getSRatedAC) flexResult.getpMax should beEquivalentTo(ev4500.getSRatedAC) } emAgent.send(evcsAgent, IssueNoCtrl(4500L)) // we currently have an empty battery in ev4500 - // time to charge fully ~= 7.2727273h = 26182 ticks (rounded) from now - // current tick is 4500, thus: 4500 + 26182 = 30682 + // time to charge to minimal soc ~= 1.45454545455h = 5236 ticks (rounded) from now + // current tick is 4500, thus: 4500 + 5236 = 9736 emAgent.expectMsg( FlexCtrlCompletion( modelUuid = evcsInputModel.getUuid, requestAtNextActivation = true, - requestAtTick = Some(30682L) + requestAtTick = Some(9736L) ) ) @@ -1641,13 +1641,13 @@ class EvcsAgentModelCalculationSpec // already sent out after EV departed resultListener.expectNoMessage() - /* TICK 8100 + /* TICK 9736 - flex control changes - charging with 10 kW */ // sending flex request at very next activated tick - emAgent.send(evcsAgent, RequestFlexOptions(8100L)) + emAgent.send(evcsAgent, RequestFlexOptions(9736L)) emAgent.expectMsgType[ProvideFlexOptions] match { case ProvideMinMaxFlexOptions( @@ -1658,30 +1658,29 @@ class EvcsAgentModelCalculationSpec ) => modelUuid shouldBe evcsInputModel.getUuid referencePower shouldBe ev4500.getSRatedAC - minPower shouldBe 0d.asKiloWatt // battery is still below lowest soc for discharging + minPower shouldBe 0d.asKiloWatt // battery is exactly at margin maxPower shouldBe ev4500.getSRatedAC } resultListener.expectMsgPF() { case FlexOptionsResultEvent(flexResult) => flexResult.getInputModel shouldBe evcsInputModel.getUuid - flexResult.getTime shouldBe 8100L.toDateTime + flexResult.getTime shouldBe 9736L.toDateTime flexResult.getpRef should beEquivalentTo(ev4500.getSRatedAC) flexResult.getpMin should beEquivalentTo(0d.asKiloWatt) flexResult.getpMax should beEquivalentTo(ev4500.getSRatedAC) } - emAgent.send(evcsAgent, IssuePowerCtrl(8100L, 10.asKiloWatt)) + emAgent.send(evcsAgent, IssuePowerCtrl(9736L, 10.asKiloWatt)) evService.expectNoMessage() - // ev4500 is now at 11 kWh - // time to charge fully = 6.9 h = 24840 ticks from now - // current tick is 8100, thus: 8100 + 24840 = 32940 + // ev4500 is now at 16 kWh + // time to charge fully = 6.4 h = 23040 ticks from now + // current tick is 9736, thus: 9736 + 23040 = 32776 emAgent.expectMsg( FlexCtrlCompletion( modelUuid = evcsInputModel.getUuid, - requestAtTick = Some(32940L), - revokeRequestAtTick = Some(30682L), + requestAtTick = Some(32776L), requestAtNextActivation = true // since battery is still below lowest soc, it's still considered empty ) @@ -1721,7 +1720,8 @@ class EvcsAgentModelCalculationSpec - charging with 16 kW */ - val ev11700 = evA.copyWithDeparture(36000L) + // with stored energy right at minimal SOC + val ev11700 = evA.copyWithDeparture(36000L).copyWith(11.6d.asKiloWattHour) val activation3 = 3L @@ -1778,16 +1778,15 @@ class EvcsAgentModelCalculationSpec // no departing evs here evService.expectNoMessage() - // ev4500 is now at 21 kWh, ev11700 just arrived - // ev4500: time to charge fully = 7.375 h = 26550 ticks from now - // ev11700: time to charge fully = 7.25 h = 26100 ticks from now - // current tick is 11700, thus: 11700 + 26100 = 37800 - // BUT: departing tick 36000 is earlier + // ev4500 is now at ~ 21.45555556 kWh, ev11700 just arrived with 11.6 kWh + // ev4500: time to charge fully ~= 7.3180556 h = 26345 ticks from now + // ev11700: time to charge fully = 5.8 h = 20880 ticks from now + // current tick is 11700, thus: 11700 + 20880 = 32580 emAgent.expectMsg( FlexCtrlCompletion( modelUuid = evcsInputModel.getUuid, - requestAtTick = Some(36000L), - revokeRequestAtTick = Some(32940L), + requestAtTick = Some(32580L), + revokeRequestAtTick = Some(32776L), requestAtNextActivation = true // since battery is still below lowest soc, it's still considered empty ) @@ -1810,16 +1809,16 @@ class EvcsAgentModelCalculationSpec resultListener.expectMsgPF() { case ParticipantResultEvent(result: EvResult) => result.getInputModel shouldBe ev4500.getUuid - result.getTime shouldBe 8100L.toDateTime + result.getTime shouldBe 9736L.toDateTime result.getP should beEquivalentTo(10d.asKiloWatt) result.getQ should beEquivalentTo(0d.asMegaVar) - result.getSoc should beEquivalentTo(13.75d.asPercent) + result.getSoc should beEquivalentTo(20d.asPercent, 1e-2) } resultListener.expectMsgPF() { case ParticipantResultEvent(result: EvcsResult) => result.getInputModel shouldBe evcsInputModel.getUuid - result.getTime shouldBe 8100L.toDateTime + result.getTime shouldBe 9736L.toDateTime result.getP should beEquivalentTo(10d.asKiloWatt) result.getQ should beEquivalentTo(0d.asMegaVar) } @@ -1864,15 +1863,15 @@ class EvcsAgentModelCalculationSpec // no departing evs here evService.expectNoMessage() - // ev4500 is now at 35 kWh, ev11700 at 14 kWh - // ev4500: time to discharge to lowest soc = 1.9 h = 6840 ticks from now - // ev11700: time to discharge fully = 0.24 h = 864 ticks from now - // current tick is 18000, thus: 18000 + 864 = 18864 + // ev4500 is now at ~ 35.455556 kWh, ev11700 at 25.6 kWh + // ev4500: time to discharge to lowest soc ~= 1.9455556 h = 7004 ticks from now + // ev11700: time to discharge to lowest soc ~= 1.4 h = 5040 ticks from now + // current tick is 18000, thus: 18000 + 5040 = 23040 emAgent.expectMsg( FlexCtrlCompletion( modelUuid = evcsInputModel.getUuid, - requestAtTick = Some(18864L), - revokeRequestAtTick = Some(36000L) + requestAtTick = Some(23040L), + revokeRequestAtTick = Some(32580L) ) ) @@ -1898,13 +1897,13 @@ class EvcsAgentModelCalculationSpec result.getTime shouldBe 11700L.toDateTime result.getP should beEquivalentTo(8d.asKiloWatt) result.getQ should beEquivalentTo(0d.asMegaVar) - result.getSoc should beEquivalentTo(26.25d.asPercent) + result.getSoc should beEquivalentTo(26.819d.asPercent, 1e-2) case ParticipantResultEvent(result: EvResult) if result.getInputModel == ev11700.getUuid => result.getTime shouldBe 11700L.toDateTime result.getP should beEquivalentTo(8d.asKiloWatt) result.getQ should beEquivalentTo(0d.asMegaVar) - result.getSoc should beEquivalentTo(0d.asPercent) + result.getSoc should beEquivalentTo(20.0d.asPercent) } resultListener.expectMsgPF() { @@ -1915,12 +1914,12 @@ class EvcsAgentModelCalculationSpec result.getQ should beEquivalentTo(0d.asMegaVar) } - /* TICK 18864 + /* TICK 23040 - ev11700 at lowest soc - discharging with 10 kW */ - emAgent.send(evcsAgent, RequestFlexOptions(18864L)) + emAgent.send(evcsAgent, RequestFlexOptions(23040L)) emAgent.expectMsgType[ProvideFlexOptions] match { case ProvideMinMaxFlexOptions( @@ -1939,7 +1938,7 @@ class EvcsAgentModelCalculationSpec resultListener.expectMsgPF() { case FlexOptionsResultEvent(flexResult) => flexResult.getInputModel shouldBe evcsInputModel.getUuid - flexResult.getTime shouldBe 18864L.toDateTime + flexResult.getTime shouldBe 23040L.toDateTime flexResult.getpRef should beEquivalentTo(combinedChargingPower) flexResult.getpMin should beEquivalentTo( ev4500.getSRatedAC.multiply( @@ -1949,18 +1948,18 @@ class EvcsAgentModelCalculationSpec flexResult.getpMax should beEquivalentTo(combinedChargingPower) } - emAgent.send(evcsAgent, IssuePowerCtrl(18864L, (-10).asKiloWatt)) + emAgent.send(evcsAgent, IssuePowerCtrl(23040L, (-10).asKiloWatt)) // no departing evs here evService.expectNoMessage() - // ev4500 is now at 32.6 kWh, ev11700 at 11.6 kWh (lowest soc) - // ev4500: time to discharge to lowest soc = 1.66 h = 5976 ticks from now - // current tick is 18864, thus: 18864 + 5976 = 24840 + // ev4500 is now at 21.455556 kWh, ev11700 at 11.6 kWh (lowest soc) + // ev4500: time to discharge to lowest soc = 0.5455556 h = 1964 ticks from now + // current tick is 18864, thus: 23040 + 1964 = 25004 emAgent.expectMsg( FlexCtrlCompletion( modelUuid = evcsInputModel.getUuid, - requestAtTick = Some(24840L) + requestAtTick = Some(25004L) ) ) @@ -1986,13 +1985,13 @@ class EvcsAgentModelCalculationSpec result.getTime shouldBe 18000L.toDateTime result.getP should beEquivalentTo((-10d).asKiloWatt) result.getQ should beEquivalentTo(0d.asMegaVar) - result.getSoc should beEquivalentTo(43.75d.asPercent) + result.getSoc should beEquivalentTo(44.3194d.asPercent, 1e-2) case ParticipantResultEvent(result: EvResult) if result.getInputModel == ev11700.getUuid => result.getTime shouldBe 18000L.toDateTime result.getP should beEquivalentTo((-10d).asKiloWatt) result.getQ should beEquivalentTo(0d.asMegaVar) - result.getSoc should beEquivalentTo(24.137931034483d.asPercent) + result.getSoc should beEquivalentTo(44.137931034d.asPercent, 1e-6) } resultListener.expectMsgPF() { @@ -2003,72 +2002,12 @@ class EvcsAgentModelCalculationSpec result.getQ should beEquivalentTo(0d.asMegaVar) } - /* TICK 21600 - - changing flex control - - discharging with 9 kW - */ - - emAgent.send(evcsAgent, IssuePowerCtrl(21600L, (-9).asKiloWatt)) - - // no departing evs here - evService.expectNoMessage() - - // ev4500 is now at 25 kWh - // time to discharge to lowest soc = 1 h = 3600 ticks from now - // current tick is 18864, thus: 21600 + 3600 = 25200 - emAgent.expectMsg( - FlexCtrlCompletion( - modelUuid = evcsInputModel.getUuid, - requestAtTick = Some(25200L), - revokeRequestAtTick = Some(24840L) - ) - ) - - emAgent.expectMsgType[ParticipantResultEvent] match { - case result => - result.systemParticipantResult.getP should equalWithTolerance( - (-9).asKiloWatt, - testingTolerance - ) - result.systemParticipantResult.getQ should equalWithTolerance( - 0.asMegaVar, - testingTolerance - ) - } - - Range(0, 2) - .map { _ => - resultListener.expectMsgType[ParticipantResultEvent] - } - .foreach { - case ParticipantResultEvent(result: EvResult) - if result.getInputModel == ev4500.getUuid => - result.getTime shouldBe 18864L.toDateTime - result.getP should beEquivalentTo((-10d).asKiloWatt) - result.getQ should beEquivalentTo(0d.asMegaVar) - result.getSoc should beEquivalentTo(40.75d.asPercent) - case ParticipantResultEvent(result: EvResult) - if result.getInputModel == ev11700.getUuid => - result.getTime shouldBe 18864L.toDateTime - result.getP should beEquivalentTo(0d.asKiloWatt) - result.getQ should beEquivalentTo(0d.asMegaVar) - result.getSoc should beEquivalentTo(20d.asPercent) - } - - resultListener.expectMsgPF() { - case ParticipantResultEvent(result: EvcsResult) => - result.getInputModel shouldBe evcsInputModel.getUuid - result.getTime shouldBe 18864L.toDateTime - result.getP should beEquivalentTo((-10d).asKiloWatt) - result.getQ should beEquivalentTo(0d.asMegaVar) - } - - /* TICK 25200 + /* TICK 25004L - both evs at lowest soc - no power */ - emAgent.send(evcsAgent, RequestFlexOptions(25200L)) + emAgent.send(evcsAgent, RequestFlexOptions(25004L)) emAgent.expectMsgType[ProvideFlexOptions] match { case ProvideMinMaxFlexOptions( @@ -2085,13 +2024,13 @@ class EvcsAgentModelCalculationSpec resultListener.expectMsgPF() { case FlexOptionsResultEvent(flexResult) => flexResult.getInputModel shouldBe evcsInputModel.getUuid - flexResult.getTime shouldBe 25200L.toDateTime + flexResult.getTime shouldBe 25004L.toDateTime flexResult.getpRef should beEquivalentTo(combinedChargingPower) flexResult.getpMin should beEquivalentTo(0.asKiloWatt) flexResult.getpMax should beEquivalentTo(combinedChargingPower) } - emAgent.send(evcsAgent, IssuePowerCtrl(25200L, 0.asKiloWatt)) + emAgent.send(evcsAgent, IssuePowerCtrl(25004L, 0.asKiloWatt)) // no departing evs here evService.expectNoMessage() @@ -2122,13 +2061,13 @@ class EvcsAgentModelCalculationSpec .foreach { case ParticipantResultEvent(result: EvResult) if result.getInputModel == ev4500.getUuid => - result.getTime shouldBe 21600L.toDateTime - result.getP should beEquivalentTo((-9d).asKiloWatt) + result.getTime shouldBe 23040L.toDateTime + result.getP should beEquivalentTo((-10d).asKiloWatt) result.getQ should beEquivalentTo(0d.asMegaVar) - result.getSoc should beEquivalentTo(31.25d.asPercent) + result.getSoc should beEquivalentTo(26.819445d.asPercent, 1e-2) case ParticipantResultEvent(result: EvResult) if result.getInputModel == ev11700.getUuid => - result.getTime shouldBe 21600L.toDateTime + result.getTime shouldBe 23040L.toDateTime result.getP should beEquivalentTo(0d.asKiloWatt) result.getQ should beEquivalentTo(0d.asMegaVar) result.getSoc should beEquivalentTo(20d.asPercent) @@ -2137,8 +2076,8 @@ class EvcsAgentModelCalculationSpec resultListener.expectMsgPF() { case ParticipantResultEvent(result: EvcsResult) => result.getInputModel shouldBe evcsInputModel.getUuid - result.getTime shouldBe 21600L.toDateTime - result.getP should beEquivalentTo((-9d).asKiloWatt) + result.getTime shouldBe 23040L.toDateTime + result.getP should beEquivalentTo((-10d).asKiloWatt) result.getQ should beEquivalentTo(0d.asMegaVar) } @@ -2176,13 +2115,13 @@ class EvcsAgentModelCalculationSpec .foreach { case ParticipantResultEvent(result: EvResult) if result.getInputModel == ev4500.getUuid => - result.getTime shouldBe 25200L.toDateTime + result.getTime shouldBe 25004L.toDateTime result.getP should beEquivalentTo(0d.asKiloWatt) result.getQ should beEquivalentTo(0d.asMegaVar) - result.getSoc should beEquivalentTo(20d.asPercent) + result.getSoc should beEquivalentTo(20d.asPercent, 1e-2) case ParticipantResultEvent(result: EvResult) if result.getInputModel == ev11700.getUuid => - result.getTime shouldBe 25200L.toDateTime + result.getTime shouldBe 25004L.toDateTime result.getP should beEquivalentTo(0d.asKiloWatt) result.getQ should beEquivalentTo(0d.asMegaVar) result.getSoc should beEquivalentTo(20d.asPercent) @@ -2191,7 +2130,7 @@ class EvcsAgentModelCalculationSpec resultListener.expectMsgPF() { case ParticipantResultEvent(result: EvcsResult) => result.getInputModel shouldBe evcsInputModel.getUuid - result.getTime shouldBe 25200L.toDateTime + result.getTime shouldBe 25004L.toDateTime result.getP should beEquivalentTo(0d.asKiloWatt) result.getQ should beEquivalentTo(0d.asMegaVar) } diff --git a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala index b28b4ad0b8..ee56f9e3b9 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala @@ -434,19 +434,35 @@ class EvcsModelSpec /* 1: empty */ // 2: empty - (0.0, 0.0, 0.0, 15.0, 0.0, 15.0), + (0.0, 0.0, 0.0, 15.0, 15.0, 15.0), + // 2: at lower margin + (0.0, 3.0, 0.0, 15.0, 10.0, 15.0), // 2: mid-way full (charged to 7.5 kWh) - (0.0, 0.0, 5.0, 15.0, -5.0, 15.0), + (0.0, 0.0, 5.0, 15.0, 10.0, 15.0), // 2: mid-way full (set to 7.5 kWh) - (0.0, 7.5, 0.0, 15.0, -5.0, 15.0), + (0.0, 7.5, 0.0, 15.0, 10.0, 15.0), // 2: almost full (12.5 kWh) - (0.0, 5.0, 5.0, 12.5, -5.0, 15.0), + (0.0, 5.0, 5.0, 12.5, 10.0, 15.0), // 2: full (set) - (0.0, 15.0, 0.0, 10.0, -5.0, 10.0), + (0.0, 15.0, 0.0, 10.0, 10.0, 10.0), + + /* 1: at lower margin (set to 2 kWh) */ + // 2: empty + (2.0, 0.0, 0.0, 13.0, 5.0, 15.0), + // 2: at lower margin + (2.0, 3.0, 0.0, 13.0, 0.0, 15.0), + // 2: mid-way full (charged to 7.5 kWh) + (2.0, 0.0, 5.0, 13.0, -5.0, 15.0), + // 2: mid-way full (set to 7.5 kWh) + (2.0, 7.5, 0.0, 13.0, -5.0, 15.0), + // 2: almost full (12.5 kWh) + (2.0, 5.0, 5.0, 10.5, -5.0, 15.0), + // 2: full (set) + (2.0, 15.0, 0.0, 8.0, -5.0, 10.0), /* 1: mid-way full (set to 5 kWh) */ // 2: empty - (5.0, 0.0, 0.0, 10.0, -10.0, 15.0), + (5.0, 0.0, 0.0, 10.0, 5.0, 15.0), // 2: mid-way full (charged to 7.5 kWh) (5.0, 0.0, 5.0, 10.0, -15.0, 15.0), // 2: mid-way full (set to 7.5 kWh) @@ -458,7 +474,7 @@ class EvcsModelSpec /* 1: full (set to 10 kWh) */ // 2: empty - (10.0, 0.0, 0.0, 5.0, -10.0, 5.0), + (10.0, 0.0, 0.0, 5.0, 5.0, 5.0), // 2: mid-way full (charged to 7.5 kWh) (10.0, 0.0, 5.0, 5.0, -15.0, 5.0), // 2: mid-way full (set to 7.5 kWh) @@ -609,21 +625,21 @@ class EvcsModelSpec (10.0, 15.0, 0.0, N, N, false, N), /* setPower is positive (charging) */ - (0.0, 0.0, 4.0, S(2.0, 7200L), S(2.0, 10800L), true, S(7200L)), - (5.0, 0.0, 4.0, S(2.0, 7200L), S(2.0, 10800L), true, S(7200L)), - (0.0, 7.5, 4.0, S(2.0, 7200L), S(2.0, 10800L), true, S(7200L)), - (9.0, 0.0, 4.0, S(2.0, 5400L), S(2.0, 10800L), true, S(5400L)), + (0.0, 0.0, 4.0, S(2.0, 7200L), S(2.0, 9000L), true, S(7200L)), + (5.0, 0.0, 4.0, N, S(4.0, 6300L), true, S(6300L)), + (0.0, 7.5, 4.0, S(4.0, 5400L), N, true, S(5400L)), + (9.0, 0.0, 4.0, N, S(4.0, 6300L), true, S(6300L)), (5.0, 14.0, 4.0, S(2.0, 7200L), S(2.0, 5400L), false, S(5400L)), (9.0, 14.0, 4.0, S(2.0, 5400L), S(2.0, 5400L), false, S(5400L)), (10.0, 14.0, 4.0, N, S(4.0, 4500L), false, S(4500L)), (6.0, 15.0, 4.0, S(4.0, 7200L), N, false, S(7200L)), /* setPower is set to > (ev2 * 2) (charging) */ - (0.0, 0.0, 13.0, S(8.0, 7200L), S(5.0, 10800L), true, S(7200L)), - (7.0, 0.0, 11.0, S(6.0, 5400L), S(5.0, 10800L), true, S(5400L)), - (0.0, 5.0, 15.0, S(10.0, 7200L), S(5.0, 10800L), true, S(7200L)), - (0.0, 12.5, 15.0, S(10.0, 7200L), S(5.0, 5400L), true, S(5400L)), - (0.0, 0.0, 15.0, S(10.0, 7200L), S(5.0, 10800L), true, S(7200L)), + (0.0, 0.0, 13.0, S(8.0, 4500L), S(5.0, 5760L), true, S(4500L)), + (7.0, 0.0, 11.0, S(6.0, 5400L), S(5.0, 5760L), true, S(5400L)), + (0.0, 5.0, 15.0, S(10.0, 4320L), S(5.0, 10800L), true, S(4320L)), + (0.0, 12.5, 15.0, S(10.0, 4320L), S(5.0, 5400L), true, S(4320L)), + (0.0, 0.0, 15.0, S(10.0, 4320L), S(5.0, 5760L), true, S(4320L)), (5.0, 7.5, 15.0, S(10.0, 5400L), S(5.0, 9000L), false, S(5400L)), /* setPower is negative (discharging) */ From 707178aa025e42ea4ac9718cc2ec651f2d6bc0f5 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Sun, 6 Nov 2022 21:55:00 +0100 Subject: [PATCH 16/17] EvResults for EVs that are departing at the current tick --- .../model/participant/evcs/EvcsModel.scala | 24 +++++- .../EvcsAgentModelCalculationSpec.scala | 28 ++++--- .../participant/evcs/EvcsModelSpec.scala | 79 ++++++++++++++++++- 3 files changed, 116 insertions(+), 15 deletions(-) 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 409a296864..f031e95f0b 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 @@ -333,7 +333,6 @@ final case class EvcsModel( // the current tick excluded .incl(lastTick) .excl(currentTick) - // FIXME departing EVs don't get final results // in order to create 0kW entries for EVs that do not // start charging right away at lastTick, create mock @@ -342,7 +341,7 @@ final case class EvcsModel( _ -> ChargingSchedule.Entry(lastTick, lastTick, 0d.asKiloWatt) } - val (_, _, evResults, evcsResults) = + val (currentEvs, currentSchedules, evResults, evcsResults) = startAndStopTicks.foldLeft( lastEvMap, startingSchedules, @@ -426,7 +425,26 @@ final case class EvcsModel( ) } - (evResults, evcsResults) + // special case: also add EVs that are departing at current tick + // because they won't be included when the next results are created + val departingEvResults = currentSchedules + .map { case evUuid -> _ => + currentEvs(evUuid) + } + .filter { + // only take those that are departing now + _.getDepartureTick.toLong == currentTick + } + .map { + createEvResult( + _, + currentTick, + 0d.asKiloWatt, + voltageMagnitude + ) + } + + (evResults ++ departingEvResults, evcsResults) } private def createEvResult( 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 69c6919c56..d2ca00831c 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala @@ -1488,7 +1488,7 @@ class EvcsAgentModelCalculationSpec flexResult.getpMax should beEquivalentTo(ev900.getSRatedAC) } - emAgent.send(evcsAgent, IssuePowerCtrl(900L, ev900.getSRatedAC)) + emAgent.send(evcsAgent, IssueNoCtrl(900L)) // at 4500 ev is departing emAgent.expectMsg( @@ -1550,14 +1550,24 @@ class EvcsAgentModelCalculationSpec } // results arrive right after departure request - resultListener.expectMsgPF() { - case ParticipantResultEvent(result: EvResult) => - result.getInputModel shouldBe ev900.getUuid - result.getTime shouldBe 900L.toDateTime - result.getP should beEquivalentTo(ev900.getSRatedAC) - result.getQ should beEquivalentTo(0d.asMegaVar) - result.getSoc should beEquivalentTo(0d.asPercent) - } + Range(0, 2) + .map { _ => + resultListener.expectMsgType[ParticipantResultEvent] + } + .foreach { + case ParticipantResultEvent(result: EvResult) + if result.getTime.equals(900L.toDateTime) => + result.getInputModel shouldBe ev900.getUuid + result.getP should beEquivalentTo(ev900.getSRatedAC) + result.getQ should beEquivalentTo(0d.asMegaVar) + result.getSoc should beEquivalentTo(0d.asPercent) + case ParticipantResultEvent(result: EvResult) + if result.getTime.equals(4500L.toDateTime) => + result.getInputModel shouldBe ev900.getUuid + result.getP should beEquivalentTo(0d.asKiloWatt) + result.getQ should beEquivalentTo(0d.asMegaVar) + result.getSoc should beEquivalentTo(18.96551724137931d.asPercent) + } resultListener.expectMsgPF() { case ParticipantResultEvent(result: EvcsResult) => diff --git a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala index ee56f9e3b9..05e186989f 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/evcs/EvcsModelSpec.scala @@ -207,7 +207,7 @@ class EvcsModelSpec 10.0.asKiloWatt, 10.0.asKiloWattHour, 0d.asKiloWattHour, - 7200L // is ignored here + 10800L ) val schedule = ChargingSchedule( @@ -302,7 +302,7 @@ class EvcsModelSpec 10.0.asKiloWatt, 10.0.asKiloWattHour, 0d.asKiloWattHour, - 7200L // is ignored here + 18000L ) val ev2 = new MockEvModel( @@ -312,7 +312,7 @@ class EvcsModelSpec 10.0.asKiloWatt, 10.0.asKiloWattHour, 0d.asKiloWattHour, - 7200L // is ignored here + 18000L ) val schedule1 = ChargingSchedule( @@ -407,6 +407,79 @@ class EvcsModelSpec } } + "EV is departing at current tick" in { + + val ev = new MockEvModel( + UUID.randomUUID(), + "TestEv", + 5.0.asKiloWatt, // using AC charging here + 10.0.asKiloWatt, + 10.0.asKiloWattHour, + 0d.asKiloWattHour, + 7200L // equals the current tick + ) + + val schedule = ChargingSchedule( + ev, + Seq( + Entry(3600L, 7200L, 2d.asKiloWatt) + ) + ) + + val lastTick = 1800L + val currentTick = 7200L + + val lastState = EvcsState( + Set(ev), + Map(ev -> Some(schedule)), + lastTick + ) + + val (actualEvResults, actualEvcsResults) = + evcsStandardModel.createResults( + lastState, + currentTick, + 1d.asPu + ) + + // tick, p in kW, soc in % + val expectedEvResults = + Seq( + (1800L, 0d, 0d), + (3600L, 2d, 0d), + // this result normally does not appear + // if EV does not depart at current tick + (7200L, 0d, 20d) + ) + + // tick, p in kW + val expectedEvcsResults = + Seq( + (1800L, 0d), + (3600L, 2d) + ) + + actualEvResults should have size expectedEvResults.size + + actualEvResults should have size expectedEvResults.size + actualEvResults.zip(expectedEvResults).foreach { + case (actual, (startTick, p, soc)) => + actual.getTime shouldBe startTick.toDateTime(simulationStart) + actual.getP should beEquivalentTo(p.asKiloWatt) + actual.getQ should beEquivalentTo(zeroKW) + actual.getSoc should beEquivalentTo(soc.asPercent) + } + + actualEvcsResults should have size expectedEvcsResults.size + actualEvcsResults.zip(expectedEvcsResults).foreach { + case (actual, (startTick, p)) => + actual.getTime shouldBe startTick.toDateTime(simulationStart) + actual.getInputModel shouldBe evcsStandardModel.getUuid + actual.getP should beEquivalentTo(p.asKiloWatt) + actual.getQ should beEquivalentTo(zeroKW) + } + } + } "handle flexibility correctly" when { From f182f158d14e7cf38b303bf0eec744220e84e543 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Sun, 6 Nov 2022 21:56:02 +0100 Subject: [PATCH 17/17] Renaming chargedEnergyInScheduleSlice to chargedEnergyInScheduleEntry --- .../ie3/simona/model/participant/evcs/EvcsModel.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 f031e95f0b..b371c8d882 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 @@ -265,7 +265,7 @@ final case class EvcsModel( /* Determine the energy charged within this slice of the schedule and accumulate it */ accumulatedEnergy.add( - chargedEnergyInScheduleSlice(trimmedEntry) + chargedEnergyInScheduleEntry(trimmedEntry) ) } /* Update EV with the charged energy during the charging interval */ @@ -388,7 +388,7 @@ final case class EvcsModel( // update EV val newEvStoredEnergy = ev.getStoredEnergy.add( - chargedEnergyInScheduleSlice(entry) + chargedEnergyInScheduleEntry(entry) ) val newEv = ev.copyWith(newEvStoredEnergy) @@ -499,11 +499,11 @@ final case class EvcsModel( * interval. * * @param scheduleEntry - * Definition of the schedule slice + * The schedule entry * @return - * The energy charged during this slice + * The energy charged during the time interval of the schedule entry */ - private def chargedEnergyInScheduleSlice( + private def chargedEnergyInScheduleEntry( scheduleEntry: ChargingSchedule.Entry ): ComparableQuantity[Energy] = scheduleEntry.chargingPower