Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor external simulation setup. #1137

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Refactor `RuntimeConfig` [#1172](https://github.com/ie3-institute/simona/issues/1172)
- Renamed some methods and variables within `ThermalGrid` and `ThermalHouse` [#1193](https://github.com/ie3-institute/simona/issues/1193)
- Replaced Java Durations with Scala Durations [#1068](https://github.com/ie3-institute/simona/issues/1068)
- Refactor external simulation setup [#1136](https://github.com/ie3-institute/simona/issues/1136)

### Fixed
- Fix rendering of references in documentation [#505](https://github.com/ie3-institute/simona/issues/505)
Expand Down
19 changes: 12 additions & 7 deletions docs/readthedocs/usersguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,12 @@ Within the ``rawOutputData`` folder you can find the raw simulation results. For
## Setting up and running an external simulation

SIMONA is capable of running an external sub-simulation by integration within the same time system (ticks) as SIMONA.
The information flow between SIMONA and the external simulation is partitioned into a control stream (see ``edu.ie3.simona.api.ExtSimAdapter``) and a number of optional data streams.
Currently, only a data stream transporting electric vehicle movement information is implemented (see ``edu.ie3.simona.service.ev.ExtEvDataService``).
The information flow between SIMONA and the external simulation is partitioned into a control stream (see ``edu.ie3.simona.api.ExtSimAdapter``) and a number of optional data connections.
Currently, available data connections are:
- electric vehicle movement information (see ``edu.ie3.simona.service.ev.ExtEvDataService``)
- energy management data (see ``edu.ie3.simona.service.em.ExtEmDataService``)
- primary data (see ``edu.ie3.simona.service.primary.ExtPrimaryDataService``)
- result data (see ``edu.ie3.simona.service.results.ExtResultDataProvider``)

An external simulation has to depend on [SimonaAPI](https://github.com/ie3-institute/simonaAPI) and make use of some of its interfaces (see below).
In order to run an external simulation, several requirements have to be fulfilled and a bunch of preparation steps have to be followed.
Expand All @@ -168,23 +172,24 @@ 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 ``Optional<Long> initialize()`` and ``Optional<Long> 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*).
- A class (called *main class* here) needs to extend ``edu.ie3.simona.api.schedule.ExtSimulation`` and thus implement the three methods ``Set<ExtDataConnection> getDataConnections()``, ``Optional<Long> initialize()`` and ``Optional<Long> doActivity(long tick)``. The method ``getDataConnections`` is called to return all data connection between the external simulation and SIMONA, ``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, that should be exchanged with SIMONA, a data connection (see ``edu.ie3.simona.api.data.ExtDataConnection``) needs to be created, such as ``edu.ie3.simona.api.data.ev.ExtEvDataConnection``. All data connections should be returned by the implementation of ``edu.ie3.simona.api.schedule.ExtSimulation``.
- 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. One method is used to set up the external simulation with the provided ``edu.ie3.simona.api.simulation.ExtSimAdapterData``. The second method is called after the setup and returns the external simulation.
- 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``.
- A <em>very simple</em> example for an external simulation can be found [here](https://github.com/ie3-institute/ExtSimSample)

**SIMONA**

- For EV simulation: The EVCS that are used by the external simulation have to be loaded by SIMONA from the according input data. EVCS are identified by their UUIDs.
- The grid with all system participants that are either used in or receive data from external simulations have to be loaded by SIMONA.
- The external data connections uses the UUIDs of the assets and participants to provide data or send results.


### Preparation

These steps have to be performed each time updates to the external simulation need to be deployed.

- Execute ``gradlew shadowJar`` inside the external simulation project.
- Copy the resulting *jar* (usually placed inside ``<external project>/build/libs``) to ``./input/ext_sim/``.
- Copy the resulting *jar* (usually placed inside ``<external project>/build/libs``) into the external simulation folder. This folder is set via the given config.

Now, when a simulation with SIMONA is started (see {ref}`usersguide:running a standalone simulation`), the external simulation is triggered at each tick that it requested.

Expand Down
1 change: 1 addition & 0 deletions src/main/scala/edu/ie3/simona/config/SimonaConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ object SimonaConfig {
)

final case class Input(
extSimDir: Option[String],
grid: Input.Grid,
primary: Input.Primary = Input.Primary(),
weather: Input.Weather = Input.Weather(),
Expand Down
37 changes: 27 additions & 10 deletions src/main/scala/edu/ie3/simona/sim/SimonaSim.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors}
import org.apache.pekko.actor.typed.{ActorRef, Behavior, PostStop, Terminated}
import org.apache.pekko.actor.{ActorRef => ClassicRef}

import java.nio.file.Path

/** Main entrance point to a simona simulation as the guardian actor. This actor
* starts the initialization of all actors and waits for the simulation to end.
* This actor does NOT report back any simulation status except of if the
Expand Down Expand Up @@ -69,9 +71,6 @@ object SimonaSim {
): Behavior[Request] =
Behaviors
.receivePartial[Request] { case (ctx, Start(_)) =>
val resultEventListeners =
simonaSetup.resultEventListener(ctx)

val runtimeEventListener = simonaSetup.runtimeEventListener(ctx)

val timeAdvancer =
Expand All @@ -80,12 +79,16 @@ object SimonaSim {

// External simulations have to be scheduled for initialization first,
// so that the phase switch permanently activates them first
val extSimulationData = simonaSetup.extSimulations(ctx, scheduler)
val extSimDir =
simonaSetup.simonaConfig.simona.input.extSimDir.map(Path.of(_))

val extSimulationData =
simonaSetup.extSimulations(ctx, scheduler, extSimDir)

/* start services */
// primary service proxy
val primaryServiceProxy =
simonaSetup.primaryServiceProxy(ctx, scheduler)
simonaSetup.primaryServiceProxy(ctx, scheduler, extSimulationData)

// weather service
val weatherService =
Expand All @@ -99,6 +102,9 @@ object SimonaSim {
extSimulationData.evDataService,
)

val resultEventListeners =
simonaSetup.resultEventListener(ctx, extSimulationData)

/* start grid agents */
val gridAgents = simonaSetup.gridAgents(
ctx,
Expand All @@ -118,18 +124,29 @@ object SimonaSim {
/* watch all actors */
resultEventListeners.foreach(ctx.watch)
ctx.watch(runtimeEventListener)
extSimulationData.extSimAdapters.map(_.toTyped).foreach(ctx.watch)
extSimulationData.extResultListeners.values.foreach(ref =>
ctx.watch(ref)
)
extSimulationData.extSimAdapters.foreach(extSimAdapter =>
ctx.watch(extSimAdapter.toTyped)
)
otherActors.foreach(ctx.watch)

// Start simulation
timeAdvancer ! TimeAdvancer.Start()

val delayedActors = resultEventListeners.appended(runtimeEventListener)

extSimulationData.extResultListeners.foreach(ref =>
delayedActors.appended(ref)
)

idle(
ActorData(
starter,
extSimulationData.extSimAdapters,
runtimeEventListener,
resultEventListeners.appended(runtimeEventListener),
delayedActors,
otherActors,
)
)
Expand Down Expand Up @@ -192,9 +209,9 @@ object SimonaSim {
ctx.stop(ref)
}

actorData.extSimAdapters.foreach { ref =>
ctx.unwatch(ref)
ref ! ExtSimAdapter.Stop(simulationSuccessful)
actorData.extSimAdapters.foreach { extSimAdapter =>
ctx.unwatch(extSimAdapter)
extSimAdapter ! ExtSimAdapter.Stop(simulationSuccessful)
}

// if the simulation is successful, we're waiting for the delayed
Expand Down
53 changes: 31 additions & 22 deletions src/main/scala/edu/ie3/simona/sim/setup/ExtSimLoader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,54 @@ import edu.ie3.simona.api.ExtLinkInterface

import java.io.{File, IOException}
import java.net.URLClassLoader
import java.nio.file.Path
import java.util.ServiceLoader
import scala.jdk.CollectionConverters._

/** Finds and loads jars containing external simulations.
*/
object ExtSimLoader extends LazyLogging {

private val extSimPath = "input" + java.io.File.separator + "ext_sim"
private def buildDir(path: Path): File = {
if (path.isAbsolute) {
return path.toFile
}

private def getStandardDirectory: File = {
val workingDir = new File(System.getProperty("user.dir"))
if (!workingDir.isDirectory)
throw new IOException("Error when accessing working directory.")

new File(workingDir, extSimPath)
new File(workingDir, path.toString)
}

def scanInputFolder(
extSimDir: File = getStandardDirectory
): Iterable[File] = {
if (!extSimDir.isDirectory) {
logger.warn(
s"External simulation directory ${extSimDir.getPath} does not exist or is not a directory, no external simulation loaded."
)
return Iterable.empty
}

val allowedExtensions = Seq("jar")

extSimDir
.listFiles()
.filter { file =>
val name = file.getName
file.canRead &&
name.contains('.') &&
allowedExtensions.contains(
name.substring(name.lastIndexOf('.') + 1).toLowerCase
extSimDirOption: Option[Path] = None
): Iterable[File] = extSimDirOption.map(buildDir) match {
case Some(extSimDir) =>
if (!extSimDir.isDirectory) {
logger.warn(
s"External simulation directory ${extSimDir.getPath} does not exist or is not a directory, no external simulation loaded."
)
return Iterable.empty
}

val allowedExtensions = Seq("jar")

extSimDir
.listFiles()
.filter { file =>
val name = file.getName
file.canRead &&
name.contains('.') &&
allowedExtensions.contains(
name.substring(name.lastIndexOf('.') + 1).toLowerCase
)
}
case None =>
logger.info(
"No external simulation directory given. No external simulation loaded."
)
Iterable.empty
}

def loadExtLink(myJar: File): Option[ExtLinkInterface] = {
Expand Down
Loading