From 8b973adb2c4cd81503e146f76f08300d207cc733 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Fri, 22 Mar 2024 16:47:31 +0100 Subject: [PATCH 01/18] Providing detailed information about next tick per agent --- CHANGELOG.md | 1 + .../agent/participant/ParticipantAgent.scala | 93 +++++++++++++----- .../ParticipantAgentFundamentals.scala | 30 ------ .../flex/MinMaxFlexibilityMessage.scala | 6 ++ .../messages/services/EvMessage.scala | 4 +- .../messages/services/ServiceMessage.scala | 17 +++- .../simona/service/ev/ExtEvDataService.scala | 94 +++++++++++++------ 7 files changed, 162 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33813e38c3..42efb5c00b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Converting `SimonaSim` to pekko typed/terminating SimonSim when initialization fails [#210](https://github.com/ie3-institute/simona/issues/210) - Converting the `GridAgent` and the `DBFSAlgorithm` to `pekko typed` [#666](https://github.com/ie3-institute/simona/issues/666) - Validation of grid will throw exception instead of just logging errors [#463](https://github.com/ie3-institute/simona/issues/463) +- External simulation should provide detailed information about next tick per agent [#776](https://github.com/ie3-institute/simona/issues/776) ### Fixed - Removed a repeated line in the documentation of vn_simona config [#658](https://github.com/ie3-institute/simona/issues/658) diff --git a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala index a01c5677a1..e573ac7949 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -50,12 +50,14 @@ import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ FlexResponse, IssueFlexControl, RequestFlexOptions, + ScheduleFlexRequest, } import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegistrationResponseMessage.RegistrationSuccessfulMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ PrimaryServiceRegistrationMessage, ProvisionMessage, RegistrationResponseMessage, + ScheduleProvisionMessage, } import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.scala.quantities.ReactivePower @@ -193,6 +195,14 @@ abstract class ParticipantAgent[ fromOutsideBaseStateData, ) + case Event( + msg: ScheduleProvisionMessage, + baseStateData: BaseStateData[PD], + ) => + val updatedStateData = handleProvisionScheduling(msg, baseStateData) + + stay() using updatedStateData + case Event( msg: ProvisionMessage[Data], baseStateData: BaseStateData[PD], @@ -347,6 +357,18 @@ abstract class ParticipantAgent[ scheduler, )(stateData.baseStateData.outputConfig) + case Event( + msg: ScheduleProvisionMessage, + stateData @ DataCollectionStateData( + baseStateData: BaseStateData[PD], + _, + _, + ), + ) => + val updatedStateData = handleProvisionScheduling(msg, baseStateData) + + stay() using stateData.copy(baseStateData = updatedStateData) + case Event( msg: ProvisionMessage[_], stateData @ DataCollectionStateData( @@ -355,28 +377,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, @@ -384,7 +391,7 @@ abstract class ParticipantAgent[ baseStateData.requestValueStore, baseStateData.voltageValueStore, baseStateData.additionalActivationTicks, - foreSeenDataTicks, + foreseenDataTicks, ) val updatedStateData: DataCollectionStateData[PD] = stateData .copy( @@ -611,16 +618,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 { @@ -646,6 +650,51 @@ abstract class ParticipantAgent[ .getOrElse(createInitialState(baseStateData)) } + private def handleProvisionScheduling( + msg: ScheduleProvisionMessage, + baseStateData: BaseStateData[PD], + ): BaseStateData[PD] = { + val foreseenDataTicks = + baseStateData.foreseenDataTicks + (msg.serviceRef -> Some(msg.tick)) + + val updatedStateData = BaseStateData.updateBaseStateData( + baseStateData, + baseStateData.resultValueStore, + baseStateData.requestValueStore, + baseStateData.voltageValueStore, + baseStateData.additionalActivationTicks, + foreseenDataTicks, + ) + + updatedStateData match { + case modelStateData: ParticipantModelBaseStateData[_, _, _, _] => + val maybeEmAgent = modelStateData.flexStateData.map(_.emAgent) + + maybeEmAgent match { + case Some(emAgent) => + emAgent ! ScheduleFlexRequest( + modelStateData.model.getUuid, + msg.tick, + Some(msg.unlockKey), + ) + case None => + scheduler ! ScheduleActivation( + self.toTyped, + msg.tick, + Some(msg.unlockKey), + ) + } + case _ => + scheduler ! ScheduleActivation( + self.toTyped, + msg.tick, + Some(msg.unlockKey), + ) + } + + updatedStateData + } + /** Handle an incoming data provision message in Idle, try to figure out who's * about to send information in this tick as well. Map together all senders * with their yet apparent information. 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 2ec46f6450..277b455939 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala @@ -514,36 +514,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) diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFlexibilityMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFlexibilityMessage.scala index af72bf52c6..986cc3afe4 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFlexibilityMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFlexibilityMessage.scala @@ -114,4 +114,10 @@ object MinMaxFlexibilityMessage { ): ProvideMinMaxFlexOptions = ProvideMinMaxFlexOptions(modelUuid, power, power, power) } + + /** Indicates that flex options have not changed since last provision + * @param modelUuid + */ + case class FlexOptionsUnchanged(override val modelUuid: UUID) + extends ProvideFlexOptions } 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..181d58d8d7 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 @@ -48,7 +48,7 @@ object EvMessage { override val tick: Long, override val serviceRef: ActorRef, override val data: EvData, - override val nextDataTick: Option[Long] = None, + override val nextDataTick: Option[Long], override val unlockKey: Option[ScheduleKey] = None, ) extends EvMessage with ProvisionMessage[EvData] @@ -72,7 +72,7 @@ object EvMessage { /** Holds arrivals for one charging station * * @param arrivals - * EVs arriving at the charging station + * EVs arriving at the charging station, which might be empty */ final case class ArrivingEvsData( arrivals: Seq[EvModelWrapper] 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..6cf02c0f2d 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,22 @@ 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] } + + /** @param serviceRef + * @param tick + * @param unlockKey + */ + case class ScheduleProvisionMessage( + serviceRef: ActorRef, + tick: Long, + unlockKey: ScheduleKey, + ) extends ServiceMessage + } 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 c827cb52fb..a0c5cb219f 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -6,18 +6,23 @@ 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.ontology.messages.services.ServiceMessage.{ + ScheduleProvisionMessage, + ServiceRegistrationMessage, +} import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, @@ -29,6 +34,9 @@ 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.typed.scaladsl.adapter.ClassicActorRefOps +import org.apache.pekko.actor.{ActorContext, ActorRef, Props} import java.util.UUID import scala.jdk.CollectionConverters._ @@ -142,8 +150,6 @@ class ExtEvDataService(override val scheduler: ActorRef) serviceStateData.uuidToActorRef.get(evcs) match { case None => // Actor is not registered yet - agentToBeRegistered ! RegistrationSuccessfulMessage(self, None) - serviceStateData.copy( uuidToActorRef = serviceStateData.uuidToActorRef + (evcs -> agentToBeRegistered) @@ -185,7 +191,10 @@ class ExtEvDataService(override val scheduler: ActorRef) case departingEvsRequest: RequestDepartingEvs => requestDepartingEvs(tick, departingEvsRequest.departures) case arrivingEvsProvision: ProvideArrivingEvs => - handleArrivingEvs(tick, arrivingEvsProvision.arrivals)( + handleArrivingEvs( + tick, + arrivingEvsProvision.arrivals, + )( serviceStateData, ctx, ) @@ -263,33 +272,62 @@ class ExtEvDataService(override val scheduler: ActorRef) serviceStateData: ExtEvStateData, ctx: ActorContext, ): (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) + val arrivingEvs = allArrivingEvs.asScala - actorToEvs.zip(keys).foreach { case ((actor, arrivingEvs), key) => - actor ! ProvideEvDataMessage( - tick, + val keys = ScheduleLock.multiKey( + ctx, + scheduler.toTyped, + tick, + serviceStateData.uuidToActorRef.size, + ) + + if (tick == INIT_SIM_TICK) { + serviceStateData.uuidToActorRef.foreach { case (uuid, actor) => + + val firstTick: Option[Long] = arrivingEvs + .getOrElse( + uuid, + throw new CriticalFailureException( + s"No initial message found for EVCS $actor($uuid)" + ), + ) + .nextTick + + actor ! RegistrationSuccessfulMessage( self, - ArrivingEvsData(arrivingEvs), - unlockKey = Some(key), + firstTick, ) } + } else { + val actorToEvs = serviceStateData.uuidToActorRef.map { + case (uuid, actor) => + actor -> arrivingEvs + .get(uuid) + .map(_.asScala.map(EvModelWrapper.apply).toSeq) + .getOrElse(Seq.empty) + } + if (actorToEvs.nonEmpty) { + + actorToEvs.zip(keys).foreach { case ((actor, arrivingEvs), key) => + if (arrivingEvs.nonEmpty) + actor ! ProvideEvDataMessage( + tick, + self, + ArrivingEvsData(arrivingEvs), + Some(arrivingEvs.nextTick), + unlockKey = Some(key), + ) + else + actor ! ScheduleProvisionMessage( + self, + tick, + unlockKey = key, + ) + } + + } } ( From 7c482ea75965482f6700c015160fe875ce336bae Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Fri, 22 Mar 2024 16:52:11 +0100 Subject: [PATCH 02/18] Removing unused scheduleKey --- .../agent/participant/ParticipantAgentFundamentals.scala | 5 +---- .../ie3/simona/ontology/messages/services/EvMessage.scala | 2 -- .../ontology/messages/services/PrimaryDataMessage.scala | 2 -- .../simona/ontology/messages/services/ServiceMessage.scala | 1 - .../simona/ontology/messages/services/WeatherMessage.scala | 2 -- .../scala/edu/ie3/simona/service/ev/ExtEvDataService.scala | 2 -- .../ie3/simona/service/primary/PrimaryServiceWorker.scala | 2 -- 7 files changed, 1 insertion(+), 15 deletions(-) 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 277b455939..ff060dd980 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala @@ -72,10 +72,7 @@ import edu.ie3.simona.ontology.messages.PowerMessage.{ AssetPowerChangedMessage, AssetPowerUnchangedMessage, } -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.{ 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 181d58d8d7..1b7a714514 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 @@ -49,7 +48,6 @@ object EvMessage { override val serviceRef: ActorRef, override val data: EvData, override val nextDataTick: Option[Long], - override val unlockKey: Option[ScheduleKey] = None, ) extends EvMessage with ProvisionMessage[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 6cf02c0f2d..eed3832f63 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 @@ -80,7 +80,6 @@ object ServiceMessage { * the rest of the simulation */ val nextDataTick: Option[Long] - val unlockKey: Option[ScheduleKey] } /** @param serviceRef 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/service/ev/ExtEvDataService.scala b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala index a0c5cb219f..0cfe0c47b2 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -284,7 +284,6 @@ class ExtEvDataService(override val scheduler: ActorRef) if (tick == INIT_SIM_TICK) { serviceStateData.uuidToActorRef.foreach { case (uuid, actor) => - val firstTick: Option[Long] = arrivingEvs .getOrElse( uuid, @@ -317,7 +316,6 @@ class ExtEvDataService(override val scheduler: ActorRef) self, ArrivingEvsData(arrivingEvs), Some(arrivingEvs.nextTick), - unlockKey = Some(key), ) else actor ! ScheduleProvisionMessage( 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] } From 3cd05a83de2741cf4f16c2bcabfc5cbadf4c83a8 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Fri, 22 Mar 2024 17:08:09 +0100 Subject: [PATCH 03/18] Towards a functioning ExtEvDataService... --- .../simona/service/ev/ExtEvDataService.scala | 73 ++++++++++--------- 1 file changed, 39 insertions(+), 34 deletions(-) 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 0cfe0c47b2..42fade723a 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -273,25 +273,25 @@ class ExtEvDataService(override val scheduler: ActorRef) ctx: ActorContext, ): (ExtEvStateData, Option[Long]) = { - val arrivingEvs = allArrivingEvs.asScala - - val keys = ScheduleLock.multiKey( - ctx, - scheduler.toTyped, - tick, - serviceStateData.uuidToActorRef.size, - ) + val allArrivingEvsData = allArrivingEvs.asScala if (tick == INIT_SIM_TICK) { + val keys = ScheduleLock.multiKey( + ctx, + scheduler.toTyped, + tick, + serviceStateData.uuidToActorRef.size, + ) + serviceStateData.uuidToActorRef.foreach { case (uuid, actor) => - val firstTick: Option[Long] = arrivingEvs + val firstTick: Option[Long] = allArrivingEvsData .getOrElse( uuid, throw new CriticalFailureException( s"No initial message found for EVCS $actor($uuid)" ), ) - .nextTick + .maybeNextTick actor ! RegistrationSuccessfulMessage( self, @@ -299,33 +299,38 @@ class ExtEvDataService(override val scheduler: ActorRef) ) } } else { - val actorToEvs = serviceStateData.uuidToActorRef.map { - case (uuid, actor) => - actor -> arrivingEvs - .get(uuid) - .map(_.asScala.map(EvModelWrapper.apply).toSeq) - .getOrElse(Seq.empty) - } - - if (actorToEvs.nonEmpty) { - actorToEvs.zip(keys).foreach { case ((actor, arrivingEvs), key) => - if (arrivingEvs.nonEmpty) - actor ! ProvideEvDataMessage( - tick, - self, - ArrivingEvsData(arrivingEvs), - Some(arrivingEvs.nextTick), - ) - else - actor ! ScheduleProvisionMessage( - self, - tick, - unlockKey = key, - ) + val actorToEvsData = allArrivingEvsData + .flatMap { case (evcs, arrivingEvsData) => + serviceStateData.uuidToActorRef + .get(evcs) + .map((_, arrivingEvsData)) + .orElse { + log.warning( + "A corresponding actor ref for UUID {} could not be found", + evcs, + ) + None + } } - + .partitionMap() // TODO + + actorToEvsData.zip(keys).foreach { case ((actor, arrivingEvsData), key) => + if (arrivingEvsData.arrivals.nonEmpty) + actor ! ProvideEvDataMessage( + tick, + self, + arrivingEvsData.arrivals, + Some(arrivingEvsData.maybeNextTick), + ) + else + actor ! ScheduleProvisionMessage( + self, + tick, + unlockKey = key, + ) } + } ( From 6fc8187572f85858770739b4d5082776879e9bb0 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Fri, 22 Mar 2024 19:49:58 +0100 Subject: [PATCH 04/18] Removing PhaseSwitch implementation and utilization --- .../scheduler/core/PhaseSwitchCore.scala | 160 ------------- .../scala/edu/ie3/simona/sim/SimonaSim.scala | 26 +-- .../simona/sim/setup/ExtSimSetupData.scala | 3 - .../ie3/simona/sim/setup/SimonaSetup.scala | 6 +- .../sim/setup/SimonaStandaloneSetup.scala | 14 +- .../immutable/PrioritySwitchBiSet.scala | 213 ------------------ .../edu/ie3/simona/sim/SimonaSimSpec.scala | 4 +- .../simona/sim/setup/SimonaSetupSpec.scala | 2 +- .../immutable/PrioritySwitchBiSetSpec.scala | 174 -------------- 9 files changed, 20 insertions(+), 582 deletions(-) delete mode 100644 src/main/scala/edu/ie3/simona/scheduler/core/PhaseSwitchCore.scala delete mode 100644 src/main/scala/edu/ie3/util/scala/collection/immutable/PrioritySwitchBiSet.scala delete mode 100644 src/test/scala/edu/ie3/util/scala/collection/immutable/PrioritySwitchBiSetSpec.scala 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/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 f96a144a36..ef2e6f533b 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -95,14 +95,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 6788a652f2..1cb3da574e 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -190,20 +190,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) @@ -211,7 +210,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 @@ -222,7 +221,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) @@ -231,7 +230,7 @@ class SimonaStandaloneSetup( InitExtEvData(extEvData), ScheduleLock.singleKey( context, - extScheduler, + scheduler, INIT_SIM_TICK, ), ) @@ -254,10 +253,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/sim/SimonaSimSpec.scala b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala index c6ffda8fab..8a88b2163f 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 @@ -451,7 +451,7 @@ object SimonaSimSpec { override def extSimulations( context: ActorContext[_], - rootScheduler: ActorRef[SchedulerMessage], + scheduler: ActorRef[SchedulerMessage], ): ExtSimSetupData = ExtSimSetupData(Iterable.empty, Map.empty, None) } 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 d8cc467dd9..12c7681835 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala @@ -56,7 +56,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) - } - - } - -} From 19bed20afa51db2f4069c3e06ca1949ed6677d05 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Tue, 2 Apr 2024 15:04:47 +0200 Subject: [PATCH 05/18] More implementation & cleanup --- .../flex/MinMaxFlexibilityMessage.scala | 6 --- .../messages/services/EvMessage.scala | 2 +- .../messages/services/ServiceMessage.scala | 7 ++- .../simona/service/ev/ExtEvDataService.scala | 52 +++++++++++++------ 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFlexibilityMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFlexibilityMessage.scala index 986cc3afe4..af72bf52c6 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFlexibilityMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/flex/MinMaxFlexibilityMessage.scala @@ -114,10 +114,4 @@ object MinMaxFlexibilityMessage { ): ProvideMinMaxFlexOptions = ProvideMinMaxFlexOptions(modelUuid, power, power, power) } - - /** Indicates that flex options have not changed since last provision - * @param modelUuid - */ - case class FlexOptionsUnchanged(override val modelUuid: UUID) - extends ProvideFlexOptions } 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 1b7a714514..fa83e37602 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 @@ -70,7 +70,7 @@ object EvMessage { /** Holds arrivals for one charging station * * @param arrivals - * EVs arriving at the charging station, which might be empty + * EVs arriving at the charging station */ final case class ArrivingEvsData( arrivals: Seq[EvModelWrapper] 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 eed3832f63..603ac38ace 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 @@ -82,9 +82,14 @@ object ServiceMessage { val nextDataTick: Option[Long] } - /** @param serviceRef + /** Scheduling a new data provision for given tick with the system participant + * + * @param serviceRef + * The service to schedule * @param tick + * The tick at which the data provision is to be scheduled * @param unlockKey + * The unlock key for possibly scheduling an activation */ case class ScheduleProvisionMessage( serviceRef: ActorRef, 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 42fade723a..44c96e213c 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -300,7 +300,7 @@ class ExtEvDataService(override val scheduler: ActorRef) } } else { - val actorToEvsData = allArrivingEvsData + val (actorsToSchedule, actorsWithArrivals) = allArrivingEvsData .flatMap { case (evcs, arrivingEvsData) => serviceStateData.uuidToActorRef .get(evcs) @@ -313,22 +313,40 @@ class ExtEvDataService(override val scheduler: ActorRef) None } } - .partitionMap() // TODO - - actorToEvsData.zip(keys).foreach { case ((actor, arrivingEvsData), key) => - if (arrivingEvsData.arrivals.nonEmpty) - actor ! ProvideEvDataMessage( - tick, - self, - arrivingEvsData.arrivals, - Some(arrivingEvsData.maybeNextTick), - ) - else - actor ! ScheduleProvisionMessage( - self, - tick, - unlockKey = key, - ) + .partitionMap { + case (actor, arrivingEvsData) if arrivingEvsData.arrivals.isEmpty => + Left(actor, arrivingEvsData.maybeNextTick) + case (actor, arrivingEvsData) => + Right((actor, arrivingEvsData)) + } + + if (actorsToSchedule.toSeq.nonEmpty) { + val keys = ScheduleLock.multiKey( + ctx, + scheduler.toTyped, + tick, + actorsToSchedule.size, + ) + + actorsToSchedule.zip(keys).foreach { + case ((actor, maybeNextTick), key) => + maybeNextTick.foreach { nextTick => + actor ! ScheduleProvisionMessage( + self, + nextTick, + unlockKey = key, + ) + } + } + } + + actorsWithArrivals.foreach { case (actor, arrivingEvsData) => + actor ! ProvideEvDataMessage( + tick, + self, + arrivingEvsData.arrivals, + Some(arrivingEvsData.maybeNextTick), + ) } } From 2ac0ceba4da373dc528626b3f0dc4564a192c596 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Tue, 9 Apr 2024 13:01:54 +0200 Subject: [PATCH 06/18] Adapting to changes in simonaAPI interface Signed-off-by: Sebastian Peter --- build.gradle | 2 +- .../agent/participant/ParticipantAgent.scala | 69 --- .../ParticipantAgentFundamentals.scala | 6 +- .../evcs/EvcsAgentFundamentals.scala | 54 +- .../messages/services/EvMessage.scala | 2 +- .../messages/services/ServiceMessage.scala | 16 - .../simona/service/ev/ExtEvDataService.scala | 108 ++-- .../EvcsAgentModelCalculationSpec.scala | 125 ++--- .../scheduler/PhaseSwitchSchedulerSpec.scala | 502 ------------------ .../service/ev/ExtEvDataServiceSpec.scala | 242 ++++++--- .../primary/PrimaryServiceWorkerSpec.scala | 4 - .../edu/ie3/simona/sim/SimonaSimSpec.scala | 4 +- 12 files changed, 283 insertions(+), 851 deletions(-) delete mode 100644 src/test/scala/edu/ie3/simona/scheduler/PhaseSwitchSchedulerSpec.scala diff --git a/build.gradle b/build.gradle index 161bc59d69..ed46bbe283 100644 --- a/build.gradle +++ b/build.gradle @@ -88,7 +88,7 @@ dependencies { exclude group: 'edu.ie3' } - implementation('com.github.ie3-institute:simonaAPI:0.4.0') { + implementation('com.github.ie3-institute:simonaAPI:0.5-SNAPSHOT') { exclude group: 'org.apache.logging.log4j' exclude group: 'org.slf4j' /* Exclude our own nested dependencies */ 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 e573ac7949..fb9620ddf7 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -45,23 +45,19 @@ import edu.ie3.simona.model.participant.{ } import edu.ie3.simona.ontology.messages.Activation import edu.ie3.simona.ontology.messages.PowerMessage.RequestAssetPowerMessage -import edu.ie3.simona.ontology.messages.SchedulerMessage.ScheduleActivation import edu.ie3.simona.ontology.messages.flex.FlexibilityMessage.{ FlexResponse, IssueFlexControl, RequestFlexOptions, - ScheduleFlexRequest, } import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegistrationResponseMessage.RegistrationSuccessfulMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.{ PrimaryServiceRegistrationMessage, ProvisionMessage, RegistrationResponseMessage, - ScheduleProvisionMessage, } 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} @@ -195,14 +191,6 @@ abstract class ParticipantAgent[ fromOutsideBaseStateData, ) - case Event( - msg: ScheduleProvisionMessage, - baseStateData: BaseStateData[PD], - ) => - val updatedStateData = handleProvisionScheduling(msg, baseStateData) - - stay() using updatedStateData - case Event( msg: ProvisionMessage[Data], baseStateData: BaseStateData[PD], @@ -357,18 +345,6 @@ abstract class ParticipantAgent[ scheduler, )(stateData.baseStateData.outputConfig) - case Event( - msg: ScheduleProvisionMessage, - stateData @ DataCollectionStateData( - baseStateData: BaseStateData[PD], - _, - _, - ), - ) => - val updatedStateData = handleProvisionScheduling(msg, baseStateData) - - stay() using stateData.copy(baseStateData = updatedStateData) - case Event( msg: ProvisionMessage[_], stateData @ DataCollectionStateData( @@ -650,51 +626,6 @@ abstract class ParticipantAgent[ .getOrElse(createInitialState(baseStateData)) } - private def handleProvisionScheduling( - msg: ScheduleProvisionMessage, - baseStateData: BaseStateData[PD], - ): BaseStateData[PD] = { - val foreseenDataTicks = - baseStateData.foreseenDataTicks + (msg.serviceRef -> Some(msg.tick)) - - val updatedStateData = BaseStateData.updateBaseStateData( - baseStateData, - baseStateData.resultValueStore, - baseStateData.requestValueStore, - baseStateData.voltageValueStore, - baseStateData.additionalActivationTicks, - foreseenDataTicks, - ) - - updatedStateData match { - case modelStateData: ParticipantModelBaseStateData[_, _, _, _] => - val maybeEmAgent = modelStateData.flexStateData.map(_.emAgent) - - maybeEmAgent match { - case Some(emAgent) => - emAgent ! ScheduleFlexRequest( - modelStateData.model.getUuid, - msg.tick, - Some(msg.unlockKey), - ) - case None => - scheduler ! ScheduleActivation( - self.toTyped, - msg.tick, - Some(msg.unlockKey), - ) - } - case _ => - scheduler ! ScheduleActivation( - self.toTyped, - msg.tick, - Some(msg.unlockKey), - ) - } - - updatedStateData - } - /** Handle an incoming data provision message in Idle, try to figure out who's * about to send information in this tick as well. Map together all senders * with their yet apparent information. 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 ff060dd980..be3060c236 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala @@ -1065,9 +1065,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/evcs/EvcsAgentFundamentals.scala b/src/main/scala/edu/ie3/simona/agent/participant/evcs/EvcsAgentFundamentals.scala index 9bf4dde900..0d6e3f8638 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 @@ -210,7 +210,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) @@ -318,7 +318,7 @@ protected trait EvcsAgentFundamentals .values .collectFirst { // filter secondary data for arriving EVs data - case _: ArrivingEvsData => + case _: ArrivingEvs => handleArrivingEvsAndGoIdle( tick, scheduler, @@ -485,32 +485,40 @@ protected trait EvcsAgentFundamentals val relevantData = createCalcRelevantData(modelBaseStateData, tick) - val lastState = getLastOrInitialStateData(modelBaseStateData, tick) + // TODO also adapt for em-controlled and test + 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 result value store and relevant 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 fa83e37602..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 @@ -72,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/ServiceMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/services/ServiceMessage.scala index 603ac38ace..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 @@ -81,20 +81,4 @@ object ServiceMessage { */ val nextDataTick: Option[Long] } - - /** Scheduling a new data provision for given tick with the system participant - * - * @param serviceRef - * The service to schedule - * @param tick - * The tick at which the data provision is to be scheduled - * @param unlockKey - * The unlock key for possibly scheduling an activation - */ - case class ScheduleProvisionMessage( - serviceRef: ActorRef, - tick: Long, - unlockKey: ScheduleKey, - ) extends ServiceMessage - } 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 44c96e213c..10b4329ac8 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -19,11 +19,7 @@ import edu.ie3.simona.exceptions.{ 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.{ - ScheduleProvisionMessage, - ServiceRegistrationMessage, -} -import edu.ie3.simona.scheduler.ScheduleLock +import edu.ie3.simona.ontology.messages.services.ServiceMessage.ServiceRegistrationMessage import edu.ie3.simona.service.ServiceStateData.{ InitializeServiceStateData, ServiceBaseStateData, @@ -35,11 +31,11 @@ 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.typed.scaladsl.adapter.ClassicActorRefOps 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 { @@ -181,6 +177,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" @@ -189,14 +189,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, + asScala(arrivingEvsProvision.arrivals), + arrivingEvsProvision.maybeNextTick.toScala.map(Long2long), )( - serviceStateData, - ctx, + serviceStateData ) } } @@ -228,16 +228,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) @@ -267,94 +267,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 allArrivingEvsData = allArrivingEvs.asScala - if (tick == INIT_SIM_TICK) { - val keys = ScheduleLock.multiKey( - ctx, - scheduler.toTyped, - tick, - serviceStateData.uuidToActorRef.size, - ) - serviceStateData.uuidToActorRef.foreach { case (uuid, actor) => - val firstTick: Option[Long] = allArrivingEvsData - .getOrElse( - uuid, - throw new CriticalFailureException( - s"No initial message found for EVCS $actor($uuid)" - ), - ) - .maybeNextTick + 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, - firstTick, + maybeNextTick, ) } - } else { - val (actorsToSchedule, actorsWithArrivals) = allArrivingEvsData - .flatMap { case (evcs, arrivingEvsData) => - serviceStateData.uuidToActorRef - .get(evcs) - .map((_, arrivingEvsData)) - .orElse { - log.warning( - "A corresponding actor ref for UUID {} could not be found", - evcs, - ) - None - } - } - .partitionMap { - case (actor, arrivingEvsData) if arrivingEvsData.arrivals.isEmpty => - Left(actor, arrivingEvsData.maybeNextTick) - case (actor, arrivingEvsData) => - Right((actor, arrivingEvsData)) - } - - if (actorsToSchedule.toSeq.nonEmpty) { - val keys = ScheduleLock.multiKey( - ctx, - scheduler.toTyped, - tick, - actorsToSchedule.size, - ) - - actorsToSchedule.zip(keys).foreach { - case ((actor, maybeNextTick), key) => - maybeNextTick.foreach { nextTick => - actor ! ScheduleProvisionMessage( - self, - nextTick, - unlockKey = key, - ) - } - } - } + } else { + serviceStateData.uuidToActorRef.foreach { case (evcs, actor) => + val evs = + allArrivingEvs.getOrElse(evcs, Seq.empty) - actorsWithArrivals.foreach { case (actor, arrivingEvsData) => actor ! ProvideEvDataMessage( tick, self, - arrivingEvsData.arrivals, - Some(arrivingEvsData.maybeNextTick), + 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/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala index 52114eb5be..93858b0b4b 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala @@ -36,10 +36,7 @@ import edu.ie3.simona.ontology.messages.PowerMessage.{ AssetPowerUnchangedMessage, RequestAssetPowerMessage, } -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 @@ -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, 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 @@ -833,8 +818,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 +831,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 +840,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 +874,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 +908,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( @@ -1071,7 +1054,7 @@ class EvcsAgentModelCalculationSpec evService.expectMsg(RegisterForEvDataMessage(evcsInputModelQv.getUuid)) evService.send( evcsAgent, - RegistrationSuccessfulMessage(evService.ref, None), + RegistrationSuccessfulMessage(evService.ref, Some(0)), ) emAgent.expectMsg( @@ -1107,7 +1090,7 @@ class EvcsAgentModelCalculationSpec ) 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)), @@ -1212,11 +1195,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 +1250,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 +1268,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 +1386,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 +1484,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 +1519,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 5eed45eb46..019a871763 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 -> Integer.valueOf(2)).asJava ) } "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) @@ -278,14 +313,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 @@ -300,18 +335,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) @@ -319,7 +356,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -330,17 +367,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) ) @@ -373,7 +423,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) @@ -387,20 +437,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) @@ -408,14 +460,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) ) @@ -432,20 +484,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) @@ -453,7 +507,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -464,18 +518,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 @@ -486,29 +553,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) @@ -516,7 +585,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -526,15 +595,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 @@ -545,18 +626,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 8a88b2163f..59aaaea924 100644 --- a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala @@ -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,6 +453,6 @@ object SimonaSimSpec { context: ActorContext[_], scheduler: ActorRef[SchedulerMessage], ): ExtSimSetupData = - ExtSimSetupData(Iterable.empty, Map.empty, None) + ExtSimSetupData(Iterable.empty, Map.empty) } } From b8620355f2699d6829b4b96f7615efcce3c0e36a Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Fri, 21 Jun 2024 14:07:58 +0200 Subject: [PATCH 07/18] Adapting changelog to changed issue Signed-off-by: Sebastian Peter --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1873b9d44..6a5ca87d1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,7 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refactoring of `GridAgent` messages [#736](https://github.com/ie3-institute/simona/issues/736) - Rewrote PVModelTest from groovy to scala [#646](https://github.com/ie3-institute/simona/issues/646) - Making configuration of `RefSystem` via config optional [#769](https://github.com/ie3-institute/simona/issues/769) -- External simulation should provide detailed information about next tick per agent [#776](https://github.com/ie3-institute/simona/issues/776) +- External simulation should provide information about next tick of MobSim [#776](https://github.com/ie3-institute/simona/issues/776) ### Fixed - Removed a repeated line in the documentation of vn_simona config [#658](https://github.com/ie3-institute/simona/issues/658) From 500bc4d3edadedb9b99de7320b82aecf2fe803dc Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Wed, 24 Jul 2024 11:48:28 +0200 Subject: [PATCH 08/18] Providing test for empty arrivals Signed-off-by: Sebastian Peter --- .../evcs/EvcsAgentFundamentals.scala | 3 +- .../EvcsAgentModelCalculationSpec.scala | 71 ++++++++++++++++++- 2 files changed, 71 insertions(+), 3 deletions(-) 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 6e7ea8f3d4..a023227083 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 @@ -485,7 +485,6 @@ protected trait EvcsAgentFundamentals val relevantData = createCalcRelevantData(modelBaseStateData, tick) - // TODO also adapt for em-controlled and test val updatedBaseStateData = { if (relevantData.arrivals.nonEmpty) { val lastState = getLastOrInitialStateData(modelBaseStateData, tick) @@ -510,7 +509,7 @@ protected trait EvcsAgentFundamentals newState, ) - /* Update the base state data with the updated result value store and relevant data store */ + /* Update the base state data with the updated state data store */ modelBaseStateData.copy( stateDataStore = updatedStateDataStore ) 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 e6154e8db9..8a81d51d5a 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala @@ -767,7 +767,7 @@ class EvcsAgentModelCalculationSpec evService.send( evcsAgent, ProvideEvDataMessage( - 0L, + 0, evService.ref, ArrivingEvs(Seq(EvModelWrapper(evA))), Some(900), @@ -797,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, From 2d32cf3ade1854d721cb74b4de0238dae964d145 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Thu, 8 Aug 2024 13:13:51 +0200 Subject: [PATCH 09/18] Adapt to changed ev protocol Signed-off-by: Sebastian Peter --- .../service/ev/ExtEvDataServiceSpec.scala | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) 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 ff8310e679..8e6cf9e07d 100644 --- a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala @@ -301,9 +301,11 @@ class ExtEvDataServiceSpec } "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) @@ -311,7 +313,7 @@ class ExtEvDataServiceSpec scheduler.send( evService, - SimonaService.Create(InitExtEvData(extData), key), + SimonaService.Create(InitExtEvData(extEvData), key), ) scheduler.expectMsgType[ScheduleActivation] @@ -322,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 @@ -344,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), From a5753d57db499885ee07fc109d5650ef98a93c61 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Fri, 9 Aug 2024 11:19:29 +0200 Subject: [PATCH 10/18] Handling requests also in case activation has already arrived Signed-off-by: Sebastian Peter --- .../agent/participant/evcs/EvcsAgent.scala | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) 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..c00b76bbf1 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 @@ -14,13 +14,18 @@ import edu.ie3.simona.agent.participant.data.Data.PrimaryData.{ 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.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.{ @@ -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 From 1da5700dbd11f04fe2d5f740e380551cceea07fc Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Fri, 9 Aug 2024 15:51:45 +0200 Subject: [PATCH 11/18] Explanatory commentary Signed-off-by: Sebastian Peter --- src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala | 2 ++ 1 file changed, 2 insertions(+) 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 aa0cc5d217..66cfaa96f1 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -146,6 +146,8 @@ class ExtEvDataService(override val scheduler: ActorRef) serviceStateData.uuidToActorRef.get(evcs) match { case None => // Actor is not registered yet + // (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) From 2b40f2eee3051a472a7b6678bc513653c86e0cc2 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Fri, 9 Aug 2024 17:18:17 +0200 Subject: [PATCH 12/18] Updating simonaAPI to release version 0.5 Signed-off-by: Sebastian Peter --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d173660f3b..88e8998076 100644 --- a/build.gradle +++ b/build.gradle @@ -89,7 +89,7 @@ dependencies { exclude group: 'edu.ie3' } - implementation('com.github.ie3-institute:simonaAPI:0.5-SNAPSHOT') { + implementation('com.github.ie3-institute:simonaAPI:0.5.0') { exclude group: 'org.apache.logging.log4j' exclude group: 'org.slf4j' /* Exclude our own nested dependencies */ From 1f0a8857923d0aeccb4e0721466861becf228597 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 12 Aug 2024 14:00:29 +0200 Subject: [PATCH 13/18] Refactoring old EvMovement classes Signed-off-by: Sebastian Peter --- .../agent/grid/GridAgentController.scala | 4 ++-- .../participant/ServiceRegistration.scala | 8 ++++---- .../data/secondary/SecondaryDataService.scala | 2 +- .../agent/participant/evcs/EvcsAgent.scala | 4 ++-- .../evcs/EvcsAgentFundamentals.scala | 6 +++--- .../EvcsAgentModelCalculationSpec.scala | 20 +++++++++---------- 6 files changed, 22 insertions(+), 22 deletions(-) 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 393e7417e7..f953165ede 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/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 c00b76bbf1..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,7 +12,7 @@ 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.{ BaseStateData, @@ -59,7 +59,7 @@ object EvcsAgent { ) val neededServices: Vector[Class[_ <: SecondaryDataService[_]]] = Vector( - classOf[ActorEvMovementsService] + classOf[ActorExtEvDataService] ) } 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 3421b7d9e8..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, @@ -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 ) 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 8a81d51d5a..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 @@ -186,7 +186,7 @@ class EvcsAgentModelCalculationSpec inputModel = evcsInputModel, modelConfig = modelConfig, secondaryDataServices = Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ), simulationStartDate = simulationStartDate, simulationEndDate = simulationEndDate, @@ -248,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 @@ -296,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, @@ -873,7 +873,7 @@ class EvcsAgentModelCalculationSpec inputModel = evcsInputModelQv, modelConfig = modelConfig, secondaryDataServices = Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ), simulationStartDate = simulationStartDate, simulationEndDate = simulationEndDate, @@ -1055,7 +1055,7 @@ class EvcsAgentModelCalculationSpec inputModel = evcsInputModelQv, modelConfig = modelConfig, secondaryDataServices = Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ), simulationStartDate = simulationStartDate, simulationEndDate = simulationEndDate, @@ -1092,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 @@ -1155,7 +1155,7 @@ class EvcsAgentModelCalculationSpec startDate shouldBe simulationStartDate endDate shouldBe simulationEndDate services shouldBe Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ) outputConfig shouldBe defaultOutputConfig additionalActivationTicks shouldBe empty @@ -1189,7 +1189,7 @@ class EvcsAgentModelCalculationSpec inputModel = SimpleInputContainer(evcsInputModelQv), modelConfig = modelConfig, secondaryDataServices = Iterable( - ActorEvMovementsService(evService.ref) + ActorExtEvDataService(evService.ref) ), simulationStartDate = simulationStartDate, simulationEndDate = simulationEndDate, @@ -1230,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 From 22450d0782cd3899a6ece14d33003f6d9edbc3e9 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 12 Aug 2024 14:59:38 +0200 Subject: [PATCH 14/18] Updating UML documentation related to mobility simulation (class diagram needs some more work) Signed-off-by: Sebastian Peter --- docs/uml/main/ExtEvSimulationClasses.puml | 34 +-- .../uml/protocol/ExtEvSimulationSequence.puml | 217 +++++++++++++----- 2 files changed, 178 insertions(+), 73 deletions(-) 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..954da645af 100644 --- a/docs/uml/protocol/ExtEvSimulationSequence.puml +++ b/docs/uml/protocol/ExtEvSimulationSequence.puml @@ -2,97 +2,202 @@ !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) + 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 From f74c24aa953603f5272949a6cc56d0bef1b853c6 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 12 Aug 2024 16:15:37 +0200 Subject: [PATCH 15/18] Improved format Signed-off-by: Sebastian Peter --- docs/uml/protocol/ExtEvSimulationSequence.puml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/uml/protocol/ExtEvSimulationSequence.puml b/docs/uml/protocol/ExtEvSimulationSequence.puml index 954da645af..dde2f869d6 100644 --- a/docs/uml/protocol/ExtEvSimulationSequence.puml +++ b/docs/uml/protocol/ExtEvSimulationSequence.puml @@ -190,13 +190,12 @@ group Provide arriving EVs EvcsAgent2 -> Scheduler: ! Completion(t2) deactivate EvcsAgent2 - end ExtSimulation -> ExtSimAdapter: ! CompletionMessage(t2) deactivate ExtSimulation - activate ExtSimAdapter + ExtSimAdapter -> Scheduler: ! Completion(t2) deactivate ExtSimAdapter From f562a13b0624c8c86f75241ef5346316d0687463 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Wed, 14 Aug 2024 18:29:26 +0200 Subject: [PATCH 16/18] Converting all instances of `eval-rst` to the equivalent myst syntax --- CHANGELOG.md | 1 + docs/readthedocs/index.md | 9 +-- docs/readthedocs/models/cts_model.md | 4 +- docs/readthedocs/models/pv_model.md | 69 +++++------------- docs/readthedocs/models/reference_system.md | 73 +++++++++---------- .../models/three_winding_transformer_model.md | 5 +- docs/readthedocs/references.md | 25 +++---- 7 files changed, 71 insertions(+), 115 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 216d239639..6344047499 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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) ### Fixed - Removed a repeated line in the documentation of vn_simona config [#658](https://github.com/ie3-institute/simona/issues/658) 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..be079dbe74 100644 --- a/docs/readthedocs/models/cts_model.md +++ b/docs/readthedocs/models/cts_model.md @@ -39,9 +39,7 @@ $$ $$ 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..6f193edf30 100644 --- a/docs/readthedocs/models/pv_model.md +++ b/docs/readthedocs/models/pv_model.md @@ -52,11 +52,8 @@ $$ $$ **References:** - -```{eval-rst} -* :cite:cts:`Maleki.2017` -* :cite:ts:`Spencer.1971` -``` +* {cite:cts}`Maleki.2017` +* {cite:cts}`Spencer.1971` ### Hour Angle @@ -103,12 +100,9 @@ $$ **Note:** The used formulas are based on *\"DIN 5034-2: Tageslicht in Innenräumen, Grundlagen.\"* and therefore valid especially for Germany and Europe. For international calculations a more general formulation that can be found in [Maleki, S.A., Hizam, H., & Gomes, C. (2017). Estimation of Hourly, Daily and Monthly Global Solar Radiation on Inclined Surfaces: Models Re-Visited.](https://res.mdpi.com/d_attachment/energies/energies-10-00134/article_deploy/energies-10-00134-v2.pdf) might be used. **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 +121,8 @@ $$ **$\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 @@ -146,11 +138,8 @@ $$ **$\omega$**= hour angle **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 @@ -191,11 +180,8 @@ $$ **$\omega$** = hour angle **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 @@ -210,11 +196,8 @@ airmass = \sqrt{(707.8\overline{8} \cdot \cos({\theta_z}))^2 +2 \cdot 707.8\over $$ **References:** - -```{eval-rst} -* :cite:ts:`Schoenberg.1929` -* :cite:ts:`WikiAirMass` -``` +* {cite:cts}`Schoenberg.1929` +* {cite:cts}`WikiAirMass` ### Extraterrestrial Radiation @@ -236,11 +219,8 @@ $$ **J** = day angle **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 @@ -295,10 +275,7 @@ $$ **$E_{beam,H}$** = beam radiation (horizontal surface) **Reference:** - -```{eval-rst} -* :cite:ts:`Duffie.2013` p. 88 -``` +* {cite:cts}`Duffie.2013` p. 88 ### Diffuse Radiation on Sloped Surface @@ -407,12 +384,9 @@ $$ **$E_{dif,H}$** = diffuse radiation (horizontal surface) **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,10 +401,7 @@ $$ **$\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..756259cfa2 100644 --- a/docs/readthedocs/models/three_winding_transformer_model.md +++ b/docs/readthedocs/models/three_winding_transformer_model.md @@ -40,7 +40,4 @@ More details on the physical model transformation can be found in the Ph.D. thes - The tap changer is always apparent in subgrid A. If subgrid B or C require an adaption, they send a tap request with provided power values, subgrid A takes a decision and re-evaluates the outcome **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: ``` From cf7aafc13e9e31bb2987e277d277e0bbbf8f1a26 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Wed, 14 Aug 2024 18:35:27 +0200 Subject: [PATCH 17/18] Fixing codacy issues --- docs/readthedocs/models/cts_model.md | 2 ++ docs/readthedocs/models/pv_model.md | 20 +++++++++++++++++++ .../models/three_winding_transformer_model.md | 1 + 3 files changed, 23 insertions(+) diff --git a/docs/readthedocs/models/cts_model.md b/docs/readthedocs/models/cts_model.md index be079dbe74..5750820624 100644 --- a/docs/readthedocs/models/cts_model.md +++ b/docs/readthedocs/models/cts_model.md @@ -39,8 +39,10 @@ $$ $$ Reference: + * {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. The same relationship is used to determine the quantity of heat which is stored in the storage by converting the equation to: diff --git a/docs/readthedocs/models/pv_model.md b/docs/readthedocs/models/pv_model.md index 6f193edf30..c847b6e158 100644 --- a/docs/readthedocs/models/pv_model.md +++ b/docs/readthedocs/models/pv_model.md @@ -52,9 +52,11 @@ $$ $$ **References:** + * {cite:cts}`Maleki.2017` * {cite:cts}`Spencer.1971` + ### Hour Angle The hour angle is a conceptual description of the rotation of the earth around its polar axis. It starts with a negative value in the morning, arrives at 0° at noon (solar time) and ends with a positive value in the evening. The hour angle (in radian!) is calculated as follows @@ -100,10 +102,12 @@ $$ **Note:** The used formulas are based on *\"DIN 5034-2: Tageslicht in Innenräumen, Grundlagen.\"* and therefore valid especially for Germany and Europe. For international calculations a more general formulation that can be found in [Maleki, S.A., Hizam, H., & Gomes, C. (2017). Estimation of Hourly, Daily and Monthly Global Solar Radiation on Inclined Surfaces: Models Re-Visited.](https://res.mdpi.com/d_attachment/energies/energies-10-00134/article_deploy/energies-10-00134-v2.pdf) might be used. **References:** + * {cite:cts}`Watter.2013` * {cite:cts}`Maleki.2017` * {cite:cts}`Wang.2019` + ### Sunrise Angle The hour angles at sunrise and sunset are very useful quantities to know. These two values have the same absolute value, however the sunset angle ($\omega_{SS}$) is positive and the sunrise angle ($\omega_{SR}$) is negative. Both can be calculated from: @@ -121,9 +125,11 @@ $$ **$\phi$** = observer's latitude **References:** + * {cite:cts}`Maleki.2017` * {cite:cts}`Itaca_Sun` + ### Solar Altitude Angle Represents the angle between the horizontal and the line to the sun, that is, the complement of the zenith angle. @@ -138,9 +144,11 @@ $$ **$\omega$**= hour angle **References:** + * {cite:cts}`Maleki.2017` p. 5 * {cite:cts}`Itaca_Sun` + ### Zenith Angle Represents the angle between the vertical and the line to the sun, that is, the angle of incidence of beam radiation on a horizontal surface. @@ -180,9 +188,11 @@ $$ **$\omega$** = hour angle **References:** + * {cite:cts}`Quaschning.2013` * {cite:cts}`Maleki.2017` p. 18 + ### Air Mass Calculating the air mass ratio by dividing the radius of the earth with approx. effective height of the atmosphere (each in kilometer) @@ -196,9 +206,11 @@ airmass = \sqrt{(707.8\overline{8} \cdot \cos({\theta_z}))^2 +2 \cdot 707.8\over $$ **References:** + * {cite:cts}`Schoenberg.1929` * {cite:cts}`WikiAirMass` + ### Extraterrestrial Radiation The extraterrestrial radiation $I_0$ is calculated by multiplying the eccentricity correction factor @@ -219,9 +231,11 @@ $$ **J** = day angle **References:** + * {cite:cts}`Zheng.2017` p. 53, formula 2.3b * {cite:cts}`Iqbal.1983` + ### Beam Radiation on Sloped Surface For our use case, $\omega_{2}$ is normally set to the hour angle one hour after $\omega_{1}$. Within one hour distance to sunrise/sunset, we adjust $\omega_{1}$ and $\omega_{2}$ accordingly: @@ -275,8 +289,10 @@ $$ **$E_{beam,H}$** = beam radiation (horizontal surface) **Reference:** + * {cite:cts}`Duffie.2013` p. 88 + ### Diffuse Radiation on Sloped Surface The diffuse radiation is computed using the Perez model, which divides the radiation in three parts. First, there is an intensified radiation from the direct vicinity of the sun. Furthermore, there is Rayleigh scattering, backscatter (which lead to increased in intensity on the horizon) and isotropic radiation considered. @@ -384,10 +400,12 @@ $$ **$E_{dif,H}$** = diffuse radiation (horizontal surface) **References:** + * {cite:cts}`Perez.1987` * {cite:cts}`Perez.1990` * {cite:cts}`Myers.2017` p. 96f + ### Reflected Radiation on Sloped Surface $$ @@ -401,8 +419,10 @@ $$ **$\rho$** = albedo **Reference:** + * {cite:cts}`Maleki.2017` p. 19 + ### Output Received energy is calculated as the sum of all three types of irradiation. diff --git a/docs/readthedocs/models/three_winding_transformer_model.md b/docs/readthedocs/models/three_winding_transformer_model.md index 756259cfa2..9b77dc0cc1 100644 --- a/docs/readthedocs/models/three_winding_transformer_model.md +++ b/docs/readthedocs/models/three_winding_transformer_model.md @@ -40,4 +40,5 @@ More details on the physical model transformation can be found in the Ph.D. thes - The tap changer is always apparent in subgrid A. If subgrid B or C require an adaption, they send a tap request with provided power values, subgrid A takes a decision and re-evaluates the outcome **References:** + * {cite:cts}`Kittl_2022` From 47a75991bef39a338e81adba143b01782aaafc7c Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Wed, 14 Aug 2024 18:41:43 +0200 Subject: [PATCH 18/18] Fixing more codacy issues --- docs/readthedocs/models/pv_model.md | 4 ++-- docs/readthedocs/models/three_winding_transformer_model.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/readthedocs/models/pv_model.md b/docs/readthedocs/models/pv_model.md index c847b6e158..b36df95f00 100644 --- a/docs/readthedocs/models/pv_model.md +++ b/docs/readthedocs/models/pv_model.md @@ -53,7 +53,7 @@ $$ **References:** -* {cite:cts}`Maleki.2017` +* {cite:cts}`Maleki.2017` * {cite:cts}`Spencer.1971` @@ -126,7 +126,7 @@ $$ **References:** -* {cite:cts}`Maleki.2017` +* {cite:cts}`Maleki.2017` * {cite:cts}`Itaca_Sun` diff --git a/docs/readthedocs/models/three_winding_transformer_model.md b/docs/readthedocs/models/three_winding_transformer_model.md index 9b77dc0cc1..d10790848b 100644 --- a/docs/readthedocs/models/three_winding_transformer_model.md +++ b/docs/readthedocs/models/three_winding_transformer_model.md @@ -41,4 +41,4 @@ More details on the physical model transformation can be found in the Ph.D. thes **References:** -* {cite:cts}`Kittl_2022` +- {cite:cts}`Kittl_2022`