diff --git a/CHANGELOG.md b/CHANGELOG.md index a8bb09e63a..2fbc565944 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changed from ComparableQuantity to squants in power flow [#554](https://github.com/ie3-institute/simona/issues/554) - Reduce log level on missing diffuse irradiance [#629](https://github.com/ie3-institute/simona/issues/629) - Updated to gradle 8.4 [#648](https://github.com/ie3-institute/simona/issues/648) +- Introducing new scheduling infrastructure: + - Two-parted scheduler in akka typed [#378](https://github.com/ie3-institute/simona/issues/378) + - Adapting to simonaAPI 0.3.0 (adapted message protocol) ## [3.0.0] - 2023-08-07 diff --git a/build.gradle b/build.gradle index a8143d697f..d080a6dc43 100644 --- a/build.gradle +++ b/build.gradle @@ -93,7 +93,7 @@ dependencies { exclude group: 'edu.ie3' } - implementation('com.github.ie3-institute:simonaAPI:0.2.0') { + implementation('com.github.ie3-institute:simonaAPI:0.3.0') { exclude group: 'org.apache.logging.log4j' exclude group: 'org.slf4j' /* Exclude our own nested dependencies */ diff --git a/docs/readthedocs/usersguide.md b/docs/readthedocs/usersguide.md index 7bb6670547..d34f221966 100644 --- a/docs/readthedocs/usersguide.md +++ b/docs/readthedocs/usersguide.md @@ -151,7 +151,7 @@ In order to run an external simulation, several requirements have to be fulfille - The external simulation should be implemented in its own project (repository). - The project should include the *shadowJar* gradle plugin (``id "com.github.johnrengelman.shadow" version "x.y.z"``). -- A class (called *main class* here) needs to extend ``edu.ie3.simona.api.schedule.ExtSimulation`` and thus implement the two methods ``List initialize()`` and ``List doActivity(long tick)``. The method ``initialize`` is called when the external simulation needs to be initialized whereas the method ``doActivity`` is called when time step ``tick`` is triggered. ``initialize`` and ``doActivity`` must return a list of subsequent new ticks that the sub simulation should be scheduled at. +- A class (called *main class* here) needs to extend ``edu.ie3.simona.api.schedule.ExtSimulation`` and thus implement the two methods ``Optional initialize()`` and ``Optional doActivity(long tick)``. The method ``initialize`` is called when the external simulation needs to be initialized whereas the method ``doActivity`` is called when time step ``tick`` is triggered. ``initialize`` and ``doActivity`` can return a subsequent new tick that the sub simulation should be activated with next. - For each data stream, a sub-interface of ``edu.ie3.simona.api.data.ExtDataSimulation`` needs to be implemented, such as ``edu.ie3.simona.api.data.ev.ExtEvSimulation``, and all methods of the interface have to be implemented. The *main class* could be the implementing class here. - In order for SIMONA to use the external simulation, a class that extends ``edu.ie3.simona.api.ExtLinkInterface`` has to reside inside the project. The class has to implement the corresponding methods by returning the control stream and data stream implementations (could all be the same *main class*). - When loading the external simulations, SIMONA is looking for the corresponding service files of the class ``edu.ie3.simona.api.ExtLinkInterface``. Therefor every external simulation needs the following service file: ``src/main/resources/META-INF/services/edu.ie3.simona.api.ExtLinkInterface``. The service file needs to contain the relative path to the class that extends ``edu.ie3.simona.api.ExtLinkInterface``. diff --git a/src/main/scala/edu/ie3/simona/actor/ActorUtil.scala b/src/main/scala/edu/ie3/simona/actor/ActorUtil.scala new file mode 100644 index 0000000000..3ee6f55c62 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/actor/ActorUtil.scala @@ -0,0 +1,20 @@ +/* + * © 2023. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.actor + +import akka.actor.typed.Behavior +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} + +object ActorUtil { + def stopOnError[M]( + ctx: ActorContext[M], + msg: String + ): Behavior[M] = { + ctx.log.error(s"$msg. Stopping.") + Behaviors.stopped + } +} diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala index ec89f470af..d59ea031cd 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala @@ -72,8 +72,7 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { case Event( TriggerWithIdMessage( StartGridSimulationTrigger(currentTick), - triggerId, - _ + triggerId ), gridAgentBaseData: GridAgentBaseData ) => @@ -452,11 +451,9 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { environmentRefs.scheduler ! CompletionMessage( simTriggerId, Some( - Vector( - ScheduleTriggerMessage( - ActivityStartTrigger(currentTick + resolution), - self - ) + ScheduleTriggerMessage( + ActivityStartTrigger(currentTick + resolution), + self ) ) ) @@ -1011,8 +1008,8 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { /** Normally only reached by the superior (dummy) agent! * * Triggers a state transition to [[SimulateGrid]], informs the - * [[edu.ie3.simona.scheduler.SimScheduler]] about the finish of this sweep - * and requests a new trigger for itself for a new sweep (which means a new + * [[edu.ie3.simona.scheduler.Scheduler]] about the finish of this sweep and + * requests a new trigger for itself for a new sweep (which means a new * [[StartGridSimulationTrigger]]) * * @param gridAgentBaseData @@ -1032,9 +1029,7 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { environmentRefs.scheduler ! CompletionMessage( oldTrigger, Some( - Vector( - ScheduleTriggerMessage(StartGridSimulationTrigger(currentTick), self) - ) + ScheduleTriggerMessage(StartGridSimulationTrigger(currentTick), self) ) ) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala index 38026c8e53..3709cb63b5 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -35,8 +35,6 @@ import edu.ie3.util.TimeUtil import java.time.ZonedDateTime import java.time.temporal.ChronoUnit import java.util.UUID -import scala.concurrent.ExecutionContext -import scala.concurrent.duration._ import scala.language.postfixOps object GridAgent { @@ -103,8 +101,7 @@ class GridAgent( InitializeGridAgentTrigger( gridAgentInitData: GridAgentInitData ), - triggerId, - _ + triggerId ), _ ) => @@ -174,12 +171,14 @@ class GridAgent( log.debug("Je suis initialized") - goto(Idle) using gridAgentBaseData replying CompletionMessage( + environmentRefs.scheduler ! CompletionMessage( triggerId, Some( - Vector(ScheduleTriggerMessage(ActivityStartTrigger(resolution), self)) + ScheduleTriggerMessage(ActivityStartTrigger(resolution), self) ) ) + + goto(Idle) using gridAgentBaseData } when(Idle) { @@ -191,25 +190,25 @@ class GridAgent( stay() case Event( - TriggerWithIdMessage(ActivityStartTrigger(currentTick), triggerId, _), + TriggerWithIdMessage(ActivityStartTrigger(currentTick), triggerId), gridAgentBaseData: GridAgentBaseData ) => log.debug("received activity start trigger {}", triggerId) unstashAll() - goto(SimulateGrid) using gridAgentBaseData replying CompletionMessage( + environmentRefs.scheduler ! CompletionMessage( triggerId, Some( - Vector( - ScheduleTriggerMessage( - StartGridSimulationTrigger(currentTick), - self - ) + ScheduleTriggerMessage( + StartGridSimulationTrigger(currentTick), + self ) ) ) + goto(SimulateGrid) using gridAgentBaseData + case Event(StopMessage(_), data: GridAgentBaseData) => // shutdown children data.gridEnv.nodeToAssetAgents.foreach { case (_, actors) => 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 d19bfcc201..23fbb1654d 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgent.scala @@ -111,8 +111,7 @@ abstract class ParticipantAgent[ outputConfig ) ), - triggerId, - _ + triggerId ), _: ParticipantUninitializedStateData[PD] ) => @@ -136,7 +135,7 @@ abstract class ParticipantAgent[ when(Idle) { case Event( - TriggerWithIdMessage(ActivityStartTrigger(currentTick), triggerId, _), + TriggerWithIdMessage(ActivityStartTrigger(currentTick), triggerId), modelBaseStateData: ParticipantModelBaseStateData[PD, CD, M] ) if modelBaseStateData.services.isEmpty => /* An activity start trigger is sent and no data is awaited (neither secondary nor primary). Therefore go straight @@ -166,7 +165,7 @@ abstract class ParticipantAgent[ ) case Event( - TriggerWithIdMessage(ActivityStartTrigger(currentTick), triggerId, _), + TriggerWithIdMessage(ActivityStartTrigger(currentTick), triggerId), modelBaseStateData: ParticipantModelBaseStateData[PD, CD, M] ) => /* An activity start trigger is sent, but I'm not sure yet, if secondary data will arrive. Figure out, if someone @@ -179,7 +178,7 @@ abstract class ParticipantAgent[ ) case Event( - TriggerWithIdMessage(ActivityStartTrigger(currentTick), triggerId, _), + TriggerWithIdMessage(ActivityStartTrigger(currentTick), triggerId), fromOutsideBaseStateData: FromOutsideBaseStateData[M, PD] ) => /* An activity start trigger is sent, but I'm still expecting primary data. Go to HandleInformation and wait for @@ -285,8 +284,7 @@ abstract class ParticipantAgent[ case Event( TriggerWithIdMessage( ActivityStartTrigger(activationTick), - triggerId, - _ + triggerId ), stateData: DataCollectionStateData[PD] ) => @@ -409,7 +407,7 @@ abstract class ParticipantAgent[ stay() case Event(_: ProvisionMessage[_], _) | - Event(TriggerWithIdMessage(ActivityStartTrigger(_), _, _), _) => + Event(TriggerWithIdMessage(ActivityStartTrigger(_), _), _) => /* I got faced to new data, also I'm not ready to handle it, yet OR I got asked to do something else, while I'm * still busy, I will put it aside and answer it later */ stash() 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 d8cf6fec59..8488249c69 100644 --- a/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala +++ b/src/main/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentals.scala @@ -139,12 +139,12 @@ protected trait ParticipantAgentFundamentals[ /* Confirm final initialization */ val (_, triggerId) = releaseTickAndTriggerId() - val newTriggerMessages = - ServiceActivationBaseStateData.tickToScheduleTriggerMessages( + val newTriggerMessage = + ServiceActivationBaseStateData.tickToScheduleTriggerMessage( senderToMaybeTick._2, self ) - scheduler ! CompletionMessage(triggerId, newTriggerMessages) + scheduler ! CompletionMessage(triggerId, newTriggerMessage) goto(Idle) using stateData } @@ -710,11 +710,11 @@ protected trait ParticipantAgentFundamentals[ scheduler: ActorRef ): FSM.State[AgentState, ParticipantStateData[PD]] = { /* Determine the very next tick, where activation is required */ - val (maybeActivationTriggers, updatedBaseStateData) = + val (maybeActivationTrigger, updatedBaseStateData) = popNextActivationTrigger(baseStateData) val (_, triggerId) = releaseTickAndTriggerId() - scheduler ! CompletionMessage(triggerId, maybeActivationTriggers) + scheduler ! CompletionMessage(triggerId, maybeActivationTrigger) unstashAll() goto(Idle) using updatedBaseStateData } @@ -728,29 +728,27 @@ protected trait ParticipantAgentFundamentals[ * @param baseStateData * Base state data to be updated * @return - * An [[Option]] to a [[Seq]] of new [[ScheduleTriggerMessage]] s as well - * as the updated base state data. If the next activation tick is an - * additional activation, this tick is removed from the list of desired - * additional activations. + * An [[Option]] of new [[ScheduleTriggerMessage]] as well as the updated + * base state data. If the next activation tick is an additional + * activation, this tick is removed from the list of desired additional + * activations. */ def popNextActivationTrigger( baseStateData: BaseStateData[PD] - ): (Option[Seq[ScheduleTriggerMessage]], BaseStateData[PD]) = { + ): (Option[ScheduleTriggerMessage], BaseStateData[PD]) = { /* Determine what comes next: An additional activation or new data - or both at once */ val nextAdditionalActivation = baseStateData.additionalActivationTicks.headOption val nextDataTick = baseStateData.foreseenDataTicks.values.toSeq.sorted.headOption.flatten - /* return a [[Option]] to a [[Seq]] of [[ScheduleTriggerMessage]]s */ - def toMessageSeq: (Long, ActorRef) => Option[Seq[ScheduleTriggerMessage]] = + /* return a [[Option]] of [[ScheduleTriggerMessage]]s */ + def toMessage: (Long, ActorRef) => Option[ScheduleTriggerMessage] = (tick: Long, actorToBeTriggered: ActorRef) => Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(tick), - actorToBeTriggered - ) + ScheduleTriggerMessage( + ActivityStartTrigger(tick), + actorToBeTriggered ) ) @@ -758,7 +756,7 @@ protected trait ParticipantAgentFundamentals[ case (None, Some(dataTick)) => /* There is only a data tick available */ ( - toMessageSeq(dataTick, self), + toMessage(dataTick, self), baseStateData ) case (Some(additionalTick), Some(dataTick)) @@ -766,7 +764,7 @@ protected trait ParticipantAgentFundamentals[ /* The next foreseen activation will be based on foreseen data arrival. Do nothing else, then creating a * trigger. */ ( - toMessageSeq(dataTick, self), + toMessage(dataTick, self), baseStateData ) case (Some(additionalTick), _) => @@ -784,7 +782,7 @@ protected trait ParticipantAgentFundamentals[ ) ( - toMessageSeq(additionalTick, self), + toMessage(additionalTick, self), updatedBaseStateData ) case (None, None) => diff --git a/src/main/scala/edu/ie3/simona/api/ExtMessageUtils.scala b/src/main/scala/edu/ie3/simona/api/ExtMessageUtils.scala index 97005139d6..676452b3de 100644 --- a/src/main/scala/edu/ie3/simona/api/ExtMessageUtils.scala +++ b/src/main/scala/edu/ie3/simona/api/ExtMessageUtils.scala @@ -17,23 +17,21 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ } import edu.ie3.simona.ontology.trigger.Trigger.ActivityStartTrigger -import scala.jdk.CollectionConverters.CollectionHasAsScala +import scala.jdk.OptionConverters.RichOptional object ExtMessageUtils { implicit class RichExtCompletion( private val extCompl: ExtCompletionMessage ) { def toSimona(triggerId: Long, triggerActor: ActorRef): CompletionMessage = { - val newTriggers = - Option.when(!extCompl.newTriggers.isEmpty) { - extCompl.newTriggers.asScala.map { tick => - ScheduleTriggerMessage(ActivityStartTrigger(tick), triggerActor) - }.toSeq + val newTrigger = + extCompl.nextActivation.toScala.map { tick => + ScheduleTriggerMessage(ActivityStartTrigger(tick), triggerActor) } CompletionMessage( triggerId, - newTriggers + newTrigger ) } } diff --git a/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala b/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala index 8db8eb4126..8a70174169 100644 --- a/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala +++ b/src/main/scala/edu/ie3/simona/api/ExtSimAdapter.scala @@ -18,9 +18,9 @@ import edu.ie3.simona.api.ExtSimAdapter.{ import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage import edu.ie3.simona.api.simulation.ExtSimAdapterData import edu.ie3.simona.api.simulation.ontology.{ - Terminate, + TerminationMessage, TerminationCompleted, - ActivityStartTrigger => ExtActivityStartTrigger, + ActivationMessage, CompletionMessage => ExtCompletionMessage } import edu.ie3.simona.logging.SimonaActorLogging @@ -61,18 +61,15 @@ final case class ExtSimAdapter(scheduler: ActorRef) InitializeExtSimAdapterTrigger( InitExtSimAdapter(extSimData) ), - triggerId, - _ + triggerId ) => // triggering first time at init tick - sender() ! CompletionMessage( + scheduler ! CompletionMessage( triggerId, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(INIT_SIM_TICK), - self - ) + ScheduleTriggerMessage( + ActivityStartTrigger(INIT_SIM_TICK), + self ) ) ) @@ -82,9 +79,9 @@ final case class ExtSimAdapter(scheduler: ActorRef) } def receiveIdle(implicit stateData: ExtSimAdapterStateData): Receive = { - case TriggerWithIdMessage(ActivityStartTrigger(tick), triggerId, _) => + case TriggerWithIdMessage(ActivityStartTrigger(tick), triggerId) => stateData.extSimData.queueExtMsg( - new ExtActivityStartTrigger(tick) + new ActivationMessage(tick) ) log.debug( "Tick {} (trigger id {}) has been scheduled in external simulation", @@ -129,7 +126,9 @@ final case class ExtSimAdapter(scheduler: ActorRef) case StopMessage(simulationSuccessful) => // let external sim know that we have terminated - stateData.extSimData.queueExtMsg(new Terminate(simulationSuccessful)) + stateData.extSimData.queueExtMsg( + new TerminationMessage(simulationSuccessful) + ) case _: TerminationCompleted => // external simulation has terminated as well, we can exit diff --git a/src/main/scala/edu/ie3/simona/event/RuntimeEvent.scala b/src/main/scala/edu/ie3/simona/event/RuntimeEvent.scala index 0ae167e013..923a908f7e 100644 --- a/src/main/scala/edu/ie3/simona/event/RuntimeEvent.scala +++ b/src/main/scala/edu/ie3/simona/event/RuntimeEvent.scala @@ -19,14 +19,14 @@ object RuntimeEvent { /** Indicates that the scheduler has finished a pre-defined advancement in * ticks and is ready to carry out the next task. In contrast to the * [[CheckWindowPassed]] event, whenever a [[Ready]] event is scheduled, the - * scheduled of [[edu.ie3.simona.scheduler.SimScheduler]] will be stopped and + * scheduled of [[edu.ie3.simona.scheduler.Scheduler]] will be stopped and * further commands are necessary to continue the schedule. * * @param tick * the last tick that has been processed * @param duration * duration that has been passed since the last time a [[Ready]] event has - * been issued + * been issued in milliseconds */ final case class Ready(tick: Long, duration: Long) extends RuntimeEvent @@ -34,7 +34,7 @@ object RuntimeEvent { * finished * * @param duration - * duration needed for the initialization process + * duration needed for the initialization process in milliseconds */ final case class InitComplete(duration: Long) extends RuntimeEvent @@ -44,7 +44,7 @@ object RuntimeEvent { * [[edu.ie3.simona.event.listener.RuntimeEventListener]] to print status * information about the current simulation run. In contrast to the [[Ready]] * event, when this event is thrown, the - * [[edu.ie3.simona.scheduler.SimScheduler]] does not necessarily hold the + * [[edu.ie3.simona.scheduler.Scheduler]] does not necessarily hold the * schedule. Hence, this event only indicates, that the defined check window * has passed and the schedule will move on afterwards without a stop. * @@ -52,7 +52,7 @@ object RuntimeEvent { * the tick of the simulation that has been passed * @param duration * the duration that has been taken since the last time a - * [[CheckWindowPassed]] event has been issued + * [[CheckWindowPassed]] event has been issued in milliseconds */ final case class CheckWindowPassed(tick: Long, duration: Long) extends RuntimeEvent @@ -73,7 +73,7 @@ object RuntimeEvent { * @param tick * the tick when the event is issued * @param duration - * the duration of the overall simulation + * the duration of the overall simulation in milliseconds * @param noOfFailedPF * the number of failed power flow calculations * @param errorInSim @@ -82,7 +82,7 @@ object RuntimeEvent { final case class Done( tick: Long, duration: Long, - noOfFailedPF: Int, + noOfFailedPF: Int, // FIXME remove errorInSim: Boolean ) extends RuntimeEvent diff --git a/src/main/scala/edu/ie3/simona/exceptions/SchedulerException.scala b/src/main/scala/edu/ie3/simona/exceptions/SchedulerException.scala index 55cc2d64b8..c46b25b7ad 100644 --- a/src/main/scala/edu/ie3/simona/exceptions/SchedulerException.scala +++ b/src/main/scala/edu/ie3/simona/exceptions/SchedulerException.scala @@ -7,7 +7,7 @@ package edu.ie3.simona.exceptions /** Exception that is thrown whenever something invalid happend in - * [[edu.ie3.simona.scheduler.SimScheduler]] + * [[edu.ie3.simona.scheduler.Scheduler]] * * @param message * specific error message diff --git a/src/main/scala/edu/ie3/simona/ontology/messages/SchedulerMessage.scala b/src/main/scala/edu/ie3/simona/ontology/messages/SchedulerMessage.scala index f1064b4208..21a8ab35ea 100644 --- a/src/main/scala/edu/ie3/simona/ontology/messages/SchedulerMessage.scala +++ b/src/main/scala/edu/ie3/simona/ontology/messages/SchedulerMessage.scala @@ -8,30 +8,45 @@ package edu.ie3.simona.ontology.messages import akka.actor.ActorRef import edu.ie3.simona.ontology.trigger.Trigger -import edu.ie3.simona.scheduler.SimScheduler sealed trait SchedulerMessage -/** Messages that should be send and received to and from [[SimScheduler]]. - * Every message that is NOT a one way message (e.g. is only received by the - * [[SimScheduler]] should include the [[ActorRef]] of the agent that should - * receive the message. This is necessary for routing in cluster mode. +/** Messages that should be send and received to and from + * [[edu.ie3.simona.scheduler.Scheduler]]. Every message that is NOT a one way + * message (e.g. is only received by the [[edu.ie3.simona.scheduler.Scheduler]] + * should include the [[ActorRef]] of the agent that should receive the + * message. This is necessary for routing in cluster mode. */ object SchedulerMessage { - /** Tell the [[SimScheduler]] to initialize the simulation with all + /** Tell the [[edu.ie3.simona.scheduler.Scheduler]] to initialize the + * simulation with all * [[edu.ie3.simona.ontology.trigger.Trigger.InitializeTrigger]] s */ case object InitSimMessage extends SchedulerMessage - /** Tell the [[SimScheduler]] to start the simulation + /** Starts simulation by activating the next (or first) tick + * @param pauseTick + * Last tick that can be activated or completed before the simulation is + * paused + * + * TODO rename to StartSimMessage */ final case class StartScheduleMessage( - pauseScheduleAtTick: Option[Long] = None + pauseTick: Option[Long] = None ) extends SchedulerMessage - /** schedule a new trigger TO the [[SimScheduler]]. This message should send - * only to the [[SimScheduler]] + /** Notifies TimeAdvancer that the simulation should stop because of some + * error + * @param errorMsg + * The error message + * + * TODO only for TimeAdvancer + */ + final case class Stop(errorMsg: String) extends SchedulerMessage + + /** schedule a new trigger TO the [[edu.ie3.simona.scheduler.Scheduler]]. This + * message should send only to the [[edu.ie3.simona.scheduler.Scheduler]] * * @param trigger * to schedule @@ -44,16 +59,16 @@ object SchedulerMessage { ) extends SchedulerMessage /** Confirm the end of an action e.g. fsm state transitions for one tick to - * and ONLY to the [[SimScheduler]] + * and ONLY to the [[edu.ie3.simona.scheduler.Scheduler]] * * @param triggerId * the triggerId we want to confirm the completion - * @param newTriggers - * optional new triggers to schedule + * @param newTrigger + * optional new trigger to schedule */ final case class CompletionMessage( triggerId: Long, - newTriggers: Option[Seq[ScheduleTriggerMessage]] = None + newTrigger: Option[ScheduleTriggerMessage] = None ) extends SchedulerMessage /** a message that is send by the scheduler to an agent including an unique id @@ -61,8 +76,7 @@ object SchedulerMessage { */ final case class TriggerWithIdMessage( trigger: Trigger, - triggerId: Long, - receiverActor: ActorRef + triggerId: Long ) extends SchedulerMessage /** respond to agent that the send trigger is illegal diff --git a/src/main/scala/edu/ie3/simona/ontology/trigger/Trigger.scala b/src/main/scala/edu/ie3/simona/ontology/trigger/Trigger.scala index 6a07a103b2..405f860f1e 100644 --- a/src/main/scala/edu/ie3/simona/ontology/trigger/Trigger.scala +++ b/src/main/scala/edu/ie3/simona/ontology/trigger/Trigger.scala @@ -13,7 +13,7 @@ import edu.ie3.simona.api.ExtSimAdapter.InitExtSimAdapter import edu.ie3.simona.service.ServiceStateData import edu.ie3.simona.util.SimonaConstants -sealed trait Trigger { +trait Trigger { def tick: Long } @@ -64,7 +64,7 @@ object Trigger { ) extends InitializeTrigger /** Trigger to start a general activity e.g. reactivate the actor. May only be - * sent by SimScheduler + * sent by [[edu.ie3.simona.scheduler.Scheduler]] */ final case class ActivityStartTrigger(tick: Long) extends Trigger diff --git a/src/main/scala/edu/ie3/simona/scheduler/RuntimeNotifier.scala b/src/main/scala/edu/ie3/simona/scheduler/RuntimeNotifier.scala new file mode 100644 index 0000000000..17182e4498 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/scheduler/RuntimeNotifier.scala @@ -0,0 +1,179 @@ +/* + * © 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 akka.actor.typed.ActorRef +import edu.ie3.simona.event.RuntimeEvent +import edu.ie3.simona.event.RuntimeEvent._ +import edu.ie3.simona.scheduler.RuntimeNotifier.now +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK + +/** Determines runtime events at different stages of the simulation and notifies + * listeners + * + * @param eventListener + * The event listener to send runtime notifications to + * @param readyCheckWindow + * Size of a check window in ticks + * @param lastCheck + * Last check window for which a notification has been issued + * @param simStartTime + * Time in milliseconds when the simulation as a whole was started (before + * initialization) + * @param lastStartTime + * Time in milliseconds when the simulation was last started (before + * initialization or after a pause) + * @param lastCheckWindowTime + * Time when in milliseconds when + */ +final case class RuntimeNotifier( + eventListener: ActorRef[RuntimeEvent], + readyCheckWindow: Option[Int], + lastCheck: Long = -1, + simStartTime: Long = -1, + lastStartTime: Long = -1, + lastCheckWindowTime: Long = -1 +) { + + /** Notifier listeners that simulation has started or continued with given + * tick + * @param tick + * Tick that the simulation has started or continued with + * @param pauseTick + * Next tick that the simulation pauses (if applicable) + * @param endTick + * Last tick of the simulation + * @return + * Updated notifier + */ + def starting( + tick: Long, + pauseTick: Option[Long], + endTick: Long + ): RuntimeNotifier = { + val nowTime = now() + + val pauseOrEndTick = pauseTick.map(math.min(_, endTick)).getOrElse(endTick) + + notify(tick match { + case INIT_SIM_TICK => Initializing + case _ => + Simulating(tick, pauseOrEndTick) + }) + + if (tick > 0L) + copy(lastStartTime = nowTime) + else + copy(simStartTime = nowTime, lastStartTime = nowTime) + } + + /** Notifier listeners that simulation is pausing at given tick + * @param pauseTick + * Last tick before simulation pauses or ends + * @return + * Updated notifier + */ + def pausing(pauseTick: Long): RuntimeNotifier = { + notify(Ready(pauseTick, now() - simStartTime)) + + this + } + + /** Notifier listeners that simulation has completed the given tick. This + * usually means issuing CheckWindowPassed messages. + * + * @param completedTick + * Tick that has just been completed + * @return + * Updated notifier + */ + def completing(completedTick: Long): RuntimeNotifier = { + val nowTime = now() + + // check if InitComplete should be sent, then adjust lastCheck + val adjustedLastCheck = if (lastCheck <= -1) { + if (completedTick >= INIT_SIM_TICK) + notify(InitComplete(nowTime - simStartTime)) + 0 + } else + lastCheck + + readyCheckWindow + .flatMap { checkWindow => + val completedWindows = + (adjustedLastCheck + checkWindow) to completedTick by checkWindow + + completedWindows.lastOption + .map { lastPassedCheck => + // at least one window has been passed + completedWindows.foreach { tick => + notify(CheckWindowPassed(tick, nowTime - lastCheckWindowTime)) + } + + copy( + lastCheck = lastPassedCheck, + lastCheckWindowTime = nowTime + ) + } + } + .getOrElse { + // no check window set or no windows passed + copy(lastCheck = adjustedLastCheck) + } + } + + /** Notifier listeners that simulation has ended successfully with given tick + * + * @param endTick + * Last tick of the simulation + */ + def finishing(endTick: Long): Unit = { + notify( + Done( + endTick, + simStartTime - now(), + 0, + errorInSim = false + ) + ) + } + + /** Notifier listeners that simulation has ended with error at given tick + * + * @param endTick + * last tick of the simulation (usually when error happened) + * @param errorMsg + * The error message + */ + def error(endTick: Long, errorMsg: String): Unit = { + notify( + Error(errorMsg) + ) + + notify( + Done( + endTick, + simStartTime - now(), + 0, + errorInSim = true + ) + ) + } + + private def notify(event: RuntimeEvent): Unit = + eventListener ! event + +} + +object RuntimeNotifier { + + /** @return + * Current time in milliseconds + */ + private def now(): Long = System.nanoTime() / 1000000L + +} diff --git a/src/main/scala/edu/ie3/simona/scheduler/Scheduler.scala b/src/main/scala/edu/ie3/simona/scheduler/Scheduler.scala new file mode 100644 index 0000000000..e841cda2d3 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/scheduler/Scheduler.scala @@ -0,0 +1,265 @@ +/* + * © 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 akka.actor +import akka.actor.ActorRef +import akka.actor.typed.Behavior +import akka.actor.typed.scaladsl.adapter.TypedActorRefOps +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import edu.ie3.simona.actor.ActorUtil.stopOnError +import edu.ie3.simona.ontology.messages.SchedulerMessage +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + CompletionMessage, + ScheduleTriggerMessage, + TriggerWithIdMessage +} +import edu.ie3.simona.ontology.trigger.Trigger +import edu.ie3.simona.ontology.trigger.Trigger.ActivityStartTrigger +import edu.ie3.simona.scheduler.SchedulerData.ActivationData + +/** Scheduler that activates actors at specific ticks and keeps them + * synchronized by waiting for the completions of all activations. Can be + * stacked into scheduler hierarchies. + */ +object Scheduler { + + def apply( + parent: actor.typed.ActorRef[SchedulerMessage] + ): Behavior[SchedulerMessage] = inactive( + SchedulerData(parent) + ) + + private def inactive( + data: SchedulerData, + lastActiveTick: Long = Long.MinValue + ): Behavior[SchedulerMessage] = + Behaviors.receive { + case (ctx, TriggerWithIdMessage(ActivityStartTrigger(tick), triggerId)) => + checkActivation(data, tick).map(stopOnError(ctx, _)).getOrElse { + sendCurrentTriggers(data, ActivationData(tick, triggerId)) match { + case (newSchedulerData, newActivationData) => + active(newSchedulerData, newActivationData) + } + } + + case ( + ctx, + ScheduleTriggerMessage(trigger, actorToBeScheduled) + ) => + checkTriggerSchedule(lastActiveTick + 1L, trigger) + .map(stopOnError(ctx, _)) + .getOrElse { + val oldEarliestTick = data.triggerQueue.headKeyOption + + val updatedData = scheduleTrigger(data, trigger, actorToBeScheduled) + val newEarliestTick = updatedData.triggerQueue.headKeyOption + + // also potentially schedule with parent if the new earliest tick is + // different from the old earliest tick (including if nothing had + // been scheduled before) + if (newEarliestTick != oldEarliestTick) + data.parent ! ScheduleTriggerMessage( + ActivityStartTrigger(trigger.tick), + ctx.self.toClassic + ) + + inactive(updatedData, lastActiveTick) + } + + case (ctx, unexpected: SchedulerMessage) => + stopOnError( + ctx, + s"Received unexpected message $unexpected when inactive" + ) + + } + + private def active( + data: SchedulerData, + activationData: ActivationData + ): Behavior[SchedulerMessage] = Behaviors.receive { + + case ( + ctx, + ScheduleTriggerMessage(trigger, actorToBeScheduled) + ) => + checkTriggerSchedule(activationData.tick, trigger) + .map(stopOnError(ctx, _)) + .getOrElse { + sendCurrentTriggers( + scheduleTrigger(data, trigger, actorToBeScheduled), + activationData + ) match { + case (newSchedulerData, newActivationData) => + active(newSchedulerData, newActivationData) + } + } + + case (ctx, CompletionMessage(triggerId, newTrigger)) => + val tick = activationData.tick + + checkCompletion(activationData, triggerId) + .toLeft(handleCompletion(activationData, triggerId)) + .flatMap { updatedActivationData => + // schedule new triggers, if present + newTrigger + .map { newTrigger => + checkTriggerSchedule(tick, newTrigger.trigger) + .toLeft( + scheduleTrigger( + data, + newTrigger.trigger, + newTrigger.actorToBeScheduled + ) + ) + } + .getOrElse(Right(data)) + .map((_, updatedActivationData)) + } + .map { case (updatedData, updatedActivationData) => + if (isTickCompleted(updatedData, updatedActivationData)) { + // send completion to parent, if all completed + completeWithParent(updatedData, updatedActivationData, ctx) + inactive(updatedData, tick) + } else { + // there might be new triggers for current tick, send them out + sendCurrentTriggers(updatedData, updatedActivationData) match { + case (newSchedulerData, newActivationData) => + active(newSchedulerData, newActivationData) + } + } + } + .fold(stopOnError(ctx, _), identity) + + case (ctx, unexpected: SchedulerMessage) => + stopOnError(ctx, s"Received unexpected message $unexpected when active") + } + + private def checkActivation( + data: SchedulerData, + newTick: Long + ): Option[String] = + data.triggerQueue.headKeyOption.flatMap { minScheduledKey => + Option.when(newTick != minScheduledKey) { + s"The next tick to activate is $minScheduledKey, not $newTick" + } + } + + private def checkTriggerSchedule( + minTick: Long, + trigger: Trigger + ): Option[String] = { + Option.when(trigger.tick < minTick) { + s"Cannot schedule an event $trigger at tick ${trigger.tick} when the last or currently activated tick is $minTick" + } + } + + private def checkCompletion( + activationData: ActivationData, + triggerId: Long + ): Option[String] = + Option + .when(activationData.triggerIdToActiveTrigger.isEmpty) { + s"No completions expected, received completion for trigger id $triggerId" + } + .orElse { + Option.unless( + activationData.triggerIdToActiveTrigger.contains(triggerId) + ) { + s"Trigger id $triggerId is not part of expected trigger ids ${activationData.triggerIdToActiveTrigger.keys}" + } + } + + private def sendCurrentTriggers( + data: SchedulerData, + activationData: ActivationData + ): (SchedulerData, ActivationData) = { + val newActivationData = + data.triggerQueue + .getAndRemoveSet(activationData.tick) + .foldLeft(activationData) { + case ( + updatedActivationData, + actor + ) => + data.actorToTrigger + .remove(actor) + .map { triggerWithId => + // track the trigger id with the scheduled trigger + updatedActivationData.triggerIdToActiveTrigger += + triggerWithId.triggerId -> actor + + actor ! triggerWithId + + updatedActivationData + } + .getOrElse(updatedActivationData) + + } + + (data, newActivationData) + } + + private def scheduleTrigger( + data: SchedulerData, + trigger: Trigger, + actorToBeScheduled: ActorRef + ): SchedulerData = { + val newTriggerId = data.lastTriggerId + 1L + val triggerWithIdMessage = + TriggerWithIdMessage( + trigger, + newTriggerId + ) + + /* update trigger queue */ + data.triggerQueue.set( + trigger.tick, + actorToBeScheduled + ) + + data.actorToTrigger += ( + actorToBeScheduled -> triggerWithIdMessage + ) + + data.copy( + lastTriggerId = newTriggerId + ) + } + + private def handleCompletion( + data: ActivationData, + triggerId: Long + ): ActivationData = { + data.triggerIdToActiveTrigger.remove(triggerId) + data + } + + /** Returns true if current tick can be completed with parent. + */ + private def isTickCompleted( + data: SchedulerData, + activationData: ActivationData + ): Boolean = + activationData.triggerIdToActiveTrigger.isEmpty && + !data.triggerQueue.headKeyOption.contains(activationData.tick) + + private def completeWithParent( + data: SchedulerData, + activationData: ActivationData, + ctx: ActorContext[SchedulerMessage] + ): Unit = { + val newTriggers = data.triggerQueue.headKeyOption.map(tick => + ScheduleTriggerMessage(ActivityStartTrigger(tick), ctx.self.toClassic) + ) + data.parent ! CompletionMessage( + activationData.activationTriggerId, + newTriggers + ) + } +} diff --git a/src/main/scala/edu/ie3/simona/scheduler/SchedulerData.scala b/src/main/scala/edu/ie3/simona/scheduler/SchedulerData.scala new file mode 100644 index 0000000000..e89a11491f --- /dev/null +++ b/src/main/scala/edu/ie3/simona/scheduler/SchedulerData.scala @@ -0,0 +1,41 @@ +/* + * © 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 akka.actor +import akka.actor.ActorRef +import edu.ie3.simona.ontology.messages.SchedulerMessage +import edu.ie3.simona.ontology.messages.SchedulerMessage.TriggerWithIdMessage +import edu.ie3.util.scala.collection.mutable.PriorityMultiBiSet + +import scala.collection.mutable + +final case class SchedulerData( + parent: actor.typed.ActorRef[ + SchedulerMessage + ], // FIXME typed: Scheduler or time advancer + lastTriggerId: Long = 0L, + triggerQueue: PriorityMultiBiSet[Long, ActorRef] = PriorityMultiBiSet.empty[ + Long, + ActorRef + ], + actorToTrigger: mutable.Map[ActorRef, TriggerWithIdMessage] = + mutable.Map.empty +) + +object SchedulerData { + + /** Data that is only required when scheduler is active + */ + final case class ActivationData( + tick: Long, + activationTriggerId: Long, + triggerIdToActiveTrigger: mutable.Map[Long, ActorRef] = + mutable.Map.empty[Long, ActorRef] + ) + +} diff --git a/src/main/scala/edu/ie3/simona/scheduler/SchedulerHelper.scala b/src/main/scala/edu/ie3/simona/scheduler/SchedulerHelper.scala deleted file mode 100644 index db9860b1cc..0000000000 --- a/src/main/scala/edu/ie3/simona/scheduler/SchedulerHelper.scala +++ /dev/null @@ -1,823 +0,0 @@ -/* - * © 2020. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.scheduler - -import akka.actor.ActorRef -import edu.ie3.simona.event.RuntimeEvent.{ - CheckWindowPassed, - Done, - InitComplete, - Ready -} -import edu.ie3.simona.exceptions.SchedulerException -import edu.ie3.simona.logging.SimonaActorLogging -import edu.ie3.simona.ontology.messages.SchedulerMessage._ -import edu.ie3.simona.ontology.trigger.Trigger -import edu.ie3.simona.scheduler.SimSchedulerStateData.{ - ScheduledTrigger, - SchedulerStateData, - TriggerData -} -import edu.ie3.simona.util.SimonaConstants -import edu.ie3.util.TimeUtil -import edu.ie3.util.scala.collection.mutable.{CountingMap, PriorityMultiQueue} - -import scala.annotation.tailrec -import scala.collection.mutable - -/** Main functionalities of [[SimScheduler]]. While the entry points for the - * methods can be found in [[SimScheduler#schedulerReceive()]], the - * functionalities that are carried out from the [[SimScheduler]] are located - * here - * - * @version 0.1 - * @since 04.05.20 - */ -trait SchedulerHelper extends SimonaActorLogging { - this: SimScheduler => - - // user config parameters - protected val endTick: Long = TimeUtil.withDefaults - .zonedDateTimeDifferenceInSeconds( - TimeUtil.withDefaults.toZonedDateTime(simonaTimeConfig.startDateTime), - TimeUtil.withDefaults.toZonedDateTime(simonaTimeConfig.endDateTime) - ) - - // if currentTick % checkWindowTick == 0 and all completionMessages have been - // received for currentTick, a CheckWindowPassed RuntimeEvent is issued - private val schedulerReadyCheckWindow = - simonaTimeConfig.schedulerReadyCheckWindow - - protected def sendEligibleTrigger( - stateData: SchedulerStateData - ): SchedulerStateData = - stateData.copy( - trigger = - sendEligibleTrigger(stateData.trigger, stateData.time.nowInTicks) - ) - - /** Send out all trigger that are eligible to be send for the current state of - * the state data. This method modifies the trigger data within the provided - * [[SimSchedulerStateData]]. - * - * @param triggerData - * the trigger data that should be updated - * @param nowInTicks - * the current tick - * @return - * possibly modified trigger data with updated trigger information - */ - protected def sendEligibleTrigger( - triggerData: TriggerData, - nowInTicks: Long - ): TriggerData = { - - triggerData.triggerQueue.pollTo(nowInTicks).foreach { - case scheduledTrigger @ ScheduledTrigger(triggerWithIdMessage, actor) => - // track that we wait for a response for this tick - triggerData.awaitingResponseMap.add(triggerWithIdMessage.trigger.tick) - - // track the trigger id with the scheduled trigger - triggerData.triggerIdToScheduledTriggerMap += - triggerWithIdMessage.triggerId -> scheduledTrigger - - actor ! triggerWithIdMessage - } - - triggerData - } - - /** The actual scheduling and moving forward in time process if possible. This - * method first decides whether the current tick should be used for a - * simulation hold (if [[SchedulerStateData.time.pauseScheduleAtTick]] is not - * None) or not. If yes, the simulation checks if there are trigger to send - * out for the hold tick (which might be provided by [[CompletionMessage]] s - * that have been received @ the hold tick and if yes sends them out. If no - * new triggers for the hold tick are provided AND no completion messages are - * left to receive for the hold tick, it stops the schedule. (see - * [[this.doPauseInSimStep()]] for details) - * - * If [[SchedulerStateData.time.pauseScheduleAtTick]] is None, the normal - * routine of time advancement is carried out. For details please see - * documentation in the code below. - * - * @param stateData - * the state data that should be used - * @return - * modified state data if advancement in time took place or the same state - * data if no time advancement took place - */ - @tailrec - protected final def doSimStep( - stateData: SchedulerStateData - ): SchedulerStateData = { - - val nowInTicks = stateData.time.nowInTicks - - stateData.time.pauseScheduleAtTick match { - case Some(pauseScheduleAtTick) if pauseScheduleAtTick == nowInTicks - 1 => - /* we want to pause at this tick, the - 1 is necessary as in the recursive definition we - * need to move on one more tick to send the trigger of the last tick */ - doPauseInSimStep(stateData, nowInTicks - 1) - - case _ => - /* we do not want to pause the schedule, go on with normal schedule handling */ - if (notFinishedAndTriggerAvailable(nowInTicks, stateData)) { - - /* if we do not exceed nowInSeconds OR do not wait on any responses we can send out new triggers */ - if ( - canWeSendTrigger( - stateData.trigger.awaitingResponseMap, - nowInTicks - ) - ) { - - /* send out eligible triggers, where eligible means all triggers with 'tick <= nowInTicks' */ - val eligibleTriggerSendStateData = sendEligibleTrigger(stateData) - - /* conditionally send information to listeners that all triggers for a specific step has been send out (= Ready)*/ - val updatedStateData = maybeCheckWindowPassed( - eligibleTriggerSendStateData, - schedulerReadyCheckWindow, - eligibleTriggerSendStateData.time.nowInTicks - ) - - /* if we do not exceed (nowInTicks+1) OR do not wait on any responses, we can move on in time by one tick */ - if ( - nowInTicks <= endTick && canWeSendTrigger( - updatedStateData.trigger.awaitingResponseMap, - nowInTicks + 1 - ) - ) { - doSimStep( - updatedStateData.copy( - time = updatedStateData.time - .copy(nowInTicks = nowInTicks + 1) - ) - ) - } else { - /* we cannot move on in time for (nowInTicks+1), return updated data */ - updatedStateData - } - - } else { - /* we cannot send out more triggers, return unmodified state data */ - stateData - } - - } else { - /* no triggers should be send out anymore as we reached the last tick - now we only have to - * wait for all agents to reply with completion messages */ - - /* Please keep this dead code for now at it is NOT 100% clear (but 95%) if this part is needed or not - /* check if we passed the check window has been passed */ - val stateDataAfterCheckWindow = maybeCheckWindowPassed( - stateData, - schedulerReadyCheckWindow, - nowInTicks) */ - - maybeFinishSimulation(stateData) - } - } - } - - /** Executes several routines if the simulation should pause at a specific - * tick. The condition to pause has to be checked from the outside and the - * tick to pause has to be pass in. The method then executes several checks - * if more trigger needs to be send out, listeners needs to be informed with - * specific events and so on. For details please take a look at the comments - * in the code below. - * - * @param stateData - * the current state data that should be processed - * @param pauseTick - * the pause tick - * @return - * conditionally modified state data - */ - private def doPauseInSimStep( - stateData: SchedulerStateData, - pauseTick: Long - ): SchedulerStateData = { - - /* notify about the fact that the simulation stopped here, if we do not wait on trigger completion */ - val stateDataAfterReady = maybeReady(stateData, pauseTick) - - /* it might be possible that this pause tick is also the end tick, finish if we do not wait on trigger completion */ - val stateDataAfterFinish = if (endTick == pauseTick) { - maybeFinishSimulation(stateDataAfterReady) - } else { - stateDataAfterReady - } - - /* if we are in tick pauseScheduleAtTick, it might be possible that we still need to schedule triggers due to - * incoming triggers for this tick inside completion messages */ - if ( - !noScheduledTriggersForCurrentTick( - stateData.trigger.triggerQueue, - pauseTick - ) - ) { - /* send out eligible triggers, where eligible means all triggers with 'tick <= nowInTicks' */ - val eligibleTriggerSendStateData = sendEligibleTrigger( - stateDataAfterFinish - ) - - val updatedStateData = maybeCheckWindowPassed( - eligibleTriggerSendStateData, - schedulerReadyCheckWindow, - eligibleTriggerSendStateData.time.nowInTicks - ) - updatedStateData - - } else { - stateDataAfterFinish - } - } - - /** Check if a) the trigger queue is empty b) the next trigger's tick is after - * the tick, the simulation is in, or after the last tick of the simulation - * - * @param triggerQueue - * Queue of triggers to be send out - * @param nowInTicks - * Current tick, the simulation is in - * @return - * a boolean - */ - private def noScheduledTriggersForCurrentTick( - triggerQueue: PriorityMultiQueue[Long, ScheduledTrigger], - nowInTicks: Long - ): Boolean = - triggerQueue.headKeyOption match { - case Some(nextTriggerTick) => - nextTriggerTick > nowInTicks || nextTriggerTick > endTick - case None => - true - } - - /** Checks if the simulation has not reached its endTick yet and if there are - * still trigger available in the trigger queue that can be send out - * - * @param nowInTicks - * the current tick of the simulation - * @param stateData - * the state data with the trigger data - * @return - * either true if the end tick is not reached yet or if there are still - * trigger to be scheduled whose tick <= endTick, false otherwise - */ - private def notFinishedAndTriggerAvailable( - nowInTicks: Long, - stateData: SchedulerStateData - ): Boolean = - nowInTicks <= endTick || - stateData.trigger.triggerQueue.headKeyOption.exists(_ <= endTick) - - /** Checks if we can move on in time in the schedule by comparing the awaiting - * response map data with the current tick - * - * @param awaitingResponseMap - * the map containing all information about triggers we still wait for - * completion messages - * @param nowInTicks - * the current tick - * @return - * true if trigger can be send, false otherwise - */ - private def canWeSendTrigger( - awaitingResponseMap: CountingMap[Long], - nowInTicks: Long - ): Boolean = - awaitingResponseMap.minKeyOption match { - case Some(minKey) => - nowInTicks <= minKey - case None => - true // map empty, no completions awaited - } - - /** Checks if the provided state data and the current tick is eligible to - * issue a [[CheckWindowPassed]] event - * - * @param stateData - * the current state data - * @param readyCheckWindow - * the ready check window tick - * @param nowInTicks - * the current tick - * @return - * conditionally adapted state data or the provided state data - */ - private def maybeCheckWindowPassed( - stateData: SchedulerStateData, - readyCheckWindow: Option[Int], - nowInTicks: Long - ): SchedulerStateData = { - - val readyCheckVal = readyCheckWindow.getOrElse(3600) // defaults to 1h - - val awaitingResponseMap = stateData.trigger.awaitingResponseMap - - if ( - nowInTicks > 0 && (nowInTicks % readyCheckVal == 0) - && !awaitingResponseMap.contains(nowInTicks) - && noScheduledTriggersForCurrentTick( - stateData.trigger.triggerQueue, - nowInTicks - ) - && stateData.event.lastCheckWindowPassedTick < nowInTicks - ) { - // calculate duration - val duration = calcDuration(stateData.time.checkStepStartTime) - - // notify listeners - notifyListener(CheckWindowPassed(nowInTicks, duration)) - - // update state data with ne time system time until next step - stateData.copy( - time = stateData.time.copy(checkStepStartTime = System.nanoTime), - event = stateData.event.copy(lastCheckWindowPassedTick = nowInTicks) - ) - } else { - stateData - } - - } - - /** Checks if all conditions to issue a [[Ready]] event are met and if yes do - * so as well as returns a copy of the provided state data with updated - * runtime and time information. If now [[Ready]] event can be issued, the - * original state data is returned. - * - * @param stateData - * the state data that should be processed - * @param nowInTicks - * the current time step - * @return - * either updated state data based on the rule in the description or the - * original state data - */ - private def maybeReady( - stateData: SchedulerStateData, - nowInTicks: Long - ): SchedulerStateData = { - /* ready notification can only be send if we don't have any triggers we wait for anymore in the current tick + - * the next trigger in the queue is not the current tick */ - if ( - !stateData.trigger.awaitingResponseMap.contains(nowInTicks) - && noScheduledTriggersForCurrentTick( - stateData.trigger.triggerQueue, - nowInTicks - ) - ) { - /* ready! - notify listener */ - notifyListener( - Ready( - nowInTicks, - calcDuration(stateData.time.readyStepStartTime) - ) - ) - - /* set simulation on hold + reset readyStepStartTime*/ - stateData.copy( - runtime = stateData.runtime.copy(scheduleStarted = false), - time = stateData.time.copy(readyStepStartTime = System.nanoTime()) - ) - } else { - stateData - } - } - - /** Determines if the simulation is finished based on the provided state data. - * This method only checks if a) there are no missing completion messages b) - * if the trigger queue is empty OR if the remaining the trigger queue have a - * higher tick number than the current nowInTicks When using this method, one - * has to check manually as well if other criteria are fulfilled as well to - * finish the simulation. - * - * @param stateData - * the state data that should be checked - * @return - * either unmodified state data or state data with runtime parameter - * 'scheduleStarted = false' - */ - private def maybeFinishSimulation( - stateData: SchedulerStateData - ): SchedulerStateData = { - if ( - stateData.trigger.awaitingResponseMap.isEmpty && noScheduledTriggersForCurrentTick( - stateData.trigger.triggerQueue, - stateData.time.nowInTicks - ) - ) { - finishSimulation(stateData) - } else { - stateData - } - } - - /** Finish the simulation by informing all agents that the simulation is done, - * issuing a [[Done]] event, notifying the sender that send the - * [[StartScheduleMessage]] about the simulation status and return a state - * data copy with disabled schedule - * - * @param stateData - * the state data that should be used for processing - * @param errorInSim - * true if an error occurred in the simulation, false otherwise - * @return - * a state data with scheduleStarted == false - */ - private def finishSimulation( - stateData: SchedulerStateData, - errorInSim: Boolean = false - ): SchedulerStateData = { - val totalSimDuration: Long = stateData.time.simStartTime - .map(startTime => calcDuration(startTime)) - .getOrElse(0) - - /* unwatch all agents that have triggered themselves as the simulation will shutdown now */ - stateData.trigger.triggerQueue.allValues.foreach(trig => - context.unwatch(trig.agent) - ) - - /* notify listeners */ - /*The usage of min is necessary because the scheduler overshoots the target tick by 1 at the end of the simulation*/ - notifyListener( - Done( - Math.min(stateData.time.nowInTicks, endTick), - totalSimDuration, - stateData.runtime.noOfFailedPF, - errorInSim - ) - ) - - /* notify start sender */ - if (errorInSim) { - stateData.runtime.initSender ! SimulationFailureMessage - } else { - stateData.runtime.initSender ! SimulationSuccessfulMessage - } - - /* disable schedule */ - stateData.copy(runtime = stateData.runtime.copy(scheduleStarted = false)) - } - - protected def finishSimulationOnError( - stateData: SchedulerStateData - ): SchedulerStateData = { - finishSimulation(stateData, errorInSim = true) - } - - /** Validation to ensure that a [[StartScheduleMessage]] received by the - * [[SimScheduler]] can be used to continue the schedule safely - * - * @param pauseScheduleAtTick - * the optional next pause tick the schedule should hold on - * @param nowInTicks - * the current tick the simulation is standing at - * @param endTick - * the end tick of the simulation - */ - protected final def validStartScheduleRequest( - pauseScheduleAtTick: Option[Long], - nowInTicks: Long, - endTick: Long - ): Unit = { - - pauseScheduleAtTick.foreach(pauseScheduleAtTick => { - if (pauseScheduleAtTick < nowInTicks) { - throw new SchedulerException( - s"Cannot pause schedule @ already passed tick! Current tick is: $nowInTicks, " + - s"requested pause is $pauseScheduleAtTick!" - ) - } - - if (pauseScheduleAtTick > endTick) - log.warning( - s"Requested pause for tick '$pauseScheduleAtTick' but simulation end in config is '$endTick'. " + - s"Will simulate until tick '$endTick'!" - ) - }) - - if (nowInTicks > endTick) - throw new SchedulerException( - s"Requested tick $nowInTicks is after end tick $endTick. Did you remember to adapt simona.time.endDateTime in the config?" - ) - } - - /** Either do a simulation step (move forward in tick time if possible), carry - * out the initialization of the agents (only @ tick 0) or just return the - * unmodified state data - * - * @param stateData - * the state data that should be used for the initialization or the - * simulation step - * @return - * either the same or modified state data based on the executed steps - */ - protected final def doSimStepOrInitAgents( - stateData: SchedulerStateData - ): SchedulerStateData = { - if (stateData.runtime.initComplete && stateData.runtime.scheduleStarted) - doSimStep(stateData) - else if (!stateData.runtime.initComplete && stateData.runtime.initStarted) - sendEligibleTrigger(stateData) - else - stateData - } - - /** Main method to handle all [[CompletionMessage]] s received by the - * [[SimScheduler]]. Based on the received completion message, the provided - * stateData is updated. In particular the awaitingResponseMap and the - * triggerIdToScheduledTriggerMap are updated if the provided - * [[CompletionMessage]] is valid. For an invalid message, the data is not - * modified and the error is logged. - * - * Depending on nowInTicks, the method behaves differently. If nowInTicks == - * 0, the method checks if the last completion message received leads to a - * fully initialized simulation and if yes, it sets - * [[SchedulerStateData.runtime.initComplete]] to true. Futhermore, if - * [[this.autoStart]] is enabled, the simulation schedule is executed - * afterwards. Otherwise, it just returned the modified state data. - * - * If nowInTicks > 0, a copy of the provided state data with updated trigger - * information (as described above) is returned - * - * @param completionMessage - * the completion message that should be processed - * @param inputStateData - * the current state data - * @return - * state data with updated trigger and maybe updated runtime information - */ - protected final def handleCompletionMessage( - completionMessage: CompletionMessage, - inputStateData: SchedulerStateData - ): SchedulerStateData = { - - /* schedule new triggers, if any */ - val updatedStateData = completionMessage.newTriggers.map( - _.foldLeft(inputStateData)((updatedStateData, newTrigger) => - scheduleTrigger(newTrigger, updatedStateData) - ) - ) match { - /* after scheduling the new triggers, if any, we go on with either the same or updated state data */ - case stateDataOpt => - val triggerId = completionMessage.triggerId - val stateData = stateDataOpt.getOrElse(inputStateData) - val triggerData = stateData.trigger - - val ( - updatedAwaitingResponseMap, - updatedTriggerIdToScheduledTriggerMap - ) = updateAwaitingResponseAndTriggerIdToScheduledTriggerMap( - triggerId, - triggerData.awaitingResponseMap, - triggerData.triggerIdToScheduledTriggerMap - ).getOrElse { - log.error( - s"Received bad completion notice $completionMessage from ${sender().path}" - ) - ( - triggerData.awaitingResponseMap, - triggerData.triggerIdToScheduledTriggerMap - ) - } - - /* unwatch sender */ - context.unwatch(sender()) - - /* if we are in the init tick, check if initialization is completed, otherwise just return state data with - * updated trigger information */ - // initialization should always take place @ init tick -> initialization is done if we do not wait for - // responses on init triggers @ init tick anymore - if (stateData.time.nowInTicks != SimonaConstants.INIT_SIM_TICK) { - /* normal behavior */ - val updatedStateData = stateData.copy( - trigger = triggerData.copy( - awaitingResponseMap = updatedAwaitingResponseMap, - triggerIdToScheduledTriggerMap = - updatedTriggerIdToScheduledTriggerMap - ) - ) - - /* if there are no triggers left to send and we are only receiving completion messages, we still might - * pass a ready check window, this call is to ensure that for the last tick of the simulation a CheckWindowPassed() - * is issued lastTick % schedulerReadyCheckWindow == 0 */ - maybeCheckWindowPassed( - updatedStateData, - schedulerReadyCheckWindow, - stateData.time.nowInTicks - 1 - ) - - maybeCheckWindowPassed( - updatedStateData, - schedulerReadyCheckWindow, - stateData.time.nowInTicks - ) - } else { - /* we are @ the init tick (SimonaSim#initTick), if no init triggers are in the queue and in the await map - * anymore we're done with the init process, hence we check for this case */ - val initDone = - isInitDone(updatedAwaitingResponseMap, triggerData.triggerQueue) - - /* steps to be carried out when init is done */ - val updateTime = if (initDone) { - val initDuration = calcDuration( - stateData.time.initStartTime - ) - - notifyListener(InitComplete(initDuration)) - - /* trigger a message to self to start schedule */ - // this is a) autostart after init and b) never pause schedule at specific tick - if (autoStart) - self ! StartScheduleMessage() - - /* Advance time, if init is done */ - stateData.time.copy(nowInTicks = stateData.time.nowInTicks + 1) - } else { - stateData.time - } - - // set initComplete to initDone value - stateData.copy( - runtime = stateData.runtime.copy(initComplete = initDone), - trigger = triggerData.copy( - awaitingResponseMap = updatedAwaitingResponseMap, - triggerIdToScheduledTriggerMap = - updatedTriggerIdToScheduledTriggerMap - ), - time = updateTime - ) - } - } - - updatedStateData - } - - /** Updates awaitingResponse and triggerIdToScheduledTriggerMap with the - * provided data if the provided triggerId is valid - * - * @param triggerId - * the trigger id that should be processed - * @param awaitingResponseMap - * the mapping of ticks to scheduled trigger that are send out and a - * completion message has not been received yet - * @param triggerIdToScheduledTriggerMap - * the mapping of the trigger id to the send out trigger - * @return - * either a tuple with an updated awaiting response map and updated trigger - * id to scheduled trigger map or none if the provided trigger id is - * invalid - */ - private def updateAwaitingResponseAndTriggerIdToScheduledTriggerMap( - triggerId: Long, - awaitingResponseMap: CountingMap[Long], - triggerIdToScheduledTriggerMap: mutable.Map[Long, ScheduledTrigger] - ): Option[ - ( - CountingMap[Long], - mutable.Map[Long, ScheduledTrigger] - ) - ] = { - triggerIdToScheduledTriggerMap.get(triggerId) match { - case None => - None - case Some(scheduledTrigger) => - val tick = scheduledTrigger.triggerWithIdMessage.trigger.tick - if (awaitingResponseMap.contains(tick)) { - - awaitingResponseMap.subtract(tick) - - triggerIdToScheduledTriggerMap -= triggerId - - Some( - awaitingResponseMap, - triggerIdToScheduledTriggerMap - ) - } else { - None - } - } - } - - /** Checks if initialization of the simulation is complete. It is complete, if - * two conditions are fulfilled:
  1. there is no trigger with tick - * [[SimonaConstants.INIT_SIM_TICK]] inside the provided trigger queue - * left
  2. there is no sent out trigger with tick - * [[SimonaConstants.INIT_SIM_TICK]], which is not completed, yet
- * - * @param awaitingResponseMap - * a map containing a tick to scheduled trigger mapping - * @param triggerQueue - * the trigger queue with all triggers that should be send out - * @return - * true if init is done, false otherwise - */ - private def isInitDone( - awaitingResponseMap: CountingMap[Long], - triggerQueue: PriorityMultiQueue[Long, ScheduledTrigger] - ): Boolean = - !awaitingResponseMap.contains(SimonaConstants.INIT_SIM_TICK) && - !triggerQueue.headKeyOption.contains(SimonaConstants.INIT_SIM_TICK) - - /** Calculate the duration between a given start time and the current system - * time in milliseconds - * - * @param startTime - * the start time - * @return - * the duration between the given start time and the current system time in - * milliseconds - */ - protected def calcDuration(startTime: Long): Long = { - (System.nanoTime - startTime) / 1000000L // in msec - } - - /** Adds the provided trigger to the trigger queue to schedule it at the - * requested tick - * - * @param triggerMessage - * message containing trigger to be scheduled and it's receiver - * @param stateData - * the state data that should be updated - * @return - * a copy of the provided state data with updated trigger data - */ - protected final def scheduleTrigger( - triggerMessage: ScheduleTriggerMessage, - stateData: SchedulerStateData - ): SchedulerStateData = - scheduleTrigger( - triggerMessage.trigger, - triggerMessage.actorToBeScheduled, - stateData - ) - - /** Adds the provided trigger to the trigger queue to schedule it at the - * requested tick - * - * @param trigger - * the trigger that should be scheduled - * @param actorToBeScheduled - * the actor that should receive the trigger - * @param stateData - * the state data that should be updated - * @return - * a copy of the provided state data with updated trigger data - */ - protected final def scheduleTrigger( - trigger: Trigger, - actorToBeScheduled: ActorRef, - stateData: SchedulerStateData - ): SchedulerStateData = { - - // watch the actor to be scheduled to know when it dies - context.watch(actorToBeScheduled) - - // if the tick of this trigger is too much in the past, we cannot schedule it - if (stateData.time.nowInTicks > trigger.tick) { - actorToBeScheduled ! IllegalTriggerMessage( - s"Cannot schedule an event $trigger at tick ${trigger.tick} when 'nowInSeconds' is at ${stateData.time.nowInTicks}!", - actorToBeScheduled - ) - - stateData - - } else { - - /* update trigger id counter & create new triggerWithIdMessage */ - val updatedTriggerIdCounter = stateData.trigger.triggerIdCounter + 1 - val triggerWithIdMessage = - TriggerWithIdMessage( - trigger, - updatedTriggerIdCounter, - actorToBeScheduled - ) - - /* update trigger queue */ - stateData.trigger.triggerQueue.add( - trigger.tick, - ScheduledTrigger( - triggerWithIdMessage, - actorToBeScheduled - ) - ) - - /* return copy of state data */ - stateData.copy( - trigger = stateData.trigger.copy( - triggerIdCounter = updatedTriggerIdCounter - ) - ) - } - - } - -} diff --git a/src/main/scala/edu/ie3/simona/scheduler/SimScheduler.scala b/src/main/scala/edu/ie3/simona/scheduler/SimScheduler.scala deleted file mode 100644 index 40a36dbeb7..0000000000 --- a/src/main/scala/edu/ie3/simona/scheduler/SimScheduler.scala +++ /dev/null @@ -1,219 +0,0 @@ -/* - * © 2020. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.scheduler - -import akka.actor.{Actor, ActorRef, Props, Terminated} -import edu.ie3.simona.config.SimonaConfig -import edu.ie3.simona.event.RuntimeEvent.{Error, Initializing, Simulating} -import edu.ie3.simona.event.notifier.Notifier -import edu.ie3.simona.ontology.messages.SchedulerMessage._ -import edu.ie3.simona.scheduler.SimSchedulerStateData.SchedulerStateData - -object SimScheduler { - - def props( - simonaTimeConfig: SimonaConfig.Simona.Time, - listener: Iterable[ActorRef], - stopOnFailedPowerFlow: Boolean, - autoStart: Boolean = - true // note: this can become simona config dependant in the future - ): Props = - Props( - new SimScheduler( - simonaTimeConfig, - listener, - stopOnFailedPowerFlow, - autoStart - ) - ) -} - -/** Scheduler for the discrete events of the simulation - * - * @param simonaTimeConfig - * time configuration parameters - * @param listener - * listener for scheduler [[edu.ie3.simona.event.RuntimeEvent]] s - * @param stopOnFailedPowerFlow - * true, if the scheduler should end the simulation if a power flow failed, - * false if we just skip a failed power flow and go on with the simulation - * @param autoStart - * if autostart is set to true, the simulation starts right after finishing - * the initialization process - */ -class SimScheduler( - val simonaTimeConfig: SimonaConfig.Simona.Time, - override val listener: Iterable[ActorRef], - val stopOnFailedPowerFlow: Boolean, - val autoStart: Boolean = - true // note: this can become simona config dependant in the future -) extends Actor - with Notifier - with SchedulerHelper - with SimSchedulerStateData { - - override def receive: Receive = schedulerReceive(SchedulerStateData()) - - def schedulerReceive(stateData: SchedulerStateData): Receive = { - - /* start the initialization process based on all up to this call scheduled initialization triggers */ - case InitSimMessage => - /* initialize agents */ - // notify listeners - notifyListener(Initializing) - - // set init sender - val startSender = sender() - - // initializing process - val initStartTime = System.nanoTime - sendEligibleTrigger(stateData) - - context become schedulerReceive( - stateData.copy( - runtime = stateData.runtime - .copy(initStarted = true, initSender = startSender), - time = stateData.time.copy( - initStartTime = initStartTime - ) - ) - ) - - /* start the schedule with optional pause schedule @ provided tick */ - case msg @ StartScheduleMessage(pauseScheduleAtTick) => - /* check if the request is valid */ - validStartScheduleRequest( - pauseScheduleAtTick, - stateData.time.nowInTicks, - endTick - ) - - if ( - stateData.runtime.initComplete && !stateData.runtime.scheduleStarted - ) { - /* do simulation steps */ - val currentTime = System.nanoTime - - /* notify listener that we are running */ - notifyListener( - Simulating( - stateData.time.nowInTicks, - pauseScheduleAtTick - .map(pauseScheduleAtTick => - if (pauseScheduleAtTick > endTick) endTick - else pauseScheduleAtTick - ) - .getOrElse(endTick) - ) - ) - - // if autostart is false, then it might be possible that the init sender is not equal to the start sender - // and reporting the end state of the simulation would not be possible - val updatedSender = - if (!autoStart) sender() else stateData.runtime.initSender - - val updatedStateData = doSimStep( - stateData.copy( - runtime = stateData.runtime - .copy(scheduleStarted = true, initSender = updatedSender), - time = stateData.time - .copy( - pauseScheduleAtTick = pauseScheduleAtTick, - simStartTime = - stateData.time.simStartTime.orElse(Some(currentTime)), - checkStepStartTime = currentTime, - readyStepStartTime = currentTime - ) - ) - ) - - context become schedulerReceive(updatedStateData) - } else if (!stateData.runtime.initComplete) { - // init is not complete yet, do nothing - log.error( - "Cannot start schedule before initialization took place! Please send an initialization start message to the scheduler first!" - ) - } else { - // schedule is already running, do nothing - log.error(s"Schedule already started! Discarding $msg!") - } - - /* schedule new triggers */ - case triggerToSchedule: ScheduleTriggerMessage => - val updatedStateData = doSimStepOrInitAgents( - scheduleTrigger(triggerToSchedule, stateData) - ) - context become schedulerReceive(updatedStateData) - - /* process completion messages */ - case completionMessage: CompletionMessage => - val updatedStateData = doSimStepOrInitAgents( - handleCompletionMessage(completionMessage, stateData) - ) - - context become schedulerReceive(updatedStateData) - - case PowerFlowFailedMessage => - /* config dependant we either go into onErrorReceive and terminate when we have - * all completion messages received or we just go on with the normal schedule*/ - val updatedStateData = stateData.copy( - runtime = stateData.runtime - .copy(noOfFailedPF = stateData.runtime.noOfFailedPF + 1) - ) - if (stopOnFailedPowerFlow) { - /* go to onError receive state */ - context become schedulerReceiveOnError(updatedStateData) - } else { - context become schedulerReceive(updatedStateData) - } - - /* received whenever a watched agent dies */ - case Terminated(_) => - // inform start sender about the error & stop scheduler gracefully - notifyListener( - Error(s"Received termination message from ${sender().path}") - ) - stateData.runtime.initSender ! SimulationFailureMessage - context.stop(self) - - /* all unhandled messages */ - case unhandledMessage => - log.error(s"Received unhandled message: $unhandledMessage") - - } - - /* receive method that is used as state where the scheduler received a PowerFlowFailedMessage and should stop on - a failed power flow. here we only wait for all missing completion messages and then terminating the simulation*/ - def schedulerReceiveOnError(stateData: SchedulerStateData): Receive = { - /* process completion messages, terminate simulation afterwards */ - case completionMessage: CompletionMessage => - val updatedStateData = - handleCompletionMessage(completionMessage, stateData) - - if (updatedStateData.trigger.awaitingResponseMap.isEmpty) - finishSimulationOnError(updatedStateData) - - context become schedulerReceiveOnError(updatedStateData) - - case PowerFlowFailedMessage => - /* config dependant we either go into onErrorReceive and terminate when we have - * all completion messages received or we just go on with the normal schedule*/ - val updatedStateData = stateData.copy( - runtime = stateData.runtime - .copy(noOfFailedPF = stateData.runtime.noOfFailedPF + 1) - ) - context become schedulerReceiveOnError(updatedStateData) - - /* all unhandled messages */ - case unhandledMessage => - log.error( - s"Received unhandled message when terminating due to error: $unhandledMessage" - ) - - } - -} diff --git a/src/main/scala/edu/ie3/simona/scheduler/SimSchedulerStateData.scala b/src/main/scala/edu/ie3/simona/scheduler/SimSchedulerStateData.scala deleted file mode 100644 index 6390df0ec7..0000000000 --- a/src/main/scala/edu/ie3/simona/scheduler/SimSchedulerStateData.scala +++ /dev/null @@ -1,126 +0,0 @@ -/* - * © 2020. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.scheduler - -import akka.actor.{Actor, ActorRef} -import edu.ie3.simona.ontology.messages.SchedulerMessage.TriggerWithIdMessage -import edu.ie3.simona.util.SimonaConstants -import edu.ie3.util.scala.collection.mutable.{CountingMap, PriorityMultiQueue} - -import scala.collection.mutable - -/** Trait containing the state data of the [[SimScheduler]] - */ -private[scheduler] trait SimSchedulerStateData { - this: SimScheduler => -} - -object SimSchedulerStateData { - - /** Class holding all different kinds of state data a [[SimScheduler]] needs - * - * @param runtime - * state data about the current runtime - * @param trigger - * state data about trigger - * @param time - * state data about ticks and simulation time information - */ - private[scheduler] final case class SchedulerStateData( - runtime: RuntimeData = RuntimeData(), - trigger: TriggerData = TriggerData(), - time: TimeData = TimeData(), - event: EventData = EventData() - ) - - private[scheduler] final case class EventData( - lastCheckWindowPassedTick: Long = 0 - ) - - /** Status information that are needed by a [[SimScheduler]] instance to - * determine specific behavior - * - * @param initComplete - * true if the initialization process has been finished, false otherwise - * @param initStarted - * true if the initialization process has started or has been finished (= - * started and finished afterwards) already, false otherwise - * @param scheduleStarted - * true if the schedule has been started, e.g. moving on in time, false - * otherwise (incl. false if the scheduled has been paused) - * @param initSender - * sender of the init message, only altered if autostart == false as then - * initSender != startSender is possible - */ - private[scheduler] final case class RuntimeData( - initComplete: Boolean = false, - initStarted: Boolean = false, - scheduleStarted: Boolean = false, - initSender: ActorRef = Actor.noSender, - noOfFailedPF: Int = 0 - ) - - /** Holds information about [[edu.ie3.simona.ontology.trigger.Trigger]] that - * has been scheduled, trigger to be scheduled as well as trigger that are - * not completed yet - * - * @param triggerIdCounter - * no of triggers that has been scheduled for now - * @param triggerQueue - * holds trigger that needs to be scheduled in ascending tick order - * @param triggerIdToScheduledTriggerMap - * the triggerId mapped on its trigger for fast access - * @param awaitingResponseMap - * maps a specific tick to all triggers that are not completed yet - */ - private[scheduler] final case class TriggerData( - triggerIdCounter: Int = 0, - triggerQueue: PriorityMultiQueue[Long, ScheduledTrigger] = - PriorityMultiQueue.empty[Long, ScheduledTrigger], - triggerIdToScheduledTriggerMap: mutable.Map[Long, ScheduledTrigger] = - mutable.Map.empty[Long, ScheduledTrigger], - awaitingResponseMap: CountingMap[Long] = CountingMap.empty[Long] - ) - - /** Time data information - * - * @param nowInTicks - * the current tick of the simulation (by default starting with init tick) - * @param initStartTime - * the real time when the initialization process has started - * @param simStartTime - * the real time information when the simulation has started - * @param checkStepStartTime - * the real time when the check window has passed the last time - * @param readyStepStartTime - * the real time the latest ready event has been issued - * @param pauseScheduleAtTick - * the next tick the simulation should pause - */ - private[scheduler] final case class TimeData( - nowInTicks: Long = SimonaConstants.INIT_SIM_TICK, - initStartTime: Long = 0L, - simStartTime: Option[Long] = None, - checkStepStartTime: Long = 0L, - readyStepStartTime: Long = 0L, - pauseScheduleAtTick: Option[Long] = None - ) - - /** Wrapper class for trigger, that are already scheduled for execution @ - * [[TriggerWithIdMessage.trigger.tick]] in the - * [[edu.ie3.simona.scheduler.SimScheduler]] - * - * @param triggerWithIdMessage - * the trigger that has to be scheduled - * @param agent - * the agent that wants to be scheduled - */ - private[scheduler] final case class ScheduledTrigger( - triggerWithIdMessage: TriggerWithIdMessage, - agent: ActorRef - ) -} diff --git a/src/main/scala/edu/ie3/simona/scheduler/TimeAdvancer.scala b/src/main/scala/edu/ie3/simona/scheduler/TimeAdvancer.scala new file mode 100644 index 0000000000..75b5def893 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/scheduler/TimeAdvancer.scala @@ -0,0 +1,252 @@ +/* + * © 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 akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import akka.actor.typed.scaladsl.adapter.ClassicActorRefOps +import akka.actor.typed.{ActorRef, Behavior} +import edu.ie3.simona.actor.ActorUtil.stopOnError +import edu.ie3.simona.event.RuntimeEvent +import edu.ie3.simona.ontology.messages.SchedulerMessage +import edu.ie3.simona.ontology.messages.SchedulerMessage._ +import edu.ie3.simona.ontology.trigger.Trigger.ActivityStartTrigger +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK + +/** Unit that is in control of time advancement within the simulation. + * Represents the root entity of any scheduler hierarchy. + */ +object TimeAdvancer { + + /** @param simulation + * The root actor of the simulation + * @param eventListener + * listener that receives runtime events + * @param checkWindow + * interval in which check window messages are sent + * @param endTick + * last tick of the simulation + */ + def apply( + simulation: akka.actor.ActorRef, + eventListener: Option[ActorRef[RuntimeEvent]], + checkWindow: Option[Int], + endTick: Long + ): Behavior[SchedulerMessage] = Behaviors.receivePartial { + case (_, ScheduleTriggerMessage(trigger, actorToBeScheduled)) => + inactive( + TimeAdvancerData(simulation, actorToBeScheduled.toTyped, endTick), + eventListener.map(RuntimeNotifier(_, checkWindow)), + trigger.tick, + trigger.tick, + 0L + ) + + case (ctx, Stop(errorMsg: String)) => + endWithFailure(ctx, simulation, None, INIT_SIM_TICK, errorMsg) + } + + /** TimeAdvancer is inactive and waiting for a StartScheduleMessage to start + * or continue + * @param data + * constant time advancer data + * @param notifier + * notifier for runtime events + * @param startingTick + * tick that the simulation continues with at the beginning/after the last + * pause + * @param nextActiveTick + * tick that the schedulee wants to be activated for next + * @param nextTriggerId + * next trigger id to use for activation + */ + private def inactive( + data: TimeAdvancerData, + notifier: Option[RuntimeNotifier], + startingTick: Long, + nextActiveTick: Long, + nextTriggerId: Long + ): Behavior[SchedulerMessage] = Behaviors.receivePartial { + case (_, StartScheduleMessage(pauseTick)) => + val updatedNotifier = notifier.map { + _.starting( + startingTick, + pauseTick, + data.endTick + ) + } + + data.schedulee ! TriggerWithIdMessage( + ActivityStartTrigger(nextActiveTick), + nextTriggerId + ) + + active( + data, + updatedNotifier, + nextActiveTick, + nextTriggerId, + pauseTick + ) + + case (ctx, Stop(errorMsg: String)) => + endWithFailure(ctx, data.simulation, notifier, startingTick, errorMsg) + } + + /** TimeAdvancer is active and waiting for the current activation of the + * schedulee to complete + * + * @param data + * constant time advancer data + * @param notifier + * notifier for runtime events + * @param activeTick + * tick that is currently active + * @param expectedTriggerId + * the trigger id that we expect to receive with completion + * @param pauseTick + * the tick that we should pause at (if applicable) + */ + private def active( + data: TimeAdvancerData, + notifier: Option[RuntimeNotifier], + activeTick: Long, + expectedTriggerId: Long, + pauseTick: Option[Long] + ): Behavior[SchedulerMessage] = Behaviors.receivePartial { + case (ctx, CompletionMessage(triggerId, nextTrigger)) => + checkCompletion(activeTick, expectedTriggerId, nextTrigger, triggerId) + .map(endWithFailure(ctx, data.simulation, notifier, activeTick, _)) + .getOrElse { + val nextTriggerId = triggerId + 1L + + (nextTrigger, pauseTick) match { + case (Some(nextTrig), _) if nextTrig.trigger.tick > data.endTick => + // next tick is after endTick, finish simulation + endSuccessfully(data, notifier) + + case (Some(nextTrig), Some(pauseTick)) + if nextTrig.trigger.tick > pauseTick => + // next tick is after pause tick, pause sim + val updatedNotifier = notifier.map { + _.completing( + math.min(nextTrig.trigger.tick - 1, pauseTick) + ) + .pausing(pauseTick) + } + + // pause, inactivate + inactive( + data, + updatedNotifier, + pauseTick + 1, + nextTrig.trigger.tick, + nextTriggerId + ) + + case (Some(nextTrig), _) => + // next tick is ok, continue + val updatedNotifier = notifier.map { notifier => + val notifierCompleted = + notifier.completing(nextTrig.trigger.tick - 1) + + if (activeTick == INIT_SIM_TICK) + notifierCompleted.starting( + nextTrig.trigger.tick, + pauseTick, + data.endTick + ) + else + notifierCompleted + } + + // activate next + data.schedulee ! TriggerWithIdMessage( + nextTrig.trigger, + nextTriggerId + ) + active( + data, + updatedNotifier, + nextTrig.trigger.tick, + nextTriggerId, + pauseTick + ) + + case (None, _) => + // there is no next tick, finish + ctx.log.info("No next tick supplied, stopping simulation.") + endSuccessfully(data, notifier) + + } + } + + case (ctx, Stop(errorMsg: String)) => + endWithFailure(ctx, data.simulation, notifier, activeTick, errorMsg) + + } + + private def endSuccessfully( + data: TimeAdvancerData, + notifier: Option[RuntimeNotifier] + ): Behavior[SchedulerMessage] = { + data.simulation ! SimulationSuccessfulMessage + + notifier.foreach { + // we do not want a check window message for the endTick + _.completing(data.endTick - 1) + .finishing(data.endTick) + } + + // we do not stop here, but wait until we are terminated + Behaviors.empty + } + + private def endWithFailure( + ctx: ActorContext[SchedulerMessage], + simulation: akka.actor.ActorRef, + notifier: Option[RuntimeNotifier], + tick: Long, + errorMsg: String + ): Behavior[SchedulerMessage] = { + simulation ! SimulationFailureMessage + notifier.foreach(_.error(tick, errorMsg)) + + stopOnError(ctx, errorMsg) + } + + private def checkCompletion( + activeTick: Long, + expectedTriggerId: Long, + nextTrigger: Option[ScheduleTriggerMessage], + triggerId: Long + ): Option[String] = + Option + .when(triggerId != expectedTriggerId) { + s"Received completion message with trigger id $triggerId, although $expectedTriggerId was expected." + } + .orElse { + nextTrigger.filter(_.trigger.tick <= activeTick).map { nextTrig => + s"The next trigger has tick ${nextTrig.trigger.tick}, although current active tick was $activeTick." + } + } + + /** This data container stores objects that are not supposed to change for a + * [[TimeAdvancer]] during simulation + * + * @param simulation + * The root actor of the simulation + * @param schedulee + * scheduler or other actor whose time advancement is controlled + * @param endTick + * the last tick of the simulation + */ + private final case class TimeAdvancerData( + simulation: akka.actor.ActorRef, + schedulee: ActorRef[SchedulerMessage], + endTick: Long + ) +} diff --git a/src/main/scala/edu/ie3/simona/service/ServiceStateData.scala b/src/main/scala/edu/ie3/simona/service/ServiceStateData.scala index 3a4b25353b..8efb4c9ff1 100644 --- a/src/main/scala/edu/ie3/simona/service/ServiceStateData.scala +++ b/src/main/scala/edu/ie3/simona/service/ServiceStateData.scala @@ -39,14 +39,14 @@ object ServiceStateData { object ServiceActivationBaseStateData { - /** Build an optional [[Seq]] of [[ScheduleTriggerMessage]] s based on the - * given optional next tick and the sender + /** Build an optional [[ScheduleTriggerMessage]] based on the given optional + * next tick and the sender */ - val tickToScheduleTriggerMessages - : (Option[Long], ActorRef) => Option[Seq[ScheduleTriggerMessage]] = + val tickToScheduleTriggerMessage + : (Option[Long], ActorRef) => Option[ScheduleTriggerMessage] = (maybeTick, sender) => maybeTick.map(tick => - Seq(ScheduleTriggerMessage(ActivityStartTrigger(tick), sender)) + ScheduleTriggerMessage(ActivityStartTrigger(tick), sender) ) } } diff --git a/src/main/scala/edu/ie3/simona/service/SimonaService.scala b/src/main/scala/edu/ie3/simona/service/SimonaService.scala index b98aafc33a..3721d09506 100644 --- a/src/main/scala/edu/ie3/simona/service/SimonaService.scala +++ b/src/main/scala/edu/ie3/simona/service/SimonaService.scala @@ -55,8 +55,7 @@ abstract class SimonaService[ InitializeServiceTrigger( initializeStateData: InitializeServiceStateData ), - triggerId, - _ + triggerId ) => // init might take some time and could go wrong if invalid initialize service data is received // execute complete and unstash only if init is carried out successfully @@ -119,7 +118,7 @@ abstract class SimonaService[ } // activity start trigger for this service - case TriggerWithIdMessage(ActivityStartTrigger(tick), triggerId, _) => + case TriggerWithIdMessage(ActivityStartTrigger(tick), triggerId) => /* The scheduler sends out an activity start trigger. Announce new data to all registered recipients. */ val (updatedStateData, maybeNewTriggers) = announceInformation(tick)(stateData) @@ -147,7 +146,7 @@ abstract class SimonaService[ * initialization data. This method should perform all heavyweight tasks * before the actor becomes ready. The return values are a) the state data of * the initialized service and b) optional triggers that should be send to - * the [[edu.ie3.simona.scheduler.SimScheduler]] together with the completion + * the [[edu.ie3.simona.scheduler.Scheduler]] together with the completion * message that is send in response to the trigger that is send to start the * initialization process * @@ -159,7 +158,7 @@ abstract class SimonaService[ */ def init( initServiceData: InitializeServiceStateData - ): Try[(S, Option[Seq[ScheduleTriggerMessage]])] + ): Try[(S, Option[ScheduleTriggerMessage])] /** Handle a request to register for information from this service * @@ -190,6 +189,6 @@ abstract class SimonaService[ */ protected def announceInformation(tick: Long)(implicit serviceStateData: S - ): (S, Option[Seq[ScheduleTriggerMessage]]) + ): (S, Option[ScheduleTriggerMessage]) } 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 f5f8b78aff..6bee6b7e71 100644 --- a/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala +++ b/src/main/scala/edu/ie3/simona/service/ev/ExtEvDataService.scala @@ -71,7 +71,7 @@ class ExtEvDataService(override val scheduler: ActorRef) ): Try[ ( ExtEvStateData, - Option[Seq[SchedulerMessage.ScheduleTriggerMessage]] + Option[SchedulerMessage.ScheduleTriggerMessage] ) ] = initServiceData match { @@ -179,7 +179,7 @@ class ExtEvDataService(override val scheduler: ActorRef) tick: Long )(implicit serviceStateData: ExtEvStateData): ( ExtEvStateData, - Option[Seq[SchedulerMessage.ScheduleTriggerMessage]] + Option[SchedulerMessage.ScheduleTriggerMessage] ) = { serviceStateData.extEvMessage.getOrElse( throw ServiceException( @@ -197,7 +197,7 @@ class ExtEvDataService(override val scheduler: ActorRef) private def requestFreeLots(tick: Long)(implicit serviceStateData: ExtEvStateData - ): (ExtEvStateData, Option[Seq[ScheduleTriggerMessage]]) = { + ): (ExtEvStateData, Option[ScheduleTriggerMessage]) = { serviceStateData.uuidToActorRef.foreach { case (_, evcsActor) => evcsActor ! EvFreeLotsRequest(tick) } @@ -225,7 +225,7 @@ class ExtEvDataService(override val scheduler: ActorRef) requestedDepartingEvs: java.util.Map[UUID, java.util.List[UUID]] )(implicit serviceStateData: ExtEvStateData - ): (ExtEvStateData, Option[Seq[ScheduleTriggerMessage]]) = { + ): (ExtEvStateData, Option[ScheduleTriggerMessage]) = { val departingEvResponses: Map[UUID, Option[Seq[EvModel]]] = requestedDepartingEvs.asScala.flatMap { case (evcs, departingEvs) => @@ -264,40 +264,37 @@ class ExtEvDataService(override val scheduler: ActorRef) allArrivingEvs: java.util.Map[UUID, java.util.List[EvModel]] )(implicit serviceStateData: ExtEvStateData - ): (ExtEvStateData, Option[Seq[ScheduleTriggerMessage]]) = { - - val scheduleTriggerMsgs = - allArrivingEvs.asScala.flatMap { case (evcs, arrivingEvs) => - serviceStateData.uuidToActorRef.get(evcs) match { - case Some(evcsActor) => - evcsActor ! ProvideEvDataMessage( - tick, - ArrivingEvsData(arrivingEvs.asScala.toSeq) - ) + ): (ExtEvStateData, Option[ScheduleTriggerMessage]) = { + + allArrivingEvs.asScala.foreach { case (evcs, arrivingEvs) => + serviceStateData.uuidToActorRef.get(evcs) match { + case Some(evcsActor) => + evcsActor ! ProvideEvDataMessage( + tick, + ArrivingEvsData(arrivingEvs.asScala.toSeq) + ) - // schedule activation of participant - Some( - ScheduleTriggerMessage( - ActivityStartTrigger(tick), - evcsActor - ) - ) + // schedule activation of participant + scheduler ! ScheduleTriggerMessage( + ActivityStartTrigger(tick), + evcsActor + ) - case None => - log.warning( - "A corresponding actor ref for UUID {} could not be found", - evcs - ) - None + case None => + log.warning( + "A corresponding actor ref for UUID {} could not be found", + evcs + ) + None - } } + } ( serviceStateData.copy( extEvMessage = None ), - Option.when(scheduleTriggerMsgs.nonEmpty)(scheduleTriggerMsgs.toSeq) + None ) } diff --git a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala index be36bb7412..932973f1b2 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceProxy.scala @@ -108,14 +108,13 @@ case class PrimaryServiceProxy( simulationStart ) ), - triggerId, - _ + triggerId ) => /* The proxy is asked to initialize itself. If that happened successfully, change the logic of receiving * messages */ prepareStateData(primaryConfig, simulationStart) match { case Success(stateData) => - sender() ! CompletionMessage(triggerId, newTriggers = None) + scheduler ! CompletionMessage(triggerId, newTrigger = None) context become onMessage(stateData) case Failure(exception) => log.error( 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 a4b7ee6078..f1413095ad 100644 --- a/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala +++ b/src/main/scala/edu/ie3/simona/service/primary/PrimaryServiceWorker.scala @@ -62,7 +62,7 @@ final case class PrimaryServiceWorker[V <: Value]( ): Try[ ( PrimaryServiceInitializedStateData[V], - Option[Seq[SchedulerMessage.ScheduleTriggerMessage]] + Option[SchedulerMessage.ScheduleTriggerMessage] ) ] = { (initServiceData match { @@ -151,7 +151,7 @@ final case class PrimaryServiceWorker[V <: Value]( source ) val triggerMessage = - ServiceActivationBaseStateData.tickToScheduleTriggerMessages( + ServiceActivationBaseStateData.tickToScheduleTriggerMessage( maybeNextTick, self ) @@ -204,7 +204,7 @@ final case class PrimaryServiceWorker[V <: Value]( tick: Long )(implicit serviceBaseStateData: PrimaryServiceInitializedStateData[V]): ( PrimaryServiceInitializedStateData[V], - Option[Seq[SchedulerMessage.ScheduleTriggerMessage]] + Option[SchedulerMessage.ScheduleTriggerMessage] ) = { /* Get the information to distribute */ val wallClockTime = tick.toDateTime(serviceBaseStateData.startDateTime) @@ -236,12 +236,12 @@ final case class PrimaryServiceWorker[V <: Value]( baseStateData: PrimaryServiceInitializedStateData[V] ): ( PrimaryServiceInitializedStateData[V], - Option[Seq[SchedulerMessage.ScheduleTriggerMessage]] + Option[SchedulerMessage.ScheduleTriggerMessage] ) = { val (maybeNextActivationTick, remainderActivationTicks) = baseStateData.activationTicks.pop val triggerMessages = - ServiceActivationBaseStateData.tickToScheduleTriggerMessages( + ServiceActivationBaseStateData.tickToScheduleTriggerMessage( maybeNextActivationTick, self ) @@ -272,7 +272,7 @@ final case class PrimaryServiceWorker[V <: Value]( serviceBaseStateData: PrimaryServiceInitializedStateData[V] ): ( PrimaryServiceInitializedStateData[V], - Option[Seq[SchedulerMessage.ScheduleTriggerMessage]] + Option[SchedulerMessage.ScheduleTriggerMessage] ) = value.toPrimaryData match { case Success(primaryData) => announcePrimaryData(tick, primaryData, serviceBaseStateData) @@ -304,12 +304,12 @@ final case class PrimaryServiceWorker[V <: Value]( serviceBaseStateData: PrimaryServiceInitializedStateData[V] ): ( PrimaryServiceInitializedStateData[V], - Option[Seq[SchedulerMessage.ScheduleTriggerMessage]] + Option[SchedulerMessage.ScheduleTriggerMessage] ) = { val (maybeNextTick, remainderActivationTicks) = serviceBaseStateData.activationTicks.pop val triggerMessages = ServiceActivationBaseStateData - .tickToScheduleTriggerMessages(maybeNextTick, self) + .tickToScheduleTriggerMessage(maybeNextTick, self) val updatedStateData = serviceBaseStateData.copy( maybeNextActivationTick = maybeNextTick, diff --git a/src/main/scala/edu/ie3/simona/service/weather/WeatherService.scala b/src/main/scala/edu/ie3/simona/service/weather/WeatherService.scala index 14d489801f..cbba8f4224 100644 --- a/src/main/scala/edu/ie3/simona/service/weather/WeatherService.scala +++ b/src/main/scala/edu/ie3/simona/service/weather/WeatherService.scala @@ -109,7 +109,7 @@ final case class WeatherService( * initialization data. This method should perform all heavyweight tasks * before the actor becomes ready. The return values are a) the state data of * the initialized service and b) optional triggers that should be send to - * the [[edu.ie3.simona.scheduler.SimScheduler]] together with the completion + * the [[edu.ie3.simona.scheduler.Scheduler]] together with the completion * message that is send in response to the trigger that is send to start the * initialization process * @@ -121,7 +121,7 @@ final case class WeatherService( */ override def init( initServiceData: InitializeServiceStateData - ): Try[(WeatherInitializedStateData, Option[Seq[ScheduleTriggerMessage]])] = + ): Try[(WeatherInitializedStateData, Option[ScheduleTriggerMessage])] = initServiceData match { case InitWeatherServiceStateData(sourceDefinition) => val weatherSource = @@ -146,7 +146,7 @@ final case class WeatherService( Success( weatherInitializedStateData, ServiceActivationBaseStateData - .tickToScheduleTriggerMessages(maybeNextTick, self) + .tickToScheduleTriggerMessage(maybeNextTick, self) ) case invalidData => @@ -290,7 +290,7 @@ final case class WeatherService( */ override protected def announceInformation(tick: Long)(implicit serviceStateData: WeatherInitializedStateData - ): (WeatherInitializedStateData, Option[Seq[ScheduleTriggerMessage]]) = { + ): (WeatherInitializedStateData, Option[ScheduleTriggerMessage]) = { /* Pop the next activation tick and update the state data */ val ( @@ -318,7 +318,7 @@ final case class WeatherService( ( updatedStateData, - ServiceActivationBaseStateData.tickToScheduleTriggerMessages( + ServiceActivationBaseStateData.tickToScheduleTriggerMessage( maybeNextTick, self ) diff --git a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala index aaa1812107..a527061833 100644 --- a/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala +++ b/src/main/scala/edu/ie3/simona/sim/SimonaSim.scala @@ -7,6 +7,7 @@ package edu.ie3.simona.sim import akka.actor.SupervisorStrategy.Stop +import akka.actor.typed.scaladsl.adapter.TypedActorRefOps import akka.actor.{ Actor, ActorRef, @@ -19,8 +20,9 @@ import akka.actor.{ import com.typesafe.scalalogging.LazyLogging import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData +import edu.ie3.simona.event.RuntimeEvent import edu.ie3.simona.ontology.messages.SchedulerMessage._ -import edu.ie3.simona.ontology.messages.StopMessage +import edu.ie3.simona.ontology.messages.{SchedulerMessage, StopMessage} import edu.ie3.simona.ontology.trigger.Trigger.{ InitializeGridAgentTrigger, InitializeServiceTrigger @@ -72,11 +74,13 @@ class SimonaSim(simonaSetup: SimonaSetup) simonaSetup.systemParticipantsListener(context) // runtime event listener - val runtimeEventListener: Seq[ActorRef] = + val runtimeEventListener: akka.actor.typed.ActorRef[RuntimeEvent] = simonaSetup.runtimeEventListener(context) /* start scheduler */ - val scheduler: ActorRef = simonaSetup.scheduler(context, runtimeEventListener) + val timeAdvancer: akka.actor.typed.ActorRef[SchedulerMessage] = + simonaSetup.timeAdvancer(context, self, runtimeEventListener) + val scheduler: ActorRef = simonaSetup.scheduler(context, timeAdvancer) /* start services */ // primary service proxy @@ -126,7 +130,8 @@ class SimonaSim(simonaSetup: SimonaSetup) /* watch all actors */ systemParticipantsListener.foreach(context.watch) - runtimeEventListener.foreach(context.watch) + context.watch(runtimeEventListener.toClassic) + context.watch(timeAdvancer.toClassic) context.watch(scheduler) context.watch(primaryServiceProxy) context.watch(weatherService) @@ -146,11 +151,11 @@ class SimonaSim(simonaSetup: SimonaSetup) } // tell scheduler to process all init messages - scheduler ! InitSimMessage + timeAdvancer ! StartScheduleMessage() context become simonaSimReceive(data.copy(initSimSender = sender())) case StartScheduleMessage(pauseScheduleAtTick) => - scheduler ! StartScheduleMessage(pauseScheduleAtTick) + timeAdvancer ! StartScheduleMessage(pauseScheduleAtTick) case msg @ (SimulationSuccessfulMessage | SimulationFailureMessage) => val simulationSuccessful = msg match { @@ -277,8 +282,8 @@ class SimonaSim(simonaSetup: SimonaSetup) _ ! StopMessage(simulationSuccessful) ) - runtimeEventListener.foreach(context.unwatch) - runtimeEventListener.foreach(context.stop) + context.unwatch(runtimeEventListener.toClassic) + context.stop(runtimeEventListener.toClassic) logger.debug("Stopping all listeners requested.") } 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 e9cbd9f258..a065dc65fa 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -11,6 +11,8 @@ import edu.ie3.datamodel.graph.SubGridGate import edu.ie3.datamodel.models.input.connector.Transformer3WInput import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData +import edu.ie3.simona.event.RuntimeEvent +import edu.ie3.simona.ontology.messages.SchedulerMessage import edu.ie3.simona.service.primary.PrimaryServiceProxy.InitPrimaryServiceProxyStateData import edu.ie3.simona.service.weather.WeatherService.InitWeatherServiceStateData @@ -36,14 +38,16 @@ trait SimonaSetup { */ val buildActorSystem: () => ActorSystem - /** Creates a sequence of runtime event listeners + /** Creates the runtime event listener * * @param context * Actor context to use * @return - * A sequence of actor references to runtime event listeners + * An actor reference to the runtime event listener */ - def runtimeEventListener(context: ActorContext): Seq[ActorRef] + def runtimeEventListener( + context: ActorContext + ): akka.actor.typed.ActorRef[RuntimeEvent] /** Creates a sequence of system participant event listeners * @@ -102,16 +106,35 @@ trait SimonaSetup { scheduler: ActorRef ): ExtSimSetupData + /** Creates the time advancer + * + * @param context + * Actor context to use + * @param simulation + * The simulation root actor ([[edu.ie3.simona.sim.SimonaSim]]) + * @param runtimeEventListener + * Runtime event listener + * @return + * An actor reference to the time advancer + */ + def timeAdvancer( + context: ActorContext, + simulation: ActorRef, + runtimeEventListener: akka.actor.typed.ActorRef[RuntimeEvent] + ): akka.actor.typed.ActorRef[SchedulerMessage] + /** Creates a scheduler service * * @param context * Actor context to use + * @param timeAdvancer + * The time advancer, sitting at the root of the scheduler hierarchy * @return * An actor reference to the scheduler */ def scheduler( context: ActorContext, - runtimeEventListener: Seq[ActorRef] + timeAdvancer: akka.actor.typed.ActorRef[SchedulerMessage] ): ActorRef /** Creates all the needed grid agents 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 a8901650a2..70973e5e76 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -27,11 +27,12 @@ import edu.ie3.simona.event.RuntimeEvent import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} import edu.ie3.simona.exceptions.agent.GridAgentInitializationException import edu.ie3.simona.io.grid.GridProvider +import edu.ie3.simona.ontology.messages.SchedulerMessage import edu.ie3.simona.ontology.trigger.Trigger.{ InitializeExtSimAdapterTrigger, InitializeServiceTrigger } -import edu.ie3.simona.scheduler.SimScheduler +import edu.ie3.simona.scheduler.{Scheduler, TimeAdvancer} import edu.ie3.simona.service.ev.ExtEvDataService import edu.ie3.simona.service.ev.ExtEvDataService.InitExtEvData import edu.ie3.simona.service.primary.PrimaryServiceProxy @@ -40,6 +41,7 @@ import edu.ie3.simona.service.weather.WeatherService import edu.ie3.simona.service.weather.WeatherService.InitWeatherServiceStateData import edu.ie3.simona.util.ResultFileHierarchy import edu.ie3.util.TimeUtil +import edu.ie3.simona.util.TickUtil.RichZonedDateTime import java.util.concurrent.LinkedBlockingQueue import scala.jdk.CollectionConverters._ @@ -213,32 +215,55 @@ class SimonaStandaloneSetup( ExtSimSetupData(extSimAdapters, extDataServices.flatten) } - override def scheduler( + override def timeAdvancer( context: ActorContext, - runtimeEventListener: Seq[ActorRef] - ): ActorRef = context.simonaActorOf( - SimScheduler.props( - simonaConfig.simona.time, - runtimeEventListener, - simonaConfig.simona.time.stopOnFailedPowerFlow + simulation: ActorRef, + runtimeEventListener: akka.actor.typed.ActorRef[RuntimeEvent] + ): akka.actor.typed.ActorRef[SchedulerMessage] = { + val startDateTime = TimeUtil.withDefaults.toZonedDateTime( + simonaConfig.simona.time.startDateTime + ) + val endDateTime = TimeUtil.withDefaults.toZonedDateTime( + simonaConfig.simona.time.endDateTime ) - ) - override def runtimeEventListener(context: ActorContext): Seq[ActorRef] = { - Seq( - context - .spawn( - RuntimeEventListener( - simonaConfig.simona.runtime.listener, - runtimeEventQueue, - startDateTimeString = simonaConfig.simona.time.startDateTime - ), - RuntimeEventListener.getClass.getSimpleName - ) - .toClassic + context.spawn( + TimeAdvancer( + simulation, + Some(runtimeEventListener), + simonaConfig.simona.time.schedulerReadyCheckWindow, + endDateTime.toTick(startDateTime) + ), + TimeAdvancer.getClass.getSimpleName ) } + override def scheduler( + context: ActorContext, + timeAdvancer: akka.actor.typed.ActorRef[SchedulerMessage] + ): ActorRef = + context + .spawn( + Scheduler( + timeAdvancer + ), + Scheduler.getClass.getSimpleName + ) + .toClassic + + override def runtimeEventListener( + context: ActorContext + ): akka.actor.typed.ActorRef[RuntimeEvent] = + context + .spawn( + RuntimeEventListener( + simonaConfig.simona.runtime.listener, + runtimeEventQueue, + startDateTimeString = simonaConfig.simona.time.startDateTime + ), + RuntimeEventListener.getClass.getSimpleName + ) + override def systemParticipantsListener( context: ActorContext ): Seq[ActorRef] = { diff --git a/src/main/scala/edu/ie3/util/scala/collection/mutable/CountingMap.scala b/src/main/scala/edu/ie3/util/scala/collection/mutable/CountingMap.scala deleted file mode 100644 index 4c9b69127f..0000000000 --- a/src/main/scala/edu/ie3/util/scala/collection/mutable/CountingMap.scala +++ /dev/null @@ -1,112 +0,0 @@ -/* - * © 2021. 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.mutable - -import scala.collection.mutable - -/** A map that holds a counter of type [[Long]] for every key. Furthermore, - * during usage a sorted set is populated with all keys used, so that the - * minimal key can be retrieved easily. - * - * @param map - * The counting map - * @param sortedKeySet - * The sorted key set - * @tparam K - * The type of keys - */ -final case class CountingMap[K] private ( - private val map: mutable.HashMap[K, Long], - private val sortedKeySet: mutable.SortedSet[K] -) { - - /** Tests whether this map contains a binding for a key. - * @param key - * The key - * @return - * True if there is a binding for key in this map, false otherwise. - */ - def contains(key: K): Boolean = map.contains(key) - - /** Optionally returns the value associated with a key. - * - * @param key - * The key value - * @return - * An [[Option]] containing the value associated with key in this map, or - * None if none exists. - */ - def get(key: K): Option[Long] = map.get(key) - - /** Returns the the minimal key of all. - * - * @return - * The minimal key of all - */ - def minKeyOption: Option[K] = sortedKeySet.headOption - - /** Increase the counter for given key by 1. - * @param key - * The key - */ - def add(key: K): Unit = { - map.get(key) match { - case Some(count) => - // a count for given key already exists, increase by 1 - map.update(key, count + 1L) - case None => - // no count for given key exists yet, create one with count 1 - map.update(key, 1L) - sortedKeySet.add(key) - } - } - - /** Decrease the counter for given key by 1. If the counter hits 0, the - * key-value mapping is removed. - * @param key - * The key - */ - def subtract(key: K): Unit = - map.get(key) match { - case Some(count) => - if (count <= 1L) { - // if the counter would hit 0 at this point, remove the key and value altogether - map.remove(key) - sortedKeySet.remove(key) - } else - map.update(key, count - 1L) - - case None => - // in case that the key does not exist, do nothing - } - - /** Tests whether there is no value for any key in the queue. - * @return - * True if the queue is empty - */ - def isEmpty: Boolean = map.isEmpty - -} - -object CountingMap { - def empty[K](implicit - ev: K => Ordered[K] - ): CountingMap[K] = - CountingMap( - mutable.HashMap.empty[K, Long], - mutable.SortedSet.empty[K] - ) - - def from[K](source: Iterable[(K, Long)])(implicit - ev: K => Ordered[K] - ): CountingMap[K] = { - CountingMap( - mutable.HashMap.from(source), - mutable.SortedSet.from(source.map { case (key, _) => key }) - ) - } -} diff --git a/src/main/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiBiSet.scala b/src/main/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiBiSet.scala new file mode 100644 index 0000000000..e224b43314 --- /dev/null +++ b/src/main/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiBiSet.scala @@ -0,0 +1,196 @@ +/* + * © 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.mutable + +import scala.collection.{SortedSet, mutable} + +/** 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. Values are stored in a [[mutable.Set]] (with + * adding and removing items in about constant time). + * + * @param queue + * Queue that holds keys in order and thus provides a way to quickly retrieve + * the elements for the first key(s). + * @param table + * HashMap that provides direct access to each list given the key that it was + * added with. This is useful for quickly adding values to new and existing + * keys, running in nearly O(1). + * @param back + * HashMap 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 PriorityMultiBiSet[K, V] private ( + private val queue: mutable.SortedSet[K], + private val table: mutable.HashMap[K, mutable.Set[V]], + private val back: mutable.HashMap[V, K] +) { + + /** Get the first key of the queue, if the queue is not empty. Runs in O(1). + * @return + * The first key + */ + def headKeyOption: Option[K] = + queue.headOption + + /** Get all keys in a sorted set. + * @return + * The sorted keys + */ + def keySet: SortedSet[K] = queue + + /** Get the key that given value is mapped for, if it exists. + * @param value + * Value to retrieve the key for + * @return + * The key + */ + def getKeyOf(value: V): Option[K] = + back.get(value) + + /** Set given value to given key + * @param key + * The key to add the value for + * @param value + * The value to add + */ + def set(key: K, value: V): Unit = { + // remove old mapping for value in table and queue + back.get(value).foreach(remove(_, value)) + // add new mapping + back += (value -> key) + + table.get(key) match { + case Some(set) => + // key already exists in both structures + set += value + case None => + // key doesn't exist yet, add to both structures + queue += key + table += (key -> mutable.Set(value)) + } + } + + /** Removes the given value for given key, if it exists. + * @param key + * The key for which the value should be removed + * @param value + * The value + * @return + * Whether the key-value pair existed + */ + def remove(key: K, value: V): Boolean = { + back.remove(value) + + table.get(key).exists { set => + val existed = set.remove(value) + + if (set.isEmpty) { + table -= key + queue -= key + } + + existed + } + } + + /** Retrieves the first element in the list of the first 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 + */ + def getAndRemoveSet(key: K): Set[V] = { + table + .get(key) + .map { set => + table -= key + queue -= key + + // return an immutable set + val immutableSet = Set.from(set) + + // also remove from reverse map + immutableSet.foreach(back.remove) + + immutableSet + } + .getOrElse(Set.empty) + } + + /** 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 +} +object PriorityMultiBiSet { + + /** Creates and returns an empty PriorityMultiQueue 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 PriorityMultiQueue + */ + def empty[K: Ordering, V]: PriorityMultiBiSet[K, V] = + PriorityMultiBiSet( + mutable.SortedSet.empty[K], + mutable.HashMap[K, mutable.Set[V]](), + mutable.HashMap[V, K]() + ) + + /** Creates and returns an empty PriorityMultiQueue for given types. The + * initialKeyCapacity and loadFactor are used in the creation of the + * HashMaps. + * + * @param initialKeyCapacity + * The initial capacity of both HashMaps. The capacity increments (i.e. the + * map is recreated with a higher capacity) once the amount denoted by + * loadFactor is hit. + * @param loadFactor + * The loadFactor of the HashMaps. If the size of the map reaches capacity + * * loadFactor, the underlying table is replaced with a larger one. + * @tparam K + * Type of the key, which needs to be sortable by means of [[Ordering]] + * @tparam V + * Type of the value + * @return + * An empty PriorityMultiQueue + */ + def empty[K: Ordering, V]( + initialKeyCapacity: Int, + loadFactor: Double = mutable.HashMap.defaultLoadFactor + ): PriorityMultiBiSet[K, V] = + PriorityMultiBiSet( + mutable.SortedSet.empty[K], + new mutable.HashMap[K, mutable.Set[V]]( + initialKeyCapacity, + loadFactor + ), + new mutable.HashMap[V, K]( + initialKeyCapacity, + loadFactor + ) + ) +} diff --git a/src/main/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiQueue.scala b/src/main/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiQueue.scala deleted file mode 100644 index acd737ad5a..0000000000 --- a/src/main/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiQueue.scala +++ /dev/null @@ -1,169 +0,0 @@ -/* - * © 2021. 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.mutable - -import scala.collection.{SortedSet, mutable} - -/** Queue that is specialized at holding many values of type [[V]] for the same - * key of type [[K]]. Mutable structure. Values are stored in a - * [[mutable.ListBuffer]], which corresponds to a linked list (with adding and - * removing items to/from its head/tail in constant time). - * @param queue - * Queue that holds keys in order and thus provides a way to quickly retrieve - * the elements for the first key(s). - * @param table - * HashMap that provides direct access to each list given the key that it was - * added with. This is useful for quickly adding values to new and existing - * keys, running in nearly O(1). - * @tparam K - * Type of the key, which needs to be sortable by means of [[Ordering]] - * @tparam V - * Type of the value - */ -final case class PriorityMultiQueue[K: Ordering, V] private ( - private val queue: mutable.SortedMap[K, mutable.ListBuffer[V]], - private val table: mutable.HashMap[K, mutable.ListBuffer[V]] -) { - - /** Get the first key of the queue, if the queue is not empty. Runs in O(1). - * @return - * The first key - */ - def headKeyOption: Option[K] = - queue.headOption.map { case (key, _) => key } - - /** Get all keys in a sorted set. - * @return - * The sorted keys - */ - def keySet: SortedSet[K] = queue.keySet - - /** Add given value to the end of the list that belongs to given key - * @param key - * The key to add the value for - * @param value - * The value to add - */ - def add(key: K, value: V): Unit = { - table.get(key) match { - case Some(list) => - // list already exists in both structures - list.addOne(value) - case None => - // list doesn't exist yet, add to both structures - val list = mutable.ListBuffer(value) - queue.addOne(key, list) - table.addOne(key, list) - } - } - - /** Retrieves the first element in the list of the first 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 - */ - def poll(): Option[V] = { - queue.headOption.map { case (key, list) => - if (list.size <= 1) { - // if this was the last value for this key, remove the list - queue.remove(key) - table.remove(key) - } - - list.remove(0) - } - } - - /** Retrieves all elements for keys that are smaller or equal to given key. - * The returned elements are also removed from the queue here. - * - * @return - * All elements for keys up to and including the given key. An empty - * Iterable is returned if this queue is empty or all keys are greater than - * the given key. - */ - def pollTo(key: K): Iterable[V] = { - // a copy has to be made here because the resulting Map of - // rangeTo is linked to the original map. This means that - // the map with the values to be returned would be depleted - // with the subtractions below - val polledValues = mutable.SortedMap.from(queue.rangeTo(key)) - - val keys = polledValues.keySet - queue --= keys - table --= keys - - polledValues.values.flatten - } - - /** Get all values for all keys as an iterable. - * @return - * All values - */ - def allValues: Iterable[V] = queue.values.flatten - - /** 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 -} - -object PriorityMultiQueue { - - /** Creates and returns an empty PriorityMultiQueue 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 PriorityMultiQueue - */ - def empty[K: Ordering, V]: PriorityMultiQueue[K, V] = - PriorityMultiQueue( - mutable.SortedMap[K, mutable.ListBuffer[V]](), - mutable.HashMap[K, mutable.ListBuffer[V]]() - ) - - /** Creates and returns an empty PriorityMultiQueue for given types. The - * initialKeyCapacity and loadFactor are used in the creation of the HashMap. - * @param initialKeyCapacity - * The initial capacity of of the HashMap for keys. The capacity increments - * (i.e. the map is recreated with a higher capacity) once the amount - * denoted by loadFactor is hit. - * @param loadFactor - * The loadFactor of the HashMap. If the size of the map reaches capacity * - * loadFactor, the underlying table is replaced with a larger one. - * @tparam K - * Type of the key, which needs to be sortable by means of [[Ordering]] - * @tparam V - * Type of the value - * @return - * An empty PriorityMultiQueue - */ - def empty[K: Ordering, V]( - initialKeyCapacity: Int, - loadFactor: Double = mutable.HashMap.defaultLoadFactor - ): PriorityMultiQueue[K, V] = - PriorityMultiQueue( - mutable.SortedMap[K, mutable.ListBuffer[V]](), - new mutable.HashMap[K, mutable.ListBuffer[V]]( - initialKeyCapacity, - loadFactor - ) - ) -} diff --git a/src/test/groovy/edu/ie3/simona/model/participant/EvcsModelTest.groovy b/src/test/groovy/edu/ie3/simona/model/participant/EvcsModelTest.groovy index c83302e9d9..eca95f9342 100644 --- a/src/test/groovy/edu/ie3/simona/model/participant/EvcsModelTest.groovy +++ b/src/test/groovy/edu/ie3/simona/model/participant/EvcsModelTest.groovy @@ -17,10 +17,8 @@ import edu.ie3.util.scala.OperationInterval import edu.ie3.util.scala.quantities.Sq import spock.lang.Shared import spock.lang.Specification -import squants.energy.KilowattHours$ -import squants.energy.Kilowatts$ -import squants.energy.Power -import squants.time.Minutes$ +import squants.energy.* +import squants.time.* import tech.units.indriya.quantity.Quantities import scala.collection.immutable.Set diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala index b15cbcabd6..e7e7134fbe 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala @@ -129,8 +129,7 @@ class DBFSAlgorithmCenGridSpec centerGridAgent, TriggerWithIdMessage( InitializeGridAgentTrigger(gridAgentInitData), - triggerId, - centerGridAgent + triggerId ) ) @@ -138,11 +137,9 @@ class DBFSAlgorithmCenGridSpec CompletionMessage( 0, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(3600), - centerGridAgent - ) + ScheduleTriggerMessage( + ActivityStartTrigger(3600), + centerGridAgent ) ) ) @@ -158,8 +155,7 @@ class DBFSAlgorithmCenGridSpec centerGridAgent, TriggerWithIdMessage( ActivityStartTrigger(3600), - activityStartTriggerId, - centerGridAgent + activityStartTriggerId ) ) @@ -167,11 +163,9 @@ class DBFSAlgorithmCenGridSpec CompletionMessage( 1, Some( - Seq( - ScheduleTriggerMessage( - StartGridSimulationTrigger(3600), - centerGridAgent - ) + ScheduleTriggerMessage( + StartGridSimulationTrigger(3600), + centerGridAgent ) ) ) @@ -188,8 +182,7 @@ class DBFSAlgorithmCenGridSpec centerGridAgent, TriggerWithIdMessage( StartGridSimulationTrigger(3600), - startGridSimulationTriggerId, - centerGridAgent + startGridSimulationTriggerId ) ) @@ -505,11 +498,9 @@ class DBFSAlgorithmCenGridSpec CompletionMessage( 2, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(7200), - centerGridAgent - ) + ScheduleTriggerMessage( + ActivityStartTrigger(7200), + centerGridAgent ) ) ) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala index 6903de77a7..c11d135195 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala @@ -115,8 +115,7 @@ class DBFSAlgorithmFailedPowerFlowSpec centerGridAgent, TriggerWithIdMessage( InitializeGridAgentTrigger(gridAgentInitData), - triggerId, - centerGridAgent + triggerId ) ) @@ -124,11 +123,9 @@ class DBFSAlgorithmFailedPowerFlowSpec CompletionMessage( 0, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(3600), - centerGridAgent - ) + ScheduleTriggerMessage( + ActivityStartTrigger(3600), + centerGridAgent ) ) ) @@ -145,8 +142,7 @@ class DBFSAlgorithmFailedPowerFlowSpec centerGridAgent, TriggerWithIdMessage( ActivityStartTrigger(3600), - activityStartTriggerId, - centerGridAgent + activityStartTriggerId ) ) @@ -155,11 +151,9 @@ class DBFSAlgorithmFailedPowerFlowSpec CompletionMessage( 1, Some( - Seq( - ScheduleTriggerMessage( - StartGridSimulationTrigger(3600), - centerGridAgent - ) + ScheduleTriggerMessage( + StartGridSimulationTrigger(3600), + centerGridAgent ) ) ) @@ -176,8 +170,7 @@ class DBFSAlgorithmFailedPowerFlowSpec centerGridAgent, TriggerWithIdMessage( StartGridSimulationTrigger(3600), - startGridSimulationTriggerId, - centerGridAgent + startGridSimulationTriggerId ) ) @@ -262,11 +255,9 @@ class DBFSAlgorithmFailedPowerFlowSpec CompletionMessage( 2, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(7200), - centerGridAgent - ) + ScheduleTriggerMessage( + ActivityStartTrigger(7200), + centerGridAgent ) ) ) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala index 69f6973607..48fe0a5872 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala @@ -101,8 +101,7 @@ class DBFSAlgorithmParticipantSpec gridAgentWithParticipants, TriggerWithIdMessage( InitializeGridAgentTrigger(gridAgentInitData), - triggerId, - gridAgentWithParticipants + triggerId ) ) @@ -122,11 +121,9 @@ class DBFSAlgorithmParticipantSpec CompletionMessage( triggerId, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(3600L), - gridAgentWithParticipants - ) + ScheduleTriggerMessage( + ActivityStartTrigger(3600L), + gridAgentWithParticipants ) ) ) @@ -136,8 +133,7 @@ class DBFSAlgorithmParticipantSpec loadAgent, TriggerWithIdMessage( initializeTrigger, - loadAgentTriggerId, - loadAgent + loadAgentTriggerId ) ) @@ -151,7 +147,7 @@ class DBFSAlgorithmParticipantSpec CompletionMessage( loadAgentTriggerId, Some( - Seq(ScheduleTriggerMessage(ActivityStartTrigger(0L), loadAgent)) + ScheduleTriggerMessage(ActivityStartTrigger(0L), loadAgent) ) ) ) @@ -161,8 +157,7 @@ class DBFSAlgorithmParticipantSpec loadAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - 2, - loadAgent + 2 ) ) // the load agent should send a CompletionMessage @@ -179,8 +174,7 @@ class DBFSAlgorithmParticipantSpec gridAgentWithParticipants, TriggerWithIdMessage( ActivityStartTrigger(3600L), - activityStartTriggerId, - gridAgentWithParticipants + activityStartTriggerId ) ) @@ -189,11 +183,9 @@ class DBFSAlgorithmParticipantSpec CompletionMessage( 3, Some( - Seq( - ScheduleTriggerMessage( - StartGridSimulationTrigger(3600L), - gridAgentWithParticipants - ) + ScheduleTriggerMessage( + StartGridSimulationTrigger(3600L), + gridAgentWithParticipants ) ) ) @@ -212,8 +204,7 @@ class DBFSAlgorithmParticipantSpec gridAgentWithParticipants, TriggerWithIdMessage( StartGridSimulationTrigger(3600L), - startGridSimulationTriggerId, - gridAgentWithParticipants + startGridSimulationTriggerId ) ) @@ -309,11 +300,9 @@ class DBFSAlgorithmParticipantSpec CompletionMessage( 4, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(7200L), - gridAgentWithParticipants - ) + ScheduleTriggerMessage( + ActivityStartTrigger(7200L), + gridAgentWithParticipants ) ) ) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala index e7bd0970e4..54537765b0 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala @@ -107,8 +107,7 @@ class DBFSAlgorithmSupGridSpec superiorGridAgentFSM, TriggerWithIdMessage( InitializeGridAgentTrigger(gridAgentInitData), - triggerId, - superiorGridAgentFSM + triggerId ) ) @@ -116,11 +115,9 @@ class DBFSAlgorithmSupGridSpec CompletionMessage( 0, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(3600), - superiorGridAgentFSM - ) + ScheduleTriggerMessage( + ActivityStartTrigger(3600), + superiorGridAgentFSM ) ) ) @@ -136,8 +133,7 @@ class DBFSAlgorithmSupGridSpec superiorGridAgentFSM, TriggerWithIdMessage( ActivityStartTrigger(3600), - activityStartTriggerId, - superiorGridAgentFSM + activityStartTriggerId ) ) @@ -146,11 +142,9 @@ class DBFSAlgorithmSupGridSpec CompletionMessage( 1, Some( - Seq( - ScheduleTriggerMessage( - StartGridSimulationTrigger(3600), - superiorGridAgentFSM - ) + ScheduleTriggerMessage( + StartGridSimulationTrigger(3600), + superiorGridAgentFSM ) ) ) @@ -172,8 +166,7 @@ class DBFSAlgorithmSupGridSpec superiorGridAgentFSM, TriggerWithIdMessage( StartGridSimulationTrigger(3600), - startGridSimulationTriggerId, - superiorGridAgentFSM + startGridSimulationTriggerId ) ) @@ -212,11 +205,9 @@ class DBFSAlgorithmSupGridSpec case CompletionMessage( 2, Some( - Seq( - ScheduleTriggerMessage( - StartGridSimulationTrigger(3600), - _ - ) + ScheduleTriggerMessage( + StartGridSimulationTrigger(3600), + _ ) ) ) => @@ -224,9 +215,7 @@ class DBFSAlgorithmSupGridSpec case CompletionMessage( 3, Some( - Seq( - ScheduleTriggerMessage(ActivityStartTrigger(7200), _) - ) + ScheduleTriggerMessage(ActivityStartTrigger(7200), _) ) ) => // agent should be in Idle again and listener should contain power flow result data @@ -296,8 +285,7 @@ class DBFSAlgorithmSupGridSpec superiorGridAgentFSM, TriggerWithIdMessage( ActivityStartTrigger(3600), - activityStartTriggerId, - superiorGridAgentFSM + activityStartTriggerId ) ) @@ -306,11 +294,9 @@ class DBFSAlgorithmSupGridSpec CompletionMessage( 1, Some( - Seq( - ScheduleTriggerMessage( - StartGridSimulationTrigger(3600), - superiorGridAgentFSM - ) + ScheduleTriggerMessage( + StartGridSimulationTrigger(3600), + superiorGridAgentFSM ) ) ) @@ -328,8 +314,7 @@ class DBFSAlgorithmSupGridSpec superiorGridAgentFSM, TriggerWithIdMessage( StartGridSimulationTrigger(3600), - startGridSimulationTriggerId, - superiorGridAgentFSM + startGridSimulationTriggerId ) ) @@ -369,11 +354,9 @@ class DBFSAlgorithmSupGridSpec case CompletionMessage( _, Some( - Seq( - ScheduleTriggerMessage( - StartGridSimulationTrigger(3600), - _ - ) + ScheduleTriggerMessage( + StartGridSimulationTrigger(3600), + _ ) ) ) => @@ -382,9 +365,7 @@ class DBFSAlgorithmSupGridSpec case CompletionMessage( _, Some( - Seq( - ScheduleTriggerMessage(ActivityStartTrigger(7200), _) - ) + ScheduleTriggerMessage(ActivityStartTrigger(7200), _) ) ) => // after doing cleanup stuff, our agent should go back to idle again and listener should contain power flow result data 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 f8063b1ee5..d439b13c56 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/EvcsAgentModelCalculationSpec.scala @@ -150,8 +150,7 @@ class EvcsAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - triggerId, - evcsAgent + triggerId ) ) @@ -229,8 +228,7 @@ class EvcsAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - triggerId, - evcsAgent + triggerId ) ) @@ -376,8 +374,7 @@ class EvcsAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - triggerId, - evcsAgent + triggerId ) ) @@ -462,8 +459,7 @@ class EvcsAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - initialiseTriggerId, - evcsAgent + initialiseTriggerId ) ) @@ -518,8 +514,7 @@ class EvcsAgentModelCalculationSpec evcsAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - 1L, - evcsAgent + 1L ) ) @@ -595,8 +590,7 @@ class EvcsAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - initialiseTriggerId, - evcsAgent + initialiseTriggerId ) ) @@ -618,8 +612,7 @@ class EvcsAgentModelCalculationSpec evcsAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - 1L, - evcsAgent + 1L ) ) @@ -726,8 +719,7 @@ class EvcsAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - initialiseTriggerId, - evcsAgent + initialiseTriggerId ) ) @@ -789,8 +781,7 @@ class EvcsAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - initialiseTriggerId, - evcsAgent + initialiseTriggerId ) ) @@ -834,8 +825,7 @@ class EvcsAgentModelCalculationSpec evcsAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - 4L, - evcsAgent + 4L ) ) scheduler.expectMsg(CompletionMessage(4L)) @@ -890,8 +880,7 @@ class EvcsAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - initialiseTriggerId, - evcsAgent + initialiseTriggerId ) ) @@ -920,8 +909,7 @@ class EvcsAgentModelCalculationSpec evcsAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - 3L, - evcsAgent + 3L ) ) scheduler.expectMsg(CompletionMessage(3L)) @@ -957,8 +945,7 @@ class EvcsAgentModelCalculationSpec evcsAgent, TriggerWithIdMessage( ActivityStartTrigger(3600L), - 4L, - evcsAgent + 4L ) ) scheduler.expectMsg(CompletionMessage(4L)) @@ -995,8 +982,7 @@ class EvcsAgentModelCalculationSpec evcsAgent, TriggerWithIdMessage( ActivityStartTrigger(7200L), - 5L, - evcsAgent + 5L ) ) diff --git a/src/test/scala/edu/ie3/simona/agent/participant/FixedFeedInAgentModelCalculationSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/FixedFeedInAgentModelCalculationSpec.scala index 03e88ce9cb..06524c99be 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/FixedFeedInAgentModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/FixedFeedInAgentModelCalculationSpec.scala @@ -156,8 +156,7 @@ class FixedFeedInAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - triggerId, - fixedFeedAgent + triggerId ) ) @@ -198,9 +197,7 @@ class FixedFeedInAgentModelCalculationSpec CompletionMessage( triggerId, Some( - List( - ScheduleTriggerMessage(ActivityStartTrigger(0L), fixedFeedAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(0L), fixedFeedAgent) ) ) ) @@ -280,8 +277,7 @@ class FixedFeedInAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - triggerId, - fixedFeedAgent + triggerId ) ) @@ -360,8 +356,7 @@ class FixedFeedInAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - initialiseTriggerId, - fixedFeedAgent + initialiseTriggerId ) ) @@ -379,8 +374,7 @@ class FixedFeedInAgentModelCalculationSpec fixedFeedAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - activityStartTriggerId, - fixedFeedAgent + activityStartTriggerId ) ) @@ -447,8 +441,7 @@ class FixedFeedInAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - 0L, - fixedFeedAgent + 0L ) ) @@ -461,8 +454,7 @@ class FixedFeedInAgentModelCalculationSpec fixedFeedAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - 1, - fixedFeedAgent + 1 ) ) @@ -523,8 +515,7 @@ class FixedFeedInAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - 0, - fixedFeedAgent + 0 ) ) @@ -537,8 +528,7 @@ class FixedFeedInAgentModelCalculationSpec fixedFeedAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - 1, - fixedFeedAgent + 1 ) ) diff --git a/src/test/scala/edu/ie3/simona/agent/participant/LoadAgentFixedModelCalculationSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/LoadAgentFixedModelCalculationSpec.scala index f33465c1ca..2ab32b7789 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/LoadAgentFixedModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/LoadAgentFixedModelCalculationSpec.scala @@ -151,8 +151,7 @@ class LoadAgentFixedModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - triggerId, - loadAgent + triggerId ) ) @@ -193,9 +192,7 @@ class LoadAgentFixedModelCalculationSpec CompletionMessage( triggerId, Some( - List( - ScheduleTriggerMessage(ActivityStartTrigger(0L), loadAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(0L), loadAgent) ) ) ) @@ -275,8 +272,7 @@ class LoadAgentFixedModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - triggerId, - loadAgent + triggerId ) ) @@ -355,8 +351,7 @@ class LoadAgentFixedModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - initialiseTriggerId, - loadAgent + initialiseTriggerId ) ) @@ -374,8 +369,7 @@ class LoadAgentFixedModelCalculationSpec loadAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - activityStartTriggerId, - loadAgent + activityStartTriggerId ) ) @@ -442,8 +436,7 @@ class LoadAgentFixedModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - 0L, - loadAgent + 0L ) ) @@ -454,7 +447,7 @@ class LoadAgentFixedModelCalculationSpec /* Trigger the data generation in tick 0 */ scheduler.send( loadAgent, - TriggerWithIdMessage(ActivityStartTrigger(0L), 1L, loadAgent) + TriggerWithIdMessage(ActivityStartTrigger(0L), 1L) ) /* Appreciate the existence of two CompletionMessages */ @@ -514,8 +507,7 @@ class LoadAgentFixedModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - 0L, - loadAgent + 0L ) ) @@ -526,7 +518,7 @@ class LoadAgentFixedModelCalculationSpec /* Trigger the data generation in tick 0 */ scheduler.send( loadAgent, - TriggerWithIdMessage(ActivityStartTrigger(0L), 1, loadAgent) + TriggerWithIdMessage(ActivityStartTrigger(0L), 1) ) /* Appreciate the existence of two CompletionMessages */ diff --git a/src/test/scala/edu/ie3/simona/agent/participant/LoadAgentProfileModelCalculationSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/LoadAgentProfileModelCalculationSpec.scala index 33a669ef39..9f9b0ac9c1 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/LoadAgentProfileModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/LoadAgentProfileModelCalculationSpec.scala @@ -151,8 +151,7 @@ class LoadAgentProfileModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - triggerId, - loadAgent + triggerId ) ) @@ -192,9 +191,7 @@ class LoadAgentProfileModelCalculationSpec CompletionMessage( triggerId, Some( - List( - ScheduleTriggerMessage(ActivityStartTrigger(0L), loadAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(0L), loadAgent) ) ) ) @@ -275,8 +272,7 @@ class LoadAgentProfileModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - triggerId, - loadAgent + triggerId ) ) @@ -355,8 +351,7 @@ class LoadAgentProfileModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - initialiseTriggerId, - loadAgent + initialiseTriggerId ) ) @@ -374,8 +369,7 @@ class LoadAgentProfileModelCalculationSpec loadAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - activityStartTriggerId, - loadAgent + activityStartTriggerId ) ) @@ -384,7 +378,7 @@ class LoadAgentProfileModelCalculationSpec CompletionMessage( activityStartTriggerId, Some( - Seq(ScheduleTriggerMessage(ActivityStartTrigger(900L), loadAgent)) + ScheduleTriggerMessage(ActivityStartTrigger(900L), loadAgent) ) ) ) @@ -449,8 +443,7 @@ class LoadAgentProfileModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - 0L, - loadAgent + 0L ) ) @@ -461,22 +454,20 @@ class LoadAgentProfileModelCalculationSpec /* Trigger the data generation in tick 0, 900, 1800 */ scheduler.send( loadAgent, - TriggerWithIdMessage(ActivityStartTrigger(0L), 1L, loadAgent) + TriggerWithIdMessage(ActivityStartTrigger(0L), 1L) ) scheduler.send( loadAgent, TriggerWithIdMessage( ActivityStartTrigger(900L), - 2L, - loadAgent + 2L ) ) scheduler.send( loadAgent, TriggerWithIdMessage( ActivityStartTrigger(1800L), - 3L, - loadAgent + 3L ) ) diff --git a/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgent2ListenerSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgent2ListenerSpec.scala index d68f110f61..dcb2413323 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgent2ListenerSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgent2ListenerSpec.scala @@ -125,8 +125,7 @@ class ParticipantAgent2ListenerSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - 0, - mockAgent + 0 ) ) @@ -144,8 +143,7 @@ class ParticipantAgent2ListenerSpec mockAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - 1, - mockAgent + 1 ) ) @@ -209,8 +207,7 @@ class ParticipantAgent2ListenerSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - 0, - mockAgent + 0 ) ) @@ -228,8 +225,7 @@ class ParticipantAgent2ListenerSpec mockAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - 1, - mockAgent + 1 ) ) @@ -280,8 +276,7 @@ class ParticipantAgent2ListenerSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - 0, - mockAgent + 0 ) ) @@ -294,8 +289,7 @@ class ParticipantAgent2ListenerSpec mockAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - 1, - mockAgent + 1 ) ) @@ -372,8 +366,7 @@ class ParticipantAgent2ListenerSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - 0, - mockAgent + 0 ) ) @@ -386,8 +379,7 @@ class ParticipantAgent2ListenerSpec mockAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - 1, - mockAgent + 1 ) ) diff --git a/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentExternalSourceSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentExternalSourceSpec.scala index 8d44181de9..84bd173317 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentExternalSourceSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentExternalSourceSpec.scala @@ -177,8 +177,7 @@ class ParticipantAgentExternalSourceSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - triggerId, - mockAgent + triggerId ) ) @@ -218,24 +217,17 @@ class ParticipantAgentExternalSourceSpec RegistrationSuccessfulMessage(Some(4711L)) ) - scheduler.expectMsgClass(classOf[CompletionMessage]) match { - case CompletionMessage(actualTriggerId, newTriggers) => - actualTriggerId shouldBe triggerId - newTriggers match { - case Some(triggers) => - triggers.size shouldBe 1 - triggers.exists { - case ScheduleTriggerMessage( - ActivityStartTrigger(tick), - actorToBeScheduled - ) => - tick == 4711L && actorToBeScheduled == mockAgent - case unexpected => - fail(s"Received unexpected trigger message $unexpected") - } shouldBe true - case None => fail("Expected to get a trigger for tick 4711.") - } - } + scheduler.expectMsg( + CompletionMessage( + triggerId, + Some( + ScheduleTriggerMessage( + ActivityStartTrigger(4711L), + mockAgent + ) + ) + ) + ) /* ... as well as corresponding state and state data */ mockAgent.stateName shouldBe Idle @@ -286,8 +278,7 @@ class ParticipantAgentExternalSourceSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - triggerId, - mockAgent + triggerId ) ) @@ -378,8 +369,7 @@ class ParticipantAgentExternalSourceSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - initialiseTriggerId, - mockAgent + initialiseTriggerId ) ) @@ -445,8 +435,7 @@ class ParticipantAgentExternalSourceSpec mockAgent, TriggerWithIdMessage( ActivityStartTrigger(900L), - 1L, - scheduler.ref + 1L ) ) @@ -455,11 +444,9 @@ class ParticipantAgentExternalSourceSpec CompletionMessage( 1L, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(1800L), - mockAgent - ) + ScheduleTriggerMessage( + ActivityStartTrigger(1800L), + mockAgent ) ) ) @@ -520,8 +507,7 @@ class ParticipantAgentExternalSourceSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - initialiseTriggerId, - mockAgent + initialiseTriggerId ) ) @@ -541,8 +527,7 @@ class ParticipantAgentExternalSourceSpec mockAgent, TriggerWithIdMessage( ActivityStartTrigger(900L), - 1L, - scheduler.ref + 1L ) ) @@ -590,11 +575,9 @@ class ParticipantAgentExternalSourceSpec CompletionMessage( 1L, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(1800L), - mockAgent - ) + ScheduleTriggerMessage( + ActivityStartTrigger(1800L), + mockAgent ) ) ) @@ -655,8 +638,7 @@ class ParticipantAgentExternalSourceSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - 0L, - mockAgent + 0L ) ) @@ -698,8 +680,7 @@ class ParticipantAgentExternalSourceSpec mockAgent, TriggerWithIdMessage( ActivityStartTrigger(900L), - 1L, - scheduler.ref + 1L ) ) @@ -707,11 +688,9 @@ class ParticipantAgentExternalSourceSpec CompletionMessage( 1L, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(1800L), - mockAgent - ) + ScheduleTriggerMessage( + ActivityStartTrigger(1800L), + mockAgent ) ) ) @@ -803,8 +782,7 @@ class ParticipantAgentExternalSourceSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - 0, - mockAgent + 0 ) ) @@ -836,19 +814,16 @@ class ParticipantAgentExternalSourceSpec mockAgent, TriggerWithIdMessage( ActivityStartTrigger(900L), - 1L, - scheduler.ref + 1L ) ) scheduler.expectMsg( CompletionMessage( 1L, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(1800L), - mockAgent - ) + ScheduleTriggerMessage( + ActivityStartTrigger(1800L), + mockAgent ) ) ) @@ -870,19 +845,16 @@ class ParticipantAgentExternalSourceSpec mockAgent, TriggerWithIdMessage( ActivityStartTrigger(1800L), - 2L, - scheduler.ref + 2L ) ) scheduler.expectMsg( CompletionMessage( 2L, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(2700L), - mockAgent - ) + ScheduleTriggerMessage( + ActivityStartTrigger(2700L), + mockAgent ) ) ) @@ -904,8 +876,7 @@ class ParticipantAgentExternalSourceSpec mockAgent, TriggerWithIdMessage( ActivityStartTrigger(2700L), - 3L, - scheduler.ref + 3L ) ) scheduler.expectMsg(CompletionMessage(3L, None)) diff --git a/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentalsSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentalsSpec.scala index 207ddb5bc6..a929093a6d 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentalsSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/ParticipantAgentFundamentalsSpec.scala @@ -71,7 +71,7 @@ class ParticipantAgentFundamentalsSpec ) /* Get one instance of the mock for participant agent */ - val mockAgentTestRef: TestFSMRef[AgentState, ParticipantStateData[ + private val mockAgentTestRef: TestFSMRef[AgentState, ParticipantStateData[ ApparentPower ], ParticipantAgentMock] = TestFSMRef( @@ -81,7 +81,7 @@ class ParticipantAgentFundamentalsSpec ) val mockAgent: ParticipantAgentMock = mockAgentTestRef.underlyingActor - val powerValues: Map[Long, ApparentPower] = + private val powerValues: Map[Long, ApparentPower] = Map( 0L -> ApparentPower( Megawatts(1.0), @@ -118,7 +118,7 @@ class ParticipantAgentFundamentalsSpec ) /* Calculates the reactive power as the square of the active power */ - val activeToReactivePowerFuncOpt: Option[ + private val activeToReactivePowerFuncOpt: Option[ PartialFunction[squants.Power, ReactivePower] ] = Some( @@ -248,20 +248,11 @@ class ParticipantAgentFundamentalsSpec ) mockAgent.popNextActivationTrigger(baseStateData) match { - case (Some(activationSeq), actualBaseStateData) => - /* There is exactly one activation trigger for tick 0 */ - activationSeq.size shouldBe 1 - activationSeq.headOption match { - case Some( - ScheduleTriggerMessage( - ActivityStartTrigger(tick), - actorToBeScheduled - ) - ) => - tick shouldBe 0L - actorToBeScheduled shouldBe mockAgentTestRef - case _ => fail("Sequence of activation triggers has wrong content.") - } + case (Some(activation), actualBaseStateData) => + activation shouldBe ScheduleTriggerMessage( + ActivityStartTrigger(0L), + mockAgentTestRef + ) /* Base state data haven't changed */ actualBaseStateData shouldBe baseStateData case _ => @@ -279,20 +270,11 @@ class ParticipantAgentFundamentalsSpec ) mockAgent.popNextActivationTrigger(baseStateData) match { - case (Some(activationSeq), actualBaseStateData) => - /* There is exactly one activation trigger for tick 1 */ - activationSeq.size shouldBe 1 - activationSeq.headOption match { - case Some( - ScheduleTriggerMessage( - ActivityStartTrigger(tick), - actorToBeScheduled - ) - ) => - tick shouldBe 0L - actorToBeScheduled shouldBe mockAgentTestRef - case _ => fail("Sequence of activation triggers has wrong content.") - } + case (Some(activation), actualBaseStateData) => + activation shouldBe ScheduleTriggerMessage( + ActivityStartTrigger(0L), + mockAgentTestRef + ) /* Additional activation tick has been popped from base state data */ actualBaseStateData.additionalActivationTicks.corresponds( Array(10L, 20L) @@ -313,20 +295,11 @@ class ParticipantAgentFundamentalsSpec ) mockAgent.popNextActivationTrigger(baseStateData) match { - case (Some(activationSeq), actualBaseStateData) => - /* There is exactly one activation trigger for tick 1 */ - activationSeq.size shouldBe 1 - activationSeq.headOption match { - case Some( - ScheduleTriggerMessage( - ActivityStartTrigger(tick), - actorToBeScheduled - ) - ) => - tick shouldBe 0L - actorToBeScheduled shouldBe mockAgentTestRef - case _ => fail("Sequence of activation triggers has wrong content.") - } + case (Some(activation), actualBaseStateData) => + activation shouldBe ScheduleTriggerMessage( + ActivityStartTrigger(0L), + mockAgentTestRef + ) /* Additional activation tick has been popped from base state data */ actualBaseStateData.additionalActivationTicks.corresponds( Array(10L, 20L) diff --git a/src/test/scala/edu/ie3/simona/agent/participant/PvAgentModelCalculationSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/PvAgentModelCalculationSpec.scala index ac11139fbc..fab6e15b24 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/PvAgentModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/PvAgentModelCalculationSpec.scala @@ -71,8 +71,6 @@ import squants.{Each, Power} import squants.energy.{Kilowatts, Megawatts, Watts} import squants.motion.MetersPerSecond import squants.thermal.Celsius -import tech.units.indriya.quantity.Quantities -import tech.units.indriya.unit.Units.{CELSIUS, METRE_PER_SECOND} import java.util.concurrent.TimeUnit @@ -182,8 +180,7 @@ class PvAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - triggerId, - pvAgent + triggerId ) ) @@ -261,8 +258,7 @@ class PvAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - triggerId, - pvAgent + triggerId ) ) @@ -362,9 +358,7 @@ class PvAgentModelCalculationSpec CompletionMessage( triggerId, Some( - scala.collection.immutable.Seq( - ScheduleTriggerMessage(ActivityStartTrigger(4711), pvAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(4711), pvAgent) ) ) ) @@ -417,8 +411,7 @@ class PvAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - triggerId, - pvAgent + triggerId ) ) @@ -503,8 +496,7 @@ class PvAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - initialiseTriggerId, - pvAgent + initialiseTriggerId ) ) @@ -565,8 +557,7 @@ class PvAgentModelCalculationSpec pvAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - 1L, - scheduler.ref + 1L ) ) @@ -576,9 +567,7 @@ class PvAgentModelCalculationSpec CompletionMessage( 1L, Some( - scala.collection.immutable.Seq( - ScheduleTriggerMessage(ActivityStartTrigger(3600L), pvAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(3600L), pvAgent) ) ) ) @@ -651,8 +640,7 @@ class PvAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - initialiseTriggerId, - pvAgent + initialiseTriggerId ) ) @@ -673,8 +661,7 @@ class PvAgentModelCalculationSpec pvAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - 1L, - scheduler.ref + 1L ) ) @@ -720,9 +707,7 @@ class PvAgentModelCalculationSpec CompletionMessage( 1L, Some( - scala.collection.immutable.Seq( - ScheduleTriggerMessage(ActivityStartTrigger(3600L), pvAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(3600L), pvAgent) ) ) ) @@ -797,8 +782,7 @@ class PvAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - 0L, - pvAgent + 0L ) ) @@ -840,8 +824,7 @@ class PvAgentModelCalculationSpec pvAgent, TriggerWithIdMessage( ActivityStartTrigger(3600L), - 1L, - scheduler.ref + 1L ) ) @@ -851,9 +834,7 @@ class PvAgentModelCalculationSpec CompletionMessage( 1L, Some( - scala.collection.immutable.Seq( - ScheduleTriggerMessage(ActivityStartTrigger(7200L), pvAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(7200L), pvAgent) ) ) ) @@ -899,8 +880,7 @@ class PvAgentModelCalculationSpec primaryServiceProxy = primaryServiceProxy.ref ) ), - 0L, - pvAgent + 0L ) ) @@ -935,17 +915,14 @@ class PvAgentModelCalculationSpec pvAgent, TriggerWithIdMessage( ActivityStartTrigger(0L), - 1L, - scheduler.ref + 1L ) ) scheduler.expectMsg( CompletionMessage( 1L, Some( - scala.collection.immutable.Seq( - ScheduleTriggerMessage(ActivityStartTrigger(3600L), pvAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(3600L), pvAgent) ) ) ) @@ -968,17 +945,14 @@ class PvAgentModelCalculationSpec pvAgent, TriggerWithIdMessage( ActivityStartTrigger(3600L), - 3L, - scheduler.ref + 3L ) ) scheduler.expectMsg( CompletionMessage( 3L, Some( - scala.collection.immutable.Seq( - ScheduleTriggerMessage(ActivityStartTrigger(7200L), pvAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(7200L), pvAgent) ) ) ) @@ -1001,8 +975,7 @@ class PvAgentModelCalculationSpec pvAgent, TriggerWithIdMessage( ActivityStartTrigger(7200L), - 5L, - scheduler.ref + 5L ) ) scheduler.expectMsg(CompletionMessage(5L)) diff --git a/src/test/scala/edu/ie3/simona/agent/participant/WecAgentModelCalculationSpec.scala b/src/test/scala/edu/ie3/simona/agent/participant/WecAgentModelCalculationSpec.scala index 242e948866..3015aaf2ca 100644 --- a/src/test/scala/edu/ie3/simona/agent/participant/WecAgentModelCalculationSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/participant/WecAgentModelCalculationSpec.scala @@ -75,7 +75,6 @@ import squants.thermal.Celsius import java.time.ZonedDateTime import java.util.concurrent.TimeUnit -import scala.collection._ class WecAgentModelCalculationSpec extends ParticipantAgentSpec( @@ -185,8 +184,7 @@ class WecAgentModelCalculationSpec ) ) ), - triggerId, - wecAgent + triggerId ) ) @@ -273,8 +271,7 @@ class WecAgentModelCalculationSpec ) ) ), - triggerId, - wecAgent + triggerId ) ) @@ -318,7 +315,7 @@ class WecAgentModelCalculationSpec foreseenDataTicks shouldBe Map.empty voltageValueStore shouldBe ValueStore( resolution * 10, - immutable.Map(0L -> Each(1.0)) + Map(0L -> Each(1.0)) ) resultValueStore shouldBe ValueStore.forResult(resolution, 10) requestValueStore shouldBe ValueStore[ApparentPower](resolution * 10) @@ -340,9 +337,7 @@ class WecAgentModelCalculationSpec CompletionMessage( triggerId, Some( - immutable.Seq( - ScheduleTriggerMessage(ActivityStartTrigger(4711), wecAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(4711), wecAgent) ) ) ) @@ -402,8 +397,7 @@ class WecAgentModelCalculationSpec ) ) ), - triggerId, - wecAgent + triggerId ) ) @@ -443,7 +437,7 @@ class WecAgentModelCalculationSpec ApparentPower ]( resolution * 10, - immutable.Map( + Map( 0L -> ApparentPower( Megawatts(0d), Megavars(0d) @@ -493,8 +487,7 @@ class WecAgentModelCalculationSpec ) ) ), - initialiseTriggerId, - wecAgent + initialiseTriggerId ) ) @@ -559,8 +552,7 @@ class WecAgentModelCalculationSpec wecAgent, TriggerWithIdMessage( ActivityStartTrigger(900L), - 1L, - scheduler.ref + 1L ) ) @@ -570,9 +562,7 @@ class WecAgentModelCalculationSpec CompletionMessage( 1L, Some( - immutable.Seq( - ScheduleTriggerMessage(ActivityStartTrigger(1800L), wecAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(1800L), wecAgent) ) ) ) @@ -651,8 +641,7 @@ class WecAgentModelCalculationSpec ) ) ), - initialiseTriggerId, - wecAgent + initialiseTriggerId ) ) @@ -673,8 +662,7 @@ class WecAgentModelCalculationSpec wecAgent, TriggerWithIdMessage( ActivityStartTrigger(900L), - 1L, - scheduler.ref + 1L ) ) @@ -724,9 +712,7 @@ class WecAgentModelCalculationSpec CompletionMessage( 1L, Some( - immutable.Seq( - ScheduleTriggerMessage(ActivityStartTrigger(1800L), wecAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(1800L), wecAgent) ) ) ) @@ -807,8 +793,7 @@ class WecAgentModelCalculationSpec ) ) ), - 0L, - wecAgent + 0L ) ) @@ -850,8 +835,7 @@ class WecAgentModelCalculationSpec wecAgent, TriggerWithIdMessage( ActivityStartTrigger(900L), - 1L, - scheduler.ref + 1L ) ) @@ -861,9 +845,7 @@ class WecAgentModelCalculationSpec CompletionMessage( 1L, Some( - immutable.Seq( - ScheduleTriggerMessage(ActivityStartTrigger(1800L), wecAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(1800L), wecAgent) ) ) ) @@ -912,8 +894,7 @@ class WecAgentModelCalculationSpec ) ) ), - 0L, - wecAgent + 0L ) ) @@ -948,17 +929,14 @@ class WecAgentModelCalculationSpec wecAgent, TriggerWithIdMessage( ActivityStartTrigger(900L), - 1L, - scheduler.ref + 1L ) ) scheduler.expectMsg( CompletionMessage( 1L, Some( - immutable.Seq( - ScheduleTriggerMessage(ActivityStartTrigger(1800L), wecAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(1800L), wecAgent) ) ) ) @@ -981,17 +959,14 @@ class WecAgentModelCalculationSpec wecAgent, TriggerWithIdMessage( ActivityStartTrigger(1800L), - 3L, - scheduler.ref + 3L ) ) scheduler.expectMsg( CompletionMessage( 3L, Some( - immutable.Seq( - ScheduleTriggerMessage(ActivityStartTrigger(2700L), wecAgent) - ) + ScheduleTriggerMessage(ActivityStartTrigger(2700L), wecAgent) ) ) ) @@ -1014,8 +989,7 @@ class WecAgentModelCalculationSpec wecAgent, TriggerWithIdMessage( ActivityStartTrigger(2700L), - 5L, - scheduler.ref + 5L ) ) scheduler.expectMsg(CompletionMessage(5L)) diff --git a/src/test/scala/edu/ie3/simona/api/ExtSimAdapterSpec.scala b/src/test/scala/edu/ie3/simona/api/ExtSimAdapterSpec.scala index 512d60038c..3acdbd57f3 100644 --- a/src/test/scala/edu/ie3/simona/api/ExtSimAdapterSpec.scala +++ b/src/test/scala/edu/ie3/simona/api/ExtSimAdapterSpec.scala @@ -13,9 +13,9 @@ import edu.ie3.simona.api.ExtSimAdapter.InitExtSimAdapter import edu.ie3.simona.api.data.ontology.ScheduleDataServiceMessage import edu.ie3.simona.api.simulation.ExtSimAdapterData import edu.ie3.simona.api.simulation.ontology.{ - Terminate, + ActivationMessage, TerminationCompleted, - ActivityStartTrigger => ExtActivityStartTrigger, + TerminationMessage, CompletionMessage => ExtCompletionMessage } import edu.ie3.simona.ontology.messages.SchedulerMessage.{ @@ -31,10 +31,9 @@ import edu.ie3.simona.ontology.trigger.Trigger.{ import edu.ie3.simona.test.common.TestKitWithShutdown import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import org.scalatest.wordspec.AnyWordSpecLike -import org.scalatest.prop.TableDrivenPropertyChecks._ import scala.concurrent.duration.DurationInt -import scala.jdk.CollectionConverters.SeqHasAsJava +import scala.jdk.OptionConverters.RichOption class ExtSimAdapterSpec extends TestKitWithShutdown( @@ -70,8 +69,7 @@ class ExtSimAdapterSpec extData ) ), - triggerId, - extSimAdapter + triggerId ) ) @@ -79,11 +77,9 @@ class ExtSimAdapterSpec CompletionMessage( triggerId, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(INIT_SIM_TICK), - extSimAdapter - ) + ScheduleTriggerMessage( + ActivityStartTrigger(INIT_SIM_TICK), + extSimAdapter ) ) ) @@ -107,8 +103,7 @@ class ExtSimAdapterSpec extData ) ), - 1L, - extSimAdapter + 1L ) ) @@ -122,8 +117,7 @@ class ExtSimAdapterSpec ActivityStartTrigger( INIT_SIM_TICK ), - triggerId, - extSimAdapter + triggerId ) ) @@ -133,7 +127,7 @@ class ExtSimAdapterSpec message = "No message received" ) extData.receiveMessageQueue.size() shouldBe 1 - extData.receiveMessageQueue.take() shouldBe new ExtActivityStartTrigger( + extData.receiveMessageQueue.take() shouldBe new ActivationMessage( INIT_SIM_TICK ) scheduler.expectNoMessage() @@ -142,7 +136,7 @@ class ExtSimAdapterSpec val nextTick = 900L extData.send( new ExtCompletionMessage( - List[java.lang.Long](nextTick).asJava + Option[java.lang.Long](nextTick).toJava ) ) @@ -150,11 +144,9 @@ class ExtSimAdapterSpec CompletionMessage( triggerId, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(nextTick), - extSimAdapter - ) + ScheduleTriggerMessage( + ActivityStartTrigger(nextTick), + extSimAdapter ) ) ) @@ -177,8 +169,7 @@ class ExtSimAdapterSpec extData ) ), - 1L, - extSimAdapter + 1L ) ) @@ -193,8 +184,7 @@ class ExtSimAdapterSpec ActivityStartTrigger( tick ), - triggerId, - extSimAdapter + triggerId ) ) @@ -236,8 +226,7 @@ class ExtSimAdapterSpec extData ) ), - 1L, - extSimAdapter + 1L ) ) @@ -254,7 +243,9 @@ class ExtSimAdapterSpec message = "No message received" ) extData.receiveMessageQueue.size() shouldBe 1 - extData.receiveMessageQueue.take() shouldBe new Terminate(simSuccessful) + extData.receiveMessageQueue.take() shouldBe new TerminationMessage( + simSuccessful + ) // up until now, extSimAdapter should still be running stopWatcher.expectNoMessage() diff --git a/src/test/scala/edu/ie3/simona/integration/RunSimonaStandaloneIT.scala b/src/test/scala/edu/ie3/simona/integration/RunSimonaStandaloneIT.scala index 4d4226f25c..55681c3e2a 100644 --- a/src/test/scala/edu/ie3/simona/integration/RunSimonaStandaloneIT.scala +++ b/src/test/scala/edu/ie3/simona/integration/RunSimonaStandaloneIT.scala @@ -126,7 +126,7 @@ class RunSimonaStandaloneIT private def checkRuntimeEvents( runtimeEvents: Iterable[RuntimeEvent] ): Unit = { - runtimeEvents.toVector.size shouldBe 12 + runtimeEvents.toVector.size shouldBe 11 val groupedRuntimeEvents = runtimeEvents.toVector.groupBy { case Initializing => Initializing case InitComplete(_) => InitComplete @@ -149,7 +149,7 @@ class RunSimonaStandaloneIT groupedRuntimeEvents .get(CheckWindowPassed) .foreach(checkWindowsPassed => { - checkWindowsPassed.size shouldBe 8 + checkWindowsPassed.size shouldBe 7 checkWindowsPassed.foreach { case CheckWindowPassed(tick, _) => tick % 900L shouldBe 0 // config has 900 sec as check window value diff --git a/src/test/scala/edu/ie3/simona/scheduler/SchedulerSpec.scala b/src/test/scala/edu/ie3/simona/scheduler/SchedulerSpec.scala new file mode 100644 index 0000000000..12eb7ce33a --- /dev/null +++ b/src/test/scala/edu/ie3/simona/scheduler/SchedulerSpec.scala @@ -0,0 +1,564 @@ +/* + * © 2020. 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 akka.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import akka.actor.typed.scaladsl.adapter.TypedActorRefOps +import edu.ie3.simona.ontology.messages.SchedulerMessage +import edu.ie3.simona.ontology.messages.SchedulerMessage._ +import edu.ie3.simona.ontology.trigger.Trigger.{ + ActivityStartTrigger, + InitializeTrigger +} +import edu.ie3.simona.util.ActorUtils.RichTriggeredAgent +import edu.ie3.simona.util.SimonaConstants +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import org.mockito.Mockito.doReturn +import org.scalatest.matchers.should +import org.scalatest.wordspec.AnyWordSpecLike +import org.scalatestplus.mockito.MockitoSugar.mock + +class SchedulerSpec + extends ScalaTestWithActorTestKit + with AnyWordSpecLike + with should.Matchers { + + def createMockInitTrigger(): InitializeTrigger = { + val mockTrigger = mock[InitializeTrigger] + doReturn(SimonaConstants.INIT_SIM_TICK).when(mockTrigger).tick + mockTrigger + } + + "The Scheduler should work correctly" when { + + "receiving triggers before activation" in { + val parent = TestProbe[SchedulerMessage]("parent") + val scheduler = spawn( + Scheduler(parent.ref) + ) + + val agent1 = TestProbe[TriggerWithIdMessage]("agent_1") + val agent2 = TestProbe[TriggerWithIdMessage]("agent_2") + + val initTrigger1 = createMockInitTrigger() + scheduler ! ScheduleTriggerMessage( + initTrigger1, + agent1.ref.toClassic + ) + + parent.expectMessage( + ScheduleTriggerMessage( + ActivityStartTrigger(INIT_SIM_TICK), + scheduler.toClassic + ) + ) + + val initTrigger2 = createMockInitTrigger() + scheduler ! ScheduleTriggerMessage( + initTrigger2, + agent2.ref.toClassic + ) + + agent1.expectNoMessage() + agent2.expectNoMessage() + + val triggerId = 0 + scheduler ! TriggerWithIdMessage( + ActivityStartTrigger(INIT_SIM_TICK), + triggerId + ) + + val receivedTrigger1 = + agent1.expectMessageType[TriggerWithIdMessage] + receivedTrigger1.trigger shouldBe initTrigger1 + + val receivedTrigger2 = + agent2.expectMessageType[TriggerWithIdMessage] + receivedTrigger2.trigger shouldBe initTrigger2 + + scheduler ! CompletionMessage( + receivedTrigger1.triggerId, + None + ) + + parent.expectNoMessage() + + scheduler ! CompletionMessage( + receivedTrigger2.triggerId, + None + ) + + parent.expectMessage(CompletionMessage(triggerId, None)) + } + + "receiving triggers after init trigger" in { + val parent = TestProbe[SchedulerMessage]("parent") + val scheduler = spawn( + Scheduler(parent.ref) + ) + + val triggerId = 0 + scheduler ! TriggerWithIdMessage( + ActivityStartTrigger(INIT_SIM_TICK), + triggerId + ) + + val agent1 = TestProbe[TriggerWithIdMessage]("agent_1") + val agent2 = TestProbe[TriggerWithIdMessage]("agent_2") + + val initTrigger1 = createMockInitTrigger() + scheduler ! ScheduleTriggerMessage( + initTrigger1, + agent1.ref.toClassic + ) + + val initTrigger2 = createMockInitTrigger() + scheduler ! ScheduleTriggerMessage( + initTrigger2, + agent2.ref.toClassic + ) + + // trigger are sent right away + val receivedTrigger1 = + agent1.expectMessageType[TriggerWithIdMessage] + receivedTrigger1.trigger shouldBe initTrigger1 + + val receivedTrigger2 = + agent2.expectMessageType[TriggerWithIdMessage] + receivedTrigger2.trigger shouldBe initTrigger2 + + scheduler ! CompletionMessage( + receivedTrigger1.triggerId, + Some( + ScheduleTriggerMessage( + ActivityStartTrigger(0), + agent1.ref.toClassic + ) + ) + ) + + parent.expectNoMessage() + + scheduler ! CompletionMessage( + receivedTrigger2.triggerId, + None + ) + + parent.expectMessage( + CompletionMessage( + triggerId, + Some( + ScheduleTriggerMessage( + ActivityStartTrigger(0), + scheduler.ref.toClassic + ) + ) + ) + ) + } + + "scheduling two actors for different ticks" in { + val parent = TestProbe[SchedulerMessage]("parent") + val scheduler = spawn( + Scheduler(parent.ref) + ) + + val agent1 = TestProbe[TriggerWithIdMessage]("agent_1") + val agent2 = TestProbe[TriggerWithIdMessage]("agent_2") + + val initTrigger1 = createMockInitTrigger() + scheduler ! ScheduleTriggerMessage( + initTrigger1, + agent1.ref.toClassic + ) + + parent.expectMessage( + ScheduleTriggerMessage( + ActivityStartTrigger(INIT_SIM_TICK), + scheduler.toClassic + ) + ) + + val initTrigger2 = createMockInitTrigger() + scheduler ! ScheduleTriggerMessage( + initTrigger2, + agent2.ref.toClassic + ) + + /* ACTIVATE INIT TICK */ + val triggerId0 = 0 + scheduler ! TriggerWithIdMessage( + ActivityStartTrigger(INIT_SIM_TICK), + triggerId0 + ) + + agent1.expectTriggerAndComplete( + scheduler, + INIT_SIM_TICK, + Some(0) + ) + + parent.expectNoMessage() + + agent2.expectTriggerAndComplete( + scheduler, + INIT_SIM_TICK, + Some(0) + ) + + parent.expectMessage( + CompletionMessage( + triggerId0, + Some( + ScheduleTriggerMessage( + ActivityStartTrigger(0), + scheduler.toClassic + ) + ) + ) + ) + + agent1.expectNoMessage() + agent2.expectNoMessage() + + /* ACTIVATE TICK 0 */ + val triggerId1 = 1 + scheduler ! TriggerWithIdMessage( + ActivityStartTrigger(0), + triggerId1 + ) + + agent1.expectTriggerAndComplete( + scheduler, + 0, + Some(300) + ) + + parent.expectNoMessage() + + agent2.expectTriggerAndComplete( + scheduler, + 0, + Some(900) + ) + + parent.expectMessage( + CompletionMessage( + triggerId1, + Some( + ScheduleTriggerMessage( + ActivityStartTrigger(300), + scheduler.toClassic + ) + ) + ) + ) + + /* ACTIVATE TICK 300 */ + val triggerId2 = 2 + scheduler ! TriggerWithIdMessage( + ActivityStartTrigger(300), + triggerId2 + ) + + agent1.expectTriggerAndComplete( + scheduler, + 300, + Some(900) + ) + + parent.expectMessage( + CompletionMessage( + triggerId2, + Some( + ScheduleTriggerMessage( + ActivityStartTrigger(900), + scheduler.toClassic + ) + ) + ) + ) + + agent1.expectNoMessage() + agent2.expectNoMessage() + + /* ACTIVATE TICK 900 */ + val triggerId3 = 3 + scheduler ! TriggerWithIdMessage( + ActivityStartTrigger(900), + triggerId3 + ) + + agent1.expectTriggerAndComplete( + scheduler, + 900, + Some(3600) + ) + + parent.expectNoMessage() + + agent2.expectTriggerAndComplete( + scheduler, + 900, + Some(1800) + ) + + parent.expectMessage( + CompletionMessage( + triggerId3, + Some( + ScheduleTriggerMessage( + ActivityStartTrigger(1800), + scheduler.toClassic + ) + ) + ) + ) + + parent.expectNoMessage() + agent1.expectNoMessage() + agent2.expectNoMessage() + } + + "five actors are getting triggered for ten ticks" in { + val parent = TestProbe[SchedulerMessage]("parent") + val scheduler = spawn( + Scheduler(parent.ref) + ) + + val triggeredAgents = (1 to 5) + .map(i => TestProbe[TriggerWithIdMessage](s"agent_$i")) + + triggeredAgents.foreach(actor => + // send to init trigger to scheduler + scheduler ! ScheduleTriggerMessage( + createMockInitTrigger(), + actor.ref.toClassic + ) + ) + + parent.expectMessage( + ScheduleTriggerMessage( + ActivityStartTrigger(INIT_SIM_TICK), + scheduler.toClassic + ) + ) + + for (tick <- -1 to 8) { + val triggerId = tick + 2 + scheduler ! TriggerWithIdMessage(ActivityStartTrigger(tick), triggerId) + + triggeredAgents.foreach { + _.expectTriggerAndComplete( + scheduler, + tick, + Some(tick + 1) + ) + } + + parent.expectMessage( + CompletionMessage( + triggerId, + Some( + ScheduleTriggerMessage( + ActivityStartTrigger(tick + 1), + scheduler.ref.toClassic + ) + ) + ) + ) + } + } + + } + + "The Scheduler should fail and stop" when { + + "activated with wrong tick" in { + val parent = TestProbe[SchedulerMessage]("parent") + val scheduler = spawn( + Scheduler(parent.ref) + ) + + val agent1 = TestProbe[TriggerWithIdMessage]("agent_1") + + scheduler ! ScheduleTriggerMessage( + createMockInitTrigger(), + agent1.ref.toClassic + ) + + parent.expectMessageType[ScheduleTriggerMessage] + agent1.expectNoMessage() + + scheduler ! TriggerWithIdMessage( + ActivityStartTrigger(0), + 0 + ) + + // agent does not receive activation + agent1.expectNoMessage() + parent.expectNoMessage() + + // scheduler stopped + parent.expectTerminated(scheduler) + } + + "asked to schedule trigger for a past tick while inactive" in { + val parent = TestProbe[SchedulerMessage]("parent") + val scheduler = spawn( + Scheduler(parent.ref) + ) + + val agent1 = TestProbe[TriggerWithIdMessage]("agent_1") + + scheduler ! ScheduleTriggerMessage( + ActivityStartTrigger(900), + agent1.ref.toClassic + ) + + parent.expectMessage( + ScheduleTriggerMessage( + ActivityStartTrigger(900), + scheduler.toClassic + ) + ) + + val triggerId = 1 + scheduler ! TriggerWithIdMessage(ActivityStartTrigger(900), triggerId) + + agent1.expectTriggerAndComplete( + scheduler, + 900, + Some(1800) + ) + + parent.expectMessage( + CompletionMessage( + triggerId, + Some( + ScheduleTriggerMessage( + ActivityStartTrigger(1800), + scheduler.ref.toClassic + ) + ) + ) + ) + + // now inactive again + // can't schedule trigger with earlier tick than last tick (900) -> error + scheduler ! ScheduleTriggerMessage( + createMockInitTrigger(), + agent1.ref.toClassic + ) + + // agent does not receive activation + agent1.expectNoMessage() + parent.expectNoMessage() + + // scheduler stopped + parent.expectTerminated(scheduler) + } + + "asked to schedule trigger for a past tick while active" in { + val parent = TestProbe[SchedulerMessage]("parent") + val scheduler = spawn( + Scheduler(parent.ref) + ) + + scheduler ! TriggerWithIdMessage( + ActivityStartTrigger(0), + 0 + ) + + val agent1 = TestProbe[TriggerWithIdMessage]("agent_1") + + // can't schedule trigger for earlier tick than current active -> error + scheduler ! ScheduleTriggerMessage( + createMockInitTrigger(), + agent1.ref.toClassic + ) + + // agent does not receive activation + agent1.expectNoMessage() + parent.expectNoMessage() + + // scheduler stopped + parent.expectTerminated(scheduler) + } + + "receiving completion message with unexpected trigger id" in { + val parent = TestProbe[SchedulerMessage]("parent") + val scheduler = spawn( + Scheduler(parent.ref) + ) + + scheduler ! TriggerWithIdMessage( + ActivityStartTrigger(INIT_SIM_TICK), + 0 + ) + + val agent1 = TestProbe[TriggerWithIdMessage]("agent_1") + + val initTrigger1 = createMockInitTrigger() + scheduler ! ScheduleTriggerMessage( + initTrigger1, + agent1.ref.toClassic + ) + + val receivedTrigger = + agent1.expectMessageType[TriggerWithIdMessage] + receivedTrigger.trigger shouldBe initTrigger1 + + // wrong triggerId + scheduler ! CompletionMessage(receivedTrigger.triggerId + 1, None) + + parent.expectNoMessage() + + // scheduler stopped + parent.expectTerminated(scheduler) + } + + "receiving unexpected message while active" in { + val parent = TestProbe[SchedulerMessage]("parent") + val scheduler = spawn( + Scheduler(parent.ref) + ) + + scheduler ! TriggerWithIdMessage( + ActivityStartTrigger(INIT_SIM_TICK), + 0 + ) + + // scheduler is already active, can't handle activation a second time + scheduler ! TriggerWithIdMessage( + ActivityStartTrigger(0), + 1 + ) + + parent.expectNoMessage() + + // scheduler stopped + parent.expectTerminated(scheduler) + } + + "receiving unexpected message while inactive" in { + val parent = TestProbe[SchedulerMessage]("parent") + val scheduler = spawn( + Scheduler(parent.ref) + ) + + // scheduler is inactive, can't handle completion + scheduler ! CompletionMessage(0, None) + + parent.expectNoMessage() + + // scheduler stopped + parent.expectTerminated(scheduler) + } + } + +} diff --git a/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala b/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala deleted file mode 100644 index 47fb1176bb..0000000000 --- a/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala +++ /dev/null @@ -1,945 +0,0 @@ -/* - * © 2020. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.scheduler - -import akka.actor._ -import akka.testkit.{ImplicitSender, TestActorRef, TestProbe} -import com.typesafe.config.ConfigFactory -import edu.ie3.simona.config.SimonaConfig -import edu.ie3.simona.event.RuntimeEvent._ -import edu.ie3.simona.ontology.messages.SchedulerMessage._ -import edu.ie3.simona.ontology.trigger.Trigger -import edu.ie3.simona.ontology.trigger.Trigger.{ - ActivityStartTrigger, - InitializeTrigger -} -import edu.ie3.simona.scheduler.SimSchedulerSpec.{ - DummySupervisor, - RichTriggeredAgent -} -import edu.ie3.simona.test.common.{TestKitWithShutdown, UnitSpec} -import edu.ie3.simona.util.SimonaConstants -import org.mockito.Mockito.doReturn -import org.scalatestplus.mockito.MockitoSugar.mock - -import scala.reflect.ClassTag - -class SimSchedulerSpec - extends TestKitWithShutdown( - ActorSystem( - "SimSchedulerSpec", - ConfigFactory - .parseString( - """ - |akka.loggers=["edu.ie3.simona.test.common.SilentTestEventListener"] - |akka.loglevel="debug" - """.stripMargin - ) - ) - ) - with ImplicitSender { - - private val defaultTimeConfig = SimonaConfig.Simona.Time( - startDateTime = "2011-01-01 00:00:00", - endDateTime = "2011-01-01 01:00:00", - schedulerReadyCheckWindow = Some(900), - stopOnFailedPowerFlow = false - ) - - def setupScheduler( - autostart: Boolean = false, - stopOnFailedPowerFlow: Boolean = false, - timeConfig: SimonaConfig.Simona.Time = defaultTimeConfig - ): (TestActorRef[SimScheduler], TestProbe) = { - val resultEventListener = TestProbe("ResultEventListener") - - // make sure that the scheduler stops on exceptions - val supervisorRef = TestActorRef(new DummySupervisor()) - - val simScheduler = TestActorRef[SimScheduler]( - SimScheduler.props( - timeConfig, - Iterable(resultEventListener.ref), - stopOnFailedPowerFlow = stopOnFailedPowerFlow, - autoStart = autostart - ), - supervisor = supervisorRef - ) - - (simScheduler, resultEventListener) - } - - "The SimScheduler" should { - - "initialize as expected when receiving triggers before InitSimMessage" in { - val (simScheduler, resultEventListener) = setupScheduler() - - val triggeredAgent1 = TestProbe() - val initTrigger1 = createMockInitTrigger() - simScheduler ! ScheduleTriggerMessage( - initTrigger1, - triggeredAgent1.ref - ) - - val triggeredAgent2 = TestProbe() - val initTrigger2 = createMockInitTrigger() - simScheduler ! - ScheduleTriggerMessage( - initTrigger2, - triggeredAgent2.ref - ) - - triggeredAgent1.expectNoMessage() - triggeredAgent2.expectNoMessage() - - simScheduler ! InitSimMessage - - resultEventListener.expectMsg(Initializing) - - val receivedTrigger1 = - triggeredAgent1.expectMsgType[TriggerWithIdMessage] - receivedTrigger1.trigger shouldBe initTrigger1 - receivedTrigger1.receiverActor shouldBe triggeredAgent1.ref - - val receivedTrigger2 = - triggeredAgent2.expectMsgType[TriggerWithIdMessage] - receivedTrigger2.trigger shouldBe initTrigger2 - receivedTrigger2.receiverActor shouldBe triggeredAgent2.ref - - triggeredAgent1.send( - simScheduler, - CompletionMessage( - receivedTrigger1.triggerId, - None - ) - ) - - resultEventListener.expectNoMessage() - - triggeredAgent2.send( - simScheduler, - CompletionMessage( - receivedTrigger2.triggerId, - None - ) - ) - - resultEventListener.expectMsgType[InitComplete] - } - - "initialize as expected when receiving triggers after init trigger" in { - val (simScheduler, resultEventListener) = setupScheduler() - - simScheduler ! InitSimMessage - - resultEventListener.expectMsg(Initializing) - - val triggeredAgent1 = TestProbe() - val initTrigger1 = createMockInitTrigger() - simScheduler ! ScheduleTriggerMessage( - initTrigger1, - triggeredAgent1.ref - ) - - val triggeredAgent2 = TestProbe() - val initTrigger2 = createMockInitTrigger() - simScheduler ! ScheduleTriggerMessage( - initTrigger2, - triggeredAgent2.ref - ) - - // trigger are sent right away - val receivedTrigger1 = - triggeredAgent1.expectMsgType[TriggerWithIdMessage] - receivedTrigger1.trigger shouldBe initTrigger1 - receivedTrigger1.receiverActor shouldBe triggeredAgent1.ref - - val receivedTrigger2 = - triggeredAgent2.expectMsgType[TriggerWithIdMessage] - receivedTrigger2.trigger shouldBe initTrigger2 - receivedTrigger2.receiverActor shouldBe triggeredAgent2.ref - - triggeredAgent1.send( - simScheduler, - CompletionMessage( - receivedTrigger1.triggerId, - None - ) - ) - - resultEventListener.expectNoMessage() - - triggeredAgent2.send( - simScheduler, - CompletionMessage( - receivedTrigger2.triggerId, - None - ) - ) - - resultEventListener.expectMsgType[InitComplete] - } - - "start simulation when autostart is enabled" in { - val (simScheduler, resultEventListener) = setupScheduler(autostart = true) - - val triggeredAgent1 = TestProbe() - val initTrigger1 = createMockInitTrigger() - simScheduler ! ScheduleTriggerMessage( - initTrigger1, - triggeredAgent1.ref - ) - - triggeredAgent1.expectNoMessage() - - simScheduler ! InitSimMessage - - resultEventListener.expectMsg(Initializing) - - triggeredAgent1.expectInitAndComplete( - simScheduler, - resultEventListener, - Seq(0L, 900L) - ) - - resultEventListener.expectMsgType[InitComplete] - resultEventListener.expectMsg(Simulating(0L, 3600L)) - - val receivedTrigger1Start = - triggeredAgent1.expectMsgType[TriggerWithIdMessage] - receivedTrigger1Start.trigger shouldBe ActivityStartTrigger(0L) - receivedTrigger1Start.receiverActor shouldBe triggeredAgent1.ref - } - - "send triggers as expected when triggered at x*schedulerReadyCheckWindow" in { - val (simScheduler, resultEventListener) = setupScheduler() - - simScheduler ! InitSimMessage - resultEventListener.expectMsg(Initializing) - - val triggeredAgent1 = TestProbe() - - val initTrigger1 = createMockInitTrigger() - simScheduler ! ScheduleTriggerMessage( - initTrigger1, - triggeredAgent1.ref - ) - - triggeredAgent1.expectInitAndComplete( - simScheduler, - resultEventListener, - Seq(0L) - ) - - resultEventListener.expectMsgType[InitComplete] - - simScheduler ! StartScheduleMessage(Some(3600L)) - - resultEventListener.expectMsg(Simulating(0L, 3600L)) - - triggeredAgent1.expectAstAndComplete( - simScheduler, - resultEventListener, - 0L, - Seq(900L) - ) - - // no CheckWindowPassed at tick 0 - resultEventListener.expectNoMessage() - - triggeredAgent1.expectAstAndComplete( - simScheduler, - resultEventListener, - 900L, - Seq(1800L) - ) - - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 900L - - triggeredAgent1.expectAstAndComplete( - simScheduler, - resultEventListener, - 1800L, - Seq(2700L) - ) - - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L - - triggeredAgent1.expectAstAndComplete( - simScheduler, - resultEventListener, - 2700L, - Seq(3600L) - ) - - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L - - triggeredAgent1.expectAstAndComplete( - simScheduler, - resultEventListener, - 3600L - ) - - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 3600L - resultEventListener.expectMsgType[Ready].tick shouldBe 3600L - val doneMsg = resultEventListener.expectMsgType[Done] - doneMsg.tick shouldBe 3600L - doneMsg.noOfFailedPF shouldBe 0 - doneMsg.errorInSim shouldBe false - - // we expect that the scheduler answers with a SimulationSuccessfulMessage - // to the sender of the run request (this test) - expectMsg(SimulationSuccessfulMessage) - } - - "finish simulation correctly when endTick == pauseScheduleAtTick" in { - val (simScheduler, resultEventListener) = setupScheduler() - - simScheduler ! InitSimMessage - resultEventListener.expectMsg(Initializing) - - val triggeredAgent1 = TestProbe() - - val initTrigger1 = createMockInitTrigger() - simScheduler ! ScheduleTriggerMessage( - initTrigger1, - triggeredAgent1.ref - ) - triggeredAgent1.expectInitAndComplete( - simScheduler, - resultEventListener, - Seq(3600L) - ) - - resultEventListener.expectMsgType[InitComplete] - - triggeredAgent1.expectNoMessage() - - simScheduler ! StartScheduleMessage(Some(3600L)) - - resultEventListener.expectMsg(Simulating(0L, 3600L)) - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 900L - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L - - triggeredAgent1.expectAstAndComplete( - simScheduler, - resultEventListener, - 3600L - ) - - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 3600L - resultEventListener.expectMsgType[Ready].tick shouldBe 3600L - val doneMsg = resultEventListener.expectMsgType[Done] - doneMsg.tick shouldBe 3600L - doneMsg.noOfFailedPF shouldBe 0 - doneMsg.errorInSim shouldBe false - - // we expect that the scheduler answers with a SimulationSuccessfulMessage - // to the sender of the run request (this test) - expectMsg(SimulationSuccessfulMessage) - } - - "finish simulation correctly when endTick < pauseScheduleAtTick" in { - val (simScheduler, resultEventListener) = setupScheduler() - - simScheduler ! InitSimMessage - resultEventListener.expectMsg(Initializing) - - val triggeredAgent1 = TestProbe() - - val initTrigger1 = createMockInitTrigger() - simScheduler ! ScheduleTriggerMessage( - initTrigger1, - triggeredAgent1.ref - ) - triggeredAgent1.expectInitAndComplete( - simScheduler, - resultEventListener, - Seq(3600L) - ) - - resultEventListener.expectMsgType[InitComplete] - - triggeredAgent1.expectNoMessage() - - simScheduler ! StartScheduleMessage(Some(7200L)) - - resultEventListener.expectMsg(Simulating(0L, 3600L)) - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 900L - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L - - triggeredAgent1.expectAstAndComplete( - simScheduler, - resultEventListener, - 3600L - ) - - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 3600L - - // no ready msg here - - val doneMsg = resultEventListener.expectMsgType[Done] - doneMsg.tick shouldBe 3600L - doneMsg.noOfFailedPF shouldBe 0 - doneMsg.errorInSim shouldBe false - - // we expect that the scheduler answers with a SimulationSuccessfulMessage - // to the sender of the run request (this test) - expectMsg(SimulationSuccessfulMessage) - } - - "finish simulation correctly when endTick > first pauseScheduleAtTick" in { - val (simScheduler, resultEventListener) = setupScheduler( - timeConfig = defaultTimeConfig.copy(endDateTime = "2011-01-01 02:00:00") - ) - - simScheduler ! InitSimMessage - resultEventListener.expectMsg(Initializing) - - val triggeredAgent1 = TestProbe() - - simScheduler ! ScheduleTriggerMessage( - createMockInitTrigger(), - triggeredAgent1.ref - ) - triggeredAgent1.expectInitAndComplete( - simScheduler, - resultEventListener, - Seq(3600L) - ) - - resultEventListener.expectMsgType[InitComplete] - - triggeredAgent1.expectNoMessage() - - simScheduler ! StartScheduleMessage(Some(3600L)) - - resultEventListener.expectMsg(Simulating(0L, 3600L)) - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 900L - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L - - triggeredAgent1.expectAstAndComplete( - simScheduler, - resultEventListener, - 3600L, - Seq(7200L) - ) - - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 3600L - resultEventListener.expectMsgType[Ready].tick shouldBe 3600L - - simScheduler ! StartScheduleMessage(Some(7200L)) - - resultEventListener.expectMsg(Simulating(3601, 7200)) - - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 4500L - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 5400L - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 6300L - - triggeredAgent1.expectAstAndComplete( - simScheduler, - resultEventListener, - 7200L - ) - - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 7200L - resultEventListener.expectMsgType[Ready].tick shouldBe 7200L - - val doneMsg = resultEventListener.expectMsgType[Done] - doneMsg.tick shouldBe 7200L - doneMsg.noOfFailedPF shouldBe 0 - doneMsg.errorInSim shouldBe false - - // we expect that the scheduler answers with a SimulationSuccessfulMessage - // to the sender of the run request (this test) - expectMsg(SimulationSuccessfulMessage) - } - - "pause and finish simulation correctly for a pauseScheduleAtTick if endTick - pauseScheduleAtTick = 1" in { - val (simScheduler, resultEventListener) = setupScheduler() - - simScheduler ! InitSimMessage - resultEventListener.expectMsg(Initializing) - - val triggeredAgent1 = TestProbe() - - simScheduler ! ScheduleTriggerMessage( - createMockInitTrigger(), - triggeredAgent1.ref - ) - - triggeredAgent1.expectInitAndComplete( - simScheduler, - resultEventListener, - Seq(3600L) - ) - - resultEventListener.expectMsgType[InitComplete] - - triggeredAgent1.expectNoMessage() - - simScheduler ! StartScheduleMessage(Some(3599L)) - - resultEventListener.expectMsg(Simulating(0L, 3599L)) - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 900L - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L - resultEventListener.expectMsgType[Ready].tick shouldBe 3599L - - simScheduler ! StartScheduleMessage(Some(3600L)) - - resultEventListener.expectMsg(Simulating(3600L, 3600L)) - - triggeredAgent1.expectAstAndComplete( - simScheduler, - resultEventListener, - 3600L - ) - - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 3600L - resultEventListener.expectMsgType[Ready].tick shouldBe 3600L - val doneMsg = resultEventListener.expectMsgType[Done] - doneMsg.tick shouldBe 3600L - doneMsg.noOfFailedPF shouldBe 0 - doneMsg.errorInSim shouldBe false - - // we expect that the scheduler answers with a SimulationSuccessfulMessage - // to the sender of the run request (this test) - expectMsg(SimulationSuccessfulMessage) - } - - "work correctly if timeBinSize == 1 and readyCheckWindow == 1" in { - val triggeredAgents = Range.inclusive(1, 5).map(i => TestProbe(s"SPA_$i")) - - val (simScheduler, resultEventListener) = setupScheduler( - timeConfig = defaultTimeConfig.copy( - startDateTime = "2011-01-01 00:00:00", - endDateTime = "2011-01-01 00:00:10", - schedulerReadyCheckWindow = Some(1) - ) - ) - - triggeredAgents.foreach(actor => - // send to init trigger to scheduler - simScheduler ! ScheduleTriggerMessage( - createMockInitTrigger(), - actor.ref - ) - ) - - // tell scheduler to init - simScheduler ! InitSimMessage - - resultEventListener.expectMsg(Initializing) - - triggeredAgents.foreach { - _.expectInitAndComplete( - simScheduler, - resultEventListener, - Seq(1L) - ) - } - - // wait until init is done - resultEventListener.expectMsgType[InitComplete] - - // tell scheduler we want to run until tick 10 - simScheduler ! StartScheduleMessage(Some(10L)) - - // expect a simulating event - resultEventListener.expectMsg(Simulating(0L, 10L)) - - // interact with the scheduler for each tick - for (tick <- 1L to 9L) { - triggeredAgents.foreach { - _.expectAstAndComplete( - simScheduler, - resultEventListener, - tick, - Seq(tick + 1) - ) - } - // we expect exactly one check window passed event as - // all activity start triggers for the last tick should be answered - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe tick - } - - // answer with completion for the last tick, schedule another tick to test if the simulation event terminates as - // expected - triggeredAgents.foreach( - _.expectAstAndComplete( - simScheduler, - resultEventListener, - 10L, - Seq(11L) - ) - ) - - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 10L - resultEventListener.expectMsgType[Ready].tick shouldBe 10L - val doneMsg = resultEventListener.expectMsgType[Done] - doneMsg.tick shouldBe 10L - doneMsg.noOfFailedPF shouldBe 0 - doneMsg.errorInSim shouldBe false - - // finally expect a simulation successful message - expectMsg(SimulationSuccessfulMessage) - } - - /* exceptional cases */ - - "fail validation of StartScheduleMessage if pauseScheduleAtTick < nowInTicks" in { - val (simScheduler, resultEventListener) = setupScheduler() - - // observe if scheduler dies - val deathWatch = TestProbe() - deathWatch.watch(simScheduler) - - simScheduler ! InitSimMessage - resultEventListener.expectMsg(Initializing) - - val triggeredAgent1 = TestProbe() - - simScheduler ! ScheduleTriggerMessage( - createMockInitTrigger(), - triggeredAgent1.ref - ) - triggeredAgent1.expectInitAndComplete( - simScheduler, - resultEventListener, - Seq(900L) - ) - - resultEventListener.expectMsgType[InitComplete] - - simScheduler ! StartScheduleMessage(Some(1800L)) - resultEventListener.expectMsg(Simulating(0L, 1800L)) - - // should still live - deathWatch.expectNoMessage() - - // send faulty StartScheduleMessage - simScheduler ! StartScheduleMessage(Some(0L)) - - // exception should be thrown, - deathWatch.expectTerminated(simScheduler) - - resultEventListener.expectNoMessage() - } - - "do nothing when receiving StartScheduleTrigger during initialization" in { - val (simScheduler, resultEventListener) = setupScheduler() - - // observe if scheduler dies - val deathWatch = TestProbe() - deathWatch.watch(simScheduler) - - simScheduler ! InitSimMessage - resultEventListener.expectMsg(Initializing) - - simScheduler ! StartScheduleMessage(None) - - resultEventListener.expectNoMessage() - // scheduler should not die, but just continue - deathWatch.expectNoMessage() - } - - "do nothing when receiving StartScheduleTrigger and already started" in { - val (simScheduler, resultEventListener) = setupScheduler() - - // observe if scheduler dies - val deathWatch = TestProbe() - deathWatch.watch(simScheduler) - - simScheduler ! InitSimMessage - resultEventListener.expectMsg(Initializing) - - val triggeredAgent1 = TestProbe() - - simScheduler ! ScheduleTriggerMessage( - createMockInitTrigger(), - triggeredAgent1.ref - ) - triggeredAgent1.expectInitAndComplete( - simScheduler, - resultEventListener, - Seq(900L) - ) - - resultEventListener.expectMsgType[InitComplete] - - simScheduler ! StartScheduleMessage(Some(1800L)) - resultEventListener.expectMsg(Simulating(0L, 1800L)) - - simScheduler ! StartScheduleMessage(None) - - resultEventListener.expectNoMessage() - // scheduler should not die, but just continue - deathWatch.expectNoMessage() - } - - "handle PowerFlow failures when stopOnFailedPowerFlow = false" in { - val (simScheduler, resultEventListener) = setupScheduler() - - simScheduler ! InitSimMessage - resultEventListener.expectMsg(Initializing) - - val triggeredAgent1 = TestProbe() - - simScheduler ! ScheduleTriggerMessage( - createMockInitTrigger(), - triggeredAgent1.ref - ) - triggeredAgent1.expectInitAndComplete( - simScheduler, - resultEventListener, - Seq(0L) - ) - - resultEventListener.expectMsgType[InitComplete] - - simScheduler ! StartScheduleMessage(None) - resultEventListener.expectMsg(Simulating(0L, 3600L)) - - val receivedTrigger1 = triggeredAgent1.expectMsgType[TriggerWithIdMessage] - - triggeredAgent1.send(simScheduler, PowerFlowFailedMessage) - triggeredAgent1.send(simScheduler, PowerFlowFailedMessage) - triggeredAgent1.send(simScheduler, PowerFlowFailedMessage) - - resultEventListener.expectNoMessage() - - simScheduler ! CompletionMessage( - receivedTrigger1.triggerId, - None - ) - - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 900L - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L - resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 3600L - - val doneMsg = resultEventListener.expectMsgType[Done] - doneMsg.tick shouldBe 3600L - doneMsg.noOfFailedPF shouldBe 3 - doneMsg.errorInSim shouldBe false - - expectMsg(SimulationSuccessfulMessage) - } - - "handle PowerFlow failures when stopOnFailedPowerFlow = true" in { - val (simScheduler, resultEventListener) = - setupScheduler(stopOnFailedPowerFlow = true) - - simScheduler ! InitSimMessage - resultEventListener.expectMsg(Initializing) - - val triggeredAgent1 = TestProbe() - - simScheduler ! ScheduleTriggerMessage( - createMockInitTrigger(), - triggeredAgent1.ref - ) - triggeredAgent1.expectInitAndComplete( - simScheduler, - resultEventListener, - Seq(0L) - ) - - resultEventListener.expectMsgType[InitComplete] - - simScheduler ! StartScheduleMessage(None) - resultEventListener.expectMsg(Simulating(0L, 3600L)) - - val receivedTrigger1 = triggeredAgent1.expectMsgType[TriggerWithIdMessage] - - triggeredAgent1.send(simScheduler, PowerFlowFailedMessage) - triggeredAgent1.send(simScheduler, PowerFlowFailedMessage) - - resultEventListener.expectNoMessage() - - simScheduler ! CompletionMessage( - receivedTrigger1.triggerId, - None - ) - - val doneMsg = resultEventListener.expectMsgType[Done] - doneMsg.tick shouldBe 0L - doneMsg.noOfFailedPF shouldBe 2 - doneMsg.errorInSim shouldBe true - - expectMsg(SimulationFailureMessage) - } - - "terminate on child actor termination when initializing" in { - val (simScheduler, resultEventListener) = setupScheduler() - - simScheduler ! InitSimMessage - resultEventListener.expectMsg(Initializing) - - checkTerminationHandling(simScheduler, resultEventListener) - } - - "terminate on child actor termination when paused" in { - val (simScheduler, resultEventListener) = setupScheduler() - - simScheduler ! InitSimMessage - resultEventListener.expectMsg(Initializing) - - val triggeredAgent1 = TestProbe() - - simScheduler ! ScheduleTriggerMessage( - createMockInitTrigger(), - triggeredAgent1.ref - ) - triggeredAgent1.expectInitAndComplete( - simScheduler, - resultEventListener, - Seq(0L) - ) - - resultEventListener.expectMsgType[InitComplete] - - checkTerminationHandling(simScheduler, resultEventListener) - } - - "terminate on child actor termination when running" in { - val (simScheduler, resultEventListener) = setupScheduler(autostart = true) - - simScheduler ! InitSimMessage - resultEventListener.expectMsg(Initializing) - - val triggeredAgent1 = TestProbe() - - simScheduler ! ScheduleTriggerMessage( - createMockInitTrigger(), - triggeredAgent1.ref - ) - triggeredAgent1.expectInitAndComplete( - simScheduler, - resultEventListener, - Seq(0L) - ) - - resultEventListener.expectMsgType[InitComplete] - resultEventListener.expectMsg(Simulating(0L, 3600L)) - - checkTerminationHandling(simScheduler, resultEventListener) - } - } - - def createMockInitTrigger(): InitializeTrigger = { - val mockTrigger = mock[InitializeTrigger] - doReturn(SimonaConstants.INIT_SIM_TICK).when(mockTrigger).tick - mockTrigger - } - - private def checkTerminationHandling( - simScheduler: TestActorRef[SimScheduler], - resultEventListener: TestProbe - ): Unit = { - // observe scheduler for stopping - val deathWatch = TestProbe() - deathWatch.watch(simScheduler) - - // give scheduler a child that dies - val dyingActor = TestProbe() - simScheduler.watch(dyingActor.ref) - - dyingActor.ref ! PoisonPill - - resultEventListener.expectMsgType[Error] - expectMsg(SimulationFailureMessage) - - // scheduler should have died - deathWatch.expectTerminated(simScheduler) - } -} - -object SimSchedulerSpec extends UnitSpec { - - class DummySupervisor extends Actor { - override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy() { - case _: RuntimeException => SupervisorStrategy.stop - } - - override def receive: Receive = { case _ => } - } - - implicit class RichTriggeredAgent(private val triggeredAgent: TestProbe) { - - def expectInitAndComplete( - simScheduler: TestActorRef[SimScheduler], - resultEventListener: TestProbe, - newTicks: Seq[Long] = Seq.empty - ): Unit = - expectTriggerAndComplete[InitializeTrigger]( - simScheduler, - resultEventListener, - SimonaConstants.INIT_SIM_TICK, - newTicks - ) - - def expectAstAndComplete( - simScheduler: TestActorRef[SimScheduler], - resultEventListener: TestProbe, - expectedTick: Long, - newTicks: Seq[Long] = Seq.empty - ): Unit = - expectTriggerAndComplete[ActivityStartTrigger]( - simScheduler, - resultEventListener, - expectedTick, - newTicks - ) - - private def expectTriggerAndComplete[T <: Trigger]( - simScheduler: TestActorRef[SimScheduler], - resultEventListener: TestProbe, - expectedTick: Long, - newTicks: Seq[Long] = Seq.empty - )(implicit tag: ClassTag[T]): Unit = { - val receivedTrigger = - triggeredAgent.expectMsgType[TriggerWithIdMessage] - - receivedTrigger.trigger match { - case trigger: T => - trigger.tick shouldBe expectedTick - case unexpected => - fail(s"Received unexpected trigger $unexpected") - } - receivedTrigger.receiverActor shouldBe triggeredAgent.ref - - // when an agent is triggered, we can be sure that no result event - // is fired at least until the corresponding completion has been sent out - resultEventListener.expectNoMessage() - - val newTriggers = - Option.when(newTicks.nonEmpty)( - newTicks.map(tick => - ScheduleTriggerMessage( - ActivityStartTrigger(tick), - triggeredAgent.ref - ) - ) - ) - - triggeredAgent.send( - simScheduler, - CompletionMessage( - receivedTrigger.triggerId, - newTriggers - ) - ) - } - - } - -} diff --git a/src/test/scala/edu/ie3/simona/scheduler/TimeAdvancerSpec.scala b/src/test/scala/edu/ie3/simona/scheduler/TimeAdvancerSpec.scala new file mode 100644 index 0000000000..06ce65ee70 --- /dev/null +++ b/src/test/scala/edu/ie3/simona/scheduler/TimeAdvancerSpec.scala @@ -0,0 +1,796 @@ +/* + * © 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 akka.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import akka.actor.typed.scaladsl.adapter.TypedActorRefOps +import edu.ie3.simona.event.RuntimeEvent +import edu.ie3.simona.event.RuntimeEvent._ +import edu.ie3.simona.ontology.messages.SchedulerMessage +import edu.ie3.simona.ontology.messages.SchedulerMessage._ +import edu.ie3.simona.ontology.trigger.Trigger.ActivityStartTrigger +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import org.scalatest.matchers.should +import org.scalatest.wordspec.AnyWordSpecLike + +/** Also tests [[RuntimeNotifier]] + */ +class TimeAdvancerSpec + extends ScalaTestWithActorTestKit + with AnyWordSpecLike + with should.Matchers { + + "The TimeAdvancer should work correctly" when { + + "started checkWindow but without pauseTick" in { + val simulation = TestProbe[SchedulerMessage]("simulation") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val listener = TestProbe[RuntimeEvent]("listener") + + val timeAdvancer = spawn( + TimeAdvancer( + simulation.ref.toClassic, + Some(listener.ref), + Some(900), + 7200 + ) + ) + + val trig1 = ActivityStartTrigger(INIT_SIM_TICK) + timeAdvancer ! ScheduleTriggerMessage( + trig1, + scheduler.ref.toClassic + ) + + listener.expectNoMessage() + scheduler.expectNoMessage() + + // start simulation + timeAdvancer ! StartScheduleMessage() + + // tick -1 is activated + val tm1 = scheduler.expectMessageType[TriggerWithIdMessage] + tm1.trigger shouldBe trig1 + listener.expectMessage(Initializing) + + // tick -1 is completed + val trig2 = ActivityStartTrigger(0) + timeAdvancer ! CompletionMessage( + tm1.triggerId, + Some(ScheduleTriggerMessage(trig2, scheduler.ref.toClassic)) + ) + listener.expectMessageType[InitComplete] + + // tick 0 is activated automatically + val tm2 = scheduler.expectMessageType[TriggerWithIdMessage] + tm2.trigger shouldBe trig2 + listener.expectMessage(Simulating(0, 7200)) + + // tick 0 is completed + val trig3 = ActivityStartTrigger(3600) + timeAdvancer ! CompletionMessage( + tm2.triggerId, + Some(ScheduleTriggerMessage(trig3, scheduler.ref.toClassic)) + ) + listener.expectMessageType[CheckWindowPassed].tick shouldBe 900 + listener.expectMessageType[CheckWindowPassed].tick shouldBe 1800 + listener.expectMessageType[CheckWindowPassed].tick shouldBe 2700 + + // tick 3600 is activated automatically + val tm3 = scheduler.expectMessageType[TriggerWithIdMessage] + tm3.trigger shouldBe trig3 + listener.expectNoMessage() + + // tick 3600 is completed + val trig4 = ActivityStartTrigger(7200) + timeAdvancer ! CompletionMessage( + tm3.triggerId, + Some(ScheduleTriggerMessage(trig4, scheduler.ref.toClassic)) + ) + listener.expectMessageType[CheckWindowPassed].tick shouldBe 3600 + listener.expectMessageType[CheckWindowPassed].tick shouldBe 4500 + listener.expectMessageType[CheckWindowPassed].tick shouldBe 5400 + listener.expectMessageType[CheckWindowPassed].tick shouldBe 6300 + + // tick 7200 is activated automatically + val tm4 = scheduler.expectMessageType[TriggerWithIdMessage] + tm4.trigger shouldBe trig4 + listener.expectNoMessage() + + // tick 7200 is completed + timeAdvancer ! CompletionMessage( + tm4.triggerId, + None + ) + val doneMsg = listener.expectMessageType[Done] + doneMsg.tick shouldBe 7200 + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + simulation.expectMessage(SimulationSuccessfulMessage) + } + + "started without checkWindow and pauseTick" in { + val simulation = TestProbe[SchedulerMessage]("simulation") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val listener = TestProbe[RuntimeEvent]("listener") + + val timeAdvancer = spawn( + TimeAdvancer(simulation.ref.toClassic, Some(listener.ref), None, 3600) + ) + + val trig1 = ActivityStartTrigger(INIT_SIM_TICK) + timeAdvancer ! ScheduleTriggerMessage( + trig1, + scheduler.ref.toClassic + ) + + listener.expectNoMessage() + scheduler.expectNoMessage() + + // start simulation + timeAdvancer ! StartScheduleMessage() + + // tick -1 is activated + val tm1 = scheduler.expectMessageType[TriggerWithIdMessage] + tm1.trigger shouldBe trig1 + listener.expectMessage(Initializing) + + // tick -1 is completed + val trig2 = ActivityStartTrigger(0) + timeAdvancer ! CompletionMessage( + tm1.triggerId, + Some(ScheduleTriggerMessage(trig2, scheduler.ref.toClassic)) + ) + listener.expectMessageType[InitComplete] + + // tick 0 is activated automatically + val tm2 = scheduler.expectMessageType[TriggerWithIdMessage] + tm2.trigger shouldBe trig2 + listener.expectMessage(Simulating(0, 3600)) + + // tick 0 is completed + val trig3 = ActivityStartTrigger(3600) + timeAdvancer ! CompletionMessage( + tm2.triggerId, + Some(ScheduleTriggerMessage(trig3, scheduler.ref.toClassic)) + ) + + // tick 3600 is activated automatically + val tm3 = scheduler.expectMessageType[TriggerWithIdMessage] + tm3.trigger shouldBe trig3 + listener.expectNoMessage() + + // tick 3600 is completed + timeAdvancer ! CompletionMessage( + tm3.triggerId + ) + + val doneMsg = listener.expectMessageType[Done] + doneMsg.tick shouldBe 3600 + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + simulation.expectMessage(SimulationSuccessfulMessage) + } + + "paused and started after initialization" in { + val simulation = TestProbe[SchedulerMessage]("simulation") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val listener = TestProbe[RuntimeEvent]("listener") + + val timeAdvancer = spawn( + TimeAdvancer( + simulation.ref.toClassic, + Some(listener.ref), + Some(900), + 3600 + ) + ) + val trig1 = ActivityStartTrigger(INIT_SIM_TICK) + timeAdvancer ! ScheduleTriggerMessage( + trig1, + scheduler.ref.toClassic + ) + + listener.expectNoMessage() + scheduler.expectNoMessage() + + // start simulation + timeAdvancer ! StartScheduleMessage(Some(INIT_SIM_TICK)) + + // tick -1 is activated + val tm1 = scheduler.expectMessageType[TriggerWithIdMessage] + tm1.trigger shouldBe trig1 + listener.expectMessage(Initializing) + + // tick -1 is completed + val trig2 = ActivityStartTrigger(3600) + timeAdvancer ! CompletionMessage( + tm1.triggerId, + Some(ScheduleTriggerMessage(trig2, scheduler.ref.toClassic)) + ) + listener.expectMessageType[InitComplete] + listener.expectMessageType[Ready].tick shouldBe INIT_SIM_TICK + + // simulation should be paused + scheduler.expectNoMessage() + listener.expectNoMessage() + + // start again + timeAdvancer ! StartScheduleMessage() + + // tick 3600 is activated + val tm2 = scheduler.expectMessageType[TriggerWithIdMessage] + tm2.trigger shouldBe trig2 + listener.expectMessage(Simulating(0, 3600)) + + // tick 3600 is completed + timeAdvancer ! CompletionMessage( + tm2.triggerId, + None + ) + // check window events should only come now, since we paused at -1 before + listener.expectMessageType[CheckWindowPassed].tick shouldBe 900 + listener.expectMessageType[CheckWindowPassed].tick shouldBe 1800 + listener.expectMessageType[CheckWindowPassed].tick shouldBe 2700 + + val doneMsg = listener.expectMessageType[Done] + doneMsg.tick shouldBe 3600 + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + simulation.expectMessage(SimulationSuccessfulMessage) + } + + "paused and started and there is a gap between StartSchedule tick and next activation tick" in { + val simulation = TestProbe[SchedulerMessage]("simulation") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val listener = TestProbe[RuntimeEvent]("listener") + + val timeAdvancer = spawn( + TimeAdvancer( + simulation.ref.toClassic, + Some(listener.ref), + Some(900), + 5400 + ) + ) + val trig1 = ActivityStartTrigger(INIT_SIM_TICK) + timeAdvancer ! ScheduleTriggerMessage( + trig1, + scheduler.ref.toClassic + ) + + listener.expectNoMessage() + scheduler.expectNoMessage() + + // start simulation + timeAdvancer ! StartScheduleMessage(Some(3600)) + + // tick -1 is activated + val tm1 = scheduler.expectMessageType[TriggerWithIdMessage] + tm1.trigger shouldBe trig1 + listener.expectMessage(Initializing) + + // tick -1 is completed + val trig2 = ActivityStartTrigger(0) + timeAdvancer ! CompletionMessage( + tm1.triggerId, + Some(ScheduleTriggerMessage(trig2, scheduler.ref.toClassic)) + ) + listener.expectMessageType[InitComplete] + + // tick 0 is activated automatically + val tm2 = scheduler.expectMessageType[TriggerWithIdMessage] + tm2.trigger shouldBe trig2 + listener.expectMessage(Simulating(0, 3600)) + + // tick 0 is completed + val trig3 = ActivityStartTrigger(5400) + timeAdvancer ! CompletionMessage( + tm2.triggerId, + Some(ScheduleTriggerMessage(trig3, scheduler.ref.toClassic)) + ) + listener.expectMessageType[CheckWindowPassed].tick shouldBe 900 + listener.expectMessageType[CheckWindowPassed].tick shouldBe 1800 + listener.expectMessageType[CheckWindowPassed].tick shouldBe 2700 + listener.expectMessageType[CheckWindowPassed].tick shouldBe 3600 + listener.expectMessageType[Ready].tick shouldBe 3600 + + // simulation should be paused + scheduler.expectNoMessage() + listener.expectNoMessage() + + // start again + timeAdvancer ! StartScheduleMessage() + + // tick 5400 is activated + val tm3 = scheduler.expectMessageType[TriggerWithIdMessage] + tm3.trigger shouldBe trig3 + listener.expectMessage(Simulating(3601, 5400)) + + // tick 5400 is completed + timeAdvancer ! CompletionMessage( + tm3.triggerId, + None + ) + listener.expectMessageType[CheckWindowPassed].tick shouldBe 4500 + val doneMsg = listener.expectMessageType[Done] + doneMsg.tick shouldBe 5400 + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + simulation.expectMessage(SimulationSuccessfulMessage) + } + + "paused and endTick - pauseScheduleAtTick == 1" in { + val simulation = TestProbe[SchedulerMessage]("simulation") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val listener = TestProbe[RuntimeEvent]("listener") + + val timeAdvancer = spawn( + TimeAdvancer( + simulation.ref.toClassic, + Some(listener.ref), + Some(900), + 3600 + ) + ) + val trig1 = ActivityStartTrigger(INIT_SIM_TICK) + timeAdvancer ! ScheduleTriggerMessage( + trig1, + scheduler.ref.toClassic + ) + + listener.expectNoMessage() + scheduler.expectNoMessage() + + // start simulation + timeAdvancer ! StartScheduleMessage(Some(3599)) + + // tick -1 is activated + val tm1 = scheduler.expectMessageType[TriggerWithIdMessage] + tm1.trigger shouldBe trig1 + listener.expectMessage(Initializing) + + // tick -1 is completed + val trig2 = ActivityStartTrigger(0) + timeAdvancer ! CompletionMessage( + tm1.triggerId, + Some(ScheduleTriggerMessage(trig2, scheduler.ref.toClassic)) + ) + listener.expectMessageType[InitComplete] + + // tick 0 is activated automatically + val tm2 = scheduler.expectMessageType[TriggerWithIdMessage] + tm2.trigger shouldBe trig2 + listener.expectMessage(Simulating(0, 3599)) + + // tick 0 is completed + val trig3 = ActivityStartTrigger(3600) + timeAdvancer ! CompletionMessage( + tm2.triggerId, + Some(ScheduleTriggerMessage(trig3, scheduler.ref.toClassic)) + ) + listener.expectMessageType[CheckWindowPassed].tick shouldBe 900 + listener.expectMessageType[CheckWindowPassed].tick shouldBe 1800 + listener.expectMessageType[CheckWindowPassed].tick shouldBe 2700 + listener.expectMessageType[Ready].tick shouldBe 3599 + + // simulation should be paused + scheduler.expectNoMessage() + listener.expectNoMessage() + + // start again + timeAdvancer ! StartScheduleMessage() + + // tick 3600 is activated + val tm3 = scheduler.expectMessageType[TriggerWithIdMessage] + tm3.trigger shouldBe trig3 + listener.expectMessage(Simulating(3600, 3600)) + + // tick 3600 is completed + timeAdvancer ! CompletionMessage( + tm3.triggerId + ) + val doneMsg = listener.expectMessageType[Done] + doneMsg.tick shouldBe 3600 + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + simulation.expectMessage(SimulationSuccessfulMessage) + } + + "activation has been scheduled after endTick" in { + val simulation = TestProbe[SchedulerMessage]("simulation") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val listener = TestProbe[RuntimeEvent]("listener") + + val timeAdvancer = spawn( + TimeAdvancer( + simulation.ref.toClassic, + Some(listener.ref), + Some(1800), + 3600 + ) + ) + val trig1 = ActivityStartTrigger(0) + timeAdvancer ! ScheduleTriggerMessage( + trig1, + scheduler.ref.toClassic + ) + + listener.expectNoMessage() + scheduler.expectNoMessage() + + // start simulation + timeAdvancer ! StartScheduleMessage() + + // tick 0 is activated + val tm1 = scheduler.expectMessageType[TriggerWithIdMessage] + tm1.trigger shouldBe trig1 + listener.expectMessage(Simulating(0, 3600)) + + // tick 0 is completed + val trig2 = ActivityStartTrigger(3601) + timeAdvancer ! CompletionMessage( + tm1.triggerId, + Some(ScheduleTriggerMessage(trig2, scheduler.ref.toClassic)) + ) + listener.expectMessageType[InitComplete] + listener.expectMessageType[CheckWindowPassed].tick shouldBe 1800 + + // tick 3601 should not be activated! + scheduler.expectNoMessage() + + val doneMsg = listener.expectMessageType[Done] + doneMsg.tick shouldBe 3600 + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + simulation.expectMessage(SimulationSuccessfulMessage) + } + + "no next trigger has been supplied" in { + val simulation = TestProbe[SchedulerMessage]("simulation") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val listener = TestProbe[RuntimeEvent]("listener") + + val timeAdvancer = spawn( + TimeAdvancer( + simulation.ref.toClassic, + Some(listener.ref), + Some(900), + 3600 + ) + ) + val trig1 = ActivityStartTrigger(0) + timeAdvancer ! ScheduleTriggerMessage( + trig1, + scheduler.ref.toClassic + ) + + listener.expectNoMessage() + scheduler.expectNoMessage() + + // start simulation + timeAdvancer ! StartScheduleMessage() + + // tick 0 is activated + val tm1 = scheduler.expectMessageType[TriggerWithIdMessage] + tm1.trigger shouldBe trig1 + listener.expectMessage(Simulating(0, 3600)) + + // tick 0 is completed + timeAdvancer ! CompletionMessage( + tm1.triggerId, + None + ) + listener.expectMessageType[InitComplete] + listener.expectMessageType[CheckWindowPassed].tick shouldBe 900 + listener.expectMessageType[CheckWindowPassed].tick shouldBe 1800 + listener.expectMessageType[CheckWindowPassed].tick shouldBe 2700 + + // scheduler should not be activated! + scheduler.expectNoMessage() + + val doneMsg = listener.expectMessageType[Done] + doneMsg.tick shouldBe 3600 + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + simulation.expectMessage(SimulationSuccessfulMessage) + } + + "endTick < pauseScheduleAtTick" in { + val simulation = TestProbe[SchedulerMessage]("simulation") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val listener = TestProbe[RuntimeEvent]("listener") + + val timeAdvancer = spawn( + TimeAdvancer( + simulation.ref.toClassic, + Some(listener.ref), + Some(1800), + 3600 + ) + ) + val trig1 = ActivityStartTrigger(0) + timeAdvancer ! ScheduleTriggerMessage( + trig1, + scheduler.ref.toClassic + ) + + listener.expectNoMessage() + scheduler.expectNoMessage() + + // start simulation + timeAdvancer ! StartScheduleMessage(Some(7200)) + + // tick 0 is activated + val tm1 = scheduler.expectMessageType[TriggerWithIdMessage] + tm1.trigger shouldBe trig1 + listener.expectMessage(Simulating(0, 3600)) + + // tick 0 is completed + timeAdvancer ! CompletionMessage( + tm1.triggerId, + None + ) + listener.expectMessageType[InitComplete] + listener.expectMessageType[CheckWindowPassed].tick shouldBe 1800 + + // scheduler should not be activated! + scheduler.expectNoMessage() + + val doneMsg = listener.expectMessageType[Done] + doneMsg.tick shouldBe 3600 + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + simulation.expectMessage(SimulationSuccessfulMessage) + } + + "endTick == pauseScheduleAtTick" in { + val simulation = TestProbe[SchedulerMessage]("simulation") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val listener = TestProbe[RuntimeEvent]("listener") + + val timeAdvancer = spawn( + TimeAdvancer( + simulation.ref.toClassic, + Some(listener.ref), + Some(900), + 1800 + ) + ) + val trig1 = ActivityStartTrigger(0) + timeAdvancer ! ScheduleTriggerMessage( + trig1, + scheduler.ref.toClassic + ) + + listener.expectNoMessage() + scheduler.expectNoMessage() + + // start simulation + timeAdvancer ! StartScheduleMessage(Some(1800)) + + // tick 0 is activated + val tm1 = scheduler.expectMessageType[TriggerWithIdMessage] + tm1.trigger shouldBe trig1 + listener.expectMessage(Simulating(0, 1800)) + + // tick 0 is completed + timeAdvancer ! CompletionMessage( + tm1.triggerId, + None + ) + listener.expectMessageType[InitComplete] + listener.expectMessageType[CheckWindowPassed].tick shouldBe 900 + + // scheduler should not be activated! + scheduler.expectNoMessage() + + val doneMsg = listener.expectMessageType[Done] + doneMsg.tick shouldBe 1800 + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + simulation.expectMessage(SimulationSuccessfulMessage) + } + } + + "The TimeAdvancer should fail and stop" when { + + "wrong next tick has been supplied" in { + val simulation = TestProbe[SchedulerMessage]("simulation") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val listener = TestProbe[RuntimeEvent]("listener") + + val timeAdvancer = spawn( + TimeAdvancer( + simulation.ref.toClassic, + Some(listener.ref), + Some(900), + 1800 + ) + ) + val trig1 = ActivityStartTrigger(0) + timeAdvancer ! ScheduleTriggerMessage( + trig1, + scheduler.ref.toClassic + ) + + // start simulation + timeAdvancer ! StartScheduleMessage(Some(1800)) + + // tick 0 is activated + val tm1 = scheduler.expectMessageType[TriggerWithIdMessage] + tm1.trigger shouldBe trig1 + listener.expectMessage(Simulating(0, 1800)) + + // tick 0 is completed + // INIT_SIM_TICK is earlier than 0, should fail + val trig2 = ActivityStartTrigger(INIT_SIM_TICK) + timeAdvancer ! CompletionMessage( + tm1.triggerId, + Some(ScheduleTriggerMessage(trig2, scheduler.ref.toClassic)) + ) + listener.expectMessageType[Error].errMsg should include( + "tick -1, although current active tick was 0" + ) + val doneMsg = listener.expectMessageType[Done] + doneMsg.tick shouldBe 0 + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe true + + // scheduler should not be activated! + scheduler.expectNoMessage() + + scheduler.expectTerminated(timeAdvancer) + + simulation.expectMessage(SimulationFailureMessage) + } + + "activation is completed with wrong triggerId" in { + val simulation = TestProbe[SchedulerMessage]("simulation") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val listener = TestProbe[RuntimeEvent]("listener") + + val timeAdvancer = spawn( + TimeAdvancer( + simulation.ref.toClassic, + Some(listener.ref), + Some(900), + 1800 + ) + ) + val trig1 = ActivityStartTrigger(0) + timeAdvancer ! ScheduleTriggerMessage( + trig1, + scheduler.ref.toClassic + ) + + // start simulation + timeAdvancer ! StartScheduleMessage() + + // tick 0 is activated + val tm1 = scheduler.expectMessageType[TriggerWithIdMessage] + tm1.trigger shouldBe trig1 + listener.expectMessage(Simulating(0, 1800)) + + // tick 0 is completed + val trig2 = ActivityStartTrigger(INIT_SIM_TICK) + timeAdvancer ! CompletionMessage( + tm1.triggerId + 1, // WRONG trigger id + Some(ScheduleTriggerMessage(trig2, scheduler.ref.toClassic)) + ) + listener.expectMessageType[Error].errMsg should include("was expected") + val doneMsg = listener.expectMessageType[Done] + doneMsg.tick shouldBe 0 + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe true + + // scheduler should not be activated! + scheduler.expectNoMessage() + + scheduler.expectTerminated(timeAdvancer) + + simulation.expectMessage(SimulationFailureMessage) + } + + "receiving error message while uninitialized" in { + val simulation = TestProbe[SchedulerMessage]("simulation") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val listener = TestProbe[RuntimeEvent]("listener") + + val timeAdvancer = spawn( + TimeAdvancer(simulation.ref.toClassic, Some(listener.ref), None, 1800) + ) + + // Send stop message + timeAdvancer ! Stop("Test message") + + // we cannot check the console, thus just check if time advancer died + scheduler.expectTerminated(timeAdvancer) + + simulation.expectMessage(SimulationFailureMessage) + } + + "receiving error message while inactive" in { + val simulation = TestProbe[SchedulerMessage]("simulation") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val listener = TestProbe[RuntimeEvent]("listener") + + val timeAdvancer = spawn( + TimeAdvancer(simulation.ref.toClassic, Some(listener.ref), None, 1800) + ) + val trig1 = ActivityStartTrigger(1) + timeAdvancer ! ScheduleTriggerMessage( + trig1, + scheduler.ref.toClassic + ) + + // Send stop message + timeAdvancer ! Stop("Test message") + + listener.expectMessageType[Error].errMsg should include("Test message") + val doneMsg = listener.expectMessageType[Done] + doneMsg.tick shouldBe 1 + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe true + + scheduler.expectTerminated(timeAdvancer) + + simulation.expectMessage(SimulationFailureMessage) + } + + "receiving error message while active" in { + val simulation = TestProbe[SchedulerMessage]("simulation") + val scheduler = TestProbe[SchedulerMessage]("scheduler") + val listener = TestProbe[RuntimeEvent]("listener") + + val timeAdvancer = spawn( + TimeAdvancer( + simulation.ref.toClassic, + Some(listener.ref), + Some(900), + 1800 + ) + ) + val trig1 = ActivityStartTrigger(0) + timeAdvancer ! ScheduleTriggerMessage( + trig1, + scheduler.ref.toClassic + ) + + // start simulation + timeAdvancer ! StartScheduleMessage() + + // tick 0 is activated + val tm1 = scheduler.expectMessageType[TriggerWithIdMessage] + tm1.trigger shouldBe trig1 + listener.expectMessage(Simulating(0, 1800)) + + // Send stop message + timeAdvancer ! Stop("Test message") + + listener.expectMessageType[Error].errMsg should include("Test message") + val doneMsg = listener.expectMessageType[Done] + doneMsg.tick shouldBe 0 + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe true + + scheduler.expectTerminated(timeAdvancer) + + simulation.expectMessage(SimulationFailureMessage) + } + + } +} diff --git a/src/test/scala/edu/ie3/simona/service/ServiceBaseStateDataSpec.scala b/src/test/scala/edu/ie3/simona/service/ServiceBaseStateDataSpec.scala index 58f51f1aa2..04561e298b 100644 --- a/src/test/scala/edu/ie3/simona/service/ServiceBaseStateDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/ServiceBaseStateDataSpec.scala @@ -30,22 +30,20 @@ class ServiceBaseStateDataSpec "State data for services" should { "convert an undefined optional tick to None on attempt to convert it to a trigger message" in { - ServiceActivationBaseStateData.tickToScheduleTriggerMessages( + ServiceActivationBaseStateData.tickToScheduleTriggerMessage( None, self ) shouldBe None } "convert an given tick to correct sequence of scheduler messages" in { - ServiceActivationBaseStateData.tickToScheduleTriggerMessages( + ServiceActivationBaseStateData.tickToScheduleTriggerMessage( Some(5L), self ) shouldBe Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(5L), - self - ) + ScheduleTriggerMessage( + ActivityStartTrigger(5L), + self ) ) } 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 91a3e995dd..511924ee8f 100644 --- a/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/ev/ExtEvDataServiceSpec.scala @@ -81,8 +81,7 @@ class ExtEvDataServiceSpec extEvData(evService) ) ), - triggerId, - evService + triggerId ) ) @@ -120,8 +119,7 @@ class ExtEvDataServiceSpec extEvData(evService) ) ), - 1L, - evService + 1L ) ) @@ -147,8 +145,7 @@ class ExtEvDataServiceSpec extEvData(evService) ) ), - 1L, - evService + 1L ) ) scheduler.expectMsgType[CompletionMessage] @@ -194,8 +191,7 @@ class ExtEvDataServiceSpec extData ) ), - 1L, - evService + 1L ) ) scheduler.expectMsgType[CompletionMessage] @@ -207,8 +203,7 @@ class ExtEvDataServiceSpec ActivityStartTrigger( 0L ), - 2L, - evService + 2L ), scheduler.ref ) @@ -234,8 +229,7 @@ class ExtEvDataServiceSpec extData ) ), - 1L, - evService + 1L ) ) scheduler.expectMsgType[CompletionMessage] @@ -273,8 +267,7 @@ class ExtEvDataServiceSpec ActivityStartTrigger( tick ), - triggerId, - evService + triggerId ) ) @@ -346,8 +339,7 @@ class ExtEvDataServiceSpec extData ) ), - 1L, - evService + 1L ) ) scheduler.expectMsgType[CompletionMessage] @@ -370,8 +362,7 @@ class ExtEvDataServiceSpec ActivityStartTrigger( tick ), - triggerId, - evService + triggerId ) ) @@ -409,8 +400,7 @@ class ExtEvDataServiceSpec extData ) ), - 1L, - evService + 1L ) ) scheduler.expectMsgType[CompletionMessage] @@ -453,8 +443,7 @@ class ExtEvDataServiceSpec ActivityStartTrigger( tick ), - triggerId, - evService + triggerId ) ) @@ -524,8 +513,7 @@ class ExtEvDataServiceSpec extData ) ), - 1L, - evService + 1L ) ) scheduler.expectMsgType[CompletionMessage] @@ -568,8 +556,7 @@ class ExtEvDataServiceSpec ActivityStartTrigger( tick ), - triggerId, - evService + triggerId ) ) @@ -587,24 +574,13 @@ class ExtEvDataServiceSpec ) ) - scheduler.expectMsg( - CompletionMessage( - triggerId, - Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(tick), - evcs1.ref - ), - ScheduleTriggerMessage( - ActivityStartTrigger(tick), - evcs2.ref - ) - ) - ) - ) + scheduler.expectMsgAllOf( + ScheduleTriggerMessage(ActivityStartTrigger(tick), evcs1.ref), + ScheduleTriggerMessage(ActivityStartTrigger(tick), evcs2.ref) ) + scheduler.expectMsg(CompletionMessage(triggerId, None)) + // no response expected extData.receiveTriggerQueue shouldBe empty } @@ -626,8 +602,7 @@ class ExtEvDataServiceSpec extData ) ), - 1L, - evService + 1L ) ) scheduler.expectMsgType[CompletionMessage] @@ -663,8 +638,7 @@ class ExtEvDataServiceSpec ActivityStartTrigger( tick ), - triggerId, - evService + triggerId ) ) @@ -676,19 +650,14 @@ class ExtEvDataServiceSpec ) scheduler.expectMsg( - CompletionMessage( - triggerId, - Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(tick), - evcs1.ref - ) - ) - ) + ScheduleTriggerMessage( + ActivityStartTrigger(tick), + evcs1.ref ) ) + scheduler.expectMsg(CompletionMessage(triggerId, None)) + // no response expected extData.receiveTriggerQueue shouldBe empty } diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala index e43affee62..cd5f1816f8 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySpec.scala @@ -327,8 +327,7 @@ class PrimaryServiceProxySpec proxyRef ! TriggerWithIdMessage( InitializeServiceTrigger(initStateData), - 0L, - self + 0L ) expectMsg(CompletionMessage(0L, None)) } @@ -668,8 +667,7 @@ class PrimaryServiceProxySpec ) fakeProxyRef ! TriggerWithIdMessage( InitializeServiceTrigger(initStateData), - 0L, - self + 0L ) expectMsg(CompletionMessage(0L, None)) diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySqlIT.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySqlIT.scala index cbd19b5232..f44c5c6218 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySqlIT.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceProxySqlIT.scala @@ -114,8 +114,7 @@ class PrimaryServiceProxySqlIT proxyRef, TriggerWithIdMessage( InitializeServiceTrigger(initData), - triggerIdInit1, - proxyRef + triggerIdInit1 ) ) @@ -159,8 +158,7 @@ class PrimaryServiceProxySqlIT workerRef, TriggerWithIdMessage( initTriggerMsg.trigger, - triggerIdInit2, - workerRef + triggerIdInit2 ) ) @@ -168,11 +166,9 @@ class PrimaryServiceProxySqlIT CompletionMessage( triggerIdInit2, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(0L), - workerRef - ) + ScheduleTriggerMessage( + ActivityStartTrigger(0L), + workerRef ) ) ) 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 359bc6fbaf..aca3f9aaa0 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSpec.scala @@ -146,9 +146,7 @@ class PrimaryServiceWorkerSpec } /* We expect a request to be triggered in tick 0 */ maybeTriggerMessages shouldBe Some( - Seq( - ScheduleTriggerMessage(ActivityStartTrigger(0L), serviceRef) - ) + ScheduleTriggerMessage(ActivityStartTrigger(0L), serviceRef) ) case Failure(_) => fail("Initialisation with supported init data is not meant to fail.") @@ -158,10 +156,9 @@ class PrimaryServiceWorkerSpec /* Init the service actor */ serviceRef ! TriggerWithIdMessage( InitializeServiceTrigger(validInitData), - 0L, - self + 0L ) - expectCompletionMessage() + expectMsgType[CompletionMessage] "refuse registration for wrong registration request" in { serviceRef ! RegisterForWeatherMessage(51.4843281, 7.4116482) @@ -177,8 +174,8 @@ class PrimaryServiceWorkerSpec /* We cannot directly check, if the requesting actor is among the subscribers, therefore we ask the actor to * provide data to all subscribed actors and check, if the subscribed probe gets one */ - serviceRef ! TriggerWithIdMessage(ActivityStartTrigger(0L), 1L, self) - expectCompletionMessage() + serviceRef ! TriggerWithIdMessage(ActivityStartTrigger(0L), 1L) + expectMsgType[CompletionMessage] systemParticipant.expectMsgAllClassOf(classOf[ProvidePrimaryDataMessage]) } @@ -204,7 +201,7 @@ class PrimaryServiceWorkerSpec val announcePrimaryData = PrivateMethod[ ( PrimaryServiceInitializedStateData[PValue], - Option[Seq[SchedulerMessage.ScheduleTriggerMessage]] + Option[SchedulerMessage.ScheduleTriggerMessage] ) ](Symbol("announcePrimaryData")) val tick = 0L @@ -216,7 +213,7 @@ class PrimaryServiceWorkerSpec primaryData, serviceStateData ) match { - case (updatedStateData, maybeTriggerMessages) => + case (updatedStateData, maybeTriggerMessage) => /* Check updated state data */ inside(updatedStateData) { case PrimaryServiceInitializedStateData( @@ -230,22 +227,13 @@ class PrimaryServiceWorkerSpec activationTicks.size shouldBe 0 } /* Check trigger messages */ - maybeTriggerMessages match { - case Some(triggerSeq) => - triggerSeq.size shouldBe 1 - triggerSeq.headOption match { - case Some( - ScheduleTriggerMessage( - ActivityStartTrigger(triggerTick), - actorToBeScheduled - ) - ) => - triggerTick shouldBe 900L - actorToBeScheduled shouldBe serviceRef - case Some(value) => - fail(s"Got wrong trigger messages: '$value'.") - case None => fail("Did expect to get at least on trigger.") - } + maybeTriggerMessage match { + case Some(trigger) => + trigger shouldBe + ScheduleTriggerMessage( + ActivityStartTrigger(900L), + serviceRef + ) case None => fail("Expect a trigger message for tick 900.") } } @@ -265,7 +253,7 @@ class PrimaryServiceWorkerSpec val processDataAndAnnounce = PrivateMethod[ ( PrimaryServiceInitializedStateData[PValue], - Option[Seq[SchedulerMessage.ScheduleTriggerMessage]] + Option[SchedulerMessage.ScheduleTriggerMessage] ) ](Symbol("processDataAndAnnounce")) @@ -291,12 +279,16 @@ class PrimaryServiceWorkerSpec _, _ ), - maybeTriggerMessages + maybeTriggerMessage ) => nextActivationTick shouldBe Some(900L) - maybeTriggerMessages match { - case Some(triggerSeq) => triggerSeq.size shouldBe 1 - case None => fail("Expect a trigger for tick 900.") + maybeTriggerMessage match { + case Some(triggerSeq) => + triggerSeq shouldBe ScheduleTriggerMessage( + ActivityStartTrigger(900L), + serviceRef + ) + case None => fail("Expect a trigger for tick 900.") } } expectNoMessage() @@ -343,15 +335,14 @@ class PrimaryServiceWorkerSpec val triggerId = 2L serviceRef ! TriggerWithIdMessage( ActivityStartTrigger(200L), - triggerId, - self + triggerId ) inside(expectMsgClass(classOf[CompletionMessage])) { - case CompletionMessage(actualTriggerId, newTriggers) => + case CompletionMessage(actualTriggerId, newTrigger) => actualTriggerId shouldBe triggerId - newTriggers match { - case Some(triggerSeq) => triggerSeq.size shouldBe 1 - case None => fail("Expect a trigger for tick 1800.") + newTrigger match { + case Some(trigger) => trigger.trigger.tick shouldBe 1800L + case None => fail("Expect a trigger for tick 1800.") } } expectNoMessage() @@ -361,8 +352,7 @@ class PrimaryServiceWorkerSpec val triggerId = 3L serviceRef ! TriggerWithIdMessage( ActivityStartTrigger(900L), - triggerId, - self + triggerId ) inside(expectMsgClass(classOf[CompletionMessage])) { case CompletionMessage(actualTriggerId, newTriggers) => diff --git a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSqlIT.scala b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSqlIT.scala index 703f764d20..864d0c5632 100644 --- a/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSqlIT.scala +++ b/src/test/scala/edu/ie3/simona/service/primary/PrimaryServiceWorkerSqlIT.scala @@ -152,8 +152,7 @@ class PrimaryServiceWorkerSqlIT serviceRef, TriggerWithIdMessage( InitializeServiceTrigger(initData), - triggerId1, - serviceRef + triggerId1 ) ) @@ -161,11 +160,9 @@ class PrimaryServiceWorkerSqlIT CompletionMessage( triggerId1, Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(firstTick), - serviceRef - ) + ScheduleTriggerMessage( + ActivityStartTrigger(firstTick), + serviceRef ) ) ) @@ -185,8 +182,7 @@ class PrimaryServiceWorkerSqlIT serviceRef, TriggerWithIdMessage( ActivityStartTrigger(firstTick), - triggerId2, - serviceRef + triggerId2 ) ) diff --git a/src/test/scala/edu/ie3/simona/service/weather/WeatherServiceSpec.scala b/src/test/scala/edu/ie3/simona/service/weather/WeatherServiceSpec.scala index 1c0f229707..cb3517919e 100644 --- a/src/test/scala/edu/ie3/simona/service/weather/WeatherServiceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/weather/WeatherServiceSpec.scala @@ -119,33 +119,21 @@ class WeatherServiceSpec simonaConfig.simona.input.weather.datasource ) ), - triggerId, - weatherActor + triggerId + ) + + expectMsg( + CompletionMessage( + 0L, + Some( + ScheduleTriggerMessage( + ActivityStartTrigger(0L), + weatherActor + ) + ) + ) ) - expectMsgType[CompletionMessage] match { - case CompletionMessage(triggerId, newTriggers) => - triggerId shouldBe 0 - newTriggers match { - case Some(seq) => - seq.size shouldBe 1 - seq.headOption match { - case Some( - ScheduleTriggerMessage( - ActivityStartTrigger(nextTick), - actorRef - ) - ) => - nextTick shouldBe 0 - actorRef shouldBe weatherActor - case x => - fail( - s"The sequence of next triggers contains the wrong element '$x'." - ) - } - case None => fail("Expected new triggers, but got nothing.") - } - } } "announce failed weather registration on invalid coordinate" in { @@ -193,7 +181,7 @@ class WeatherServiceSpec "send out correct weather information upon activity start trigger and request the triggering for the next tick" in { /* Send out an activity start trigger as the scheduler */ - weatherActor ! TriggerWithIdMessage(ActivityStartTrigger(0L), 1L, self) + weatherActor ! TriggerWithIdMessage(ActivityStartTrigger(0L), 1L) /* Expect a weather provision (as this test is registered via the test actor) and a completion message (as the * test is also the scheduler) */ @@ -212,26 +200,18 @@ class WeatherServiceSpec nextDataTick shouldBe Some(3600L) case CompletionMessage(triggerId, nextTriggers) => triggerId shouldBe 1L - nextTriggers match { - case Some(triggerSeq) => - triggerSeq.size shouldBe 1 - triggerSeq.headOption match { - case Some(msg) => - msg shouldBe ScheduleTriggerMessage( - ActivityStartTrigger(3600L), - weatherActor - ) - case None => - fail("Did expect an ActivityStartTrigger for 3600 L.") - } - case None => fail("Did expect to get a new trigger.") - } + nextTriggers shouldBe Some( + ScheduleTriggerMessage( + ActivityStartTrigger(3600L), + weatherActor + ) + ) } } "sends out correct weather information when triggered again and does not as for triggering, if the end is reached" in { /* Send out an activity start trigger as the scheduler */ - weatherActor ! TriggerWithIdMessage(ActivityStartTrigger(3600L), 2L, self) + weatherActor ! TriggerWithIdMessage(ActivityStartTrigger(3600L), 2L) /* Expect a weather provision (as this test is registered via the test actor) and a completion message (as the * test is also the scheduler) */ diff --git a/src/test/scala/edu/ie3/simona/sim/SimonaSimFailSpec.scala b/src/test/scala/edu/ie3/simona/sim/SimonaSimFailSpec.scala index 281cd34b5f..529e326249 100644 --- a/src/test/scala/edu/ie3/simona/sim/SimonaSimFailSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/SimonaSimFailSpec.scala @@ -6,6 +6,10 @@ package edu.ie3.simona.sim +import akka.actor.typed.scaladsl.adapter.{ + ClassicActorRefOps, + ClassicActorSystemOps +} import akka.actor.{Actor, ActorContext, ActorRef, ActorSystem, Props} import akka.testkit.{TestActorRef, TestProbe} import com.typesafe.config.ConfigFactory @@ -13,10 +17,12 @@ import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.config.SimonaConfig.Simona.Input.Primary +import edu.ie3.simona.event.RuntimeEvent +import edu.ie3.simona.ontology.messages.SchedulerMessage import edu.ie3.simona.ontology.messages.SchedulerMessage.{ InitSimMessage, - ScheduleTriggerMessage, - SimulationFailureMessage + SimulationFailureMessage, + StartScheduleMessage } import edu.ie3.simona.service.primary.PrimaryServiceProxy import edu.ie3.simona.service.primary.PrimaryServiceProxy.InitPrimaryServiceProxyStateData @@ -41,25 +47,23 @@ class SimonaSimFailSpec ) { "A SimonaSim" should { "properly fail on uncaught exception" in { - val scheduler = TestProbe("scheduler") - val primaryService = TestProbe("primaryService").ref - val weatherService = TestProbe("weatherService").ref + val timeAdvancer = TestProbe("timeAdvancer") val failSim = TestActorRef.create[FailSim]( system, Props( - new FailSim(system, primaryService, weatherService, scheduler.ref) + new FailSim( + system, + timeAdvancer.ref.toTyped[SchedulerMessage] + ) ) ) - /*Expect the initialization triggers for weather and the primary service */ - scheduler.expectMsgType[ScheduleTriggerMessage] - scheduler.expectMsgType[ScheduleTriggerMessage] /* Init the simulation */ failSim ! InitSimMessage /* The sim asks the scheduler to start it's schedule */ - scheduler.expectMsg(InitSimMessage) + timeAdvancer.expectMsg(StartScheduleMessage()) /* Trigger the child to fail */ failSim.underlyingActor.getChild ! "fail" @@ -72,11 +76,12 @@ class SimonaSimFailSpec object SimonaSimFailSpec { class FailSim( actorSystem: ActorSystem, - primaryService: ActorRef, - weatherService: ActorRef, - scheduler: ActorRef + timeAdvancer: akka.actor.typed.ActorRef[SchedulerMessage] ) extends SimonaSim( - new DummySetup(actorSystem, primaryService, weatherService, scheduler) + new DummySetup( + actorSystem, + timeAdvancer + ) ) { val child: ActorRef = context.actorOf(Props(new Loser)) context.watch(child) @@ -92,78 +97,41 @@ object SimonaSimFailSpec { class DummySetup( actorSystem: ActorSystem, - primaryService: ActorRef, - weatherService: ActorRef, - scheduler: ActorRef, + timeAdvancer: akka.actor.typed.ActorRef[SchedulerMessage], override val args: Array[String] = Array.empty[String] ) extends SimonaSetup { - /** A function, that constructs the [[ActorSystem]], the simulation shall - * live in - */ override val buildActorSystem: () => ActorSystem = () => actorSystem - /** Creates a sequence of runtime event listeners - * - * @param context - * Actor context to use - * @return - * A sequence of actor references to runtime event listeners - */ - override def runtimeEventListener(context: ActorContext): Seq[ActorRef] = - Seq.empty[ActorRef] - - /** Creates a sequence of system participant event listeners - * - * @param context - * Actor context to use - * @return - * A sequence of actor references to runtime event listeners - */ + override def runtimeEventListener( + context: ActorContext + ): akka.actor.typed.ActorRef[RuntimeEvent] = + akka.actor.testkit.typed.scaladsl + .TestProbe[RuntimeEvent]()(actorSystem.toTyped) + .ref + override def systemParticipantsListener( context: ActorContext ): Seq[ActorRef] = Seq.empty[ActorRef] - /** Creates a primary service proxy. The proxy is the first instance to ask - * for primary data. If necessary, it delegates the registration request to - * it's subordinate workers. - * - * @param context - * Actor context to use - * @param scheduler - * Actor reference to it's according scheduler to use - * @return - * An actor reference to the service as well as matching data to - * initialize the service - */ override def primaryServiceProxy( context: ActorContext, scheduler: ActorRef ): (ActorRef, PrimaryServiceProxy.InitPrimaryServiceProxyStateData) = ( - primaryService, + TestProbe("primaryService")(actorSystem).ref, InitPrimaryServiceProxyStateData( new Primary(None, None, None, None), ZonedDateTime.now() ) ) - /** Creates a weather service - * - * @param context - * Actor context to use - * @param scheduler - * Actor reference to it's according scheduler to use - * @return - * An actor reference to the service as well as matching data to - * initialize the service - */ override def weatherService( context: ActorContext, scheduler: ActorRef ): (ActorRef, WeatherService.InitWeatherServiceStateData) = ( - weatherService, + TestProbe("weatherService")(actorSystem).ref, InitWeatherServiceStateData( new SimonaConfig.Simona.Input.Weather.Datasource( new SimonaConfig.Simona.Input.Weather.Datasource.CoordinateSource( @@ -185,30 +153,17 @@ object SimonaSimFailSpec { ) ) - /** Creates a scheduler service - * - * @param context - * Actor context to use - * @return - * An actor reference to the scheduler - */ + override def timeAdvancer( + context: ActorContext, + simulation: ActorRef, + runtimeEventListener: akka.actor.typed.ActorRef[RuntimeEvent] + ): akka.actor.typed.ActorRef[SchedulerMessage] = timeAdvancer + override def scheduler( context: ActorContext, - runtimeEventListener: Seq[ActorRef] - ): ActorRef = scheduler - - /** Creates all the needed grid agents - * - * @param context - * Actor context to use - * @param environmentRefs - * EnvironmentRefs to use - * @param systemParticipantListener - * Listeners that await events from system participants - * @return - * A mapping from actor reference to it's according initialization data - * to be used when setting up the agents - */ + timeAdvancer: akka.actor.typed.ActorRef[SchedulerMessage] + ): ActorRef = TestProbe("scheduler")(actorSystem).ref + override def gridAgents( context: ActorContext, environmentRefs: EnvironmentRefs, 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 63cb664025..62bb4955ef 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala @@ -14,6 +14,8 @@ import edu.ie3.datamodel.models.input.connector.{ } import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.GridAgentData +import edu.ie3.simona.event.RuntimeEvent +import edu.ie3.simona.ontology.messages.SchedulerMessage import edu.ie3.simona.service.primary.PrimaryServiceProxy import edu.ie3.simona.service.weather.WeatherService import edu.ie3.simona.test.common.UnitSpec @@ -28,7 +30,9 @@ class SimonaSetupSpec extends UnitSpec with SimonaSetup with SubGridGateMokka { override val buildActorSystem: () => ActorSystem = () => throw new NotImplementedException("This is a dummy setup") - override def runtimeEventListener(context: ActorContext): Seq[ActorRef] = + override def runtimeEventListener( + context: ActorContext + ): akka.actor.typed.ActorRef[RuntimeEvent] = throw new NotImplementedException("This is a dummy setup") override def systemParticipantsListener( @@ -53,9 +57,16 @@ class SimonaSetupSpec extends UnitSpec with SimonaSetup with SubGridGateMokka { ): ExtSimSetupData = throw new NotImplementedException("This is a dummy setup") + override def timeAdvancer( + context: ActorContext, + simulation: ActorRef, + runtimeEventListener: akka.actor.typed.ActorRef[RuntimeEvent] + ): akka.actor.typed.ActorRef[SchedulerMessage] = + throw new NotImplementedException("This is a dummy setup") + override def scheduler( context: ActorContext, - runtimeEventListener: Seq[ActorRef] + timeAdvancer: akka.actor.typed.ActorRef[SchedulerMessage] ): ActorRef = throw new NotImplementedException("This is a dummy setup") override def gridAgents( diff --git a/src/test/scala/edu/ie3/simona/test/common/AgentSpec.scala b/src/test/scala/edu/ie3/simona/test/common/AgentSpec.scala index 8d2d93ac87..2ad9e7beda 100644 --- a/src/test/scala/edu/ie3/simona/test/common/AgentSpec.scala +++ b/src/test/scala/edu/ie3/simona/test/common/AgentSpec.scala @@ -21,7 +21,6 @@ import org.scalatest.wordspec.AnyWordSpecLike class AgentSpec(actorSystem: ActorSystem) extends TestKitWithShutdown(actorSystem) with ImplicitSender - with SchedulerMessageFunctions with AnyWordSpecLike with should.Matchers with PrivateMethodTester diff --git a/src/test/scala/edu/ie3/simona/test/common/SchedulerMessageFunctions.scala b/src/test/scala/edu/ie3/simona/test/common/SchedulerMessageFunctions.scala deleted file mode 100644 index ec8a0a6072..0000000000 --- a/src/test/scala/edu/ie3/simona/test/common/SchedulerMessageFunctions.scala +++ /dev/null @@ -1,49 +0,0 @@ -/* - * © 2020. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.test.common - -import akka.testkit.TestKit -import edu.ie3.simona.ontology.messages.SchedulerMessage.CompletionMessage -import edu.ie3.simona.ontology.trigger.Trigger -import org.scalatest.wordspec.AnyWordSpecLike - -/** //ToDo: Class Description - * - * @version 0.1 - * @since 2019-08-11 - */ -trait SchedulerMessageFunctions extends TestKit with AnyWordSpecLike { - - def expectCompletionMessage(): CompletionMessage = { - expectMsgPF() { - case msg @ CompletionMessage(_, _) => msg - case x => - fail( - s"Unexpected message ${x} received when expecting CompletionMessage!" - ) - } - } - - def getTriggersFromCompletionMessage( - msg: CompletionMessage - ): Vector[Trigger] = { - msg match { - case CompletionMessage(_, newTriggers) => - newTriggers - .map(schedulerTriggerMessageSeq => - schedulerTriggerMessageSeq.foldLeft(Vector.empty[Trigger])( - (vector, scheduleTriggerMessage) => { - vector :+ scheduleTriggerMessage.trigger - } - ) - ) - .getOrElse(fail("Something went wrong during trigger extraction!")) - case _ => fail("No completion message provided!") - } - } - -} diff --git a/src/test/scala/edu/ie3/simona/util/ActorUtils.scala b/src/test/scala/edu/ie3/simona/util/ActorUtils.scala new file mode 100644 index 0000000000..5c293b1876 --- /dev/null +++ b/src/test/scala/edu/ie3/simona/util/ActorUtils.scala @@ -0,0 +1,58 @@ +/* + * © 2023. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.util + +import akka.actor.testkit.typed.scaladsl.FishingOutcomes.fail +import akka.actor.testkit.typed.scaladsl.TestProbe +import akka.actor.typed.ActorRef +import akka.actor.typed.scaladsl.adapter.TypedActorRefOps +import edu.ie3.simona.ontology.messages.SchedulerMessage +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + CompletionMessage, + ScheduleTriggerMessage, + TriggerWithIdMessage +} +import edu.ie3.simona.ontology.trigger.Trigger +import edu.ie3.simona.ontology.trigger.Trigger.ActivityStartTrigger +import org.scalatest.matchers.should.Matchers.convertToAnyShouldWrapper + +object ActorUtils { + implicit class RichTriggeredAgent( + private val triggeredAgent: TestProbe[TriggerWithIdMessage] + ) { + + def expectTriggerAndComplete[T <: Trigger]( + scheduler: ActorRef[SchedulerMessage], + expectedTick: Long, + newTick: Option[Long] = None + ): Unit = { + val receivedTrigger = + triggeredAgent.expectMessageType[TriggerWithIdMessage] + + receivedTrigger.trigger match { + case trigger: T => + trigger.tick shouldBe expectedTick + case unexpected => + fail(s"Received unexpected trigger $unexpected") + } + + val newTrigger = + newTick.map(tick => + ScheduleTriggerMessage( + ActivityStartTrigger(tick), + triggeredAgent.ref.toClassic + ) + ) + + scheduler ! CompletionMessage( + receivedTrigger.triggerId, + newTrigger + ) + } + + } +} diff --git a/src/test/scala/edu/ie3/util/scala/collection/mutable/CountingMapSpec.scala b/src/test/scala/edu/ie3/util/scala/collection/mutable/CountingMapSpec.scala deleted file mode 100644 index 1fac3e26da..0000000000 --- a/src/test/scala/edu/ie3/util/scala/collection/mutable/CountingMapSpec.scala +++ /dev/null @@ -1,111 +0,0 @@ -/* - * © 2021. 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.mutable - -import edu.ie3.simona.test.common.UnitSpec - -class CountingMapSpec extends UnitSpec { - private type Key = Int - - "A CountingMap" should { - "be created correctly emptily" in { - val emptyMap = CountingMap.empty[Key] - - emptyMap.isEmpty shouldBe true - emptyMap.minKeyOption shouldBe None - emptyMap.get(0) shouldBe None - emptyMap.contains(0) shouldBe false - } - - "be created correctly with initial values" in { - val map = CountingMap.from[Key]( - Iterable( - 0 -> 1L, - 1 -> 1L - ) - ) - - map.isEmpty shouldBe false - map.minKeyOption shouldBe Some(0) - map.get(0) shouldBe Some(1L) - map.contains(0) shouldBe true - map.get(1) shouldBe Some(1L) - map.contains(1) shouldBe true - } - - "behave correctly when adding to an empty map" in { - val map = CountingMap.empty[Key] - - map.add(0) - - map.isEmpty shouldBe false - map.minKeyOption shouldBe Some(0) - map.get(0) shouldBe Some(1L) - map.contains(0) shouldBe true - map.contains(1) shouldBe false - } - - "behave correctly when adding twice to a key" in { - val map = CountingMap.empty[Key] - - map.add(0) - map.add(0) - - map.isEmpty shouldBe false - map.minKeyOption shouldBe Some(0) - map.get(0) shouldBe Some(2L) - } - - "behave correctly when adding an item to a non-existing key in a non-empty map" in { - val map = CountingMap.empty[Key] - - map.add(0) - map.add(1) - - map.isEmpty shouldBe false - map.minKeyOption shouldBe Some(0) - map.get(0) shouldBe Some(1L) - map.get(1) shouldBe Some(1L) - - map.subtract(0) - map.minKeyOption shouldBe Some(1) - } - - "behave correctly when removing an existing item resulting in a non-empty map" in { - val map = CountingMap.empty[Key] - map.add(0) - map.add(0) - - map.subtract(0) - - map.minKeyOption shouldBe Some(0) - map.get(0) shouldBe Some(1L) - } - - "behave correctly when removing an existing item resulting in an empty map" in { - val map = CountingMap.empty[Key] - - map.add(1) - map.subtract(1) - - map.minKeyOption shouldBe None - map.get(1) shouldBe None - } - - "behave correctly when removing a non-existing item" in { - val map = CountingMap.empty[Key] - map.add(0) - map.add(1) - - map.subtract(2) - - map.minKeyOption shouldBe Some(0) - map.get(0) shouldBe Some(1L) - map.get(1) shouldBe Some(1L) - } - } -} diff --git a/src/test/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiBiSetSpec.scala b/src/test/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiBiSetSpec.scala new file mode 100644 index 0000000000..cd53afafad --- /dev/null +++ b/src/test/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiBiSetSpec.scala @@ -0,0 +1,196 @@ +/* + * © 2021. 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.mutable + +import edu.ie3.simona.test.common.UnitSpec + +import scala.collection.SortedSet + +class PriorityMultiBiSetSpec 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" + + "A PriorityMultiBiSet" should { + "be created correctly emptily" in { + val prioSet = PriorityMultiBiSet.empty[Key, Value] + + prioSet.isEmpty shouldBe true + prioSet.nonEmpty shouldBe false + prioSet.keySet shouldBe SortedSet.empty[Key] + prioSet.getAndRemoveSet(0) shouldBe Set.empty[Value] + prioSet.headKeyOption shouldBe None + } + + "behave correctly when adding to an empty map" in { + val prioSet = PriorityMultiBiSet.empty[Key, Value] + + prioSet.set(0, item1) + + prioSet.isEmpty shouldBe false + prioSet.nonEmpty shouldBe true + prioSet.headKeyOption shouldBe Some(0) + prioSet.keySet shouldBe SortedSet(0) + prioSet.getKeyOf(item1) shouldBe Some(0) + prioSet.getKeyOf(item2) shouldBe None + + prioSet.getAndRemoveSet(0) shouldBe Set(item1) + prioSet.isEmpty shouldBe true + prioSet.keySet shouldBe SortedSet.empty[Key] + prioSet.getKeyOf(item1) shouldBe None + } + + "behave correctly when adding and retrieving multiple values" in { + // trying the second constructor here + val prioSet = PriorityMultiBiSet.empty[Key, Value](5) + + prioSet.set(3, item3) + + prioSet.isEmpty shouldBe false + prioSet.nonEmpty shouldBe true + prioSet.headKeyOption shouldBe Some(3) + prioSet.keySet shouldBe SortedSet(3) + prioSet.getKeyOf(item3) shouldBe Some(3) + + prioSet.set(1, item1) + + prioSet.isEmpty shouldBe false + prioSet.nonEmpty shouldBe true + prioSet.headKeyOption shouldBe Some(1) + prioSet.keySet shouldBe SortedSet(1, 3) + prioSet.getKeyOf(item1) shouldBe Some(1) + prioSet.getKeyOf(item3) shouldBe Some(3) + + prioSet.set(3, item2) + + prioSet.isEmpty shouldBe false + prioSet.nonEmpty shouldBe true + prioSet.headKeyOption shouldBe Some(1) + prioSet.keySet shouldBe SortedSet(1, 3) + prioSet.getKeyOf(item2) shouldBe Some(3) + + prioSet.set(3, item3) + + prioSet.isEmpty shouldBe false + prioSet.nonEmpty shouldBe true + prioSet.headKeyOption shouldBe Some(1) + prioSet.keySet shouldBe SortedSet(1, 3) + prioSet.getKeyOf(item3) shouldBe Some(3) + + prioSet.getAndRemoveSet(1) shouldBe Set(item1) + + prioSet.isEmpty shouldBe false + prioSet.nonEmpty shouldBe true + prioSet.headKeyOption shouldBe Some(3) + prioSet.keySet shouldBe SortedSet(3) + + prioSet.getAndRemoveSet(1) shouldBe Set.empty[Value] + prioSet.getAndRemoveSet(3) shouldBe Set(item2, item3) + + prioSet.isEmpty shouldBe true + prioSet.nonEmpty shouldBe false + prioSet.headKeyOption shouldBe None + prioSet.keySet shouldBe SortedSet.empty[Key] + + prioSet.getAndRemoveSet(3) shouldBe Set.empty[Value] + } + + "behave correctly when removing items" in { + val prioSet = PriorityMultiBiSet.empty[Key, Value] + + prioSet.set(0, item1) + prioSet.set(3, item3) + // overwrites 0 -> item1 + prioSet.set(1, item1) + prioSet.set(2, item4) + prioSet.set(3, item2) + prioSet.set(5, item5) + + prioSet.isEmpty shouldBe false + prioSet.nonEmpty shouldBe true + prioSet.headKeyOption shouldBe Some(1) + prioSet.keySet shouldBe SortedSet(1, 2, 3, 5) + prioSet.getKeyOf(item1) shouldBe Some(1) + prioSet.getKeyOf(item2) shouldBe Some(3) + prioSet.getKeyOf(item3) shouldBe Some(3) + prioSet.getKeyOf(item4) shouldBe Some(2) + prioSet.getKeyOf(item5) shouldBe Some(5) + + // does not exist + prioSet.remove(2, item1) shouldBe false + + prioSet.isEmpty shouldBe false + prioSet.nonEmpty shouldBe true + prioSet.headKeyOption shouldBe Some(1) + prioSet.keySet shouldBe SortedSet(1, 2, 3, 5) + + prioSet.remove(2, item4) shouldBe true + + prioSet.isEmpty shouldBe false + prioSet.nonEmpty shouldBe true + prioSet.headKeyOption shouldBe Some(1) + prioSet.keySet shouldBe SortedSet(1, 3, 5) + + prioSet.getAndRemoveSet(1) shouldBe Set(item1) + + prioSet.isEmpty shouldBe false + prioSet.nonEmpty shouldBe true + prioSet.headKeyOption shouldBe Some(3) + prioSet.keySet shouldBe SortedSet(3, 5) + + prioSet.remove(3, item3) shouldBe true + + prioSet.isEmpty shouldBe false + prioSet.nonEmpty shouldBe true + prioSet.headKeyOption shouldBe Some(3) + prioSet.keySet shouldBe SortedSet(3, 5) + + prioSet.getAndRemoveSet(3) shouldBe Set(item2) + + prioSet.isEmpty shouldBe false + prioSet.nonEmpty shouldBe true + prioSet.headKeyOption shouldBe Some(5) + prioSet.keySet shouldBe SortedSet(5) + + prioSet.remove(5, item5) shouldBe true + + prioSet.isEmpty shouldBe true + prioSet.nonEmpty shouldBe false + prioSet.headKeyOption shouldBe None + prioSet.keySet shouldBe SortedSet.empty[Key] + + prioSet.getAndRemoveSet(5) shouldBe Set.empty[Value] + + // test if the table has been depleted as well - + // if it is, adding should work as expected + prioSet.set(1, item2) + + prioSet.isEmpty shouldBe false + prioSet.nonEmpty shouldBe true + prioSet.headKeyOption shouldBe Some(1) + prioSet.keySet shouldBe SortedSet(1) + + prioSet.getAndRemoveSet(1) shouldBe Set(item2) + + prioSet.isEmpty shouldBe true + prioSet.nonEmpty shouldBe false + prioSet.headKeyOption shouldBe None + prioSet.keySet shouldBe SortedSet.empty[Key] + prioSet.getKeyOf(item1) shouldBe None + prioSet.getKeyOf(item2) shouldBe None + prioSet.getKeyOf(item3) shouldBe None + prioSet.getKeyOf(item4) shouldBe None + prioSet.getKeyOf(item5) shouldBe None + + } + } +} diff --git a/src/test/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiQueueSpec.scala b/src/test/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiQueueSpec.scala deleted file mode 100644 index 43a64cd325..0000000000 --- a/src/test/scala/edu/ie3/util/scala/collection/mutable/PriorityMultiQueueSpec.scala +++ /dev/null @@ -1,170 +0,0 @@ -/* - * © 2021. 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.mutable - -import edu.ie3.simona.test.common.UnitSpec - -import scala.collection.SortedSet - -class PriorityMultiQueueSpec 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" - - "A PriorityMultiQueue" should { - "be created correctly emptily" in { - val emptyQueue = PriorityMultiQueue.empty[Key, Value] - - emptyQueue.isEmpty shouldBe true - emptyQueue.nonEmpty shouldBe false - emptyQueue.keySet shouldBe SortedSet.empty[Key] - emptyQueue.allValues shouldBe Iterable.empty[Value] - } - - "behave correctly when adding to an empty map" in { - val queue = PriorityMultiQueue.empty[Key, Value] - - queue.add(0, item1) - - queue.isEmpty shouldBe false - queue.nonEmpty shouldBe true - queue.headKeyOption shouldBe Some(0) - queue.keySet shouldBe SortedSet(0) - queue.allValues shouldBe Iterable(item1) - - queue.poll() shouldBe Some(item1) - queue.isEmpty shouldBe true - queue.allValues shouldBe Iterable.empty[Value] - } - - "behave correctly when adding multiple values to multiple keys" in { - val queue = PriorityMultiQueue.empty[Key, Value] - - queue.add(3, item3) - - queue.isEmpty shouldBe false - queue.nonEmpty shouldBe true - queue.headKeyOption shouldBe Some(3) - queue.keySet shouldBe SortedSet(3) - queue.allValues shouldBe Iterable(item3) - - queue.add(1, item1) - - queue.isEmpty shouldBe false - queue.nonEmpty shouldBe true - queue.headKeyOption shouldBe Some(1) - queue.keySet shouldBe SortedSet(1, 3) - queue.allValues shouldBe Iterable(item1, item3) - - queue.add(3, item2) - - queue.isEmpty shouldBe false - queue.nonEmpty shouldBe true - queue.headKeyOption shouldBe Some(1) - queue.keySet shouldBe SortedSet(1, 3) - queue.allValues shouldBe Iterable(item1, item3, item2) - - queue.add(3, item3) - - queue.isEmpty shouldBe false - queue.nonEmpty shouldBe true - queue.headKeyOption shouldBe Some(1) - queue.keySet shouldBe SortedSet(1, 3) - queue.allValues shouldBe Iterable(item1, item3, item2, item3) - - queue.poll() shouldBe Some(item1) - - queue.isEmpty shouldBe false - queue.nonEmpty shouldBe true - queue.headKeyOption shouldBe Some(3) - queue.keySet shouldBe SortedSet(3) - queue.allValues shouldBe Iterable(item3, item2, item3) - - queue.poll() shouldBe Some(item3) - - queue.isEmpty shouldBe false - queue.nonEmpty shouldBe true - queue.headKeyOption shouldBe Some(3) - queue.keySet shouldBe SortedSet(3) - queue.allValues shouldBe Iterable(item2, item3) - - queue.poll() shouldBe Some(item2) - - queue.isEmpty shouldBe false - queue.nonEmpty shouldBe true - queue.headKeyOption shouldBe Some(3) - queue.keySet shouldBe SortedSet(3) - queue.allValues shouldBe Iterable(item3) - - queue.poll() shouldBe Some(item3) - - queue.isEmpty shouldBe true - queue.nonEmpty shouldBe false - queue.headKeyOption shouldBe None - queue.keySet shouldBe SortedSet.empty[Key] - queue.allValues shouldBe Iterable.empty[Value] - - queue.poll() shouldBe None - } - - "behave correctly when polling up to a certain key" in { - val queue = PriorityMultiQueue.empty[Key, Value] - - queue.add(3, item3) - queue.add(1, item1) - queue.add(2, item2) - queue.add(3, item3) - queue.add(5, item1) - - queue.isEmpty shouldBe false - queue.nonEmpty shouldBe true - queue.headKeyOption shouldBe Some(1) - queue.keySet shouldBe SortedSet(1, 2, 3, 5) - queue.allValues shouldBe Iterable(item1, item2, item3, item3, item1) - - queue.pollTo(2) shouldBe Iterable(item1, item2) - - queue.isEmpty shouldBe false - queue.nonEmpty shouldBe true - queue.headKeyOption shouldBe Some(3) - queue.keySet shouldBe SortedSet(3, 5) - queue.allValues shouldBe Iterable(item3, item3, item1) - - queue.pollTo(3) shouldBe Iterable(item3, item3) - - queue.isEmpty shouldBe false - queue.nonEmpty shouldBe true - queue.headKeyOption shouldBe Some(5) - queue.keySet shouldBe SortedSet(5) - queue.allValues shouldBe Iterable(item1) - - queue.pollTo(5) shouldBe Iterable(item1) - - queue.isEmpty shouldBe true - queue.nonEmpty shouldBe false - queue.headKeyOption shouldBe None - queue.keySet shouldBe SortedSet.empty[Key] - queue.allValues shouldBe Iterable.empty[Value] - - queue.pollTo(Integer.MAX_VALUE) shouldBe Iterable.empty[Value] - - // test if the table has been depleted as well - - // if it is, adding should work as expected - queue.add(1, item2) - - queue.isEmpty shouldBe false - queue.nonEmpty shouldBe true - queue.headKeyOption shouldBe Some(1) - queue.keySet shouldBe SortedSet(1) - queue.allValues shouldBe Iterable(item2) - - } - } -}