diff --git a/CHANGELOG.md b/CHANGELOG.md index 10e0437e1a..1b9459a027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated dependabot reviewers [#888](https://github.com/ie3-institute/simona/issues/888) - Merged `HpModelTestData` with `HpTestData` to `HpInputTestData` [#872](https://github.com/ie3-institute/simona/issues/872) - Harmonised both methods that check the inner temperature of thermal house against the boundaries [#880](https://github.com/ie3-institute/simona/issues/880) +- Convert all `eval-rst` instances in rtd to myst syntax [#901](https://github.com/ie3-institute/simona/issues/901) +- External simulation should provide information about next tick of MobSim [#776](https://github.com/ie3-institute/simona/issues/776) - Prepare ThermalStorageTestData for Storage without storageVolumeLvlMin [#894](https://github.com/ie3-institute/simona/issues/894) ### Fixed diff --git a/build.gradle b/build.gradle index 50c430f3d4..8974152e9b 100644 --- a/build.gradle +++ b/build.gradle @@ -89,7 +89,7 @@ dependencies { exclude group: 'edu.ie3' } - implementation('com.github.ie3-institute:simonaAPI:0.4.0') { + implementation('com.github.ie3-institute:simonaAPI:0.5.0') { exclude group: 'org.apache.logging.log4j' exclude group: 'org.slf4j' /* Exclude our own nested dependencies */ diff --git a/docs/readthedocs/index.md b/docs/readthedocs/index.md index 714d51bcf2..966d0a975d 100644 --- a/docs/readthedocs/index.md +++ b/docs/readthedocs/index.md @@ -1,10 +1,9 @@ # Welcome to simona docs -```{eval-rst} -.. figure:: ../logo/logo_tightcrop_transparent.png - :figwidth: 25% - :align: right - :alt: logo of simona +```{image} ../logo/logo_tightcrop_transparent.png +:width: 25% +:align: right +:alt: logo of simona ``` Welcome to the documentation of simona - an agent-based discrete-event power system simulation model developed at the diff --git a/docs/readthedocs/models/cts_model.md b/docs/readthedocs/models/cts_model.md index 0ca51a7395..5750820624 100644 --- a/docs/readthedocs/models/cts_model.md +++ b/docs/readthedocs/models/cts_model.md @@ -39,9 +39,9 @@ $$ $$ Reference: -```{eval-rst} -* :cite:ts:`Quaschning.2013` -``` + +* {cite:cts}`Quaschning.2013` + That is the mathematical description of loading and unloading processes concerning the buffer storage. Whenever heat is stored within the storage or removed from the storage this equation is used. This includes the case that the whole heat demand is satisfied by the storage. diff --git a/docs/readthedocs/models/pv_model.md b/docs/readthedocs/models/pv_model.md index 8ddb822c0b..b36df95f00 100644 --- a/docs/readthedocs/models/pv_model.md +++ b/docs/readthedocs/models/pv_model.md @@ -53,10 +53,9 @@ $$ **References:** -```{eval-rst} -* :cite:cts:`Maleki.2017` -* :cite:ts:`Spencer.1971` -``` +* {cite:cts}`Maleki.2017` +* {cite:cts}`Spencer.1971` + ### Hour Angle @@ -104,11 +103,10 @@ $$ **References:** -```{eval-rst} -* :cite:cts:`Watter.2013` -* :cite:ts:`Maleki.2017` -* :cite:ts:`Wang.2019` -``` +* {cite:cts}`Watter.2013` +* {cite:cts}`Maleki.2017` +* {cite:cts}`Wang.2019` + ### Sunrise Angle @@ -127,10 +125,10 @@ $$ **$\phi$** = observer's latitude **References:** -```{eval-rst} -* :cite:cts:`Maleki.2017` -* :cite:ts:`Itaca_Sun` -``` + +* {cite:cts}`Maleki.2017` +* {cite:cts}`Itaca_Sun` + ### Solar Altitude Angle @@ -147,10 +145,9 @@ $$ **References:** -```{eval-rst} -* :cite:ts:`Maleki.2017` p. 5 -* :cite:ts:`Itaca_Sun` -``` +* {cite:cts}`Maleki.2017` p. 5 +* {cite:cts}`Itaca_Sun` + ### Zenith Angle @@ -192,10 +189,9 @@ $$ **References:** -```{eval-rst} -* :cite:ts:`Quaschning.2013` -* :cite:ts:`Maleki.2017` p. 18 -``` +* {cite:cts}`Quaschning.2013` +* {cite:cts}`Maleki.2017` p. 18 + ### Air Mass @@ -211,10 +207,9 @@ $$ **References:** -```{eval-rst} -* :cite:ts:`Schoenberg.1929` -* :cite:ts:`WikiAirMass` -``` +* {cite:cts}`Schoenberg.1929` +* {cite:cts}`WikiAirMass` + ### Extraterrestrial Radiation @@ -237,10 +232,9 @@ $$ **References:** -```{eval-rst} -* :cite:ts:`Zheng.2017` p. 53, formula 2.3b -* :cite:ts:`Iqbal.1983` -``` +* {cite:cts}`Zheng.2017` p. 53, formula 2.3b +* {cite:cts}`Iqbal.1983` + ### Beam Radiation on Sloped Surface @@ -296,9 +290,8 @@ $$ **Reference:** -```{eval-rst} -* :cite:ts:`Duffie.2013` p. 88 -``` +* {cite:cts}`Duffie.2013` p. 88 + ### Diffuse Radiation on Sloped Surface @@ -408,11 +401,10 @@ $$ **References:** -```{eval-rst} -* :cite:ts:`Perez.1987` -* :cite:ts:`Perez.1990` -* :cite:ts:`Myers.2017` p. 96f -``` +* {cite:cts}`Perez.1987` +* {cite:cts}`Perez.1990` +* {cite:cts}`Myers.2017` p. 96f + ### Reflected Radiation on Sloped Surface @@ -427,9 +419,8 @@ $$ **$\rho$** = albedo **Reference:** -```{eval-rst} -* :cite:ts:`Maleki.2017` p. 19 -``` + +* {cite:cts}`Maleki.2017` p. 19 ### Output diff --git a/docs/readthedocs/models/reference_system.md b/docs/readthedocs/models/reference_system.md index 28b066bfcc..5a0e944356 100644 --- a/docs/readthedocs/models/reference_system.md +++ b/docs/readthedocs/models/reference_system.md @@ -6,44 +6,41 @@ The reference system is built up by specifying the included voltage levels. The ## Default reference system -```{eval-rst} -.. list-table:: - :widths: 33 33 33 - :header-rows: 0 - - - * - Voltage level (id) - - Nominal voltage (vNom) - - Apparent power (sNom) - - * - LV - - 0.4 kV - - 100 kVA - - * - MV - - 10 kV - - 40 MVA - - * - MV - - 20 kV - - 60 MVA - - * - MV - - 30 kV - - 150 MVA - - * - HV - - 110 kV - - 600 MVA - - * - EHV - - 220 kV - - 800 MVA - - * - EHV - - 380 kV - - 1000 MVA - +```{list-table} +:widths: auto +:header-rows: 1 + +* - Voltage level (id) + - Nominal voltage (vNom) + - Apparent power (sNom) + +* - LV + - 0.4 kV + - 100 kVA + +* - MV + - 10 kV + - 40 MVA + +* - MV + - 20 kV + - 60 MVA + +* - MV + - 30 kV + - 150 MVA + +* - HV + - 110 kV + - 600 MVA + +* - EHV + - 220 kV + - 800 MVA + +* - EHV + - 380 kV + - 1000 MVA ``` diff --git a/docs/readthedocs/models/three_winding_transformer_model.md b/docs/readthedocs/models/three_winding_transformer_model.md index 72a97abba1..d10790848b 100644 --- a/docs/readthedocs/models/three_winding_transformer_model.md +++ b/docs/readthedocs/models/three_winding_transformer_model.md @@ -41,6 +41,4 @@ More details on the physical model transformation can be found in the Ph.D. thes **References:** -```{eval-rst} -:cite:cts:`Kittl_2022` -``` +- {cite:cts}`Kittl_2022` diff --git a/docs/readthedocs/references.md b/docs/readthedocs/references.md index 0aa2febc1e..4e96cb3f9d 100644 --- a/docs/readthedocs/references.md +++ b/docs/readthedocs/references.md @@ -1,26 +1,19 @@ # Publications and References -## Publications +## SIMONA publications The following publications discuss SIMONA and implementation details as well as outcomes where SIMONA have been used: -```{eval-rst} -The following publications discuss SIMONA and implementation details as well as outcomes where SIMONA have been used: - -.. rubric:: SIMONA Publications - -.. bibliography:: _static/bibliography/bibAboutSimona.bib - :style: unsrt - :all: - +```{bibliography} _static/bibliography/bibAboutSimona.bib +:style: unsrt +:all: +``` -References -=============== +## References References of publications SIMONA referred on: -.. rubric:: References -.. bibliography:: _static/bibliography/bibtexAll.bib - :style: custom - :all: +```{bibliography} _static/bibliography/bibtexAll.bib +:style: custom +:all: ``` diff --git a/docs/uml/main/ExtEvSimulationClasses.puml b/docs/uml/main/ExtEvSimulationClasses.puml index e35c8e130a..9543697f03 100644 --- a/docs/uml/main/ExtEvSimulationClasses.puml +++ b/docs/uml/main/ExtEvSimulationClasses.puml @@ -27,7 +27,7 @@ package ev-simulation { package simona-api { ' MIDDLE PART class ExtEvData { - ~ LinkedBlockingQueue receiveTriggerQueue + ~ LinkedBlockingQueue receiveTriggerQueue - ActorRef dataService - ActorRef extSimAdapter + List requestAvailablePublicEvCs() @@ -119,33 +119,33 @@ package simona-api { EvModelImpl --|> EvModel - interface ExtEvMessage + interface EvDataMessageFromExt class EvMovementsMessage { - Map> movements } class RequestEvcsFreeLots - RequestEvcsFreeLots --|> ExtEvMessage - EvMovementsMessage --|> ExtEvMessage + RequestEvcsFreeLots --|> EvDataMessageFromExt + EvMovementsMessage --|> EvDataMessageFromExt RequestEvcsFreeLots -[hidden]> EvMovementsMessage - interface ExtEvResponseMessage + interface EvDataResponseMessageToExt class AllDepartedEvsRepsonse { - Map> movements } class ProvideEvcsFreeLots - ProvideEvcsFreeLots --|> ExtEvResponseMessage - AllDepartedEvsRepsonse --|> ExtEvResponseMessage - ExtEvData -> ExtEvMessage - ExtEvData -> ExtEvResponseMessage + ProvideEvcsFreeLots --|> EvDataResponseMessageToExt + AllDepartedEvsRepsonse --|> EvDataResponseMessageToExt + ExtEvData -> EvDataMessageFromExt + ExtEvData -> EvDataResponseMessageToExt EvMovement -[hidden]-> RequestEvcsFreeLots - ExtEvMessage -[hidden]> ExtEvResponseMessage + EvDataMessageFromExt -[hidden]> EvDataResponseMessageToExt EvMovementsMessage -[hidden]> ProvideEvcsFreeLots ProvideEvcsFreeLots -[hidden]> AllDepartedEvsRepsonse class ScheduleDataServiceMessage { - ExtEvDataService dataService } - ExtEvResponseMessage -[hidden]> ScheduleDataServiceMessage + EvDataResponseMessageToExt -[hidden]> ScheduleDataServiceMessage ExtEvData -> ScheduleDataServiceMessage @@ -158,10 +158,10 @@ package simona-api { ExtLink --|> ExtLinkInterface interface ExtTrigger - class ActivityStartTrigger { + class ActivationMessage { - Long tick } - ActivityStartTrigger --|> ExtTrigger + ActivationMessage --|> ExtTrigger interface ExtTriggerResponse class CompletionMessage { @@ -188,7 +188,7 @@ package simona { class SimonaSim - class SimScheduler + class Scheduler class SimonaStandaloneSetup @@ -198,12 +198,12 @@ package simona { class ExtEvDataService - SimScheduler -- SimonaSim + Scheduler -- SimonaSim SimonaSim *- SimonaStandaloneSetup SimonaStandaloneSetup *- ExtSimLoader - ExtSimAdapter -- SimScheduler - ExtEvDataService -- SimScheduler + ExtSimAdapter -- Scheduler + ExtEvDataService -- Scheduler SecondaryData <|-- EvMovementData diff --git a/docs/uml/protocol/ExtEvSimulationSequence.puml b/docs/uml/protocol/ExtEvSimulationSequence.puml index 470d49981e..dde2f869d6 100644 --- a/docs/uml/protocol/ExtEvSimulationSequence.puml +++ b/docs/uml/protocol/ExtEvSimulationSequence.puml @@ -2,97 +2,201 @@ !theme plain +participant Scheduler +participant ExtSimAdapter +participant ExtSimulation +participant ExtEvDataService +participant EvcsAgent1 +participant EvcsAgent2 + ==Init== -SimScheduler -> ExtSimAdapter: ! ActivityStartTrigger(-1L) +Scheduler -> EvcsAgent1: ! Activation(-1) +activate EvcsAgent1 + +EvcsAgent1 -> ExtEvDataService: ! RegisterForEvDataMessage +deactivate EvcsAgent1 + +Scheduler -> EvcsAgent2: ! Activation(-1) +activate EvcsAgent2 + +EvcsAgent2 -> ExtEvDataService: ! RegisterForEvDataMessage +deactivate EvcsAgent2 + +Scheduler -> ExtSimAdapter: ! Activation(-1) activate ExtSimAdapter -ExtSimAdapter -> ExtSimulation: queue(ActivityStartTrigger(-1L)) +ExtSimAdapter -> ExtSimulation: queue(ActivationMessage(-1)) deactivate ExtSimAdapter activate ExtSimulation + ... Initialize external mobility simulation ... +ExtSimulation -> ExtEvDataService: ProvideArrivingEvs(_, t1) -ExtSimulation -> ExtSimAdapter: ! CompletionMessage(newTriggers) -deactivate ExtSimulation +ExtSimulation -> ExtSimAdapter: ! ScheduleDataServiceMessage(\n\tdataServiceRef\n) activate ExtSimAdapter -ExtSimAdapter -> SimScheduler: ! CompletionMessage(newTriggers) +ExtSimAdapter -> Scheduler: ! ScheduleActivation(\n\t_, dataServiceRef) deactivate ExtSimAdapter +activate Scheduler + +Scheduler -> ExtEvDataService: ! Activation(t1) +deactivate Scheduler +activate ExtEvDataService -==Sim== -SimScheduler -> ExtSimAdapter: ! ActivityStartTrigger(tick) +ExtSimulation -> ExtSimAdapter: ! CompletionMessage(newTriggers) +deactivate ExtSimulation activate ExtSimAdapter -ExtSimAdapter -> ExtSimulation: queue(ActivityStartTrigger(tick)) +ExtSimAdapter -> Scheduler: ! Completion(newTriggers) deactivate ExtSimAdapter -activate ExtSimulation -ExtSimulation -> ExtEvDataService: ! RequestEvcsFreeLots -ExtSimulation -> ExtSimAdapter: ! ScheduleDataServiceMessage(\n\tdataServiceRef\n) - -activate ExtSimAdapter -ExtSimAdapter -> SimScheduler: ! ScheduleTriggerMessage(\n\t_, dataServiceRef) -deactivate ExtSimAdapter +ExtEvDataService -> EvcsAgent1: ! RegistrationSuccessfulMessage(t1) +activate EvcsAgent1 -activate SimScheduler -SimScheduler -> ExtEvDataService: ! ActivityStartTrigger(tick) -deactivate SimScheduler +EvcsAgent1 -> Scheduler: ! Completion(t1) +deactivate EvcsAgent1 -activate ExtEvDataService -ExtEvDataService -> EvcsAgent1: ! EvFreeLotsRequest(tick) -activate EvcsAgent1 -ExtEvDataService -> EvcsAgent2: ! EvFreeLotsRequest(tick) +ExtEvDataService -> EvcsAgent2: ! RegistrationSuccessfulMessage(t1) activate EvcsAgent2 -ExtEvDataService -> SimScheduler: ! CompletionMessage(None) - -EvcsAgent2 -> ExtEvDataService: ! FreeLotsResponse(_, _) +EvcsAgent2 -> Scheduler: ! Completion(t1) deactivate EvcsAgent2 -EvcsAgent1 -> ExtEvDataService: ! FreeLotsResponse(_, _) -deactivate EvcsAgent1 -ExtEvDataService -> ExtSimulation: queue(ProvideEvcsFreeLots(_)) + +ExtEvDataService -> Scheduler: ! Completion(None) deactivate ExtEvDataService -... Running external mobility simulation,\n determining EV positions ... -ExtSimulation -> ExtEvDataService: ! EvMovementsMessage(_) -ExtSimulation -> ExtSimAdapter: ! ScheduleDataServiceMessage(\n\tdataServiceRef\n) +==Simulation== +Scheduler -> EvcsAgent1: ! Activation(t1) +Scheduler -> EvcsAgent2: ! Activation(t1) +Scheduler -> ExtSimAdapter: ! Activation(t1) activate ExtSimAdapter -ExtSimAdapter -> SimScheduler: ! ScheduleTriggerMessage(\n\t_, dataServiceRef) + +ExtSimAdapter -> ExtSimulation: queue(ActivationMessage(t1)) deactivate ExtSimAdapter +activate ExtSimulation -activate SimScheduler -SimScheduler -> ExtEvDataService: ! ActivityStartTrigger(tick) -deactivate SimScheduler +group Request free lots + ExtSimulation -> ExtEvDataService: ! RequestEvcsFreeLots -activate ExtEvDataService -ExtEvDataService -> EvcsAgent1: ! ProvideEvDataMessage(\n\ttick, _) -ExtEvDataService -> EvcsAgent2: ! ProvideEvDataMessage(\n\ttick, _) -ExtEvDataService -> SimScheduler: ! CompletionMessage(evcsTriggers) -deactivate ExtEvDataService + ExtSimulation -> ExtSimAdapter: ! ScheduleDataServiceMessage(\n\tdataServiceRef\n) + activate ExtSimAdapter -activate SimScheduler -SimScheduler -> EvcsAgent1: ! ActivityStartTrigger(tick) -activate EvcsAgent1 -SimScheduler -> EvcsAgent2: ! ActivityStartTrigger(tick) -deactivate SimScheduler + ExtSimAdapter -> Scheduler: ! ScheduleActivation(\n\t_, dataServiceRef) + deactivate ExtSimAdapter + activate Scheduler -activate EvcsAgent2 -EvcsAgent1 -> SimScheduler: ! CompletionMessage(None) -deactivate EvcsAgent1 + Scheduler -> ExtEvDataService: ! Activation(t1) + deactivate Scheduler + activate ExtEvDataService -EvcsAgent2 -> ExtEvDataService: ! DepartedEvsResponse(_, _) -activate ExtEvDataService -EvcsAgent2 -> SimScheduler: ! CompletionMessage(None) -deactivate EvcsAgent2 + ExtEvDataService -> EvcsAgent1: ! EvFreeLotsRequest(t1) + activate EvcsAgent1 -ExtEvDataService -> ExtSimulation: queue(AllDepartedEvsResponse(_)) -deactivate ExtEvDataService + ExtEvDataService -> EvcsAgent2: ! EvFreeLotsRequest(t1) + activate EvcsAgent2 -ExtSimulation -> ExtSimAdapter: ! CompletionMessage(newTriggers) -deactivate ExtSimulation + ExtEvDataService -> Scheduler: ! Completion(None) + + EvcsAgent2 -> ExtEvDataService: ! FreeLotsResponse(_, _) + deactivate EvcsAgent2 + + EvcsAgent1 -> ExtEvDataService: ! FreeLotsResponse(_, _) + deactivate EvcsAgent1 + + ExtEvDataService -> ExtSimulation: queue(ProvideEvcsFreeLots(_)) + deactivate ExtEvDataService +end + +group Request current prices (dummy implementation) + ExtSimulation -> ExtEvDataService: ! RequestCurrentPrices + + ExtSimulation -> ExtSimAdapter: ! ScheduleDataServiceMessage(\n\tdataServiceRef\n) + activate ExtSimAdapter + + ExtSimAdapter -> Scheduler: ! ScheduleActivation(\n\t_, dataServiceRef) + deactivate ExtSimAdapter + activate Scheduler + + Scheduler -> ExtEvDataService: ! Activation(t1) + deactivate Scheduler + activate ExtEvDataService + + ExtEvDataService -> ExtSimulation: queue(ProvideCurrentPrices(_)) + + ExtEvDataService -> Scheduler: ! Completion(None) + + deactivate ExtEvDataService +end + +group Request departing EVs + ExtSimulation -> ExtEvDataService: ! RequestDepartingEvs + + ExtSimulation -> ExtSimAdapter: ! ScheduleDataServiceMessage(\n\tdataServiceRef\n) + activate ExtSimAdapter + + ExtSimAdapter -> Scheduler: ! ScheduleActivation(\n\t_, dataServiceRef) + deactivate ExtSimAdapter + activate Scheduler + + Scheduler -> ExtEvDataService: ! Activation(t1) + deactivate Scheduler + activate ExtEvDataService + ExtEvDataService -> EvcsAgent1: ! DepartingEvsRequest(t1) + activate EvcsAgent1 + + ExtEvDataService -> EvcsAgent2: ! DepartingEvsRequest(t1) + activate EvcsAgent2 + + ExtEvDataService -> Scheduler: ! Completion(None) + + EvcsAgent2 -> ExtEvDataService: ! DepartingEvsResponse(_, _) + deactivate EvcsAgent2 + + EvcsAgent1 -> ExtEvDataService: ! DepartingEvsResponse(_, _) + deactivate EvcsAgent1 + + ExtEvDataService -> ExtSimulation: queue(ProvideDepartingEvs(_)) + deactivate ExtEvDataService +end + +... Running external mobility simulation,\n determining EV positions ... + +group Provide arriving EVs + ExtSimulation -> ExtEvDataService: ! ProvideArrivingEvs(_) + ExtSimulation -> ExtSimAdapter: ! ScheduleDataServiceMessage(\n\tdataServiceRef\n) + activate ExtSimAdapter + + ExtSimAdapter -> Scheduler: ! ScheduleActivation(\n\t_, dataServiceRef) + deactivate ExtSimAdapter + activate Scheduler + + Scheduler -> ExtEvDataService: ! Activation(t1) + deactivate Scheduler + activate ExtEvDataService + + ExtEvDataService -> EvcsAgent1: ! ProvideEvDataMessage(evs, t2) + activate EvcsAgent1 + + ExtEvDataService -> EvcsAgent2: ! ProvideEvDataMessage(evs, t2) + activate EvcsAgent2 + + ExtEvDataService -> Scheduler: ! Completion(None) + deactivate ExtEvDataService + + EvcsAgent1 -> Scheduler: ! Completion(t2) + deactivate EvcsAgent1 + + EvcsAgent2 -> Scheduler: ! Completion(t2) + deactivate EvcsAgent2 +end + +ExtSimulation -> ExtSimAdapter: ! CompletionMessage(t2) +deactivate ExtSimulation activate ExtSimAdapter -ExtSimAdapter -> SimScheduler: ! CompletionMessage(newTriggers) + +ExtSimAdapter -> Scheduler: ! Completion(t2) deactivate ExtSimAdapter @enduml \ No newline at end of file diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala index 5ebfefa55b..f2e78f7c53 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentController.scala @@ -14,7 +14,7 @@ import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.em.EmAgent import edu.ie3.simona.agent.participant.ParticipantAgent.ParticipantMessage import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.{ - ActorEvMovementsService, + ActorExtEvDataService, ActorWeatherService, } import edu.ie3.simona.agent.participant.evcs.EvcsAgent @@ -666,7 +666,7 @@ class GridAgentController( modelConfiguration, primaryServiceProxy, Iterable( - ActorEvMovementsService( + ActorExtEvDataService( evMovementsService ) ), diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index 36803ae3c3..8bfac96858 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -46,7 +46,6 @@ import edu.ie3.simona.model.participant.{ SystemParticipant, } import edu.ie3.simona.ontology.messages.Activation -import edu.ie3.simona.ontology.messages.SchedulerMessage.ScheduleActivation import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ FlexResponse, IssueFlexControl, @@ -60,7 +59,6 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ } import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.scala.quantities.ReactivePower -import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps import org.apache.pekko.actor.typed.{ActorRef => TypedActorRef} import org.apache.pekko.actor.{ActorRef, FSM} import squants.{Dimensionless, Power} @@ -356,28 +354,13 @@ abstract class ParticipantAgent[ isYetTriggered, ), ) => - /* We yet have received at least one data provision message. Handle all messages, that follow up for this tick, by - * adding the received data to the collection state data and checking, if everything is at its place */ - val unexpectedSender = baseStateData.foreseenDataTicks.exists { - case (ref, None) => msg.serviceRef == ref - case _ => false - } - - if (data.contains(msg.serviceRef) || unexpectedSender) { + if (data.contains(msg.serviceRef)) { /* Update the yet received information */ val updatedData = data + (msg.serviceRef -> Some(msg.data)) - /* If we have received unexpected data, we also have not been scheduled before */ - if (unexpectedSender) - scheduler ! ScheduleActivation( - self.toTyped, - msg.tick, - msg.unlockKey, - ) - /* Depending on if a next data tick can be foreseen, either update the entry in the base state data or remove * it */ - val foreSeenDataTicks = + val foreseenDataTicks = baseStateData.foreseenDataTicks + (msg.serviceRef -> msg.nextDataTick) val updatedBaseStateData = BaseStateData.updateBaseStateData( baseStateData, @@ -385,7 +368,7 @@ abstract class ParticipantAgent[ baseStateData.requestValueStore, baseStateData.voltageValueStore, baseStateData.additionalActivationTicks, - foreSeenDataTicks, + foreseenDataTicks, ) val updatedStateData: DataCollectionStateData[PD] = stateData .copy( @@ -612,16 +595,13 @@ abstract class ParticipantAgent[ actorRef -> None } - val unforeseenPossible = - baseStateData.foreseenDataTicks.exists(_._2.isEmpty) - val nextStateData = DataCollectionStateData( baseStateData, expectedSenders, yetTriggered = true, ) - if (expectedSenders.nonEmpty || unforeseenPossible) { + if (expectedSenders.nonEmpty) { /* Do await provision messages in HandleInformation */ goto(HandleInformation) using nextStateData } else { 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 860881a535..ad6941ac8a 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala @@ -71,10 +71,7 @@ import edu.ie3.simona.model.participant.{ SystemParticipant, } import edu.ie3.simona.ontology.messages.Activation -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} +import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ @@ -513,36 +510,6 @@ protected trait ParticipantAgentFundamentals[ } } - val unexpectedSender = baseStateData.foreseenDataTicks.exists { - case (ref, None) => msg.serviceRef == ref - case _ => false - } - - /* If we have received unexpected data, we also have not been scheduled before */ - if (unexpectedSender) { - baseStateData match { - case modelStateData: ParticipantModelBaseStateData[_, _, _, _] => - val maybeEmAgent = modelStateData.flexStateData.map(_.emAgent) - - maybeEmAgent match { - case Some(emAgent) => - emAgent ! ScheduleFlexRequest( - modelStateData.model.getUuid, - msg.tick, - msg.unlockKey, - ) - case None => - scheduler ! ScheduleActivation( - self.toTyped, - msg.tick, - msg.unlockKey, - ) - } - case _ => - false - } - } - /* If the sender announces a new next tick, add it to the list of expected ticks, else remove the current entry */ val foreseenDataTicks = baseStateData.foreseenDataTicks + (msg.serviceRef -> msg.nextDataTick) @@ -1097,9 +1064,9 @@ protected trait ParticipantAgentFundamentals[ false } - // Only for completing initialization: - // if we are EM-managed, there is no new tick for the - // scheduler, since we are activated by the EmAgent from now on + // If we're completing initialization and we're EM-managed: + // There is no new tick for the scheduler, + // since we are activated by the EmAgent from now on scheduler ! Completion( self.toTyped, maybeNextTick.filterNot(_ => emManaged), diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ServiceRegistration.scala b/src/main/scala/edu/ie3/simona/agent/participant/ServiceRegistration.scala index b8b2ef33ad..5308b4e768 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ServiceRegistration.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ServiceRegistration.scala @@ -12,7 +12,7 @@ import edu.ie3.simona.agent.participant.data.Data.PrimaryData.PrimaryDataWithApp import edu.ie3.simona.agent.participant.data.Data.SecondaryData import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.{ - ActorEvMovementsService, + ActorExtEvDataService, ActorPriceService, ActorWeatherService, } @@ -83,8 +83,8 @@ trait ServiceRegistration[ case ActorWeatherService(serviceRef) => registerForWeather(serviceRef, inputModel) Some(serviceRef) - case ActorEvMovementsService(serviceRef) => - registerForEvMovements(serviceRef, inputModel) + case ActorExtEvDataService(serviceRef) => + registerForEvData(serviceRef, inputModel) Some(serviceRef) } @@ -124,7 +124,7 @@ trait ServiceRegistration[ * Input model of the simulation mode * @return */ - private def registerForEvMovements( + private def registerForEvData( serviceRef: ActorRef, inputModel: I, ): Unit = { diff --git a/src/main/scala/edu/ie3/simona/agent/participant/data/secondary/SecondaryDataService.scala b/src/main/scala/edu/ie3/simona/agent/participant/data/secondary/SecondaryDataService.scala index 7d2646b87b..ebac2ad136 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/data/secondary/SecondaryDataService.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/data/secondary/SecondaryDataService.scala @@ -24,6 +24,6 @@ object SecondaryDataService { final case class ActorWeatherService(override val actorRef: ActorRef) extends SecondaryDataService[WeatherData] - final case class ActorEvMovementsService(override val actorRef: ActorRef) + final case class ActorExtEvDataService(override val actorRef: ActorRef) extends SecondaryDataService[EvData] } diff --git a/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgent.scala index b2c428242d..cdab067fa7 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgent.scala @@ -12,15 +12,20 @@ import edu.ie3.simona.agent.participant.data.Data.PrimaryData.{ ZERO_POWER, } import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService -import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.ActorEvMovementsService +import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.ActorExtEvDataService import edu.ie3.simona.agent.participant.statedata.BaseStateData.ParticipantModelBaseStateData -import edu.ie3.simona.agent.participant.statedata.ParticipantStateData +import edu.ie3.simona.agent.participant.statedata.{ + BaseStateData, + DataCollectionStateData, + ParticipantStateData, +} import edu.ie3.simona.agent.participant.statedata.ParticipantStateData.ParticipantInitializeStateData import edu.ie3.simona.agent.participant.{ ParticipantAgent, ParticipantAgentFundamentals, } import edu.ie3.simona.agent.state.AgentState.Idle +import edu.ie3.simona.agent.state.ParticipantAgentState.HandleInformation import edu.ie3.simona.config.SimonaConfig.EvcsRuntimeConfig import edu.ie3.simona.model.participant.evcs.EvcsModel import edu.ie3.simona.model.participant.evcs.EvcsModel.{ @@ -54,7 +59,7 @@ object EvcsAgent { ) val neededServices: Vector[Class[_ <: SecondaryDataService[_]]] = Vector( - classOf[ActorEvMovementsService] + classOf[ActorExtEvDataService] ) } @@ -105,6 +110,50 @@ class EvcsAgent( stay() using updatedStateData } + when(HandleInformation) { + // FreeLotsRequest and DepartingEvsRequest also need to be handled + // in case the activation has arrived first + + case Event( + EvFreeLotsRequest(tick), + stateData: DataCollectionStateData[ApparentPower], + ) => + stateData.baseStateData match { + case modelStateData: BaseStateData.ParticipantModelBaseStateData[ + ApparentPower, + EvcsRelevantData, + EvcsState, + EvcsModel, + ] => + handleFreeLotsRequest(tick, modelStateData) + stay() + case x => + throw new IllegalStateException( + s"Unsupported base state data '$x' when receiving FreeLotsRequest" + ) + } + + case Event( + DepartingEvsRequest(tick, departingEvs), + stateData: DataCollectionStateData[ApparentPower], + ) => + stateData.baseStateData match { + case modelStateData: BaseStateData.ParticipantModelBaseStateData[ + ApparentPower, + EvcsRelevantData, + EvcsState, + EvcsModel, + ] => + val updatedStateData = + handleDepartingEvsRequest(tick, departingEvs, modelStateData) + stay() using stateData.copy(baseStateData = updatedStateData) + case x => + throw new IllegalStateException( + s"Unsupported base state data '$x' when receiving DepartingEvsRequest" + ) + } + } + /** Determine the average result within the given tick window * * @param tickToResults 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 37bbbae246..d98e837e48 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 @@ -19,7 +19,7 @@ import edu.ie3.simona.agent.participant.ParticipantAgentFundamentals import edu.ie3.simona.agent.participant.data.Data.PrimaryData.ApparentPower import edu.ie3.simona.agent.participant.data.Data.SecondaryData import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService -import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.ActorEvMovementsService +import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.ActorExtEvDataService import edu.ie3.simona.agent.participant.evcs.EvcsAgent.neededServices import edu.ie3.simona.agent.participant.statedata.BaseStateData.{ FlexControlledData, @@ -212,7 +212,7 @@ protected trait EvcsAgentFundamentals .getOrElse(tick, Map.empty) .collectFirst { // filter secondary data for arriving EVs data - case (_, arrivingEvsData: ArrivingEvsData) => + case (_, arrivingEvsData: ArrivingEvs) => arrivingEvsData.arrivals } .getOrElse(Seq.empty) @@ -327,7 +327,7 @@ protected trait EvcsAgentFundamentals .values .collectFirst { // filter secondary data for arriving EVs data - case _: ArrivingEvsData => + case _: ArrivingEvs => handleArrivingEvsAndGoIdle( tick, scheduler, @@ -359,7 +359,7 @@ protected trait EvcsAgentFundamentals EvcsModel, ], ): Unit = { - val evServiceRef = getService[ActorEvMovementsService]( + val evServiceRef = getService[ActorExtEvDataService]( modelBaseStateData.services ) @@ -398,7 +398,7 @@ protected trait EvcsAgentFundamentals EvcsState, EvcsModel, ] = { - val evServiceRef = getService[ActorEvMovementsService]( + val evServiceRef = getService[ActorExtEvDataService]( baseStateData.services ) @@ -494,32 +494,39 @@ 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, - lastState, - ) + val currentEvs = modelBaseStateData.model.determineCurrentEvs( + relevantData, + lastState, + ) - // if new EVs arrived, a new scheduling must be calculated. - val newSchedule = modelBaseStateData.model.calculateNewScheduling( - relevantData, - currentEvs, - ) + // if new EVs arrived, a new scheduling must be calculated. + val newSchedule = modelBaseStateData.model.calculateNewScheduling( + relevantData, + currentEvs, + ) - // create new current state - val newState = EvcsState(currentEvs, newSchedule, tick) + // create new current state + val newState = EvcsState(currentEvs, newSchedule, tick) - val updatedStateDataStore = ValueStore.updateValueStore( - modelBaseStateData.stateDataStore, - tick, - newState, - ) + val updatedStateDataStore = ValueStore.updateValueStore( + modelBaseStateData.stateDataStore, + tick, + newState, + ) - /* Update the base state data with the updated result value store and relevant data store */ - val updatedBaseStateData = modelBaseStateData.copy( - stateDataStore = updatedStateDataStore - ) + /* Update the base state data with the updated state data store */ + modelBaseStateData.copy( + stateDataStore = updatedStateDataStore + ) + } else + // Empty arrivals means that there is no data for this EVCS at the current tick, + // thus we just return and wait for the next activation + modelBaseStateData + } // We're only here if we're not flex-controlled, thus sending a Completion is always right goToIdleReplyCompletionAndScheduleTriggerForNextAction( diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/EvMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/EvMessage.scala index 05a5be6886..1e8ae341a9 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/EvMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/EvMessage.scala @@ -12,7 +12,6 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ ProvisionMessage, ServiceRegistrationMessage, } -import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import org.apache.pekko.actor.ActorRef import java.util.UUID @@ -48,8 +47,7 @@ object EvMessage { override val tick: Long, override val serviceRef: ActorRef, override val data: EvData, - override val nextDataTick: Option[Long] = None, - override val unlockKey: Option[ScheduleKey] = None, + override val nextDataTick: Option[Long], ) extends EvMessage with ProvisionMessage[EvData] @@ -74,7 +72,7 @@ object EvMessage { * @param arrivals * EVs arriving at the charging station */ - final case class ArrivingEvsData( + final case class ArrivingEvs( arrivals: Seq[EvModelWrapper] ) extends EvData {} diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/PrimaryDataMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/PrimaryDataMessage.scala index 73aa482b64..4721af5f2f 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/PrimaryDataMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/PrimaryDataMessage.scala @@ -8,7 +8,6 @@ package edu.ie3.simona.ontology.messages.services import edu.ie3.simona.agent.participant.data.Data.PrimaryData.ApparentPower import edu.ie3.simona.ontology.messages.services.ServiceMessage.ProvisionMessage -import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import org.apache.pekko.actor.ActorRef sealed trait PrimaryDataMessage @@ -30,7 +29,6 @@ object PrimaryDataMessage { override val serviceRef: ActorRef, override val data: ApparentPower, override val nextDataTick: Option[Long], - override val unlockKey: Option[ScheduleKey] = None, ) extends ProvisionMessage[ApparentPower] with PrimaryDataMessage } diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala index 33946b669f..d7444454fd 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala @@ -17,7 +17,7 @@ import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey */ sealed trait ServiceMessage -case object ServiceMessage { +object ServiceMessage { /** Message used to register for a service */ @@ -75,7 +75,10 @@ case object ServiceMessage { val tick: Long val serviceRef: ActorRef val data: D + + /** Next tick at which data could arrive. If None, no data is expected for + * the rest of the simulation + */ val nextDataTick: Option[Long] - val unlockKey: Option[ScheduleKey] } } diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/services/WeatherMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/WeatherMessage.scala index 9d17284d63..cb9e8349ba 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/services/WeatherMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/services/WeatherMessage.scala @@ -11,7 +11,6 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ ProvisionMessage, ServiceRegistrationMessage, } -import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.util.scala.quantities.Irradiance import org.apache.pekko.actor.ActorRef import squants.{Temperature, Velocity} @@ -54,7 +53,6 @@ object WeatherMessage { override val serviceRef: ActorRef, override val data: WeatherData, override val nextDataTick: Option[Long], - override val unlockKey: Option[ScheduleKey] = None, ) extends WeatherMessage with ProvisionMessage[WeatherData] diff --git a/src/main/scala/edu/ie3/simona/scheduler/core/PhaseSwitchCore.scala b/src/main/scala/edu/ie3/simona/scheduler/core/PhaseSwitchCore.scala deleted file mode 100644 index eff9128068..0000000000 --- a/src/main/scala/edu/ie3/simona/scheduler/core/PhaseSwitchCore.scala +++ /dev/null @@ -1,160 +0,0 @@ -/* - * © 2023. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.scheduler.core - -import edu.ie3.simona.exceptions.CriticalFailureException -import edu.ie3.simona.scheduler.core.Core.{ - ActiveCore, - Actor, - CoreFactory, - InactiveCore, -} -import edu.ie3.util.scala.collection.immutable.PrioritySwitchBiSet - -/** A scheduler core that activates actors in phases when active. This means - * that only one actor at any given time is activated and the completion of - * said actor is necessary before activating other actors scheduled for the - * same tick. - * - * When multiple actors are scheduled for the same tick, they are always - * activated in the same order, which is given by the initial scheduling of - * said actors. Thus, if e.g. actor 1 has been scheduled for initialization - * before actor 2, actor 1 will be activated before actor 2 for the - * initialization tick and all consecutive ticks. - */ -object PhaseSwitchCore extends CoreFactory { - - override def create(): PhaseSwitchInactive = - PhaseSwitchInactive(PrioritySwitchBiSet.empty, None) - - final case class PhaseSwitchInactive private ( - private val activationQueue: PrioritySwitchBiSet[Long, Actor], - private val lastActiveTick: Option[Long], - ) extends InactiveCore { - override def activate(newTick: Long): ActiveCore = { - val nextScheduledTick = activationQueue.headKeyOption.getOrElse( - throw new CriticalFailureException( - "No activation scheduled, cannot activate." - ) - ) - - if (nextScheduledTick != newTick) - throw new CriticalFailureException( - s"Cannot activate with new tick $newTick because $nextScheduledTick is the next scheduled tick." - ) - - PhaseSwitchActive(activationQueue, activeTick = newTick) - } - - override def handleSchedule( - actor: Actor, - newTick: Long, - ): (Option[Long], InactiveCore) = { - lastActiveTick.filter(newTick < _).foreach { lastActive => - throw new CriticalFailureException( - s"Cannot schedule an activation for $actor at tick $newTick because the last active tick is $lastActive" - ) - } - - val oldEarliestTick = activationQueue.headKeyOption - - val updatedQueue = activationQueue.set(newTick, actor) - val newEarliestTick = updatedQueue.headKeyOption - - val maybeScheduleTick = - Option - .when(newEarliestTick != oldEarliestTick)(newEarliestTick) - .flatten - - (maybeScheduleTick, copy(activationQueue = updatedQueue)) - } - - } - - private final case class PhaseSwitchActive( - private val activationQueue: PrioritySwitchBiSet[Long, Actor], - activeTick: Long, - private val phase: Int = 0, - private val activeActors: Set[Actor] = Set.empty, - ) extends ActiveCore { - - override def handleCompletion(actor: Actor): ActiveCore = { - if (!activeActors.contains(actor)) - throw new CriticalFailureException( - s"Actor $actor is not part of the expected completing actors" - ) - - copy(activeActors = activeActors.excl(actor)) - } - - override def maybeComplete(): Option[(Option[Long], InactiveCore)] = { - Option.when( - activeActors.isEmpty && activationQueue.headKeyOption.forall( - _ > activeTick - ) - ) { - ( - activationQueue.headKeyOption, - PhaseSwitchInactive(activationQueue, Some(activeTick)), - ) - } - } - - override def handleSchedule( - actor: Actor, - newTick: Long, - ): ActiveCore = { - if (newTick == activeTick) { - // what's done, is done: old phases are completed, - // thus they cannot handle new activation schedulings - if (activationQueue.indexOf(actor).exists(_ < phase)) { - val activeActor = activationQueue.values(phase) - throw new CriticalFailureException( - s"Cannot schedule an activation at tick $newTick for $actor while actor $activeActor is active now" - ) - } - } else { - if (newTick < activeTick) - throw new CriticalFailureException( - s"Cannot schedule an activation at tick $newTick for $actor while tick $activeTick is currently active" - ) - } - - copy(activationQueue = activationQueue.set(newTick, actor)) - } - - override def takeNewActivations(): (Iterable[Actor], ActiveCore) = { - Option - .when(activeActors.isEmpty) { - // only one actor can be active at a time, and only - // if the last actor of the current tick has completed - activationQueue.takeNextValueFor(activeTick) - } - .flatten - .map { case (actor, updatedQueue) => - val newPhase = activationQueue - .indexOf(actor) - .getOrElse( - throw new RuntimeException( - "Actor not scheduled, should not happen" - ) - ) - ( - Iterable.single(actor), - copy( - activationQueue = updatedQueue, - phase = newPhase, - activeActors = activeActors.incl(actor), - ), - ) - } - .getOrElse((Iterable.empty, this)) - } - - } - -} diff --git a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala index 052a19b479..66cfaa96f1 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -6,19 +6,20 @@ package edu.ie3.simona.service.ev -import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps -import org.apache.pekko.actor.{ActorContext, ActorRef, Props} import edu.ie3.simona.api.data.ev.ExtEvData import edu.ie3.simona.api.data.ev.model.EvModel import edu.ie3.simona.api.data.ev.ontology._ import edu.ie3.simona.api.data.ontology.DataMessageFromExt import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException -import edu.ie3.simona.exceptions.{InitializationException, ServiceException} +import edu.ie3.simona.exceptions.{ + CriticalFailureException, + InitializationException, + ServiceException, +} import edu.ie3.simona.model.participant.evcs.EvModelWrapper import edu.ie3.simona.ontology.messages.services.EvMessage._ import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegistrationResponseMessage.RegistrationSuccessfulMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.ServiceRegistrationMessage -import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceBaseStateData, @@ -29,9 +30,12 @@ import edu.ie3.simona.service.ev.ExtEvDataService.{ } import edu.ie3.simona.service.{ExtDataSupport, ServiceStateData, SimonaService} import edu.ie3.simona.util.ReceiveDataMap +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import org.apache.pekko.actor.{ActorContext, ActorRef, Props} import java.util.UUID import scala.jdk.CollectionConverters._ +import scala.jdk.OptionConverters._ import scala.util.{Failure, Success, Try} object ExtEvDataService { @@ -142,8 +146,8 @@ class ExtEvDataService(override val scheduler: ActorRef) serviceStateData.uuidToActorRef.get(evcs) match { case None => // Actor is not registered yet - agentToBeRegistered ! RegistrationSuccessfulMessage(self, None) - + // (not sending confirmation message yet, because we're waiting + // for MobSim to tell us what the first tick is going to be) serviceStateData.copy( uuidToActorRef = serviceStateData.uuidToActorRef + (evcs -> agentToBeRegistered) @@ -175,6 +179,10 @@ class ExtEvDataService(override val scheduler: ActorRef) ExtEvStateData, Option[Long], ) = { + def asScala[E] + : java.util.Map[UUID, java.util.List[E]] => Map[UUID, Seq[E]] = map => + map.asScala.view.mapValues(_.asScala.toSeq).toMap + serviceStateData.extEvMessage.getOrElse( throw ServiceException( "ExtEvDataService was triggered without ExtEvMessage available" @@ -185,11 +193,14 @@ class ExtEvDataService(override val scheduler: ActorRef) case _: RequestEvcsFreeLots => requestFreeLots(tick) case departingEvsRequest: RequestDepartingEvs => - requestDepartingEvs(tick, departingEvsRequest.departures) + requestDepartingEvs(tick, asScala(departingEvsRequest.departures)) case arrivingEvsProvision: ProvideArrivingEvs => - handleArrivingEvs(tick, arrivingEvsProvision.arrivals)( - serviceStateData, - ctx, + handleArrivingEvs( + tick, + asScala(arrivingEvsProvision.arrivals), + arrivingEvsProvision.maybeNextTick.toScala.map(Long2long), + )( + serviceStateData ) } } @@ -241,16 +252,16 @@ class ExtEvDataService(override val scheduler: ActorRef) private def requestDepartingEvs( tick: Long, - requestedDepartingEvs: java.util.Map[UUID, java.util.List[UUID]], + requestedDepartingEvs: Map[UUID, Seq[UUID]], )(implicit serviceStateData: ExtEvStateData ): (ExtEvStateData, Option[Long]) = { val departingEvResponses = - requestedDepartingEvs.asScala.flatMap { case (evcs, departingEvs) => + requestedDepartingEvs.flatMap { case (evcs, departingEvs) => serviceStateData.uuidToActorRef.get(evcs) match { case Some(evcsActor) => - evcsActor ! DepartingEvsRequest(tick, departingEvs.asScala.toSeq) + evcsActor ! DepartingEvsRequest(tick, departingEvs) Some(evcs) @@ -280,44 +291,46 @@ class ExtEvDataService(override val scheduler: ActorRef) private def handleArrivingEvs( tick: Long, - allArrivingEvs: java.util.Map[UUID, java.util.List[EvModel]], + allArrivingEvs: Map[UUID, Seq[EvModel]], + maybeNextTick: Option[Long], )(implicit - serviceStateData: ExtEvStateData, - ctx: ActorContext, + serviceStateData: ExtEvStateData ): (ExtEvStateData, Option[Long]) = { - val actorToEvs = allArrivingEvs.asScala.flatMap { - case (evcs, arrivingEvs) => - serviceStateData.uuidToActorRef - .get(evcs) - .map((_, arrivingEvs.asScala.map(EvModelWrapper.apply).toSeq)) - .orElse { - log.warning( - "A corresponding actor ref for UUID {} could not be found", - evcs, - ) - None - } - } - if (actorToEvs.nonEmpty) { - val keys = - ScheduleLock.multiKey(ctx, scheduler.toTyped, tick, actorToEvs.size) + if (tick == INIT_SIM_TICK) { + + maybeNextTick.getOrElse( + throw new CriticalFailureException( + s"After initialization, a first simulation tick needs to be provided by the external mobility simulation." + ) + ) + + serviceStateData.uuidToActorRef.foreach { case (_, actor) => + actor ! RegistrationSuccessfulMessage( + self, + maybeNextTick, + ) + } + + } else { + serviceStateData.uuidToActorRef.foreach { case (evcs, actor) => + val evs = + allArrivingEvs.getOrElse(evcs, Seq.empty) - actorToEvs.zip(keys).foreach { case ((actor, arrivingEvs), key) => actor ! ProvideEvDataMessage( tick, self, - ArrivingEvsData(arrivingEvs), - unlockKey = Some(key), + ArrivingEvs(evs.map(EvModelWrapper.apply)), + maybeNextTick, ) } - } ( serviceStateData.copy( extEvMessage = None ), + // We still don't return the next tick because departures might come earlier None, ) } diff --git a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala index b37c061d39..7ff05ccf1a 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala @@ -22,7 +22,6 @@ import edu.ie3.simona.exceptions.InitializationException import edu.ie3.simona.exceptions.WeatherServiceException.InvalidRegistrationRequestException import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegistrationResponseMessage.RegistrationSuccessfulMessage -import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceActivationBaseStateData, @@ -437,6 +436,5 @@ object PrimaryServiceWorker { override val serviceRef: ActorRef, override val data: PrimaryData, override val nextDataTick: Option[Long], - override val unlockKey: Option[ScheduleKey] = None, ) extends ServiceMessage.ProvisionMessage[PrimaryData] } diff --git a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala index 9126926147..c8196aaf30 100644 --- a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala +++ b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala @@ -12,8 +12,7 @@ import edu.ie3.simona.event.RuntimeEvent import edu.ie3.simona.event.listener.{DelayedStopHelper, RuntimeEventListener} import edu.ie3.simona.main.RunSimona.SimonaEnded import edu.ie3.simona.scheduler.TimeAdvancer -import edu.ie3.simona.scheduler.core.PhaseSwitchCore -import edu.ie3.simona.sim.setup.{ExtSimSetupData, SimonaSetup} +import edu.ie3.simona.sim.setup.SimonaSetup import edu.ie3.util.scala.Scope import org.apache.pekko.actor.typed.scaladsl.adapter._ import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} @@ -77,30 +76,23 @@ object SimonaSim { val timeAdvancer = simonaSetup.timeAdvancer(ctx, ctx.self, runtimeEventListener) - val rootPhaseSwitch = - simonaSetup.scheduler(ctx, timeAdvancer, PhaseSwitchCore) + val scheduler = simonaSetup.scheduler(ctx, timeAdvancer) // External simulations have to be scheduled for initialization first, // so that the phase switch permanently activates them first - val extSimulationData: ExtSimSetupData = - simonaSetup.extSimulations(ctx, rootPhaseSwitch) - - // scheduler for all actors besides external simulation, - // which come second in line with phase switch - val simScheduler = - simonaSetup.scheduler(ctx, rootPhaseSwitch) + val extSimulationData = simonaSetup.extSimulations(ctx, scheduler) /* start services */ // primary service proxy val primaryServiceProxy = - simonaSetup.primaryServiceProxy(ctx, simScheduler) + simonaSetup.primaryServiceProxy(ctx, scheduler) // weather service val weatherService = - simonaSetup.weatherService(ctx, simScheduler) + simonaSetup.weatherService(ctx, scheduler) val environmentRefs = EnvironmentRefs( - simScheduler, + scheduler, runtimeEventListener.toClassic, primaryServiceProxy, weatherService, @@ -116,14 +108,12 @@ object SimonaSim { val otherActors = Iterable[ActorRef[_]]( timeAdvancer, - rootPhaseSwitch, - simScheduler, + scheduler, primaryServiceProxy.toTyped, weatherService.toTyped, ) ++ gridAgents ++ - extSimulationData.extDataServices.values.map(_.toTyped) ++ - extSimulationData.extScheduler.toSeq + extSimulationData.extDataServices.values.map(_.toTyped) /* watch all actors */ resultEventListeners.foreach(ctx.watch) diff --git a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala index 40443cad5f..3209fb706d 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/ExtSimSetupData.scala @@ -6,15 +6,12 @@ package edu.ie3.simona.sim.setup -import edu.ie3.simona.ontology.messages.SchedulerMessage import org.apache.pekko.actor.{ActorRef => ClassicRef} import edu.ie3.simona.service.ev.ExtEvDataService -import org.apache.pekko.actor.typed.ActorRef final case class ExtSimSetupData( extSimAdapters: Iterable[ClassicRef], extDataServices: Map[Class[_], ClassicRef], - extScheduler: Option[ActorRef[SchedulerMessage]], ) { def evDataService: Option[ClassicRef] = diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala index bfb4e16cee..9c6effa859 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -99,14 +99,14 @@ trait SimonaSetup { * * @param context * Actor context to use - * @param rootScheduler - * Actor reference to it's according scheduler to use + * @param scheduler + * Actor reference to the scheduler to use * @return * External simulations and their init data */ def extSimulations( context: ActorContext[_], - rootScheduler: ActorRef[SchedulerMessage], + scheduler: ActorRef[SchedulerMessage], ): ExtSimSetupData /** Creates the time advancer 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 29f2bb7240..65d47863cf 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -194,20 +194,19 @@ class SimonaStandaloneSetup( override def extSimulations( context: ActorContext[_], - rootScheduler: ActorRef[SchedulerMessage], + scheduler: ActorRef[SchedulerMessage], ): ExtSimSetupData = { val jars = ExtSimLoader.scanInputFolder() val extLinks = jars.flatMap(ExtSimLoader.loadExtLink).toSeq if (extLinks.nonEmpty) { - val extScheduler = scheduler(context, parent = rootScheduler) val (extSimAdapters, extDataServices) = extLinks.zipWithIndex.map { case (extLink, index) => // external simulation always needs at least an ExtSimAdapter val extSimAdapter = context.toClassic.simonaActorOf( - ExtSimAdapter.props(extScheduler.toClassic), + ExtSimAdapter.props(scheduler.toClassic), s"$index", ) val extSimAdapterData = new ExtSimAdapterData(extSimAdapter, args) @@ -215,7 +214,7 @@ class SimonaStandaloneSetup( // send init data right away, init activation is scheduled extSimAdapter ! ExtSimAdapter.Create( extSimAdapterData, - ScheduleLock.singleKey(context, extScheduler, INIT_SIM_TICK), + ScheduleLock.singleKey(context, scheduler, INIT_SIM_TICK), ) // setup data services that belong to this external simulation @@ -226,7 +225,7 @@ class SimonaStandaloneSetup( extLink.getExtDataSimulations.asScala.zipWithIndex.map { case (_: ExtEvSimulation, dIndex) => val extEvDataService = context.toClassic.simonaActorOf( - ExtEvDataService.props(extScheduler.toClassic), + ExtEvDataService.props(scheduler.toClassic), s"$index-$dIndex", ) val extEvData = new ExtEvData(extEvDataService, extSimAdapter) @@ -235,7 +234,7 @@ class SimonaStandaloneSetup( InitExtEvData(extEvData), ScheduleLock.singleKey( context, - extScheduler, + scheduler, INIT_SIM_TICK, ), ) @@ -258,10 +257,9 @@ class SimonaStandaloneSetup( ExtSimSetupData( extSimAdapters, extDataServices.flatten.toMap, - Some(extScheduler), ) } else { - ExtSimSetupData(Iterable.empty, Map.empty, None) + ExtSimSetupData(Iterable.empty, Map.empty) } } diff --git a/src/main/scala/edu/ie3/util/scala/collection/immutable/PrioritySwitchBiSet.scala b/src/main/scala/edu/ie3/util/scala/collection/immutable/PrioritySwitchBiSet.scala deleted file mode 100644 index 5aeaf9591a..0000000000 --- a/src/main/scala/edu/ie3/util/scala/collection/immutable/PrioritySwitchBiSet.scala +++ /dev/null @@ -1,213 +0,0 @@ -/* - * © 2023. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.util.scala.collection.immutable - -import scala.collection.immutable - -/** Queue that is specialized at holding many values of type [[V]] for the same - * key of type [[K]], while only allowing each value to be linked to one key. - * Mathematically, the relation between keys and values is thus not univalent - * (right-unique), but injective. - * - * In contrast to the [[PrioritySwitchBiSet]], this data structure always - * returns values stored at the same key in a fixed order that is determined by - * the first storage of a value for any key. Also, only one value can be - * returned at a time for some key, namely the value that is in order after the - * last returned value for the same key. - * - * @param orderedValues - * Vector that holds values in a fixed order, in which they are always - * returned for all keys. This vector is never cleared, thus there should be - * a limited number of values stored in this data structure only. - * @param valueToIndex - * A reverse map to [[orderedValues]], linking every value to the index it - * has been stored at. - * @param queue - * A sorted map that holds the keys and the indices to values stored in - * [[orderedValues]]. Value indices are also ordered numerically within - * sorted sets. - * @param back - * A map that links values back to keys. Used to fastly ensure every value is - * only stored once. - * @tparam K - * Type of the key - * @tparam V - * Type of the value - */ -final case class PrioritySwitchBiSet[K, V]( - private val orderedValues: immutable.Vector[V], - private val valueToIndex: Map[V, Int], - private val queue: immutable.SortedMap[K, immutable.SortedSet[Int]], - private val back: Map[V, K], -) { - - /** Get the first key of the queue, if the queue is not empty. - * - * @return - * The first key - */ - def headKeyOption: Option[K] = - queue.headOption.map { case (key, _) => key } - - /** Get the first index of the first key of the queue, if the queue is not - * empty. - * - * @return - * The first index of the first key - */ - def headKeyIndexOption: Option[Int] = - queue.headOption.flatMap { case (_, set) => set.headOption } - - /** Get the index that given value is stored at, if it exists. - * - * @param value - * Value to retrieve the index for - * @return - * The index - */ - def indexOf(value: V): Option[Int] = valueToIndex.get(value) - - /** Set given value to given key - * - * @param key - * The key to add the value for - * @param value - * The value to add - * @return - * The altered data structure - */ - def set(key: K, value: V): PrioritySwitchBiSet[K, V] = - dequeue(value).add(key, value) - - private def dequeue(value: V): PrioritySwitchBiSet[K, V] = - back - .get(value) - .map { key => - val updatedBack = back.removed(value) - - val updatedQueue = valueToIndex - .get(value) - .map { i => - val newSet = - queue - .get(key) - .map(_.excl(i)) - .getOrElse(immutable.SortedSet.empty[Int]) - - if (newSet.isEmpty) - queue.removed(key) - else - queue.updated(key, newSet) - } - .getOrElse(queue) - - copy( - queue = updatedQueue, - back = updatedBack, - ) - } - .getOrElse(this) - - private def add(key: K, value: V): PrioritySwitchBiSet[K, V] = { - // add value to orderedValues and valueToIndex, if not present already - val (updatedStruct, i) = orderedValues.indexOf(value) match { - case -1 => - val newIndex = orderedValues.size - ( - copy( - orderedValues = orderedValues.appended(value), - valueToIndex = valueToIndex.updated(value, newIndex), - ), - newIndex, - ) - case i => - (this, i) - } - - // add value to key - val updatedSet = - queue.getOrElse(key, immutable.SortedSet.empty[Int]).incl(i) - - updatedStruct.copy( - queue = queue.updated(key, updatedSet), - back = back.updated(value, key), - ) - } - - /** Retrieves the first element in the list of given key. The returned element - * is also removed the queue here. - * - * If the list of values for given key is empty, the list is removed: There - * are no empty lists in the queue, thus also keys only exist for non-empty - * lists. - * - * @return - * The first element in the list of the first key and the changed data - * structure, if it is not empty. - */ - def takeNextValueFor(key: K): Option[(V, PrioritySwitchBiSet[K, V])] = { - queue - .get(key) - .flatMap { set => - set.headOption.map(orderedValues).map((set, _)) - } - .map { case (set, firstValue) => - val updatedQueue = - if (set.size == 1) - queue.removed(key) - else - // drop first value - queue.updated(key, set.drop(1)) - - ( - firstValue, - copy(queue = updatedQueue, back = back.removed(firstValue)), - ) - } - } - - /** Tests whether there is no value for any key in the queue. - * - * @return - * True if the queue is empty - */ - def isEmpty: Boolean = queue.isEmpty - - /** Tests whether there is any value for any key in the queue. - * - * @return - * True if the queue is non-empty - */ - def nonEmpty: Boolean = queue.nonEmpty - - /** Returns all values in order - * - * @return - * The value vector - */ - def values: Vector[V] = orderedValues - -} - -object PrioritySwitchBiSet { - - /** Creates and returns an empty PrioritySwitchBiSet for given types. - * - * @tparam K - * Type of the key, which needs to be sortable by means of [[Ordering]] - * @tparam V - * Type of the value - * @return - * An empty PrioritySwitchBiSet - */ - def empty[K: Ordering, V]: PrioritySwitchBiSet[K, V] = PrioritySwitchBiSet( - Vector.empty, - Map.empty, - immutable.SortedMap.empty, - Map.empty, - ) -} 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 08420edfbe..3e3a9c7a2c 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala @@ -17,7 +17,7 @@ import edu.ie3.simona.agent.grid.GridAgentMessages.{ } import edu.ie3.simona.agent.participant.ParticipantAgent.RequestAssetPowerMessage import edu.ie3.simona.agent.participant.data.Data.PrimaryData.ApparentPower -import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.ActorEvMovementsService +import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService.ActorExtEvDataService import edu.ie3.simona.agent.participant.evcs.EvcsAgent import edu.ie3.simona.agent.participant.statedata.BaseStateData.ParticipantModelBaseStateData import edu.ie3.simona.agent.participant.statedata.DataCollectionStateData @@ -36,10 +36,7 @@ import edu.ie3.simona.model.participant.evcs.EvcsModel.{ ScheduleEntry, } import edu.ie3.simona.ontology.messages.Activation -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} +import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage._ import edu.ie3.simona.ontology.messages.flex.MinMaxFlexibilityMessage.ProvideMinMaxFlexOptions import edu.ie3.simona.ontology.messages.services.EvMessage._ @@ -48,7 +45,6 @@ import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegistrationResp RegistrationFailedMessage, RegistrationSuccessfulMessage, } -import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.simona.test.ParticipantAgentSpec import edu.ie3.simona.test.common.input.EvcsInputTestData import edu.ie3.simona.test.common.{EvTestData, TestSpawnerClassic} @@ -65,7 +61,6 @@ import squants.energy._ import squants.{Each, Energy, Power} import java.time.ZonedDateTime -import java.util.UUID import scala.collection.immutable.{SortedMap, SortedSet} class EvcsAgentModelCalculationSpec @@ -191,7 +186,7 @@ class EvcsAgentModelCalculationSpec inputModel = evcsInputModel, modelConfig = modelConfig, secondaryDataServices = Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ), simulationStartDate = simulationStartDate, simulationEndDate = simulationEndDate, @@ -253,7 +248,7 @@ class EvcsAgentModelCalculationSpec inputModel shouldBe SimpleInputContainer(evcsInputModel) modelConfig shouldBe modelConfig secondaryDataServices shouldBe Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ) simulationStartDate shouldBe simulationStartDate simulationEndDate shouldBe simulationEndDate @@ -301,7 +296,7 @@ class EvcsAgentModelCalculationSpec startDate shouldBe simulationStartDate endDate shouldBe simulationEndDate services shouldBe Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ) outputConfig shouldBe NotifierConfig( simulationResultInfo = false, @@ -412,8 +407,6 @@ class EvcsAgentModelCalculationSpec } "do correct transitions faced with new data in Idle" in { - val lock = TestProbe("lock") - val evcsAgent = TestFSMRef( new EvcsAgent( scheduler = scheduler.ref, @@ -438,7 +431,7 @@ class EvcsAgentModelCalculationSpec evService.expectMsgType[RegisterForEvDataMessage] evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(0)), ) /* I'm not interested in the content of the CompletionMessage */ @@ -448,19 +441,17 @@ class EvcsAgentModelCalculationSpec /* Send out new data */ val arrivingEvsData = - ArrivingEvsData(Seq(EvModelWrapper(evA), EvModelWrapper(evB))) + ArrivingEvs(Seq(EvModelWrapper(evA), EvModelWrapper(evB))) - val key1 = Some(ScheduleKey(lock.ref.toTyped, UUID.randomUUID())) evService.send( evcsAgent, ProvideEvDataMessage( 0L, evService.ref, arrivingEvsData, - unlockKey = key1, + Some(900), ), ) - scheduler.expectMsg(ScheduleActivation(evcsAgent.toTyped, 0, key1)) /* Find yourself in corresponding state and state data */ evcsAgent.stateName shouldBe HandleInformation @@ -471,7 +462,9 @@ class EvcsAgentModelCalculationSpec isYetTriggered, ) => /* The next data tick is already registered */ - baseStateData.foreseenDataTicks shouldBe Map(evService.ref -> None) + baseStateData.foreseenDataTicks shouldBe Map( + evService.ref -> Some(900) + ) /* The yet sent data is also registered */ expectedSenders shouldBe Map( @@ -495,7 +488,7 @@ class EvcsAgentModelCalculationSpec /* The agent will notice, that all expected information are apparent, switch to Calculate and trigger itself * for starting the calculation */ scheduler.expectMsg( - Completion(evcsAgent.toTyped, None) + Completion(evcsAgent.toTyped, Some(900)) ) evcsAgent.stateName shouldBe Idle evcsAgent.stateData match { @@ -548,8 +541,6 @@ class EvcsAgentModelCalculationSpec } "do correct transitions triggered for activation in idle" in { - val lock = TestProbe("lock") - val evcsAgent = TestFSMRef( new EvcsAgent( scheduler = scheduler.ref, @@ -574,7 +565,7 @@ class EvcsAgentModelCalculationSpec evService.expectMsgType[RegisterForEvDataMessage] evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(0)), ) /* I'm not interested in the content of the CompletionMessage */ @@ -597,10 +588,10 @@ class EvcsAgentModelCalculationSpec isYetTriggered, ) => /* The next data tick is already registered */ - baseStateData.foreseenDataTicks shouldBe Map(evService.ref -> None) + baseStateData.foreseenDataTicks shouldBe Map(evService.ref -> Some(0)) /* The yet sent data is also registered */ - expectedSenders shouldBe Map.empty + expectedSenders shouldBe Map(evService.ref -> None) /* It is not yet triggered */ isYetTriggered shouldBe true @@ -612,24 +603,22 @@ class EvcsAgentModelCalculationSpec /* Send out new data */ val arrivingEvsData = - ArrivingEvsData(Seq(EvModelWrapper(evA), EvModelWrapper(evB))) + ArrivingEvs(Seq(EvModelWrapper(evA), EvModelWrapper(evB))) - val key1 = Some(ScheduleKey(lock.ref.toTyped, UUID.randomUUID())) evService.send( evcsAgent, ProvideEvDataMessage( 0L, evService.ref, arrivingEvsData, - unlockKey = key1, + Some(900), ), ) - scheduler.expectMsg(ScheduleActivation(evcsAgent.toTyped, 0, key1)) /* The agent will notice, that all expected information are apparent, switch to Calculate and trigger itself * for starting the calculation */ scheduler.expectMsg( - Completion(evcsAgent.toTyped, None) + Completion(evcsAgent.toTyped, Some(900)) ) evcsAgent.stateName shouldBe Idle evcsAgent.stateData match { @@ -707,7 +696,7 @@ class EvcsAgentModelCalculationSpec evService.expectMsgType[RegisterForEvDataMessage] evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(10800)), ) /* I'm not interested in the content of the CompletionMessage */ @@ -715,7 +704,7 @@ class EvcsAgentModelCalculationSpec awaitAssert(evcsAgent.stateName shouldBe Idle) evcsAgent ! RequestAssetPowerMessage( - 7200L, + 7200, Each(1.0), Each(0.0), ) @@ -728,8 +717,6 @@ class EvcsAgentModelCalculationSpec } "provide number of free lots when asked to" in { - val lock = TestProbe("lock") - val evcsAgent = TestFSMRef( new EvcsAgent( scheduler = scheduler.ref, @@ -754,7 +741,7 @@ class EvcsAgentModelCalculationSpec evService.expectMsgType[RegisterForEvDataMessage] evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(0)), ) /* I'm not interested in the content of the CompletionMessage */ @@ -777,28 +764,26 @@ class EvcsAgentModelCalculationSpec scheduler.expectNoMessage() /* Send ev for this tick */ - val key1 = Some(ScheduleKey(lock.ref.toTyped, UUID.randomUUID())) evService.send( evcsAgent, ProvideEvDataMessage( - 0L, + 0, evService.ref, - ArrivingEvsData(Seq(EvModelWrapper(evA))), - unlockKey = key1, + ArrivingEvs(Seq(EvModelWrapper(evA))), + Some(900), ), ) - scheduler.expectMsg(ScheduleActivation(evcsAgent.toTyped, 0, key1)) scheduler.send( evcsAgent, Activation(0), ) - scheduler.expectMsg(Completion(evcsAgent.toTyped)) + scheduler.expectMsg(Completion(evcsAgent.toTyped, Some(900))) /* Ask for public evcs lot count again with a later tick */ evService.send( evcsAgent, - EvFreeLotsRequest(3600L), + EvFreeLotsRequest(3600), ) // this time, only one is still free @@ -812,6 +797,75 @@ class EvcsAgentModelCalculationSpec scheduler.expectNoMessage() } + "handle empty arrivals" in { + val evcsAgent = TestFSMRef( + new EvcsAgent( + scheduler = scheduler.ref, + initStateData = initStateData, + listener = Iterable.empty, + ) + ) + + scheduler.send( + evcsAgent, + Activation(INIT_SIM_TICK), + ) + + /* Refuse registration with primary service */ + primaryServiceProxy.expectMsgType[PrimaryServiceRegistrationMessage] + primaryServiceProxy.send( + evcsAgent, + RegistrationFailedMessage(primaryServiceProxy.ref), + ) + + /* I'm not interested in the content of the RegistrationMessage */ + evService.expectMsgType[RegisterForEvDataMessage] + evService.send( + evcsAgent, + RegistrationSuccessfulMessage(evService.ref, Some(0)), + ) + + /* I'm not interested in the content of the CompletionMessage */ + scheduler.expectMsgType[Completion] + awaitAssert(evcsAgent.stateName shouldBe Idle) + + /* Send ev for this tick */ + evService.send( + evcsAgent, + ProvideEvDataMessage( + 0, + evService.ref, + ArrivingEvs(Seq(EvModelWrapper(evA))), + Some(900), + ), + ) + + scheduler.send( + evcsAgent, + Activation(0), + ) + scheduler.expectMsg(Completion(evcsAgent.toTyped, Some(900))) + + /* Send empty EV list for this tick */ + evService.send( + evcsAgent, + ProvideEvDataMessage( + 900, + evService.ref, + ArrivingEvs(Seq.empty), + Some(1800), + ), + ) + + scheduler.send( + evcsAgent, + Activation(900), + ) + scheduler.expectMsg(Completion(evcsAgent.toTyped, Some(1800))) + + scheduler.expectNoMessage() + } + val evcsAgent = TestFSMRef( new EvcsAgent( scheduler = scheduler.ref, @@ -819,7 +873,7 @@ class EvcsAgentModelCalculationSpec inputModel = evcsInputModelQv, modelConfig = modelConfig, secondaryDataServices = Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ), simulationStartDate = simulationStartDate, simulationEndDate = simulationEndDate, @@ -833,8 +887,6 @@ class EvcsAgentModelCalculationSpec ) "provide correct average power after three data ticks are available" in { - val lock = TestProbe("lock") - scheduler.send(evcsAgent, Activation(INIT_SIM_TICK)) /* Refuse registration with primary service */ @@ -848,7 +900,7 @@ class EvcsAgentModelCalculationSpec evService.expectMsgType[RegisterForEvDataMessage] evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(0)), ) /* I'm not interested in the content of the CompletionMessage */ @@ -857,26 +909,26 @@ class EvcsAgentModelCalculationSpec /* Send out the three data points */ /* ... for tick 0 */ - val key1 = Some(ScheduleKey(lock.ref.toTyped, UUID.randomUUID())) evService.send( evcsAgent, ProvideEvDataMessage( - 0L, + 0, evService.ref, - ArrivingEvsData(Seq(EvModelWrapper(evA.copyWithDeparture(3600L)))), - unlockKey = key1, + ArrivingEvs( + Seq(EvModelWrapper(evA.copyWithDeparture(3600))) + ), + Some(3600), ), ) - scheduler.expectMsg(ScheduleActivation(evcsAgent.toTyped, 0, key1)) scheduler.send(evcsAgent, Activation(0)) - scheduler.expectMsg(Completion(evcsAgent.toTyped)) + scheduler.expectMsg(Completion(evcsAgent.toTyped, Some(3600))) /* ... for tick 3600 */ // departures first evService.send( evcsAgent, - DepartingEvsRequest(3600L, Seq(evA.getUuid)), + DepartingEvsRequest(3600, Seq(evA.getUuid)), ) evService.expectMsgType[DepartingEvsResponse] match { case DepartingEvsResponse(evcs, evModels) => @@ -891,27 +943,27 @@ class EvcsAgentModelCalculationSpec } // arrivals second - val key2 = Some(ScheduleKey(lock.ref.toTyped, UUID.randomUUID())) evService.send( evcsAgent, ProvideEvDataMessage( - 3600L, + 3600, evService.ref, - ArrivingEvsData(Seq(EvModelWrapper(evB.copyWithDeparture(7200L)))), - unlockKey = key2, + ArrivingEvs( + Seq(EvModelWrapper(evB.copyWithDeparture(7200))) + ), + Some(7200), ), ) - scheduler.expectMsg(ScheduleActivation(evcsAgent.toTyped, 3600, key2)) scheduler.send(evcsAgent, Activation(3600)) - scheduler.expectMsg(Completion(evcsAgent.toTyped)) + scheduler.expectMsg(Completion(evcsAgent.toTyped, Some(7200))) /* ... for tick 7200 */ // departures first evService.send( evcsAgent, - DepartingEvsRequest(7200L, Seq(evB.getUuid)), + DepartingEvsRequest(7200, Seq(evB.getUuid)), ) evService.expectMsgType[DepartingEvsResponse] match { case DepartingEvsResponse(evcs, evModels) => @@ -925,21 +977,21 @@ class EvcsAgentModelCalculationSpec } } - val key3 = Some(ScheduleKey(lock.ref.toTyped, UUID.randomUUID())) evService.send( evcsAgent, ProvideEvDataMessage( - 7200L, + 7200, evService.ref, - ArrivingEvsData(Seq(EvModelWrapper(evA.copyWithDeparture(10800L)))), - unlockKey = key3, + ArrivingEvs( + Seq(EvModelWrapper(evA.copyWithDeparture(10800))) + ), + None, ), ) - scheduler.expectMsg(ScheduleActivation(evcsAgent.toTyped, 7200, key3)) scheduler.send(evcsAgent, Activation(7200)) - scheduler.expectMsg(Completion(evcsAgent.toTyped)) + scheduler.expectMsg(Completion(evcsAgent.toTyped, None)) /* Ask the agent for average power in tick 7500 */ evcsAgent ! RequestAssetPowerMessage( @@ -1003,7 +1055,7 @@ class EvcsAgentModelCalculationSpec inputModel = evcsInputModelQv, modelConfig = modelConfig, secondaryDataServices = Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ), simulationStartDate = simulationStartDate, simulationEndDate = simulationEndDate, @@ -1040,7 +1092,7 @@ class EvcsAgentModelCalculationSpec inputModel shouldBe SimpleInputContainer(evcsInputModelQv) modelConfig shouldBe modelConfig secondaryDataServices shouldBe Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ) simulationStartDate shouldBe simulationStartDate simulationEndDate shouldBe simulationEndDate @@ -1071,7 +1123,7 @@ class EvcsAgentModelCalculationSpec evService.expectMsg(RegisterForEvDataMessage(evcsInputModelQv.getUuid)) evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(0)), ) emAgent.expectMsg( @@ -1103,11 +1155,11 @@ class EvcsAgentModelCalculationSpec startDate shouldBe simulationStartDate endDate shouldBe simulationEndDate services shouldBe Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ) outputConfig shouldBe defaultOutputConfig additionalActivationTicks shouldBe empty - foreseenDataTicks shouldBe Map(evService.ref -> None) + foreseenDataTicks shouldBe Map(evService.ref -> Some(0)) voltageValueStore shouldBe ValueStore( resolution, SortedMap(0L -> Each(1.0)), @@ -1137,7 +1189,7 @@ class EvcsAgentModelCalculationSpec inputModel = SimpleInputContainer(evcsInputModelQv), modelConfig = modelConfig, secondaryDataServices = Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ), simulationStartDate = simulationStartDate, simulationEndDate = simulationEndDate, @@ -1178,7 +1230,7 @@ class EvcsAgentModelCalculationSpec inputModel shouldBe SimpleInputContainer(evcsInputModelQv) modelConfig shouldBe modelConfig secondaryDataServices shouldBe Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ) simulationStartDate shouldBe simulationStartDate simulationEndDate shouldBe simulationEndDate @@ -1212,11 +1264,11 @@ class EvcsAgentModelCalculationSpec evService.expectMsg(RegisterForEvDataMessage(evcsInputModelQv.getUuid)) evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(900)), ) emAgent.expectMsg( - ScheduleFlexRequest(evcsInputModelQv.getUuid, 0) + ScheduleFlexRequest(evcsInputModelQv.getUuid, 900) ) scheduler.expectMsg(Completion(evcsAgent.toTyped)) @@ -1267,7 +1319,7 @@ class EvcsAgentModelCalculationSpec result.p should approximate(Kilowatts(0)) result.q should approximate(Megavars(0)) requestAtNextActivation shouldBe false - requestAtTick shouldBe None + requestAtTick shouldBe Some(900) } // results arrive after next activation @@ -1285,11 +1337,11 @@ class EvcsAgentModelCalculationSpec ProvideEvDataMessage( 900, evService.ref, - ArrivingEvsData(Seq(ev900)), + ArrivingEvs(Seq(ev900)), + Some(4500), ), ) - emAgent.expectMsg(ScheduleFlexRequest(evcsInputModelQv.getUuid, 900)) emAgent.send(evcsAgent, RequestFlexOptions(900)) emAgent.expectMsgType[ProvideFlexOptions] match { @@ -1403,11 +1455,11 @@ class EvcsAgentModelCalculationSpec ProvideEvDataMessage( 4500, evService.ref, - ArrivingEvsData(Seq(ev4500)), + ArrivingEvs(Seq(ev4500)), + Some(11700), ), ) - emAgent.expectMsg(ScheduleFlexRequest(evcsInputModelQv.getUuid, 4500)) emAgent.send(evcsAgent, RequestFlexOptions(4500)) emAgent.expectMsgType[ProvideFlexOptions] match { @@ -1501,7 +1553,7 @@ class EvcsAgentModelCalculationSpec result.q should approximate(Megavars(0)) // since battery is still below lowest soc, it's still considered empty requestAtNextActivation shouldBe true - requestAtTick shouldBe Some(32776) + requestAtTick shouldBe Some(11700) } resultListener.expectMsgPF() { @@ -1536,11 +1588,11 @@ class EvcsAgentModelCalculationSpec ProvideEvDataMessage( 11700, evService.ref, - ArrivingEvsData(Seq(ev11700)), + ArrivingEvs(Seq(ev11700)), + None, ), ) - emAgent.expectMsg(ScheduleFlexRequest(evcsInputModelQv.getUuid, 11700)) emAgent.send(evcsAgent, RequestFlexOptions(11700)) val combinedChargingPower = diff --git a/src/test/scala/edu/ie3/simona/scheduler/PhaseSwitchSchedulerSpec.scala b/src/test/scala/edu/ie3/simona/scheduler/PhaseSwitchSchedulerSpec.scala deleted file mode 100644 index 447a2d2a44..0000000000 --- a/src/test/scala/edu/ie3/simona/scheduler/PhaseSwitchSchedulerSpec.scala +++ /dev/null @@ -1,502 +0,0 @@ -/* - * © 2023. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.scheduler - -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} -import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} -import edu.ie3.simona.scheduler.core.PhaseSwitchCore -import edu.ie3.simona.util.ActorUtils.RichActivatedActor -import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK -import org.apache.pekko.actor.testkit.typed.scaladsl.{ - ScalaTestWithActorTestKit, - TestProbe, -} -import org.scalatest.matchers.should -import org.scalatest.wordspec.AnyWordSpecLike - -/** A lot of functions are already tested in [[SchedulerSpec]] and don't need to - * be repeated here - */ -class PhaseSwitchSchedulerSpec - extends ScalaTestWithActorTestKit - with AnyWordSpecLike - with should.Matchers { - - "The Scheduler with PhaseSwitchCore should work correctly" when { - - "receiving initial activation scheduling before activation" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - val agent2 = TestProbe[Activation]("agent_2") - - scheduler ! ScheduleActivation(agent1.ref, INIT_SIM_TICK) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe INIT_SIM_TICK - val schedulerActivation = sa1.actor - - scheduler ! ScheduleActivation(agent2.ref, INIT_SIM_TICK) - - agent1.expectNoMessage() - agent2.expectNoMessage() - parent.expectNoMessage() - - // TICK -1, phase 0 - schedulerActivation ! Activation(INIT_SIM_TICK) - - agent1.expectMessage(Activation(INIT_SIM_TICK)) - agent2.expectNoMessage() - - // TICK -1, phase 1 - scheduler ! Completion(agent1.ref) - - agent1.expectNoMessage() - agent2.expectMessage(Activation(INIT_SIM_TICK)) - - parent.expectNoMessage() - - scheduler ! Completion(agent2.ref) - - parent.expectMessage(Completion(schedulerActivation)) - } - - "receiving activation scheduling in a different order than initially" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - val agent2 = TestProbe[Activation]("agent_2") - - scheduler ! ScheduleActivation(agent1.ref, INIT_SIM_TICK) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe INIT_SIM_TICK - val schedulerActivation = sa1.actor - - scheduler ! ScheduleActivation(agent2.ref, INIT_SIM_TICK) - - // TICK -1, phase 0 - schedulerActivation ! Activation(INIT_SIM_TICK) - agent1.expectMessage(Activation(INIT_SIM_TICK)) - - // TICK -1, phase 1 - scheduler ! Completion(agent1.ref) - agent2.expectMessage(Activation(INIT_SIM_TICK)) - - scheduler ! Completion(agent2.ref, Some(0)) - parent.expectMessage(Completion(schedulerActivation, Some(0))) - - // scheduling first actor after the second actor for the same tick - scheduler ! ScheduleActivation(agent1.ref, 0) - parent.expectNoMessage() - - // TICK 0, phase 0 - schedulerActivation ! Activation(0) - - agent1.expectMessage(Activation(0)) - agent2.expectNoMessage() - - // TICK -1, phase 1 - scheduler ! Completion(agent1.ref) - - agent1.expectNoMessage() - agent2.expectMessage(Activation(0)) - - scheduler ! Completion(agent2.ref) - - parent.expectMessage(Completion(schedulerActivation)) - } - - "receiving activation scheduling after init activation" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - val agent2 = TestProbe[Activation]("agent_2") - - scheduler ! ScheduleActivation(agent1.ref, INIT_SIM_TICK) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe INIT_SIM_TICK - val schedulerActivation = sa1.actor - - agent1.expectNoMessage() - - // TICK -1, phase 0 - schedulerActivation ! Activation(INIT_SIM_TICK) - - agent1.expectMessage(Activation(INIT_SIM_TICK)) - agent2.expectNoMessage() - - scheduler ! ScheduleActivation( - agent2.ref, - INIT_SIM_TICK, - ) - - // waiting for next phase - agent2.expectNoMessage() - parent.expectNoMessage() - - // TICK -1, phase 1 - scheduler ! Completion(agent1.ref) - - agent2.expectMessage(Activation(INIT_SIM_TICK)) - - scheduler ! Completion(agent2.ref) - - parent.expectMessage(Completion(schedulerActivation)) - } - - "scheduling with parent when earliest tick changes" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - val agent2 = TestProbe[Activation]("agent_2") - - scheduler ! ScheduleActivation(agent1.ref, 10) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe 10 - val schedulerActivation = sa1.actor - - scheduler ! ScheduleActivation(agent2.ref, INIT_SIM_TICK) - parent.expectMessage( - ScheduleActivation(schedulerActivation, INIT_SIM_TICK) - ) - - scheduler ! ScheduleActivation(agent2.ref, 5) - parent.expectMessage(ScheduleActivation(schedulerActivation, 5)) - - scheduler ! ScheduleActivation(agent2.ref, 11) - // expect activation for earliest tick (of agent 1) - parent.expectMessage(ScheduleActivation(schedulerActivation, 10)) - - scheduler ! ScheduleActivation(agent2.ref, 20) - // no update, 10 is still earliest - parent.expectNoMessage() - - scheduler ! ScheduleActivation(agent2.ref, 10) - parent.expectNoMessage() - - agent1.expectNoMessage() - - // TICK 10, phase 0 - schedulerActivation ! Activation(10) - - agent1.expectMessage(Activation(10)) - agent2.expectNoMessage() - - // TICK 10, phase 1 - scheduler ! Completion(agent1.ref) - - agent2.expectMessage(Activation(10)) - } - - "scheduling two actors for different ticks" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - val agent2 = TestProbe[Activation]("agent_2") - - scheduler ! ScheduleActivation( - agent1.ref, - INIT_SIM_TICK, - ) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe INIT_SIM_TICK - val schedulerActivation = sa1.actor - - scheduler ! ScheduleActivation( - agent2.ref, - INIT_SIM_TICK, - ) - - // TICK -1 - schedulerActivation ! Activation(INIT_SIM_TICK) - - agent2.expectNoMessage() - agent1.expectActivationAndComplete( - scheduler, - INIT_SIM_TICK, - Some(0), - ) - - parent.expectNoMessage() - - agent2.expectActivationAndComplete( - scheduler, - INIT_SIM_TICK, - Some(0), - ) - - parent.expectMessage(Completion(schedulerActivation, Some(0))) - - agent1.expectNoMessage() - agent2.expectNoMessage() - - // TICK 0 - schedulerActivation ! Activation(0) - - agent1.expectActivationAndComplete( - scheduler, - 0, - Some(900), - ) - - parent.expectNoMessage() - - agent2.expectActivationAndComplete( - scheduler, - 0, - Some(300), - ) - - parent.expectMessage(Completion(schedulerActivation, Some(300))) - - // TICK 300 - schedulerActivation ! Activation(300) - - agent2.expectActivationAndComplete( - scheduler, - 300, - Some(900), - ) - - parent.expectMessage(Completion(schedulerActivation, Some(900))) - - agent1.expectNoMessage() - agent2.expectNoMessage() - - // TICK 900 - schedulerActivation ! Activation(900) - - agent1.expectActivationAndComplete( - scheduler, - 900, - Some(3600), - ) - - parent.expectNoMessage() - - agent2.expectActivationAndComplete( - scheduler, - 900, - Some(1800), - ) - - parent.expectMessage(Completion(schedulerActivation, Some(1800))) - - parent.expectNoMessage() - agent1.expectNoMessage() - agent2.expectNoMessage() - } - - } - - "The Scheduler with PhaseSwitchCore should fail and stop" when { - - "activated if no activation is scheduled" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - - scheduler ! ScheduleActivation(agent1.ref, INIT_SIM_TICK) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe INIT_SIM_TICK - val schedulerActivation = sa1.actor - - // TICK -1 - schedulerActivation ! Activation(INIT_SIM_TICK) - agent1.expectActivationAndComplete(scheduler, INIT_SIM_TICK, None) - parent.expectMessage(Completion(schedulerActivation, None)) - - // No activation scheduled, this should fail now - schedulerActivation ! Activation(0) - - // scheduler stopped - parent.expectTerminated(scheduler) - } - - "activated with wrong tick" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - - scheduler ! ScheduleActivation(agent1.ref, INIT_SIM_TICK) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe INIT_SIM_TICK - val schedulerActivation = sa1.actor - - // TICK 0 - schedulerActivation ! Activation(0) - - // agent does not receive activation - agent1.expectNoMessage() - parent.expectNoMessage() - - // scheduler stopped - parent.expectTerminated(scheduler) - } - - "asked to schedule activation for a past tick while inactive" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - - scheduler ! ScheduleActivation(agent1.ref, 900) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe 900 - val schedulerActivation = sa1.actor - - // TICK 900 - schedulerActivation ! Activation(900) - - agent1.expectActivationAndComplete( - scheduler, - 900, - Some(1800), - ) - - parent.expectMessage(Completion(schedulerActivation, Some(1800))) - - // now inactive again - // can't schedule activation with earlier tick than last tick (900) -> error - scheduler ! ScheduleActivation(agent1.ref, INIT_SIM_TICK) - - // agent does not receive activation - agent1.expectNoMessage() - parent.expectNoMessage() - - // scheduler stopped - parent.expectTerminated(scheduler) - } - - "asked to schedule activation for a past tick while active" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - - scheduler ! ScheduleActivation(agent1.ref, 0) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe 0 - val schedulerActivation = sa1.actor - - // TICK 0 - schedulerActivation ! Activation(0) - agent1.expectMessage(Activation(0)) - - // can't schedule activation for earlier tick than current active -> error - scheduler ! ScheduleActivation(agent1.ref, INIT_SIM_TICK) - - // agent does not receive activation - agent1.expectNoMessage() - parent.expectNoMessage() - - // scheduler stopped - parent.expectTerminated(scheduler) - } - - "asked to schedule activation for a past phase in the current tick" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - val agent2 = TestProbe[Activation]("agent_2") - - scheduler ! ScheduleActivation(agent1.ref, 0) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe 0 - val schedulerActivation = sa1.actor - - scheduler ! ScheduleActivation(agent2.ref, 0) - - // TICK 0, phase 0 - schedulerActivation ! Activation(0) - agent1.expectMessage(Activation(0)) - - // TICK 0, phase 1 - scheduler ! Completion(agent1.ref) - agent2.expectMessage(Activation(0)) - - // schedule first actor again, this is not allowed - scheduler ! ScheduleActivation(agent1.ref, 0) - - // agent does not receive activation - agent1.expectNoMessage() - parent.expectNoMessage() - - // scheduler stopped - parent.expectTerminated(scheduler) - } - - "receiving unexpected completion message" in { - val parent = TestProbe[SchedulerMessage]("parent") - val scheduler = spawn( - Scheduler(parent.ref, PhaseSwitchCore) - ) - - val agent1 = TestProbe[Activation]("agent_1") - val agent2 = TestProbe[Activation]("agent_1") - - scheduler ! ScheduleActivation(agent1.ref, 0) - - val sa1 = parent.expectMessageType[ScheduleActivation] - sa1.tick shouldBe 0 - val schedulerActivation = sa1.actor - - // TICK 0 - schedulerActivation ! Activation(0) - agent1.expectMessage(Activation(0)) - - // receiving completion for wrong actor - scheduler ! Completion(agent2.ref) - - // parent does not receive completion - parent.expectNoMessage() - - // scheduler stopped - parent.expectTerminated(scheduler) - } - } -} diff --git a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala index 62c062c86e..8e6cf9e07d 100644 --- a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala @@ -6,9 +6,6 @@ package edu.ie3.simona.service.ev -import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps -import org.apache.pekko.actor.{ActorRef, ActorSystem} -import org.apache.pekko.testkit.{TestActorRef, TestProbe} import com.typesafe.config.ConfigFactory import edu.ie3.simona.api.data.ev.ExtEvData import edu.ie3.simona.api.data.ev.model.EvModel @@ -33,12 +30,16 @@ import edu.ie3.simona.test.common.{ } import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.quantities.PowerSystemUnits +import org.apache.pekko.actor.typed.scaladsl.adapter.ClassicActorRefOps +import org.apache.pekko.actor.ActorSystem +import org.apache.pekko.testkit.{TestActorRef, TestProbe} import org.scalatest.wordspec.AnyWordSpecLike import tech.units.indriya.quantity.Quantities import java.util.UUID import scala.concurrent.duration.DurationInt import scala.jdk.CollectionConverters._ +import scala.jdk.OptionConverters._ class ExtEvDataServiceSpec extends TestKitWithShutdown( @@ -55,15 +56,6 @@ class ExtEvDataServiceSpec with EvTestData with TestSpawnerClassic { - private val scheduler = TestProbe("scheduler") - private val extSimAdapter = TestProbe("extSimAdapter") - - private val extEvData = (dataService: ActorRef) => - new ExtEvData( - dataService, - extSimAdapter.ref, - ) - private val evcs1UUID = UUID.fromString("06a14909-366e-4e94-a593-1016e1455b30") private val evcs2UUID = @@ -71,7 +63,11 @@ class ExtEvDataServiceSpec "An uninitialized ev movement service" must { "send correct completion message after initialisation" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -79,7 +75,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extEvData(evService)), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsg( ScheduleActivation(evService.toTyped, INIT_SIM_TICK, Some(key)) @@ -90,7 +86,11 @@ class ExtEvDataServiceSpec } "stash registration request and handle it correctly once initialized" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val evcs1 = TestProbe("evcs1") @@ -106,7 +106,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extEvData(evService)), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsg( ScheduleActivation(evService.toTyped, INIT_SIM_TICK, Some(key)) @@ -114,15 +114,17 @@ class ExtEvDataServiceSpec scheduler.send(evService, Activation(INIT_SIM_TICK)) scheduler.expectMsg(Completion(evService.toTyped)) - - evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, None)) } } "An idle ev movements service" must { "handle duplicate registrations correctly" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -130,7 +132,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extEvData(evService)), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -141,19 +143,35 @@ class ExtEvDataServiceSpec val evcs2 = TestProbe("evcs2") evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) - evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, None)) + evcs1.expectNoMessage() evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) - evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, None)) + evcs2.expectNoMessage() // register first one again evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) evcs1.expectNoMessage() - evcs2.expectNoMessage() + + extEvData.sendExtMsg( + new ProvideArrivingEvs( + Map.empty[UUID, java.util.List[EvModel]].asJava, + Some(long2Long(0L)).toJava, + ) + ) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + scheduler.send(evService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(evService.toTyped)) + + evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) + evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) } "fail when activated without having received ExtEvMessage" in { + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -161,7 +179,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extEvData(evService)), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsg( ScheduleActivation(evService.toTyped, INIT_SIM_TICK, Some(key)) @@ -182,9 +200,11 @@ class ExtEvDataServiceSpec } "handle free lots requests correctly and forward them to the correct evcs" in { - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - val extData = extEvData(evService) + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -192,7 +212,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -203,12 +223,25 @@ class ExtEvDataServiceSpec val evcs2 = TestProbe("evcs2") evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) - evcs1.expectMsgType[RegistrationSuccessfulMessage] + evcs1.expectNoMessage() evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) - evcs2.expectMsgType[RegistrationSuccessfulMessage] + evcs2.expectNoMessage() + + extEvData.sendExtMsg( + new ProvideArrivingEvs( + Map.empty[UUID, java.util.List[EvModel]].asJava, + Some(long2Long(0L)).toJava, + ) + ) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + scheduler.send(evService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(evService.toTyped)) - extData.sendExtMsg( + evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) + evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) + + extEvData.sendExtMsg( new RequestEvcsFreeLots() ) @@ -231,7 +264,7 @@ class ExtEvDataServiceSpec scheduler.expectMsg(Completion(evService.toTyped)) - extData.receiveTriggerQueue shouldBe empty + extEvData.receiveTriggerQueue shouldBe empty // return free lots to ev service evcs1.send( @@ -243,7 +276,7 @@ class ExtEvDataServiceSpec ) // nothing should happen yet, waiting for second departed ev - extData.receiveTriggerQueue shouldBe empty + extEvData.receiveTriggerQueue shouldBe empty evcs2.send( evService, @@ -256,21 +289,23 @@ class ExtEvDataServiceSpec // ev service should recognize that all evcs that are expected are returned, // thus should send ProvideEvcsFreeLots awaitCond( - !extData.receiveTriggerQueue.isEmpty, + !extEvData.receiveTriggerQueue.isEmpty, max = 3.seconds, message = "No message received", ) - extData.receiveTriggerQueue.size() shouldBe 1 + extEvData.receiveTriggerQueue.size() shouldBe 1 // only evcs 1 should be included, the other one is full - extData.receiveTriggerQueue.take() shouldBe new ProvideEvcsFreeLots( + extEvData.receiveTriggerQueue.take() shouldBe new ProvideEvcsFreeLots( Map(evcs1UUID -> int2Integer(2)).asJava ) } "handle price requests correctly by returning dummy values" in { - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - val extData = extEvData(evService) + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -278,7 +313,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -289,12 +324,25 @@ class ExtEvDataServiceSpec val evcs2 = TestProbe("evcs2") evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) - evcs1.expectMsgType[RegistrationSuccessfulMessage] + evcs1.expectNoMessage() evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) - evcs2.expectMsgType[RegistrationSuccessfulMessage] + evcs2.expectNoMessage() - extData.sendExtMsg(new RequestCurrentPrices()) + extEvData.sendExtMsg( + new ProvideArrivingEvs( + Map.empty[UUID, java.util.List[EvModel]].asJava, + Some(long2Long(0L)).toJava, + ) + ) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + scheduler.send(evService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(evService.toTyped)) + + evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) + evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) + + extEvData.sendExtMsg(new RequestCurrentPrices()) // ev service should receive request at this moment // scheduler should receive schedule msg @@ -311,13 +359,13 @@ class ExtEvDataServiceSpec // ev service should recognize that all evcs that are expected are returned, // thus should send ProvideEvcsFreeLots awaitCond( - !extData.receiveTriggerQueue.isEmpty, + !extEvData.receiveTriggerQueue.isEmpty, max = 3.seconds, message = "No message received", ) - extData.receiveTriggerQueue.size() shouldBe 1 + extEvData.receiveTriggerQueue.size() shouldBe 1 // only evcs 1 should be included, the other one is full - extData.receiveTriggerQueue.take() shouldBe new ProvideCurrentPrices( + extEvData.receiveTriggerQueue.take() shouldBe new ProvideCurrentPrices( Map( evcs1UUID -> double2Double(0d), evcs2UUID -> double2Double(0d), @@ -328,9 +376,11 @@ class ExtEvDataServiceSpec } "return free lots requests right away if there are no evcs registered" in { - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - val extData = extEvData(evService) + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -338,14 +388,14 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] scheduler.send(evService, Activation(INIT_SIM_TICK)) scheduler.expectMsg(Completion(evService.toTyped)) - extData.sendExtMsg(new RequestEvcsFreeLots()) + extEvData.sendExtMsg(new RequestEvcsFreeLots()) // ev service should receive movements msg at this moment // scheduler receives schedule msg @@ -360,18 +410,20 @@ class ExtEvDataServiceSpec // ev service should send ProvideEvcsFreeLots right away awaitCond( - !extData.receiveTriggerQueue.isEmpty, + !extEvData.receiveTriggerQueue.isEmpty, max = 3.seconds, message = "No message received", ) - extData.receiveTriggerQueue.size() shouldBe 1 - extData.receiveTriggerQueue.take() shouldBe new ProvideEvcsFreeLots() + extEvData.receiveTriggerQueue.size() shouldBe 1 + extEvData.receiveTriggerQueue.take() shouldBe new ProvideEvcsFreeLots() } "handle ev departure requests correctly and return departed evs" in { - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - val extData = extEvData(evService) + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -379,7 +431,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -390,17 +442,30 @@ class ExtEvDataServiceSpec val evcs2 = TestProbe("evcs1") evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) - evcs1.expectMsgType[RegistrationSuccessfulMessage] + evcs1.expectNoMessage() evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) - evcs2.expectMsgType[RegistrationSuccessfulMessage] + evcs2.expectNoMessage() + + extEvData.sendExtMsg( + new ProvideArrivingEvs( + Map.empty[UUID, java.util.List[EvModel]].asJava, + Some(long2Long(0L)).toJava, + ) + ) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + scheduler.send(evService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(evService.toTyped)) + + evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) + evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) val departures = Map( evcs1UUID -> List(evA.getUuid).asJava, evcs2UUID -> List(evB.getUuid).asJava, ).asJava - extData.sendExtMsg( + extEvData.sendExtMsg( new RequestDepartingEvs(departures) ) @@ -433,7 +498,7 @@ class ExtEvDataServiceSpec ) // nothing should happen yet, waiting for second departed ev - extData.receiveTriggerQueue shouldBe empty + extEvData.receiveTriggerQueue shouldBe empty val updatedEvB = evB.copyWith( Quantities.getQuantity(4.0, PowerSystemUnits.KILOWATTHOUR) @@ -447,20 +512,22 @@ class ExtEvDataServiceSpec // ev service should recognize that all evs that are expected are returned, // thus should send ProvideDepartingEvs awaitCond( - !extData.receiveTriggerQueue.isEmpty, + !extEvData.receiveTriggerQueue.isEmpty, max = 3.seconds, message = "No message received", ) - extData.receiveTriggerQueue.size() shouldBe 1 - extData.receiveTriggerQueue.take() shouldBe new ProvideDepartingEvs( + extEvData.receiveTriggerQueue.size() shouldBe 1 + extEvData.receiveTriggerQueue.take() shouldBe new ProvideDepartingEvs( List[EvModel](updatedEvA, updatedEvB).asJava ) } "return ev departure requests right away if request list is empty" in { - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - val extData = extEvData(evService) + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -468,14 +535,14 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] scheduler.send(evService, Activation(INIT_SIM_TICK)) scheduler.expectMsg(Completion(evService.toTyped)) - extData.sendExtMsg( + extEvData.sendExtMsg( new RequestDepartingEvs(Map.empty[UUID, java.util.List[UUID]].asJava) ) @@ -492,20 +559,22 @@ class ExtEvDataServiceSpec // ev service should send ProvideDepartingEvs right away awaitCond( - !extData.receiveTriggerQueue.isEmpty, + !extEvData.receiveTriggerQueue.isEmpty, max = 3.seconds, message = "No message received", ) - extData.receiveTriggerQueue.size() shouldBe 1 - extData.receiveTriggerQueue.take() shouldBe new ProvideDepartingEvs( + extEvData.receiveTriggerQueue.size() shouldBe 1 + extEvData.receiveTriggerQueue.take() shouldBe new ProvideDepartingEvs( List.empty[EvModel].asJava ) } "handle ev arrivals correctly and forward them to the correct evcs" in { - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - val extData = extEvData(evService) + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -513,7 +582,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -524,18 +593,31 @@ class ExtEvDataServiceSpec val evcs2 = TestProbe("evcs2") evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) - evcs1.expectMsgType[RegistrationSuccessfulMessage] + evcs1.expectNoMessage() evcs2.send(evService, RegisterForEvDataMessage(evcs2UUID)) - evcs2.expectMsgType[RegistrationSuccessfulMessage] + evcs2.expectNoMessage() + + extEvData.sendExtMsg( + new ProvideArrivingEvs( + Map.empty[UUID, java.util.List[EvModel]].asJava, + Some(long2Long(0L)).toJava, + ) + ) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + scheduler.send(evService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(evService.toTyped)) + + evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) + evcs2.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) val arrivals = Map( evcs1UUID -> List[EvModel](evA).asJava, evcs2UUID -> List[EvModel](evB).asJava, ).asJava - extData.sendExtMsg( - new ProvideArrivingEvs(arrivals) + extEvData.sendExtMsg( + new ProvideArrivingEvs(arrivals, None.toJava) ) // ev service should receive movements msg at this moment @@ -546,29 +628,31 @@ class ExtEvDataServiceSpec // we trigger ev service scheduler.send(evService, Activation(tick)) - // schedule lock is scheduled - scheduler.expectMsgType[ScheduleActivation].tick shouldBe tick val evsMessage1 = evcs1.expectMsgType[ProvideEvDataMessage] evsMessage1.tick shouldBe tick - evsMessage1.data shouldBe ArrivingEvsData(Seq(EvModelWrapper(evA))) - evsMessage1.unlockKey should not be empty + evsMessage1.data shouldBe ArrivingEvs( + Seq(EvModelWrapper(evA)) + ) val evsMessage2 = evcs2.expectMsgType[ProvideEvDataMessage] evsMessage2.tick shouldBe tick - evsMessage2.data shouldBe ArrivingEvsData(Seq(EvModelWrapper(evB))) - evsMessage2.unlockKey should not be empty + evsMessage2.data shouldBe ArrivingEvs( + Seq(EvModelWrapper(evB)) + ) scheduler.expectMsg(Completion(evService.toTyped)) // no response expected - extData.receiveTriggerQueue shouldBe empty + extEvData.receiveTriggerQueue shouldBe empty } "skip a movements provision from an evcs that is not registered" in { - val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val scheduler = TestProbe("scheduler") + val extSimAdapter = TestProbe("extSimAdapter") - val extData = extEvData(evService) + val evService = TestActorRef(new ExtEvDataService(scheduler.ref)) + val extEvData = new ExtEvData(evService, extSimAdapter.ref) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref.toTyped, INIT_SIM_TICK) @@ -576,7 +660,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -586,15 +670,27 @@ class ExtEvDataServiceSpec val evcs1 = TestProbe("evcs1") evcs1.send(evService, RegisterForEvDataMessage(evcs1UUID)) - evcs1.expectMsgType[RegistrationSuccessfulMessage] + evcs1.expectNoMessage() + + extEvData.sendExtMsg( + new ProvideArrivingEvs( + Map.empty[UUID, java.util.List[EvModel]].asJava, + Some(long2Long(0L)).toJava, + ) + ) + extSimAdapter.expectMsg(new ScheduleDataServiceMessage(evService)) + scheduler.send(evService, Activation(INIT_SIM_TICK)) + scheduler.expectMsg(Completion(evService.toTyped)) + + evcs1.expectMsg(RegistrationSuccessfulMessage(evService.ref, Some(0L))) val arrivals = Map( evcs1UUID -> List[EvModel](evA).asJava, evcs2UUID -> List[EvModel](evB).asJava, ).asJava - extData.sendExtMsg( - new ProvideArrivingEvs(arrivals) + extEvData.sendExtMsg( + new ProvideArrivingEvs(arrivals, None.toJava) ) // ev service should receive movements msg at this moment @@ -605,18 +701,17 @@ class ExtEvDataServiceSpec // we trigger ev service scheduler.send(evService, Activation(tick)) - // schedule lock is scheduled - scheduler.expectMsgType[ScheduleActivation].tick shouldBe tick val evsMessage1 = evcs1.expectMsgType[ProvideEvDataMessage] evsMessage1.tick shouldBe tick - evsMessage1.data shouldBe ArrivingEvsData(Seq(EvModelWrapper(evA))) - evsMessage1.unlockKey should not be empty + evsMessage1.data shouldBe ArrivingEvs( + Seq(EvModelWrapper(evA)) + ) scheduler.expectMsg(Completion(evService.toTyped)) // no response expected - extData.receiveTriggerQueue shouldBe empty + extEvData.receiveTriggerQueue shouldBe empty } } } diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSpec.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSpec.scala index 1ae6457b8e..db7e9abdea 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSpec.scala @@ -251,13 +251,11 @@ class PrimaryServiceWorkerSpec actualServiceRef, actualData, actualNextDataTick, - unlockKey, ) => actualTick shouldBe 0L actualServiceRef shouldBe serviceRef actualData shouldBe primaryData actualNextDataTick shouldBe Some(900L) - unlockKey shouldBe None } } @@ -356,7 +354,6 @@ class PrimaryServiceWorkerSpec actualServiceRef, data, nextDataTick, - unlockKey, ) => tick shouldBe 900L actualServiceRef shouldBe serviceRef @@ -366,7 +363,6 @@ class PrimaryServiceWorkerSpec case _ => fail("Expected to get active power only.") } nextDataTick shouldBe None - unlockKey shouldBe None } } } diff --git a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala index ad028070d2..63be126c0f 100644 --- a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala @@ -60,7 +60,7 @@ class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { ) { override def extSimulations( context: ActorContext[_], - rootScheduler: ActorRef[SchedulerMessage], + scheduler: ActorRef[SchedulerMessage], ): ExtSimSetupData = { // We cannot return a TestProbe ref here, // needs to be a proper actor created by context @@ -68,7 +68,7 @@ class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { forwardMessage(Some(extSimAdapter.ref)), uniqueName("extSimAdapterForwarder"), ) - ExtSimSetupData(Iterable(extSim.toClassic), Map.empty, None) + ExtSimSetupData(Iterable(extSim.toClassic), Map.empty) } } ), @@ -453,8 +453,8 @@ object SimonaSimSpec { override def extSimulations( context: ActorContext[_], - rootScheduler: ActorRef[SchedulerMessage], + scheduler: ActorRef[SchedulerMessage], ): ExtSimSetupData = - ExtSimSetupData(Iterable.empty, Map.empty, None) + ExtSimSetupData(Iterable.empty, Map.empty) } } diff --git a/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala index 55e7310266..7fa3ab3469 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala @@ -58,7 +58,7 @@ class SimonaSetupSpec extends UnitSpec with SimonaSetup with SubGridGateMokka { override def extSimulations( context: ActorContext[_], - rootScheduler: ActorRef[SchedulerMessage], + scheduler: ActorRef[SchedulerMessage], ): ExtSimSetupData = throw new NotImplementedException( "This is a dummy setup" ) diff --git a/src/test/scala/edu/ie3/util/scala/collection/immutable/PrioritySwitchBiSetSpec.scala b/src/test/scala/edu/ie3/util/scala/collection/immutable/PrioritySwitchBiSetSpec.scala deleted file mode 100644 index 125cc24c5f..0000000000 --- a/src/test/scala/edu/ie3/util/scala/collection/immutable/PrioritySwitchBiSetSpec.scala +++ /dev/null @@ -1,174 +0,0 @@ -/* - * © 2023. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.util.scala.collection.immutable - -import edu.ie3.simona.test.common.UnitSpec - -class PrioritySwitchBiSetSpec extends UnitSpec { - private type Key = Int - private type Value = String - - private val item1: Value = "test1" - private val item2: Value = "test2" - private val item3: Value = "test3" - private val item4: Value = "test4" - private val item5: Value = "test5" - private val item6: Value = "test6" - - "A PriorityMultiBiSet" should { - "be created correctly emptily" in { - val prioSet = PrioritySwitchBiSet.empty[Key, Value] - - prioSet.isEmpty shouldBe true - prioSet.nonEmpty shouldBe false - prioSet.headKeyOption shouldBe None - prioSet.headKeyIndexOption shouldBe None - prioSet.values shouldBe Vector.empty - prioSet.takeNextValueFor(0) shouldBe None - } - - "behave correctly when adding to an empty map" in { - val prioSet00 = PrioritySwitchBiSet.empty[Key, Value] - - val prioSet01 = prioSet00.set(1, item1) - prioSet01.isEmpty shouldBe false - prioSet01.nonEmpty shouldBe true - prioSet01.headKeyOption shouldBe Some(1) - prioSet01.headKeyIndexOption shouldBe Some(0) - prioSet01.values shouldBe Vector(item1) - prioSet01.indexOf(item1) shouldBe Some(0) - prioSet01.indexOf(item2) shouldBe None - prioSet01.takeNextValueFor(0) shouldBe None - - val (val02, prioSet02) = prioSet01.takeNextValueFor(1).value - val02 shouldBe item1 - prioSet02.isEmpty shouldBe true - prioSet02.headKeyOption shouldBe None - prioSet02.headKeyIndexOption shouldBe None - prioSet02.values shouldBe Vector(item1) - } - - "behave correctly when adding and retrieving multiple values" in { - val prioSet00 = PrioritySwitchBiSet.empty[Key, Value] - - val prioSet01 = prioSet00.set(10, item1) - prioSet01.nonEmpty shouldBe true - prioSet01.headKeyOption shouldBe Some(10) - prioSet01.headKeyIndexOption shouldBe Some(0) - prioSet01.values shouldBe Vector(item1) - prioSet01.indexOf(item1) shouldBe Some(0) - - val prioSet02 = prioSet01.set(2, item2) - prioSet02.nonEmpty shouldBe true - prioSet02.headKeyOption shouldBe Some(2) - prioSet02.headKeyIndexOption shouldBe Some(1) - prioSet02.values shouldBe Vector(item1, item2) - prioSet02.indexOf(item2) shouldBe Some(1) - - // moving item2 to 5 - val prioSet03 = prioSet02.set(5, item2) - prioSet03.nonEmpty shouldBe true - prioSet03.headKeyOption shouldBe Some(5) - prioSet03.headKeyIndexOption shouldBe Some(1) - prioSet03.values shouldBe Vector(item1, item2) - prioSet03.indexOf(item2) shouldBe Some(1) - prioSet03.takeNextValueFor(2) shouldBe None - - val prioSet04 = prioSet03.set(5, item4) - prioSet04.nonEmpty shouldBe true - prioSet04.headKeyOption shouldBe Some(5) - prioSet04.headKeyIndexOption shouldBe Some(1) - prioSet04.values shouldBe Vector(item1, item2, item4) - prioSet04.indexOf(item4) shouldBe Some(2) - - val prioSet05 = prioSet04.set(5, item3) - prioSet05.headKeyOption shouldBe Some(5) - prioSet05.headKeyIndexOption shouldBe Some(1) - prioSet05.values shouldBe Vector(item1, item2, item4, item3) - prioSet05.indexOf(item3) shouldBe Some(3) - - val prioSet06 = prioSet05.set(15, item5) - prioSet06.headKeyOption shouldBe Some(5) - prioSet06.headKeyIndexOption shouldBe Some(1) - prioSet06.values shouldBe Vector(item1, item2, item4, item3, item5) - prioSet06.indexOf(item5) shouldBe Some(4) - - // moving item4 to 15 - val prioSet07 = prioSet06.set(15, item4) - prioSet07.headKeyOption shouldBe Some(5) - prioSet07.headKeyIndexOption shouldBe Some(1) - prioSet07.values shouldBe Vector(item1, item2, item4, item3, item5) - - // moving item1 to 15 - val prioSet08 = prioSet07.set(15, item1) - prioSet08.headKeyOption shouldBe Some(5) - prioSet08.headKeyIndexOption shouldBe Some(1) - prioSet08.values shouldBe Vector(item1, item2, item4, item3, item5) - - // priority indices should not have changed - prioSet08.indexOf(item1) shouldBe Some(0) - prioSet08.indexOf(item2) shouldBe Some(1) - prioSet08.indexOf(item3) shouldBe Some(3) - prioSet08.indexOf(item4) shouldBe Some(2) - prioSet08.indexOf(item5) shouldBe Some(4) - prioSet08.indexOf(item6) shouldBe None - - // retrieving values now. They should come in order: - // 5 -> (item2, item3), 15 -> (item1, item4, item5) - - val (val09, prioSet09) = prioSet08.takeNextValueFor(5).value - val09 shouldBe item2 - prioSet09.isEmpty shouldBe false - prioSet09.headKeyOption shouldBe Some(5) - prioSet09.headKeyIndexOption shouldBe Some(3) - - val (val10, prioSet10) = prioSet09.takeNextValueFor(5).value - val10 shouldBe item3 - prioSet10.isEmpty shouldBe false - prioSet10.headKeyOption shouldBe Some(15) - prioSet10.headKeyIndexOption shouldBe Some(0) - - val (val11, prioSet11) = prioSet10.takeNextValueFor(15).value - val11 shouldBe item1 - prioSet11.isEmpty shouldBe false - prioSet11.headKeyOption shouldBe Some(15) - prioSet11.headKeyIndexOption shouldBe Some(2) - - val (val12, prioSet12) = prioSet11.takeNextValueFor(15).value - val12 shouldBe item4 - prioSet12.isEmpty shouldBe false - prioSet12.headKeyOption shouldBe Some(15) - prioSet12.headKeyIndexOption shouldBe Some(4) - - // moving item5 to 10 - val prioSet13 = prioSet12.set(10, item5) - prioSet13.isEmpty shouldBe false - prioSet13.headKeyOption shouldBe Some(10) - prioSet13.headKeyIndexOption shouldBe Some(4) - prioSet13.takeNextValueFor(15) shouldBe None - - val (val14, prioSet14) = prioSet13.takeNextValueFor(10).value - val14 shouldBe item5 - prioSet14.isEmpty shouldBe true - prioSet14.nonEmpty shouldBe false - prioSet14.headKeyOption shouldBe None - prioSet14.headKeyIndexOption shouldBe None - - // priority indices should not have changed - prioSet14.indexOf(item1) shouldBe Some(0) - prioSet14.indexOf(item2) shouldBe Some(1) - prioSet14.indexOf(item3) shouldBe Some(3) - prioSet14.indexOf(item4) shouldBe Some(2) - prioSet14.indexOf(item5) shouldBe Some(4) - prioSet14.indexOf(item6) shouldBe None - - prioSet14.values shouldBe Vector(item1, item2, item4, item3, item5) - } - - } - -}