From a88e6c4876bbcf6169374e1a08a963f85170f2c2 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Thu, 26 Aug 2021 12:33:06 +0200 Subject: [PATCH 01/32] Fixing dependency versions --- build.gradle | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index adda2915..29c1ac97 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ ext { scalaVersion = '2.13' scalaBinaryVersion = '2.13.6' - tscfgVersion = '0.9.986' + tscfgVersion = '0.9.993' slf4jVersion = '1.7.32' scriptsLocation = 'gradle' + File.separator + 'scripts' + File.separator //location of script plugins @@ -64,11 +64,11 @@ dependencies { implementation 'tech.units:indriya:2.1.2' // logging - implementation 'org.apache.logging.log4j:log4j-api:+' // log4j - implementation 'org.apache.logging.log4j:log4j-core:+' // log4j - implementation 'org.apache.logging.log4j:log4j-slf4j-impl:+' // log4j -> slf4j + implementation 'org.apache.logging.log4j:log4j-api:2.14.1' // log4j + implementation 'org.apache.logging.log4j:log4j-core:2.14.1' // log4j + implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.14.1' // log4j -> slf4j - implementation "com.typesafe.scala-logging:scala-logging_${scalaVersion}:+" // akka scala logging + implementation "com.typesafe.scala-logging:scala-logging_${scalaVersion}:3.9.4" // akka scala logging implementation "org.slf4j:log4j-over-slf4j:${slf4jVersion}" // slf4j -> log4j // NEW scala libs // @@ -83,14 +83,14 @@ dependencies { implementation 'org.mockito:mockito-core:3.11.2' // mocking framework // config // - implementation 'com.typesafe:config:+' + implementation 'com.typesafe:config:1.4.1' implementation "com.github.carueda:tscfg_2.13:${tscfgVersion}" // cmd args parser // implementation "com.github.scopt:scopt_${scalaVersion}:+" /* Handling compressed archives */ - implementation "org.apache.commons:commons-compress:+" + implementation "org.apache.commons:commons-compress:1.21" } wrapper { From 53df4f5eeb269d2b0d26366f0ee33e98ab0ee7cc Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Thu, 26 Aug 2021 12:37:31 +0200 Subject: [PATCH 02/32] Update gradle wrapper version --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 4 ++-- gradlew.bat | 21 +++------------------ 5 files changed, 7 insertions(+), 22 deletions(-) diff --git a/build.gradle b/build.gradle index 29c1ac97..c868f489 100644 --- a/build.gradle +++ b/build.gradle @@ -94,7 +94,7 @@ dependencies { } wrapper { - gradleVersion = '7.1.1' + gradleVersion = '7.2' } tasks.withType(JavaCompile) { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 18328 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 Y(TzS~Xdo8@nTUZrK}a{fL*HNQe=4o%4FCWD delta 17568 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 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell From fb525a86bac4f5d0314f1ab299eef2edf3989ce8 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Thu, 26 Aug 2021 13:10:04 +0200 Subject: [PATCH 03/32] Introduce akka dependencies --- build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle b/build.gradle index c868f489..38d8b56c 100644 --- a/build.gradle +++ b/build.gradle @@ -82,6 +82,12 @@ dependencies { testImplementation "org.pegdown:pegdown:1.6.0" // HTML report for scalatest implementation 'org.mockito:mockito-core:3.11.2' // mocking framework + // akka actor system // + implementation platform("com.typesafe.akka:akka-bom_${scalaVersion}:2.6.14") + implementation "com.typesafe.akka:akka-actor-typed_${scalaVersion}" + implementation "com.typesafe.akka:akka-stream_${scalaVersion}" + testImplementation "com.typesafe.akka:akka-actor-testkit-typed_${scalaVersion}" + // config // implementation 'com.typesafe:config:1.4.1' implementation "com.github.carueda:tscfg_2.13:${tscfgVersion}" From f49dcf153294f8617de0a848efdfa60539bafaff Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Thu, 26 Aug 2021 13:32:47 +0200 Subject: [PATCH 04/32] Drafting protocol --- docs/diagrams/protocol/protocol.puml | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 docs/diagrams/protocol/protocol.puml diff --git a/docs/diagrams/protocol/protocol.puml b/docs/diagrams/protocol/protocol.puml new file mode 100644 index 00000000..794b361a --- /dev/null +++ b/docs/diagrams/protocol/protocol.puml @@ -0,0 +1,46 @@ +@startuml + +participant Main +participant Coordinator +participant Converter +participant GridConverter +participant RESConverter +participant Mutator +collections RESConverterWorker + +Main -> Coordinator: Convert(Config) +Coordinator -> Converter: Convert(String) +Converter --[#blue]> Mutator: spawn & watch +Converter -> Mutator: Init(Config) +Converter <- Mutator: Ready +Converter --[#blue]> GridConverter: spawn & watch +Converter -> GridConverter: ConvertNodes(List[Node]) +Converter <- GridConverter: ConvertedNodes(Map[Node,NodeInput]) + +Converter -> GridConverter: ConvertRest(...) +activate GridConverter + +Converter --[#blue]> RESConverter: spawn & watch +Converter -> RESConverter: Convert(List[RES], Map[Node,NodeInput]) +RESConverter -> RESConverterWorker: Convert(RES, Map[Node,NodeInput]) +RESConverterWorker -> Mutator: Mutate(RES) +activate Mutator +RESConverterWorker <- Mutator: MutatedEntity(UUID) +RESConverterWorker -> Mutator: Mutate(TimeSeries) +RESConverterWorker <- Mutator: MutatedTimeSeries(UUID) +RESConverter <- RESConverterWorker: Converted(UUID, UUID) +RESConverter --[#blue]> RESConverterWorker: terminate +RESConverter <--[#blue] RESConverterWorker +Converter <- RESConverter: Done + +GridConverter -> Mutator: Mutate(...) +deactivate GridConverter +GridConverter <- Mutator: Mutated(...) +Converter <- GridConverter: Done + +Converter -> Mutator: Shutdown +Converter <- Mutator: Done +deactivate Mutator + +Coordinator <- Converter: Converted(String) +@enduml \ No newline at end of file From 692245aae398a8c305497b4e90bfc005cc688233 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Thu, 26 Aug 2021 14:19:58 +0200 Subject: [PATCH 05/32] First protocol for conversion coordination --- docs/diagrams/protocol/protocol.puml | 5 +- .../edu/ie3/simbench/actor/Converter.scala | 29 +++ .../edu/ie3/simbench/actor/Coordinator.scala | 100 ++++++++ .../edu/ie3/simbench/main/RunSimbench.scala | 225 +++++++++--------- 4 files changed, 241 insertions(+), 118 deletions(-) create mode 100644 src/main/scala/edu/ie3/simbench/actor/Converter.scala create mode 100644 src/main/scala/edu/ie3/simbench/actor/Coordinator.scala diff --git a/docs/diagrams/protocol/protocol.puml b/docs/diagrams/protocol/protocol.puml index 794b361a..cf0c01c8 100644 --- a/docs/diagrams/protocol/protocol.puml +++ b/docs/diagrams/protocol/protocol.puml @@ -8,7 +8,10 @@ participant RESConverter participant Mutator collections RESConverterWorker -Main -> Coordinator: Convert(Config) +Main -> Coordinator: Start(Config) +Coordinator --[#blue]> Converter: spawn & watch +Coordinator -> Converter: Init(String) +Coordinator <- Converter: ConverterInitialized Coordinator -> Converter: Convert(String) Converter --[#blue]> Mutator: spawn & watch Converter -> Mutator: Init(Config) diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala new file mode 100644 index 00000000..3e70a236 --- /dev/null +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -0,0 +1,29 @@ +package edu.ie3.simbench.actor + +import akka.actor.typed.ActorRef +import akka.actor.typed.scaladsl.Behaviors + +object Converter { + def apply(): Behaviors.Receive[ConverterMessage] = uninitialized + + def uninitialized: Behaviors.Receive[ConverterMessage] = Behaviors.receive { + case (ctx, Init(simBenchCode, replyTo)) => + ctx.log.info(s"Converter for '$simBenchCode' started.") + // TODO + Behaviors.same + } + + /** Messages, a coordinator will understand */ + sealed trait ConverterMessage + final case class Init( + simBenchCode: String, + replyTo: ActorRef[Coordinator.CoordinatorMessage] + ) extends ConverterMessage + + /** + * Request to convert a certain SimBench model + * + * @param simBenchCode Code that denotes the model + */ + final case class Convert(simBenchCode: String) extends ConverterMessage +} diff --git a/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala b/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala new file mode 100644 index 00000000..81260535 --- /dev/null +++ b/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala @@ -0,0 +1,100 @@ +package edu.ie3.simbench.actor + +import akka.actor.typed.ActorRef +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import edu.ie3.simbench.config.SimbenchConfig + +object Coordinator { + def apply(): Behaviors.Receive[CoordinatorMessage] = uninitialized + + def uninitialized: Behaviors.Receive[CoordinatorMessage] = Behaviors.receive { + case (ctx, Start(config)) => + ctx.log.info("Starting conversion with given config.") + config.io.simbenchCodes.headOption match { + case Some(firstSimBenchCode) => + val remainingCodes = + config.io.simbenchCodes.filter(_ != firstSimBenchCode) + + /* Start a converter and issue the conversion */ + spawnConverter(ctx, firstSimBenchCode) + val initializingConverters = List(firstSimBenchCode) + idle(remainingCodes, initializingConverters) + case None => + ctx.log.error("No models to convert. Shut down.") + Behaviors.stopped + } + } + + def idle( + simBenchCodes: List[String], + initializingConverters: List[String], + activeConverters: List[String] = List.empty + ): Behaviors.Receive[CoordinatorMessage] = Behaviors.receive { + case (ctx, ConverterInitialized(simBenchCode, replyTo)) => + ctx.log.debug(s"Reading to convert SimBench mode '$simBenchCode'.") + replyTo ! Converter.Convert(simBenchCode) + + val stillInitializingConverters = + initializingConverters.filterNot(_ == simBenchCode) + val yetActiveConverters = activeConverters :+ simBenchCode + idle(simBenchCodes, stillInitializingConverters, yetActiveConverters) + case (ctx, Converted(simBenchCode)) if simBenchCodes.nonEmpty => + /* A converter has completed. Report that and start a new converter. */ + ctx.log.info( + s"SimBench model with code '$simBenchCode' is completely converted." + ) + val stillActiveConverters = activeConverters.filterNot(_ == simBenchCode) + simBenchCodes.headOption match { + case Some(nextSimBenchCode) => + /* Spawn a converter and wait until it is ready */ + val remainingCodes = simBenchCodes.filter(_ != nextSimBenchCode) + spawnConverter(ctx, nextSimBenchCode) + val yetInitializingConverters = initializingConverters :+ nextSimBenchCode + idle(remainingCodes, yetInitializingConverters, activeConverters) + case None if stillActiveConverters.nonEmpty => + ctx.log.debug( + s"Waiting for last ${stillActiveConverters.size} active converter(s)." + ) + Behaviors.same + case None => + ctx.log.info("All models have been converted. Shut down.") + Behaviors.stopped + } + } + + def spawnConverter( + ctx: ActorContext[CoordinatorMessage], + simBenchCode: String + ): Unit = { + val converter = ctx.spawn(Converter(), s"converter_$simBenchCode") + converter ! Converter.Init(simBenchCode, ctx.self) + } + + /** Messages, a coordinator will understand */ + sealed trait CoordinatorMessage + + /** + * Request to start conversion + * + * @param config The config to use for conversion + */ + final case class Start(config: SimbenchConfig) extends CoordinatorMessage + + /** + * Reporting, that a certain converter is ready for action + * + * @param simBenchCode Code of the SimBench model it will handle + * @param replyTo Reference to where to reach the converter + */ + final case class ConverterInitialized( + simBenchCode: String, + replyTo: ActorRef[Converter.ConverterMessage] + ) extends CoordinatorMessage + + /** + * Back-reporting, that a given SimBench-code has been handled + * + * @param simBenchCode Handle SimBench code + */ + final case class Converted(simBenchCode: String) extends CoordinatorMessage +} diff --git a/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala b/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala index 4482ad04..c3db382b 100644 --- a/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala +++ b/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala @@ -1,26 +1,10 @@ package edu.ie3.simbench.main -import java.nio.file.Paths -import edu.ie3.datamodel.io.naming.{ - DefaultDirectoryHierarchy, - EntityPersistenceNamingStrategy, - FileNamingStrategy -} -import edu.ie3.datamodel.io.sink.CsvFileSink -import edu.ie3.simbench.config.{ConfigValidator, SimbenchConfig} -import edu.ie3.simbench.convert.GridConverter -import edu.ie3.simbench.exception.CodeValidationException -import edu.ie3.simbench.io.{Downloader, IoUtils, SimbenchReader, Zipper} -import edu.ie3.simbench.model.SimbenchCode -import edu.ie3.util.io.FileIOUtils -import org.apache.commons.io.FilenameUtils +import akka.actor.typed.ActorSystem -import scala.concurrent.Await -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration.Duration -import scala.jdk.CollectionConverters._ -import scala.jdk.FutureConverters.CompletionStageOps -import scala.util.{Failure, Success} +import edu.ie3.simbench.actor.Coordinator +import edu.ie3.simbench.actor.Coordinator.{CoordinatorMessage, Start} +import edu.ie3.simbench.config.{ConfigValidator, SimbenchConfig} /** * This is not meant to be final production code. It is more a place for "testing" the full method stack. @@ -36,103 +20,110 @@ object RunSimbench extends SimbenchHelper { /* Validate the config */ ConfigValidator.checkValidity(simbenchConfig) - simbenchConfig.io.simbenchCodes.foreach { simbenchCode => - logger.info(s"$simbenchCode - Downloading data set from SimBench website") - val downloader = - Downloader( - simbenchConfig.io.input.download.folder, - simbenchConfig.io.input.download.baseUrl, - simbenchConfig.io.input.download.failOnExistingFiles - ) - val downloadedFile = - downloader.download( - SimbenchCode(simbenchCode).getOrElse( - throw CodeValidationException( - s"'$simbenchCode' is no valid SimBench code." - ) - ) - ) - val dataFolder = - Zipper.unzip( - downloadedFile, - downloader.downloadFolder, - simbenchConfig.io.input.download.failOnExistingFiles, - flattenDirectories = true - ) - - logger.info(s"$simbenchCode - Reading in the SimBench data set") - val simbenchReader = SimbenchReader( - simbenchCode, - dataFolder, - simbenchConfig.io.input.csv.separator, - simbenchConfig.io.input.csv.fileEnding, - simbenchConfig.io.input.csv.fileEncoding - ) - val simbenchModel = simbenchReader.readGrid() - - logger.info(s"$simbenchCode - Converting to PowerSystemDataModel") - val ( - jointGridContainer, - timeSeries, - timeSeriesMapping, - powerFlowResults - ) = - GridConverter.convert( - simbenchCode, - simbenchModel, - simbenchConfig.conversion.removeSwitches - ) - - logger.info(s"$simbenchCode - Writing converted data set to files") - /* Check, if a directory hierarchy is needed or not */ - val baseTargetDirectory = - IoUtils.ensureHarmonizedAndTerminatingFileSeparator( - simbenchConfig.io.output.targetFolder - ) - val csvSink = if (simbenchConfig.io.output.csv.directoryHierarchy) { - new CsvFileSink( - baseTargetDirectory, - new FileNamingStrategy( - new EntityPersistenceNamingStrategy(), - new DefaultDirectoryHierarchy(baseTargetDirectory, simbenchCode) - ), - false, - simbenchConfig.io.output.csv.separator - ) - } else { - new CsvFileSink( - baseTargetDirectory + simbenchCode, - new FileNamingStrategy(), - false, - simbenchConfig.io.output.csv.separator - ) - } - - csvSink.persistJointGrid(jointGridContainer) - timeSeries.foreach(csvSink.persistTimeSeries(_)) - csvSink.persistAllIgnoreNested(timeSeriesMapping.asJava) - csvSink.persistAll(powerFlowResults.asJava) - - if (simbenchConfig.io.output.compress) { - logger.info(s"$simbenchCode - Adding files to compressed archive") - val rawOutputPath = Paths.get(baseTargetDirectory + simbenchCode) - val archivePath = Paths.get( - FilenameUtils.concat(baseTargetDirectory, simbenchCode + ".tar.gz") - ) - val compressFuture = - FileIOUtils.compressDir(rawOutputPath, archivePath).asScala - compressFuture.onComplete { - case Success(_) => - FileIOUtils.deleteRecursively(rawOutputPath) - case Failure(exception) => - logger.error( - s"Compression of output files to '$archivePath' has failed. Keep raw data.", - exception - ) - } + /* Start the actor system */ + val actorSystem = ActorSystem[CoordinatorMessage]( + Coordinator(), + "Coordinator" + ) + actorSystem ! Start(simbenchConfig) - Await.ready(compressFuture, Duration("180s")) - } - } +// simbenchConfig.io.simbenchCodes.foreach { simbenchCode => +// logger.info(s"$simbenchCode - Downloading data set from SimBench website") +// val downloader = +// Downloader( +// simbenchConfig.io.input.download.folder, +// simbenchConfig.io.input.download.baseUrl, +// simbenchConfig.io.input.download.failOnExistingFiles +// ) +// val downloadedFile = +// downloader.download( +// SimbenchCode(simbenchCode).getOrElse( +// throw CodeValidationException( +// s"'$simbenchCode' is no valid SimBench code." +// ) +// ) +// ) +// val dataFolder = +// Zipper.unzip( +// downloadedFile, +// downloader.downloadFolder, +// simbenchConfig.io.input.download.failOnExistingFiles, +// flattenDirectories = true +// ) +// +// logger.info(s"$simbenchCode - Reading in the SimBench data set") +// val simbenchReader = SimbenchReader( +// simbenchCode, +// dataFolder, +// simbenchConfig.io.input.csv.separator, +// simbenchConfig.io.input.csv.fileEnding, +// simbenchConfig.io.input.csv.fileEncoding +// ) +// val simbenchModel = simbenchReader.readGrid() +// +// logger.info(s"$simbenchCode - Converting to PowerSystemDataModel") +// val ( +// jointGridContainer, +// timeSeries, +// timeSeriesMapping, +// powerFlowResults +// ) = +// GridConverter.convert( +// simbenchCode, +// simbenchModel, +// simbenchConfig.conversion.removeSwitches +// ) +// +// logger.info(s"$simbenchCode - Writing converted data set to files") +// /* Check, if a directory hierarchy is needed or not */ +// val baseTargetDirectory = +// IoUtils.ensureHarmonizedAndTerminatingFileSeparator( +// simbenchConfig.io.output.targetFolder +// ) +// val csvSink = if (simbenchConfig.io.output.csv.directoryHierarchy) { +// new CsvFileSink( +// baseTargetDirectory, +// new FileNamingStrategy( +// new EntityPersistenceNamingStrategy(), +// new DefaultDirectoryHierarchy(baseTargetDirectory, simbenchCode) +// ), +// false, +// simbenchConfig.io.output.csv.separator +// ) +// } else { +// new CsvFileSink( +// baseTargetDirectory + simbenchCode, +// new FileNamingStrategy(), +// false, +// simbenchConfig.io.output.csv.separator +// ) +// } +// +// csvSink.persistJointGrid(jointGridContainer) +// timeSeries.foreach(csvSink.persistTimeSeries(_)) +// csvSink.persistAllIgnoreNested(timeSeriesMapping.asJava) +// csvSink.persistAll(powerFlowResults.asJava) +// +// if (simbenchConfig.io.output.compress) { +// logger.info(s"$simbenchCode - Adding files to compressed archive") +// val rawOutputPath = Paths.get(baseTargetDirectory + simbenchCode) +// val archivePath = Paths.get( +// FilenameUtils.concat(baseTargetDirectory, simbenchCode + ".tar.gz") +// ) +// val compressFuture = +// FileIOUtils.compressDir(rawOutputPath, archivePath).asScala +// compressFuture.onComplete { +// case Success(_) => +// FileIOUtils.deleteRecursively(rawOutputPath) +// case Failure(exception) => +// logger.error( +// s"Compression of output files to '$archivePath' has failed. Keep raw data.", +// exception +// ) +// } +// +// Await.ready(compressFuture, Duration("180s")) +// } +// } } } From ea140dfa38e45499f9f5e1834ac1a9d1630a3bb0 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Thu, 26 Aug 2021 14:59:07 +0200 Subject: [PATCH 06/32] Introduce a mutator --- .../edu/ie3/simbench/actor/Converter.scala | 88 ++++++++++++++++-- .../edu/ie3/simbench/actor/Coordinator.scala | 90 +++++++++++++++---- .../edu/ie3/simbench/actor/Mutator.scala | 77 ++++++++++++++++ .../edu/ie3/simbench/main/RunSimbench.scala | 23 ----- 4 files changed, 233 insertions(+), 45 deletions(-) create mode 100644 src/main/scala/edu/ie3/simbench/actor/Mutator.scala diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index 3e70a236..b35e7bb3 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -1,25 +1,103 @@ package edu.ie3.simbench.actor import akka.actor.typed.ActorRef -import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.{ActorContext, Behaviors} object Converter { def apply(): Behaviors.Receive[ConverterMessage] = uninitialized + /** + * Receive information to set up myself accordingly + * + * @return Behavior to do so + */ def uninitialized: Behaviors.Receive[ConverterMessage] = Behaviors.receive { - case (ctx, Init(simBenchCode, replyTo)) => + case ( + ctx, + Init( + simBenchCode, + useDirectoryHierarchy, + targetDirectory, + csvColumnSeparator, + compress, + converter + ) + ) => ctx.log.info(s"Converter for '$simBenchCode' started.") - // TODO - Behaviors.same + + /* Initialize a mutator for this conversion and wait for it's reply */ + spawnMutator( + simBenchCode, + useDirectoryHierarchy, + targetDirectory, + csvColumnSeparator, + compress, + ctx + ) + initializing(simBenchCode, converter) + } + + /** + * Receive needed information while being in the process of initialization + * + * @param simBenchCode SimBench code to handle later + * @param coordinator Reference to the coordinator + * @return Behavior to do so + */ + def initializing( + simBenchCode: String, + coordinator: ActorRef[Coordinator.CoordinatorMessage] + ): Behaviors.Receive[ConverterMessage] = Behaviors.receive { + case (ctx, MutatorInitialized(mutator)) => + ctx.log.debug(s"Mutator for model '$simBenchCode' is ready.") + coordinator ! Coordinator.ConverterInitialized(simBenchCode, ctx.self) + idle(mutator) } - /** Messages, a coordinator will understand */ + def idle( + mutator: ActorRef[Mutator.MutatorMessage] + ): Behaviors.Receive[ConverterMessage] = Behaviors.receive { + case _ => Behaviors.same + } + + def spawnMutator( + simBenchCode: String, + useDirectoryHierarchy: Boolean, + targetDirectory: String, + csvColumnSeparator: String, + compress: Boolean, + ctx: ActorContext[ConverterMessage] + ): Unit = { + val mutator = ctx.spawn(Mutator(), s"mutator_$simBenchCode") + mutator ! Mutator.Init( + simBenchCode, + useDirectoryHierarchy, + targetDirectory, + csvColumnSeparator, + compress, + ctx.self + ) + } + + /** Messages, a converter will understand */ sealed trait ConverterMessage final case class Init( simBenchCode: String, + useDirectoryHierarchy: Boolean, + targetDirectory: String, + csvColumnSeparator: String, + compressConverted: Boolean, replyTo: ActorRef[Coordinator.CoordinatorMessage] ) extends ConverterMessage + /** + * Report, that the attached [[Mutator]] is ready for action + * + * @param replyTo Reference to the mutator + */ + final case class MutatorInitialized(replyTo: ActorRef[Mutator.MutatorMessage]) + extends ConverterMessage + /** * Request to convert a certain SimBench model * diff --git a/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala b/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala index 81260535..123a15f3 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala @@ -16,9 +16,27 @@ object Coordinator { config.io.simbenchCodes.filter(_ != firstSimBenchCode) /* Start a converter and issue the conversion */ - spawnConverter(ctx, firstSimBenchCode) + spawnConverter( + ctx, + firstSimBenchCode, + config.io.output.csv.directoryHierarchy, + config.io.output.targetFolder, + config.io.output.csv.separator, + config.io.output.compress + ) val initializingConverters = List(firstSimBenchCode) - idle(remainingCodes, initializingConverters) + + val stateData = StateData( + remainingCodes, + initializingConverters, + List.empty[String], + config.io.output.csv.directoryHierarchy, + config.io.output.targetFolder, + config.io.output.csv.separator, + config.io.output.compress + ) + + idle(stateData) case None => ctx.log.error("No models to convert. Shut down.") Behaviors.stopped @@ -26,31 +44,48 @@ object Coordinator { } def idle( - simBenchCodes: List[String], - initializingConverters: List[String], - activeConverters: List[String] = List.empty + stateData: StateData ): Behaviors.Receive[CoordinatorMessage] = Behaviors.receive { case (ctx, ConverterInitialized(simBenchCode, replyTo)) => ctx.log.debug(s"Reading to convert SimBench mode '$simBenchCode'.") replyTo ! Converter.Convert(simBenchCode) val stillInitializingConverters = - initializingConverters.filterNot(_ == simBenchCode) - val yetActiveConverters = activeConverters :+ simBenchCode - idle(simBenchCodes, stillInitializingConverters, yetActiveConverters) - case (ctx, Converted(simBenchCode)) if simBenchCodes.nonEmpty => + stateData.initializingConverters.filterNot(_ == simBenchCode) + val yetActiveConverters = stateData.activeConverters :+ simBenchCode + idle( + stateData.copy( + initializingConverters = stillInitializingConverters, + activeConverters = yetActiveConverters + ) + ) + case (ctx, Converted(simBenchCode)) if stateData.simBenchCodes.nonEmpty => /* A converter has completed. Report that and start a new converter. */ ctx.log.info( s"SimBench model with code '$simBenchCode' is completely converted." ) - val stillActiveConverters = activeConverters.filterNot(_ == simBenchCode) - simBenchCodes.headOption match { + val stillActiveConverters = + stateData.activeConverters.filterNot(_ == simBenchCode) + stateData.simBenchCodes.headOption match { case Some(nextSimBenchCode) => /* Spawn a converter and wait until it is ready */ - val remainingCodes = simBenchCodes.filter(_ != nextSimBenchCode) - spawnConverter(ctx, nextSimBenchCode) - val yetInitializingConverters = initializingConverters :+ nextSimBenchCode - idle(remainingCodes, yetInitializingConverters, activeConverters) + val remainingCodes = + stateData.simBenchCodes.filter(_ != nextSimBenchCode) + spawnConverter( + ctx, + nextSimBenchCode, + stateData.useDirectoryHierarchy, + stateData.targetDirectory, + stateData.csvColumnSeparator, + stateData.compressConverted + ) + val yetInitializingConverters = stateData.initializingConverters :+ nextSimBenchCode + idle( + stateData.copy( + simBenchCodes = remainingCodes, + initializingConverters = yetInitializingConverters + ) + ) case None if stillActiveConverters.nonEmpty => ctx.log.debug( s"Waiting for last ${stillActiveConverters.size} active converter(s)." @@ -64,12 +99,33 @@ object Coordinator { def spawnConverter( ctx: ActorContext[CoordinatorMessage], - simBenchCode: String + simBenchCode: String, + useDirectoryHierarchy: Boolean, + targetDirectory: String, + csvColumnSeparator: String, + compressConverted: Boolean ): Unit = { val converter = ctx.spawn(Converter(), s"converter_$simBenchCode") - converter ! Converter.Init(simBenchCode, ctx.self) + converter ! Converter.Init( + simBenchCode, + useDirectoryHierarchy, + targetDirectory, + csvColumnSeparator, + compressConverted, + ctx.self + ) } + final case class StateData( + simBenchCodes: List[String], + initializingConverters: List[String], + activeConverters: List[String], + useDirectoryHierarchy: Boolean, + targetDirectory: String, + csvColumnSeparator: String, + compressConverted: Boolean + ) + /** Messages, a coordinator will understand */ sealed trait CoordinatorMessage diff --git a/src/main/scala/edu/ie3/simbench/actor/Mutator.scala b/src/main/scala/edu/ie3/simbench/actor/Mutator.scala new file mode 100644 index 00000000..46557f00 --- /dev/null +++ b/src/main/scala/edu/ie3/simbench/actor/Mutator.scala @@ -0,0 +1,77 @@ +package edu.ie3.simbench.actor + +import akka.actor.typed.ActorRef +import akka.actor.typed.scaladsl.Behaviors +import edu.ie3.datamodel.io.naming.{ + DefaultDirectoryHierarchy, + EntityPersistenceNamingStrategy, + FileNamingStrategy +} +import edu.ie3.datamodel.io.sink.CsvFileSink +import edu.ie3.simbench.io.IoUtils + +object Mutator { + def apply(): Behaviors.Receive[MutatorMessage] = uninitialized + + def uninitialized: Behaviors.Receive[MutatorMessage] = Behaviors.receive { + case ( + ctx, + Init( + simBenchCode, + useDirectoryHierarchy, + targetDirectory, + csvColumnSeparator, + compress, + replyTo + ) + ) => + ctx.log.debug( + s"Initializing a mutator for SimBench code '$simBenchCode'." + ) + + val baseTargetDirectory = + IoUtils.ensureHarmonizedAndTerminatingFileSeparator(targetDirectory) + + /* Starting up a csv file sink */ + val csvSink = if (useDirectoryHierarchy) { + new CsvFileSink( + baseTargetDirectory, + new FileNamingStrategy( + new EntityPersistenceNamingStrategy(), + new DefaultDirectoryHierarchy(baseTargetDirectory, simBenchCode) + ), + false, + csvColumnSeparator + ) + } else { + new CsvFileSink( + baseTargetDirectory + simBenchCode, + new FileNamingStrategy(), + false, + csvColumnSeparator + ) + } + + replyTo ! Converter.MutatorInitialized(ctx.self) + idle(csvSink, compress) + } + + def idle( + sink: CsvFileSink, + compress: Boolean + ): Behaviors.Receive[MutatorMessage] = + Behaviors.receive { + case _ => Behaviors.ignore + } + + /** Messages, a mutator will understand */ + sealed trait MutatorMessage + final case class Init( + simBenchCode: String, + useDirectoryHierarchy: Boolean, + targetDirectory: String, + csvColumnSeparator: String, + compress: Boolean, + replyTo: ActorRef[Converter.ConverterMessage] + ) extends MutatorMessage +} diff --git a/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala b/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala index c3db382b..6222cbf1 100644 --- a/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala +++ b/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala @@ -75,29 +75,6 @@ object RunSimbench extends SimbenchHelper { // ) // // logger.info(s"$simbenchCode - Writing converted data set to files") -// /* Check, if a directory hierarchy is needed or not */ -// val baseTargetDirectory = -// IoUtils.ensureHarmonizedAndTerminatingFileSeparator( -// simbenchConfig.io.output.targetFolder -// ) -// val csvSink = if (simbenchConfig.io.output.csv.directoryHierarchy) { -// new CsvFileSink( -// baseTargetDirectory, -// new FileNamingStrategy( -// new EntityPersistenceNamingStrategy(), -// new DefaultDirectoryHierarchy(baseTargetDirectory, simbenchCode) -// ), -// false, -// simbenchConfig.io.output.csv.separator -// ) -// } else { -// new CsvFileSink( -// baseTargetDirectory + simbenchCode, -// new FileNamingStrategy(), -// false, -// simbenchConfig.io.output.csv.separator -// ) -// } // // csvSink.persistJointGrid(jointGridContainer) // timeSeries.foreach(csvSink.persistTimeSeries(_)) From c69b42d4c6f6f2517729dc1878e50bec8936bc40 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Thu, 26 Aug 2021 15:21:08 +0200 Subject: [PATCH 07/32] Shutting down mutators --- .../edu/ie3/simbench/actor/Converter.scala | 8 ++++ .../edu/ie3/simbench/actor/Mutator.scala | 43 ++++++++++++++++++- .../edu/ie3/simbench/main/RunSimbench.scala | 21 --------- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index b35e7bb3..42fb3f83 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -60,6 +60,8 @@ object Converter { case _ => Behaviors.same } + // TODO: Terminate mutator and await it's termination [[MutatorTerminated]] when terminating this actor + def spawnMutator( simBenchCode: String, useDirectoryHierarchy: Boolean, @@ -77,6 +79,7 @@ object Converter { compress, ctx.self ) + ctx.watchWith(mutator, MutatorTerminated) } /** Messages, a converter will understand */ @@ -98,6 +101,11 @@ object Converter { final case class MutatorInitialized(replyTo: ActorRef[Mutator.MutatorMessage]) extends ConverterMessage + /** + * Report, that the [[Mutator]] has terminated + */ + object MutatorTerminated extends ConverterMessage + /** * Request to convert a certain SimBench model * diff --git a/src/main/scala/edu/ie3/simbench/actor/Mutator.scala b/src/main/scala/edu/ie3/simbench/actor/Mutator.scala index 46557f00..1a8df4e1 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Mutator.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Mutator.scala @@ -9,6 +9,15 @@ import edu.ie3.datamodel.io.naming.{ } import edu.ie3.datamodel.io.sink.CsvFileSink import edu.ie3.simbench.io.IoUtils +import edu.ie3.util.io.FileIOUtils +import org.apache.commons.io.FilenameUtils + +import java.nio.file.Paths +import scala.concurrent.Await +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration.Duration +import scala.jdk.FutureConverters.CompletionStageOps +import scala.util.{Failure, Success} object Mutator { def apply(): Behaviors.Receive[MutatorMessage] = uninitialized @@ -53,15 +62,43 @@ object Mutator { } replyTo ! Converter.MutatorInitialized(ctx.self) - idle(csvSink, compress) + idle(simBenchCode, csvSink, baseTargetDirectory, compress) } def idle( + simBenchCode: String, sink: CsvFileSink, + targetDirectory: String, compress: Boolean ): Behaviors.Receive[MutatorMessage] = Behaviors.receive { - case _ => Behaviors.ignore + case (ctx, Terminate) => + ctx.log.debug("Got termination request") + + if (compress) { + ctx.log.debug("Compressing output") + val rawOutputPath = Paths.get(targetDirectory + simBenchCode) + val archivePath = Paths.get( + FilenameUtils.concat(targetDirectory, simBenchCode + ".tar.gz") + ) + val compressFuture = + FileIOUtils.compressDir(rawOutputPath, archivePath).asScala + compressFuture.onComplete { + case Success(_) => + FileIOUtils.deleteRecursively(rawOutputPath) + case Failure(exception) => + ctx.log.error( + s"Compression of output files to '$archivePath' has failed. Keep raw data.", + exception + ) + } + + Await.ready(compressFuture, Duration("180s")) + } + + sink.shutdown() + + Behaviors.stopped } /** Messages, a mutator will understand */ @@ -74,4 +111,6 @@ object Mutator { compress: Boolean, replyTo: ActorRef[Converter.ConverterMessage] ) extends MutatorMessage + + object Terminate extends MutatorMessage } diff --git a/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala b/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala index 6222cbf1..b425be05 100644 --- a/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala +++ b/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala @@ -80,27 +80,6 @@ object RunSimbench extends SimbenchHelper { // timeSeries.foreach(csvSink.persistTimeSeries(_)) // csvSink.persistAllIgnoreNested(timeSeriesMapping.asJava) // csvSink.persistAll(powerFlowResults.asJava) -// -// if (simbenchConfig.io.output.compress) { -// logger.info(s"$simbenchCode - Adding files to compressed archive") -// val rawOutputPath = Paths.get(baseTargetDirectory + simbenchCode) -// val archivePath = Paths.get( -// FilenameUtils.concat(baseTargetDirectory, simbenchCode + ".tar.gz") -// ) -// val compressFuture = -// FileIOUtils.compressDir(rawOutputPath, archivePath).asScala -// compressFuture.onComplete { -// case Success(_) => -// FileIOUtils.deleteRecursively(rawOutputPath) -// case Failure(exception) => -// logger.error( -// s"Compression of output files to '$archivePath' has failed. Keep raw data.", -// exception -// ) -// } -// -// Await.ready(compressFuture, Duration("180s")) -// } // } } } From e74b2c960ca2e5d643f15959dd7e869d8bf2e93b Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Thu, 26 Aug 2021 16:19:18 +0200 Subject: [PATCH 08/32] Download and read in SimBench data sets --- .../edu/ie3/simbench/actor/Converter.scala | 161 +++++++++++++++++- .../edu/ie3/simbench/actor/Coordinator.scala | 36 ++++ .../edu/ie3/simbench/main/RunSimbench.scala | 33 ---- 3 files changed, 190 insertions(+), 40 deletions(-) diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index 42fb3f83..a6eeccfb 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -2,6 +2,12 @@ package edu.ie3.simbench.actor import akka.actor.typed.ActorRef import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import edu.ie3.simbench.exception.CodeValidationException +import edu.ie3.simbench.io.{Downloader, SimbenchReader, Zipper} +import edu.ie3.simbench.model.SimbenchCode +import edu.ie3.simbench.model.datamodel.GridModel + +import java.nio.file.Path object Converter { def apply(): Behaviors.Receive[ConverterMessage] = uninitialized @@ -16,6 +22,12 @@ object Converter { ctx, Init( simBenchCode, + baseUrl, + downloadTargetDirectory, + failDownloadOnExistingFiles, + inputFileEnding, + inputFileEncoding, + inputFileColumnSeparator, useDirectoryHierarchy, targetDirectory, csvColumnSeparator, @@ -34,35 +46,89 @@ object Converter { compress, ctx ) - initializing(simBenchCode, converter) + initializing( + simBenchCode, + baseUrl, + downloadTargetDirectory, + failDownloadOnExistingFiles, + inputFileEnding, + inputFileEncoding, + inputFileColumnSeparator, + converter + ) } /** * Receive needed information while being in the process of initialization * - * @param simBenchCode SimBench code to handle later - * @param coordinator Reference to the coordinator + * @param simBenchCode SimBench code to handle later + * @param downloadTargetDirectory Target directory for downloads + * @param baseUrl Base url of the SimBench website + * @param failDownloadOnExistingFiles Whether or not to fail, if target files already exist + * @param inputFileEnding Ending of input files + * @param inputFileEncoding Encoding of the input files + * @param inputFileColumnSeparator Column separator of the input data + * @param coordinator Reference to the coordinator * @return Behavior to do so */ def initializing( simBenchCode: String, + baseUrl: String, + downloadTargetDirectory: String, + failDownloadOnExistingFiles: Boolean, + inputFileEnding: String, + inputFileEncoding: String, + inputFileColumnSeparator: String, coordinator: ActorRef[Coordinator.CoordinatorMessage] ): Behaviors.Receive[ConverterMessage] = Behaviors.receive { case (ctx, MutatorInitialized(mutator)) => ctx.log.debug(s"Mutator for model '$simBenchCode' is ready.") coordinator ! Coordinator.ConverterInitialized(simBenchCode, ctx.self) - idle(mutator) + idle( + StateData( + simBenchCode, + downloadTargetDirectory, + baseUrl, + failDownloadOnExistingFiles, + inputFileEnding, + inputFileEncoding, + inputFileColumnSeparator, + mutator + ) + ) } def idle( - mutator: ActorRef[Mutator.MutatorMessage] + stateData: StateData ): Behaviors.Receive[ConverterMessage] = Behaviors.receive { - case _ => Behaviors.same + case (ctx, Convert(simBenchCode)) => + ctx.log.info( + s"$simBenchCode - Downloading data set from SimBench website" + ) + val downloadDirectory = downloadModel( + simBenchCode, + stateData.downloadDirectory, + stateData.baseUrl, + stateData.failOnExistingFiles + ) + ctx.log.info(s"$simBenchCode - Reading in the SimBench data set") + val simBenchModel = readModel( + simBenchCode, + downloadDirectory, + stateData.inputFileColumnSeparator, + stateData.inputFileEnding, + stateData.inputFileEncoding + ) + /* + * TODO + * 3) Issue conversion + */ + Behaviors.same } // TODO: Terminate mutator and await it's termination [[MutatorTerminated]] when terminating this actor - def spawnMutator( + private def spawnMutator( simBenchCode: String, useDirectoryHierarchy: Boolean, targetDirectory: String, @@ -82,10 +148,91 @@ object Converter { ctx.watchWith(mutator, MutatorTerminated) } + /** + * Downloads the specified model and hands back the path, where the extracted files are located + * + * @param simBenchCode Code, that denotes the target SimBench model + * @param downloadDirectory Target directory for downloads + * @param baseUrl Base url of the SimBench website + * @param failOnExistingFiles Whether or not to fail, if target files already exist + * @return The path to the extracted data + */ + private def downloadModel( + simBenchCode: String, + downloadDirectory: String, + baseUrl: String, + failOnExistingFiles: Boolean + ): Path = { + val downloader = + Downloader( + downloadDirectory, + baseUrl, + failOnExistingFiles + ) + val downloadedFile = + downloader.download( + SimbenchCode(simBenchCode).getOrElse( + throw CodeValidationException( + s"'$simBenchCode' is no valid SimBench code." + ) + ) + ) + Zipper.unzip( + downloadedFile, + downloader.downloadFolder, + failOnExistingFiles, + flattenDirectories = true + ) + } + + /** + * Read in the raw SimBench grid model + * + * @param simBenchCode Code of model to convert + * @param downloadDirectory Directory, where the files are downloaded to + * @param csvColumnSeparator Column separator of input files + * @param fileEnding Ending of input files + * @param fileEncoding Encoding of the file + * @return The raw SimBench model + */ + private def readModel( + simBenchCode: String, + downloadDirectory: Path, + csvColumnSeparator: String, + fileEnding: String, + fileEncoding: String + ): GridModel = { + val simbenchReader = SimbenchReader( + simBenchCode, + downloadDirectory, + csvColumnSeparator, + fileEnding, + fileEncoding + ) + simbenchReader.readGrid() + } + + final case class StateData( + simBenchCode: String, + downloadDirectory: String, + baseUrl: String, + failOnExistingFiles: Boolean, + inputFileEnding: String, + inputFileEncoding: String, + inputFileColumnSeparator: String, + mutator: ActorRef[Mutator.MutatorMessage] + ) + /** Messages, a converter will understand */ sealed trait ConverterMessage final case class Init( simBenchCode: String, + baseUrl: String, + downloadTargetDirectory: String, + failDownloadOnExistingFiles: Boolean, + inputFileEnding: String, + inputFileEncoding: String, + inputFileColumnSeparator: String, useDirectoryHierarchy: Boolean, targetDirectory: String, csvColumnSeparator: String, diff --git a/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala b/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala index 123a15f3..154f0f95 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala @@ -19,6 +19,12 @@ object Coordinator { spawnConverter( ctx, firstSimBenchCode, + config.io.input.download.baseUrl, + config.io.input.download.folder, + config.io.input.download.failOnExistingFiles, + config.io.input.csv.fileEnding, + config.io.input.csv.fileEncoding, + config.io.input.csv.separator, config.io.output.csv.directoryHierarchy, config.io.output.targetFolder, config.io.output.csv.separator, @@ -30,6 +36,12 @@ object Coordinator { remainingCodes, initializingConverters, List.empty[String], + config.io.input.download.baseUrl, + config.io.input.download.folder, + config.io.input.download.failOnExistingFiles, + config.io.input.csv.fileEnding, + config.io.input.csv.fileEncoding, + config.io.input.csv.separator, config.io.output.csv.directoryHierarchy, config.io.output.targetFolder, config.io.output.csv.separator, @@ -74,6 +86,12 @@ object Coordinator { spawnConverter( ctx, nextSimBenchCode, + stateData.baseUrl, + stateData.downloadTargetDirectory, + stateData.failDownloadOnExistingFiles, + stateData.inputFileEnding, + stateData.inputFileEncoding, + stateData.inputFileColumnSeparator, stateData.useDirectoryHierarchy, stateData.targetDirectory, stateData.csvColumnSeparator, @@ -100,6 +118,12 @@ object Coordinator { def spawnConverter( ctx: ActorContext[CoordinatorMessage], simBenchCode: String, + baseUrl: String, + downloadTargetDirectory: String, + failDownloadOnExistingFiles: Boolean, + inputFileEnding: String, + inputFileEncoding: String, + inputFileColumnSeparator: String, useDirectoryHierarchy: Boolean, targetDirectory: String, csvColumnSeparator: String, @@ -108,6 +132,12 @@ object Coordinator { val converter = ctx.spawn(Converter(), s"converter_$simBenchCode") converter ! Converter.Init( simBenchCode, + baseUrl, + downloadTargetDirectory, + failDownloadOnExistingFiles, + inputFileEnding, + inputFileEncoding, + inputFileColumnSeparator, useDirectoryHierarchy, targetDirectory, csvColumnSeparator, @@ -120,6 +150,12 @@ object Coordinator { simBenchCodes: List[String], initializingConverters: List[String], activeConverters: List[String], + baseUrl: String, + downloadTargetDirectory: String, + failDownloadOnExistingFiles: Boolean, + inputFileEnding: String, + inputFileEncoding: String, + inputFileColumnSeparator: String, useDirectoryHierarchy: Boolean, targetDirectory: String, csvColumnSeparator: String, diff --git a/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala b/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala index b425be05..c3cce891 100644 --- a/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala +++ b/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala @@ -28,39 +28,6 @@ object RunSimbench extends SimbenchHelper { actorSystem ! Start(simbenchConfig) // simbenchConfig.io.simbenchCodes.foreach { simbenchCode => -// logger.info(s"$simbenchCode - Downloading data set from SimBench website") -// val downloader = -// Downloader( -// simbenchConfig.io.input.download.folder, -// simbenchConfig.io.input.download.baseUrl, -// simbenchConfig.io.input.download.failOnExistingFiles -// ) -// val downloadedFile = -// downloader.download( -// SimbenchCode(simbenchCode).getOrElse( -// throw CodeValidationException( -// s"'$simbenchCode' is no valid SimBench code." -// ) -// ) -// ) -// val dataFolder = -// Zipper.unzip( -// downloadedFile, -// downloader.downloadFolder, -// simbenchConfig.io.input.download.failOnExistingFiles, -// flattenDirectories = true -// ) -// -// logger.info(s"$simbenchCode - Reading in the SimBench data set") -// val simbenchReader = SimbenchReader( -// simbenchCode, -// dataFolder, -// simbenchConfig.io.input.csv.separator, -// simbenchConfig.io.input.csv.fileEnding, -// simbenchConfig.io.input.csv.fileEncoding -// ) -// val simbenchModel = simbenchReader.readGrid() -// // logger.info(s"$simbenchCode - Converting to PowerSystemDataModel") // val ( // jointGridContainer, From 9ff9318fe816e6e2ccfef78e9174fe42510609e1 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 27 Aug 2021 09:36:54 +0200 Subject: [PATCH 09/32] Make GridConverter an actor --- .../edu/ie3/simbench/actor/Converter.scala | 38 +++- .../edu/ie3/simbench/actor/Coordinator.scala | 6 + .../{convert => actor}/GridConverter.scala | 183 +++++++++++++----- .../simbench/convert/GridConverterSpec.scala | 1 + 4 files changed, 177 insertions(+), 51 deletions(-) rename src/main/scala/edu/ie3/simbench/{convert => actor}/GridConverter.scala (85%) diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index a6eeccfb..090f44a1 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -2,10 +2,11 @@ package edu.ie3.simbench.actor import akka.actor.typed.ActorRef import akka.actor.typed.scaladsl.{ActorContext, Behaviors} +import edu.ie3.datamodel.models.input.NodeInput import edu.ie3.simbench.exception.CodeValidationException import edu.ie3.simbench.io.{Downloader, SimbenchReader, Zipper} import edu.ie3.simbench.model.SimbenchCode -import edu.ie3.simbench.model.datamodel.GridModel +import edu.ie3.simbench.model.datamodel.{GridModel, Node} import java.nio.file.Path @@ -28,6 +29,7 @@ object Converter { inputFileEnding, inputFileEncoding, inputFileColumnSeparator, + removeSwitches, useDirectoryHierarchy, targetDirectory, csvColumnSeparator, @@ -54,6 +56,7 @@ object Converter { inputFileEnding, inputFileEncoding, inputFileColumnSeparator, + removeSwitches, converter ) } @@ -68,6 +71,7 @@ object Converter { * @param inputFileEnding Ending of input files * @param inputFileEncoding Encoding of the input files * @param inputFileColumnSeparator Column separator of the input data + * @param removeSwitches Whether or not to remove switches in final model * @param coordinator Reference to the coordinator * @return Behavior to do so */ @@ -79,6 +83,7 @@ object Converter { inputFileEnding: String, inputFileEncoding: String, inputFileColumnSeparator: String, + removeSwitches: Boolean, coordinator: ActorRef[Coordinator.CoordinatorMessage] ): Behaviors.Receive[ConverterMessage] = Behaviors.receive { case (ctx, MutatorInitialized(mutator)) => @@ -93,6 +98,7 @@ object Converter { inputFileEnding, inputFileEncoding, inputFileColumnSeparator, + removeSwitches, mutator ) ) @@ -119,9 +125,25 @@ object Converter { stateData.inputFileEnding, stateData.inputFileEncoding ) + + /* Spawning a grid converter and ask it to do some first conversions */ + val gridConverter = + ctx.spawn(GridConverter(), s"gridConverter_${stateData.simBenchCode}") + gridConverter ! GridConverter.ConvertNodes( + simBenchCode, + simBenchModel.nodes, + simBenchModel.externalNets, + simBenchModel.powerPlants, + simBenchModel.res, + simBenchModel.transformers2w, + simBenchModel.transformers3w, + simBenchModel.lines, + simBenchModel.switches, + stateData.removeSwitches, + ctx.self + ) /* - * TODO - * 3) Issue conversion + * TODO: Switch to converting state */ Behaviors.same } @@ -220,6 +242,7 @@ object Converter { inputFileEnding: String, inputFileEncoding: String, inputFileColumnSeparator: String, + removeSwitches: Boolean, mutator: ActorRef[Mutator.MutatorMessage] ) @@ -233,6 +256,7 @@ object Converter { inputFileEnding: String, inputFileEncoding: String, inputFileColumnSeparator: String, + removeSwitches: Boolean, useDirectoryHierarchy: Boolean, targetDirectory: String, csvColumnSeparator: String, @@ -259,4 +283,12 @@ object Converter { * @param simBenchCode Code that denotes the model */ final case class Convert(simBenchCode: String) extends ConverterMessage + + /** + * Feedback, that a given set of nodes have been converted + * + * @param conversion The conversion of nodes + */ + final case class NodesConverted(conversion: Map[Node, NodeInput]) + extends ConverterMessage } diff --git a/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala b/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala index 154f0f95..2952f19a 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala @@ -25,6 +25,7 @@ object Coordinator { config.io.input.csv.fileEnding, config.io.input.csv.fileEncoding, config.io.input.csv.separator, + config.conversion.removeSwitches, config.io.output.csv.directoryHierarchy, config.io.output.targetFolder, config.io.output.csv.separator, @@ -42,6 +43,7 @@ object Coordinator { config.io.input.csv.fileEnding, config.io.input.csv.fileEncoding, config.io.input.csv.separator, + config.conversion.removeSwitches, config.io.output.csv.directoryHierarchy, config.io.output.targetFolder, config.io.output.csv.separator, @@ -92,6 +94,7 @@ object Coordinator { stateData.inputFileEnding, stateData.inputFileEncoding, stateData.inputFileColumnSeparator, + stateData.removeSwitches, stateData.useDirectoryHierarchy, stateData.targetDirectory, stateData.csvColumnSeparator, @@ -124,6 +127,7 @@ object Coordinator { inputFileEnding: String, inputFileEncoding: String, inputFileColumnSeparator: String, + removeSwitches: Boolean, useDirectoryHierarchy: Boolean, targetDirectory: String, csvColumnSeparator: String, @@ -138,6 +142,7 @@ object Coordinator { inputFileEnding, inputFileEncoding, inputFileColumnSeparator, + removeSwitches, useDirectoryHierarchy, targetDirectory, csvColumnSeparator, @@ -156,6 +161,7 @@ object Coordinator { inputFileEnding: String, inputFileEncoding: String, inputFileColumnSeparator: String, + removeSwitches: Boolean, useDirectoryHierarchy: Boolean, targetDirectory: String, csvColumnSeparator: String, diff --git a/src/main/scala/edu/ie3/simbench/convert/GridConverter.scala b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala similarity index 85% rename from src/main/scala/edu/ie3/simbench/convert/GridConverter.scala rename to src/main/scala/edu/ie3/simbench/actor/GridConverter.scala index 4cb5540f..35e52052 100644 --- a/src/main/scala/edu/ie3/simbench/convert/GridConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala @@ -1,8 +1,11 @@ -package edu.ie3.simbench.convert +package edu.ie3.simbench.actor +import akka.actor.typed.ActorRef +import akka.actor.typed.scaladsl.Behaviors import com.typesafe.scalalogging.LazyLogging import edu.ie3.datamodel.io.source.TimeSeriesMappingSource import edu.ie3.datamodel.io.source.TimeSeriesMappingSource.MappingEntry +import edu.ie3.datamodel.models.input.NodeInput import edu.ie3.datamodel.models.input.connector.{ LineInput, SwitchInput, @@ -20,10 +23,9 @@ import edu.ie3.datamodel.models.input.graphics.{ NodeGraphicInput } import edu.ie3.datamodel.models.input.system._ -import edu.ie3.datamodel.models.input.NodeInput import edu.ie3.datamodel.models.result.NodeResult import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries -import edu.ie3.datamodel.models.value.{PValue, SValue, Value} +import edu.ie3.datamodel.models.value.{PValue, SValue} import edu.ie3.simbench.convert.NodeConverter.AttributeOverride.{ JoinOverride, SubnetOverride @@ -32,16 +34,9 @@ import edu.ie3.simbench.convert.types.{ LineTypeConverter, Transformer2wTypeConverter } +import edu.ie3.simbench.convert._ import edu.ie3.simbench.exception.ConversionException -import edu.ie3.simbench.model.datamodel.{ - GridModel, - Line, - Node, - NodePFResult, - Switch, - Transformer2W, - Transformer3W -} +import edu.ie3.simbench.model.datamodel._ import java.util.UUID import scala.annotation.tailrec @@ -49,6 +44,59 @@ import scala.jdk.CollectionConverters._ import scala.collection.parallel.CollectionConverters._ case object GridConverter extends LazyLogging { + def apply(): Behaviors.Receive[GridConverterMessage] = idle + + def idle: Behaviors.Receive[GridConverterMessage] = Behaviors.receive { + case ( + ctx, + ConvertNodes( + simBenchCode, + nodes, + externalNets, + powerPlants, + res, + transformers2w, + transformers3w, + lines, + switches, + removeSwitches, + converter + ) + ) => + ctx.log.debug( + s"Got asked to convert nodes of SimBench model '$simBenchCode'." + ) + val nodeConversion = convertNodes( + nodes, + externalNets, + powerPlants, + res, + transformers2w, + transformers3w, + lines, + switches, + removeSwitches + ) + converter ! Converter.NodesConverted(nodeConversion) + Behaviors.same + } + + sealed trait GridConverterMessage + final case class ConvertNodes( + simBenchCode: String, + nodes: Vector[Node], + externalNets: Vector[ExternalNet], + powerPlants: Vector[PowerPlant], + res: Vector[RES], + transformers2w: Vector[Transformer2W], + transformers3w: Vector[Transformer3W], + lines: Vector[Line[_]], + switches: Vector[Switch], + removeSwitches: Boolean = false, + replyTo: ActorRef[Converter.ConverterMessage] + ) extends GridConverterMessage + + /* FIXME: ===== From here on, it's plain object code! ===== */ /** * Converts a full simbench grid into power system data models [[JointGridContainer]]. Additionally, individual time @@ -108,43 +156,22 @@ case object GridConverter extends LazyLogging { * @param removeSwitches Whether or not to remove switches from the grid structure * @return All grid elements in converted form + a mapping from old to new node models */ + @deprecated("Use messages instead") def convertGridElements( gridInput: GridModel, removeSwitches: Boolean ): (RawGridElements, Map[Node, NodeInput]) = { - /* Set up a sub net converter, by crawling all nodes */ - val subnetConverter = SubnetConverter( - gridInput.nodes.map(node => (node.vmR, node.subnet)) - ) - - val slackNodeKeys = NodeConverter.getSlackNodeKeys( + val nodeConversion = convertNodes( + gridInput.nodes, gridInput.externalNets, gridInput.powerPlants, - gridInput.res - ) - - /* Collect overriding attributes for node conversion, based on special constellations within the grid */ - val subnetOverrides = determineSubnetOverrides( + gridInput.res, gridInput.transformers2w, gridInput.transformers3w, - gridInput.switches, gridInput.lines, - subnetConverter + gridInput.switches, + removeSwitches ) - val joinOverrides = if (removeSwitches) { - /* If switches are meant to be removed, join all nodes at closed switches */ - determineJoinOverrides(gridInput.switches, slackNodeKeys) - } else - Vector.empty - - val nodeConversion = - convertNodes( - gridInput.nodes, - slackNodeKeys, - subnetConverter, - subnetOverrides, - joinOverrides - ) val lines = convertLines(gridInput, nodeConversion).toSet.asJava val transformers2w = @@ -184,6 +211,66 @@ case object GridConverter extends LazyLogging { ) } + /** + * Convert the nodes with all needed preliminary steps. This is determination of target subnets, correction of subnet + * number for high voltage switch gear and joining of nodes in case of closed switches. + * + * @param nodes Collection of nodes + * @param externalNets Collection of external grids + * @param powerPlants Collection of power plants + * @param res Collection of renewable energy sources + * @param transformers2w Collection of two winding transformers + * @param transformers3w Collection of three winding transformers + * @param lines Collection of lines + * @param switches Collection of switches + * @param removeSwitches Whether or not to remove closed switches + * @return A mapping from raw node models to their conversion + */ + private def convertNodes( + nodes: Vector[Node], + externalNets: Vector[ExternalNet], + powerPlants: Vector[PowerPlant], + res: Vector[RES], + transformers2w: Vector[Transformer2W], + transformers3w: Vector[Transformer3W], + lines: Vector[Line[_]], + switches: Vector[Switch], + removeSwitches: Boolean = false + ): Map[Node, NodeInput] = { + /* Set up a sub net converter, by crawling all nodes */ + val subnetConverter = SubnetConverter( + nodes.map(node => (node.vmR, node.subnet)) + ) + + val slackNodeKeys = NodeConverter.getSlackNodeKeys( + externalNets, + powerPlants, + res + ) + + /* Collect overriding attributes for node conversion, based on special constellations within the grid */ + val subnetOverrides = determineSubnetOverrides( + transformers2w, + transformers3w, + switches, + lines, + subnetConverter + ) + val joinOverrides = if (removeSwitches) { + /* If switches are meant to be removed, join all nodes at closed switches */ + determineJoinOverrides(switches, slackNodeKeys) + } else + Vector.empty + + convertNodes( + nodes, + slackNodeKeys, + subnetConverter, + subnetOverrides, + joinOverrides + ) + } + /** * Determine all relevant subnet override information. SimBench has a different notion of where the border of a * subnet is. This is especially the case, if there is switch gear "upstream" of a transformer. For SimBench all @@ -192,11 +279,11 @@ case object GridConverter extends LazyLogging { * will control the switches in a manner, that the lower grid needs for. Therefore, for all nodes that are upstream * of a transformer's hv node and connected via switches, explicit subnet numbers are provided. * - * @param transformers2w Collection of two winding transformers - * @param transformers3w Collection of three winding transformers - * @param switches Collection of switches - * @param lines Collection of lines - * @param subnetConverter Converter to determine subnet numbers + * @param transformers2w Collection of two winding transformers + * @param transformers3w Collection of three winding transformers + * @param switches Collection of switches + * @param lines Collection of lines + * @param subnetConverter Converter to determine subnet numbers * @return A collection of [[SubnetOverride]]s */ private def determineSubnetOverrides( @@ -318,8 +405,8 @@ case object GridConverter extends LazyLogging { /** * Determine join overrides for all nodes, that are connected by closed switches * - * @param switches Collection of all (closed) switches - * @param slackNodeKeys Collection of node keys, that are foreseen to be slack nodes + * @param switches Collection of all (closed) switches + * @param slackNodeKeys Collection of node keys, that are foreseen to be slack nodes * @return A collection of [[JoinOverride]]s */ private def determineJoinOverrides( @@ -364,8 +451,8 @@ case object GridConverter extends LazyLogging { /** * Determine overrides per switch group * - * @param switchGroup A group of directly connected switches - * @param slackNodeKeys Collection of node keys, that are foreseen to be slack nodes + * @param switchGroup A group of directly connected switches + * @param slackNodeKeys Collection of node keys, that are foreseen to be slack nodes * @return A collection of [[JoinOverride]]s */ private def determineJoinOverridesForSwitchGroup( diff --git a/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala b/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala index b4714e1a..47b6abc8 100644 --- a/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala +++ b/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala @@ -6,6 +6,7 @@ import edu.ie3.datamodel.models.UniqueEntity import edu.ie3.datamodel.models.input.NodeInput import edu.ie3.datamodel.models.input.connector.{LineInput, Transformer2WInput} import edu.ie3.datamodel.models.input.system.{FixedFeedInInput, LoadInput} +import edu.ie3.simbench.actor.GridConverter import edu.ie3.simbench.convert.NodeConverter.AttributeOverride.JoinOverride import edu.ie3.simbench.io.SimbenchReader import edu.ie3.simbench.model.datamodel.{GridModel, Node, Switch} From 4cb8db15c82dd5201e1ecaa4a4b2fb800e9b23eb Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 27 Aug 2021 10:50:42 +0200 Subject: [PATCH 10/32] Convert branches --- .../edu/ie3/simbench/actor/Converter.scala | 89 ++++++++++++++++++- .../ie3/simbench/actor/GridConverter.scala | 73 ++++++++++++--- 2 files changed, 148 insertions(+), 14 deletions(-) diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index 090f44a1..e3c27dfa 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -2,11 +2,18 @@ package edu.ie3.simbench.actor import akka.actor.typed.ActorRef import akka.actor.typed.scaladsl.{ActorContext, Behaviors} -import edu.ie3.datamodel.models.input.NodeInput +import edu.ie3.datamodel.models.input.{MeasurementUnitInput, NodeInput} +import edu.ie3.datamodel.models.input.connector.{ + LineInput, + SwitchInput, + Transformer2WInput, + Transformer3WInput +} +import edu.ie3.datamodel.models.input.system.{FixedFeedInInput, LoadInput} import edu.ie3.simbench.exception.CodeValidationException import edu.ie3.simbench.io.{Downloader, SimbenchReader, Zipper} import edu.ie3.simbench.model.SimbenchCode -import edu.ie3.simbench.model.datamodel.{GridModel, Node} +import edu.ie3.simbench.model.datamodel.{GridModel, Node, Switch} import java.nio.file.Path @@ -142,10 +149,60 @@ object Converter { stateData.removeSwitches, ctx.self ) - /* - * TODO: Switch to converting state + converting(stateData, simBenchModel, gridConverter) + } + + def converting( + stateData: StateData, + simBenchModel: GridModel, + gridConverter: ActorRef[GridConverter.GridConverterMessage], + awaitedResults: AwaitedResults = AwaitedResults.empty + ): Behaviors.Receive[ConverterMessage] = Behaviors.receive { + case (ctx, NodesConverted(nodeConversion)) => + ctx.log.debug( + s"Nodes for SimBench model '${stateData.simBenchCode}' have been converted." + ) + ctx.log.debug( + s"Issue conversion of branch elements for model '${stateData.simBenchCode}'." + ) + gridConverter ! GridConverter.ConvertBranches( + stateData.simBenchCode, + nodeConversion, + simBenchModel.transformers2w, + simBenchModel.transformers3w, + simBenchModel.lines, + if (!stateData.removeSwitches) simBenchModel.switches + else Vector.empty[Switch], + simBenchModel.measurements, + ctx.self + ) + + /* TODO: + * 1) Issue all other conversions (res, powerplants, power flow results, ...) in parallel */ Behaviors.same + case ( + ctx, + BranchesConverted( + lines, + transformers2w, + transformers3w, + switches, + measurements + ) + ) => + ctx.log.debug( + s"Branches for SimBench model '${stateData.simBenchCode}' have been converted." + ) + /* TODO: Issue identification of islanded nodes (or later??) */ + val updatedAwaitedResults = awaitedResults.copy( + lines = Some(lines), + transformers2w = Some(transformers2w), + transformers3w = Some(transformers3w), + switches = Some(switches), + measurements = Some(measurements) + ) + converting(stateData, simBenchModel, gridConverter, updatedAwaitedResults) } // TODO: Terminate mutator and await it's termination [[MutatorTerminated]] when terminating this actor @@ -246,6 +303,22 @@ object Converter { mutator: ActorRef[Mutator.MutatorMessage] ) + final case class AwaitedResults( + nodes: Option[Vector[NodeInput]], + lines: Option[Vector[LineInput]], + transformers2w: Option[Vector[Transformer2WInput]], + transformers3w: Option[Vector[Transformer3WInput]], + switches: Option[Vector[SwitchInput]], + measurements: Option[Vector[MeasurementUnitInput]], + loads: Option[Vector[FixedFeedInInput]], + res: Option[Vector[FixedFeedInInput]], + powerPlants: Option[Vector[FixedFeedInInput]] + ) + object AwaitedResults { + def empty = + new AwaitedResults(None, None, None, None, None, None, None, None, None) + } + /** Messages, a converter will understand */ sealed trait ConverterMessage final case class Init( @@ -291,4 +364,12 @@ object Converter { */ final case class NodesConverted(conversion: Map[Node, NodeInput]) extends ConverterMessage + + final case class BranchesConverted( + lines: Vector[LineInput], + transformers2w: Vector[Transformer2WInput], + transformers3w: Vector[Transformer3WInput], + switches: Vector[SwitchInput], + measurements: Vector[MeasurementUnitInput] + ) extends ConverterMessage } diff --git a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala index 35e52052..4b79bdd4 100644 --- a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala @@ -37,6 +37,7 @@ import edu.ie3.simbench.convert.types.{ import edu.ie3.simbench.convert._ import edu.ie3.simbench.exception.ConversionException import edu.ie3.simbench.model.datamodel._ +import edu.ie3.simbench.model.datamodel.types.LineType import java.util.UUID import scala.annotation.tailrec @@ -78,6 +79,46 @@ case object GridConverter extends LazyLogging { removeSwitches ) converter ! Converter.NodesConverted(nodeConversion) + Behaviors.same + case ( + ctx, + ConvertBranches( + simBenchCode, + nodes, + transformers2w, + transformers3w, + lines, + switches, + measurements, + converter + ) + ) => + ctx.log.debug( + s"Got asked to convert branches of SimBench model '$simBenchCode'." + ) + + val convertedLines = convertLines(lines, nodes) + val convertedTransformers2w = + convertTransformers2w(transformers2w, nodes) + val convertedTransformers3w = Vector.empty[Transformer3WInput] /* Currently, no conversion strategy is known */ + if (transformers3w.nonEmpty) + ctx.log.debug( + "Creation of three winding transformers is not yet implemented." + ) + val convertedSwitches = SwitchConverter.convert(switches, nodes) + val convertedMeasurements = MeasurementConverter + .convert(measurements, nodes) + + /* We have to hand back all results and cannot pass them to the mutator, because operators and types have to be + * harmonized across the entirety of models */ + converter ! Converter.BranchesConverted( + convertedLines, + convertedTransformers2w, + convertedTransformers3w, + convertedSwitches, + convertedMeasurements + ) + Behaviors.same } @@ -96,6 +137,17 @@ case object GridConverter extends LazyLogging { replyTo: ActorRef[Converter.ConverterMessage] ) extends GridConverterMessage + final case class ConvertBranches( + simBenchCode: String, + nodes: Map[Node, NodeInput], + transformers2w: Vector[Transformer2W], + transformers3w: Vector[Transformer3W], + lines: Vector[Line[_ <: LineType]], + switches: Vector[Switch], + measurements: Vector[Measurement], + replyTo: ActorRef[Converter.ConverterMessage] + ) extends GridConverterMessage + /* FIXME: ===== From here on, it's plain object code! ===== */ /** @@ -107,6 +159,7 @@ case object GridConverter extends LazyLogging { * @param removeSwitches Whether or not to remove switches from the grid structure * @return A converted [[JointGridContainer]], a [[Vector]] of [[IndividualTimeSeries]] as well as a [[Vector]] of [[NodeResult]]s */ + @deprecated("Use messages instead") def convert( simbenchCode: String, gridInput: GridModel, @@ -173,9 +226,9 @@ case object GridConverter extends LazyLogging { removeSwitches ) - val lines = convertLines(gridInput, nodeConversion).toSet.asJava + val lines = convertLines(gridInput.lines, nodeConversion).toSet.asJava val transformers2w = - convertTransformers2w(gridInput, nodeConversion).toSet.asJava + convertTransformers2w(gridInput.transformers2w, nodeConversion).toSet.asJava val transformers3w = Set.empty[Transformer3WInput].asJava /* Currently, no conversion strategy is known */ logger.debug( "Creation of three winding transformers is not yet implemented." @@ -569,34 +622,34 @@ case object GridConverter extends LazyLogging { /** * Converts the given lines. * - * @param gridInput Total grid input model to convert + * @param lines Lines to convert * @param nodeConversion Already known conversion mapping of nodes * @return A vector of converted line models */ private def convertLines( - gridInput: GridModel, + lines: Vector[Line[_ <: LineType]], nodeConversion: Map[Node, NodeInput] ): Vector[LineInput] = { - val lineTypes = LineTypeConverter.convert(gridInput.lines) - LineConverter.convert(gridInput.lines, lineTypes, nodeConversion) + val lineTypes = LineTypeConverter.convert(lines) + LineConverter.convert(lines, lineTypes, nodeConversion) } /** * Converts the given two winding transformers. * - * @param gridInput Total grid input model to convert + * @param transformers Transformer models to convert * @param nodeConversion Already known conversion mapping of nodes * @return A vector of converted two winding transformer models */ private def convertTransformers2w( - gridInput: GridModel, + transformers: Vector[Transformer2W], nodeConversion: Map[Node, NodeInput] ): Vector[Transformer2WInput] = { val transformerTypes = Transformer2wTypeConverter.convert( - gridInput.transformers2w.map(_.transformerType) + transformers.map(_.transformerType) ) Transformer2wConverter.convert( - gridInput.transformers2w, + transformers, transformerTypes, nodeConversion ) From 53bbe700ed93b747199e6f5d89d099289338c5c0 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 27 Aug 2021 11:10:01 +0200 Subject: [PATCH 11/32] Filter islanded nodes --- .../edu/ie3/simbench/actor/Converter.scala | 56 ++++++++++++++++++- .../ie3/simbench/actor/GridConverter.scala | 33 +++++++++++ 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index e3c27dfa..d5043f32 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -178,7 +178,7 @@ object Converter { ) /* TODO: - * 1) Issue all other conversions (res, powerplants, power flow results, ...) in parallel + * 1) Issue all other conversions (power flow results, ...) in parallel */ Behaviors.same case ( @@ -194,7 +194,19 @@ object Converter { ctx.log.debug( s"Branches for SimBench model '${stateData.simBenchCode}' have been converted." ) - /* TODO: Issue identification of islanded nodes (or later??) */ + ctx.log.debug( + s"Issue filtering of islanded nodes in '${stateData.simBenchCode}'." + ) + gridConverter ! GridConverter.FilterIsolatedNodes( + stateData.simBenchCode, + awaitedResults.nodeConversion.getOrElse(Map.empty[Node, NodeInput]), + lines, + transformers2w, + transformers3w, + switches, + ctx.self + ) + val updatedAwaitedResults = awaitedResults.copy( lines = Some(lines), transformers2w = Some(transformers2w), @@ -203,6 +215,24 @@ object Converter { measurements = Some(measurements) ) converting(stateData, simBenchModel, gridConverter, updatedAwaitedResults) + + case (ctx, FilteredNodes(nodes)) => + ctx.log.debug( + s"Received nodes, that are filtered for non-islanded nodes in '${stateData.simBenchCode}'." + ) + val updatedAwaitingResults = awaitedResults.copy( + nodes = Some(nodes.values.toSeq.distinct.toVector), + nodeConversion = Some(nodes) + ) + + //TODO: Issue conversion of participants that then also respect for islanded nodes + + converting( + stateData, + simBenchModel, + gridConverter, + updatedAwaitingResults + ) } // TODO: Terminate mutator and await it's termination [[MutatorTerminated]] when terminating this actor @@ -305,6 +335,7 @@ object Converter { final case class AwaitedResults( nodes: Option[Vector[NodeInput]], + nodeConversion: Option[Map[Node, NodeInput]], lines: Option[Vector[LineInput]], transformers2w: Option[Vector[Transformer2WInput]], transformers3w: Option[Vector[Transformer3WInput]], @@ -316,7 +347,18 @@ object Converter { ) object AwaitedResults { def empty = - new AwaitedResults(None, None, None, None, None, None, None, None, None) + new AwaitedResults( + None, + None, + None, + None, + None, + None, + None, + None, + None, + None + ) } /** Messages, a converter will understand */ @@ -372,4 +414,12 @@ object Converter { switches: Vector[SwitchInput], measurements: Vector[MeasurementUnitInput] ) extends ConverterMessage + + /** + * Report, with all nodes, that are not islanded + * + * @param nodes Non-islanded nodes + */ + final case class FilteredNodes(nodes: Map[Node, NodeInput]) + extends ConverterMessage } diff --git a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala index 4b79bdd4..57c094b8 100644 --- a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala @@ -120,6 +120,29 @@ case object GridConverter extends LazyLogging { ) Behaviors.same + + case ( + ctx, + FilterIsolatedNodes( + simBenchCode, + nodeConversion, + lines, + transformers2w, + transformers3w, + switches, + converter + ) + ) => + ctx.log.debug(s"Filtering islanded nodes in '${simBenchCode}'.") + val nodes = filterIsolatedNodes( + nodeConversion, + lines.toSet.asJava, + transformers2w.toSet.asJava, + transformers3w.toSet.asJava, + switches.toSet.asJava + ) + converter ! Converter.FilteredNodes(nodes) + Behaviors.same } sealed trait GridConverterMessage @@ -148,6 +171,16 @@ case object GridConverter extends LazyLogging { replyTo: ActorRef[Converter.ConverterMessage] ) extends GridConverterMessage + final case class FilterIsolatedNodes( + simBenchCode: String, + nodeConversion: Map[Node, NodeInput], + lines: Vector[LineInput], + transformers2w: Vector[Transformer2WInput], + transformers3w: Vector[Transformer3WInput], + switches: Vector[SwitchInput], + replyTo: ActorRef[Converter.ConverterMessage] + ) extends GridConverterMessage + /* FIXME: ===== From here on, it's plain object code! ===== */ /** From 0adb4da36b8807dfa648d66004e8e56e795c0252 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 27 Aug 2021 11:20:32 +0200 Subject: [PATCH 12/32] Converting node results --- .../edu/ie3/simbench/actor/Converter.scala | 36 +++++++++++++++---- .../ie3/simbench/actor/GridConverter.scala | 28 ++++++++++----- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index d5043f32..022ee613 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -10,6 +10,7 @@ import edu.ie3.datamodel.models.input.connector.{ Transformer3WInput } import edu.ie3.datamodel.models.input.system.{FixedFeedInInput, LoadInput} +import edu.ie3.datamodel.models.result.NodeResult import edu.ie3.simbench.exception.CodeValidationException import edu.ie3.simbench.io.{Downloader, SimbenchReader, Zipper} import edu.ie3.simbench.model.SimbenchCode @@ -176,10 +177,6 @@ object Converter { simBenchModel.measurements, ctx.self ) - - /* TODO: - * 1) Issue all other conversions (power flow results, ...) in parallel - */ Behaviors.same case ( ctx, @@ -216,15 +213,23 @@ object Converter { ) converting(stateData, simBenchModel, gridConverter, updatedAwaitedResults) - case (ctx, FilteredNodes(nodes)) => + case (ctx, FilteredNodes(updatedNodeConversion)) => ctx.log.debug( s"Received nodes, that are filtered for non-islanded nodes in '${stateData.simBenchCode}'." ) val updatedAwaitingResults = awaitedResults.copy( - nodes = Some(nodes.values.toSeq.distinct.toVector), - nodeConversion = Some(nodes) + nodes = Some(updatedNodeConversion.values.toSeq.distinct.toVector), + nodeConversion = Some(updatedNodeConversion) ) + gridConverter ! GridConverter + .ConvertNodeResults( + stateData.simBenchCode, + updatedNodeConversion, + simBenchModel.nodePFResults, + ctx.self + ) + //TODO: Issue conversion of participants that then also respect for islanded nodes converting( @@ -233,6 +238,13 @@ object Converter { gridConverter, updatedAwaitingResults ) + case (ctx, NodeResultsConverted(nodeResults)) => + ctx.log.debug( + s"Received converted node results for '${stateData.simBenchCode}'." + ) + val updatedAwaitedResults = + awaitedResults.copy(nodeResults = Some(nodeResults)) + converting(stateData, simBenchModel, gridConverter, updatedAwaitedResults) } // TODO: Terminate mutator and await it's termination [[MutatorTerminated]] when terminating this actor @@ -336,6 +348,7 @@ object Converter { final case class AwaitedResults( nodes: Option[Vector[NodeInput]], nodeConversion: Option[Map[Node, NodeInput]], + nodeResults: Option[Vector[NodeResult]], lines: Option[Vector[LineInput]], transformers2w: Option[Vector[Transformer2WInput]], transformers3w: Option[Vector[Transformer3WInput]], @@ -357,6 +370,7 @@ object Converter { None, None, None, + None, None ) } @@ -422,4 +436,12 @@ object Converter { */ final case class FilteredNodes(nodes: Map[Node, NodeInput]) extends ConverterMessage + + /** + * Feedback, about converted node results + * + * @param nodeResults Converted node results + */ + final case class NodeResultsConverted(nodeResults: Vector[NodeResult]) + extends ConverterMessage } diff --git a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala index 57c094b8..bded6146 100644 --- a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala @@ -133,7 +133,7 @@ case object GridConverter extends LazyLogging { converter ) ) => - ctx.log.debug(s"Filtering islanded nodes in '${simBenchCode}'.") + ctx.log.debug(s"Filtering islanded nodes in '$simBenchCode'.") val nodes = filterIsolatedNodes( nodeConversion, lines.toSet.asJava, @@ -143,6 +143,13 @@ case object GridConverter extends LazyLogging { ) converter ! Converter.FilteredNodes(nodes) Behaviors.same + case ( + ctx, + ConvertNodeResults(simBenchCode, nodeConversion, results, replyTo) + ) => + ctx.log.debug(s"Converting node results for '$simBenchCode'.") + val convertedResults = convertNodeResults(results, nodeConversion) + Behaviors.same } sealed trait GridConverterMessage @@ -181,6 +188,13 @@ case object GridConverter extends LazyLogging { replyTo: ActorRef[Converter.ConverterMessage] ) extends GridConverterMessage + final case class ConvertNodeResults( + simBenchCode: String, + nodeConversion: Map[Node, NodeInput], + results: Vector[NodePFResult], + replyTo: ActorRef[Converter.ConverterMessage] + ) extends GridConverterMessage + /* FIXME: ===== From here on, it's plain object code! ===== */ /** @@ -642,14 +656,10 @@ case object GridConverter extends LazyLogging { input: Vector[NodePFResult], nodeConversion: Map[Node, NodeInput] ): Vector[NodeResult] = - input.par.map { nodePfResult => - val node = nodeConversion.getOrElse( - nodePfResult.node, - throw ConversionException( - s"Cannot convert power flow result for node ${nodePfResult.node}, as the needed node conversion cannot be found." - ) - ) - NodePFResultConverter.convert(nodePfResult, node) + input.par.flatMap { nodePfResult => + nodeConversion + .get(nodePfResult.node) + .map(node => NodePFResultConverter.convert(nodePfResult, node)) }.seq /** From 77461ea983e6b2d72a9560a9ca9bac95104a8cb0 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 27 Aug 2021 11:28:33 +0200 Subject: [PATCH 13/32] Updating protocol --- docs/diagrams/protocol/protocol.puml | 11 ++++++----- src/main/scala/edu/ie3/simbench/actor/Converter.scala | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/diagrams/protocol/protocol.puml b/docs/diagrams/protocol/protocol.puml index cf0c01c8..5955933d 100644 --- a/docs/diagrams/protocol/protocol.puml +++ b/docs/diagrams/protocol/protocol.puml @@ -18,10 +18,12 @@ Converter -> Mutator: Init(Config) Converter <- Mutator: Ready Converter --[#blue]> GridConverter: spawn & watch Converter -> GridConverter: ConvertNodes(List[Node]) -Converter <- GridConverter: ConvertedNodes(Map[Node,NodeInput]) - -Converter -> GridConverter: ConvertRest(...) -activate GridConverter +Converter <- GridConverter: NodesConverted(Map[Node,NodeInput]) +Converter -> GridConverter: ConvertBranches(...) +Converter <- GridConverter: BranchesConverted(...) +Converter -> GridConverter: FilterIsolatedNodes(...) +Converter <- GridConverter: FilteredNodes(...) +note left: Now all other entities\ncan be converted (not)\nconsidering islanded nodes Converter --[#blue]> RESConverter: spawn & watch Converter -> RESConverter: Convert(List[RES], Map[Node,NodeInput]) @@ -37,7 +39,6 @@ RESConverter <--[#blue] RESConverterWorker Converter <- RESConverter: Done GridConverter -> Mutator: Mutate(...) -deactivate GridConverter GridConverter <- Mutator: Mutated(...) Converter <- GridConverter: Done diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index 022ee613..b9a791c0 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -230,7 +230,8 @@ object Converter { ctx.self ) - //TODO: Issue conversion of participants that then also respect for islanded nodes + // TODO: Persist Grid structure + // TODO: Issue conversion of participants that then also respect for islanded nodes converting( stateData, From a894358a8a5bac68bdcae460ef7fb113c9f92803 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 27 Aug 2021 11:41:15 +0200 Subject: [PATCH 14/32] Simplify protocol --- docs/diagrams/protocol/protocol.puml | 10 +- .../edu/ie3/simbench/actor/Converter.scala | 128 ++++-------------- .../ie3/simbench/actor/GridConverter.scala | 111 ++++----------- 3 files changed, 54 insertions(+), 195 deletions(-) diff --git a/docs/diagrams/protocol/protocol.puml b/docs/diagrams/protocol/protocol.puml index 5955933d..4f6f1aad 100644 --- a/docs/diagrams/protocol/protocol.puml +++ b/docs/diagrams/protocol/protocol.puml @@ -17,13 +17,9 @@ Converter --[#blue]> Mutator: spawn & watch Converter -> Mutator: Init(Config) Converter <- Mutator: Ready Converter --[#blue]> GridConverter: spawn & watch -Converter -> GridConverter: ConvertNodes(List[Node]) -Converter <- GridConverter: NodesConverted(Map[Node,NodeInput]) -Converter -> GridConverter: ConvertBranches(...) -Converter <- GridConverter: BranchesConverted(...) -Converter -> GridConverter: FilterIsolatedNodes(...) -Converter <- GridConverter: FilteredNodes(...) -note left: Now all other entities\ncan be converted (not)\nconsidering islanded nodes +Converter -> GridConverter: ConvertGridStructure(...) +Converter <- GridConverter: GridStructureConverted(Map[Node,NodeInput]) +note left: Now all other entities\ncan be converted (not)\nconsidering islanded\nnodes Converter --[#blue]> RESConverter: spawn & watch Converter -> RESConverter: Convert(List[RES], Map[Node,NodeInput]) diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index b9a791c0..42233e09 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -137,9 +137,10 @@ object Converter { /* Spawning a grid converter and ask it to do some first conversions */ val gridConverter = ctx.spawn(GridConverter(), s"gridConverter_${stateData.simBenchCode}") - gridConverter ! GridConverter.ConvertNodes( + gridConverter ! GridConverter.ConvertGridStructure( simBenchCode, simBenchModel.nodes, + simBenchModel.nodePFResults, simBenchModel.externalNets, simBenchModel.powerPlants, simBenchModel.res, @@ -147,6 +148,7 @@ object Converter { simBenchModel.transformers3w, simBenchModel.lines, simBenchModel.switches, + simBenchModel.measurements, stateData.removeSwitches, ctx.self ) @@ -159,28 +161,11 @@ object Converter { gridConverter: ActorRef[GridConverter.GridConverterMessage], awaitedResults: AwaitedResults = AwaitedResults.empty ): Behaviors.Receive[ConverterMessage] = Behaviors.receive { - case (ctx, NodesConverted(nodeConversion)) => - ctx.log.debug( - s"Nodes for SimBench model '${stateData.simBenchCode}' have been converted." - ) - ctx.log.debug( - s"Issue conversion of branch elements for model '${stateData.simBenchCode}'." - ) - gridConverter ! GridConverter.ConvertBranches( - stateData.simBenchCode, - nodeConversion, - simBenchModel.transformers2w, - simBenchModel.transformers3w, - simBenchModel.lines, - if (!stateData.removeSwitches) simBenchModel.switches - else Vector.empty[Switch], - simBenchModel.measurements, - ctx.self - ) - Behaviors.same case ( ctx, - BranchesConverted( + GridStructureConverted( + nodeConversion, + nodeResults, lines, transformers2w, transformers3w, @@ -189,63 +174,23 @@ object Converter { ) ) => ctx.log.debug( - s"Branches for SimBench model '${stateData.simBenchCode}' have been converted." + s"Grid structure of model '${stateData.simBenchCode}' has been converted." ) - ctx.log.debug( - s"Issue filtering of islanded nodes in '${stateData.simBenchCode}'." - ) - gridConverter ! GridConverter.FilterIsolatedNodes( - stateData.simBenchCode, - awaitedResults.nodeConversion.getOrElse(Map.empty[Node, NodeInput]), - lines, - transformers2w, - transformers3w, - switches, - ctx.self - ) - - val updatedAwaitedResults = awaitedResults.copy( - lines = Some(lines), - transformers2w = Some(transformers2w), - transformers3w = Some(transformers3w), - switches = Some(switches), - measurements = Some(measurements) - ) - converting(stateData, simBenchModel, gridConverter, updatedAwaitedResults) - - case (ctx, FilteredNodes(updatedNodeConversion)) => - ctx.log.debug( - s"Received nodes, that are filtered for non-islanded nodes in '${stateData.simBenchCode}'." - ) - val updatedAwaitingResults = awaitedResults.copy( - nodes = Some(updatedNodeConversion.values.toSeq.distinct.toVector), - nodeConversion = Some(updatedNodeConversion) - ) - - gridConverter ! GridConverter - .ConvertNodeResults( - stateData.simBenchCode, - updatedNodeConversion, - simBenchModel.nodePFResults, - ctx.self - ) - - // TODO: Persist Grid structure // TODO: Issue conversion of participants that then also respect for islanded nodes - converting( stateData, simBenchModel, gridConverter, - updatedAwaitingResults - ) - case (ctx, NodeResultsConverted(nodeResults)) => - ctx.log.debug( - s"Received converted node results for '${stateData.simBenchCode}'." + awaitedResults.copy( + nodeConversion = Some(nodeConversion), + nodeResults = Some(nodeResults), + lines = Some(lines), + transformers2w = Some(transformers2w), + transformers3w = Some(transformers3w), + switches = Some(switches), + measurements = Some(measurements) + ) ) - val updatedAwaitedResults = - awaitedResults.copy(nodeResults = Some(nodeResults)) - converting(stateData, simBenchModel, gridConverter, updatedAwaitedResults) } // TODO: Terminate mutator and await it's termination [[MutatorTerminated]] when terminating this actor @@ -347,17 +292,13 @@ object Converter { ) final case class AwaitedResults( - nodes: Option[Vector[NodeInput]], nodeConversion: Option[Map[Node, NodeInput]], nodeResults: Option[Vector[NodeResult]], lines: Option[Vector[LineInput]], transformers2w: Option[Vector[Transformer2WInput]], transformers3w: Option[Vector[Transformer3WInput]], switches: Option[Vector[SwitchInput]], - measurements: Option[Vector[MeasurementUnitInput]], - loads: Option[Vector[FixedFeedInInput]], - res: Option[Vector[FixedFeedInInput]], - powerPlants: Option[Vector[FixedFeedInInput]] + measurements: Option[Vector[MeasurementUnitInput]] ) object AwaitedResults { def empty = @@ -368,10 +309,6 @@ object Converter { None, None, None, - None, - None, - None, - None, None ) } @@ -417,32 +354,21 @@ object Converter { /** * Feedback, that a given set of nodes have been converted * - * @param conversion The conversion of nodes + * @param nodeConversion The conversion of nodes + * @param nodeResults Node results + * @param lines Lines + * @param transformers2w Two winding transformer + * @param transformers3w Three winding transformers + * @param switches Switches + * @param measurements Measurement devices */ - final case class NodesConverted(conversion: Map[Node, NodeInput]) - extends ConverterMessage - - final case class BranchesConverted( + final case class GridStructureConverted( + nodeConversion: Map[Node, NodeInput], + nodeResults: Vector[NodeResult], lines: Vector[LineInput], transformers2w: Vector[Transformer2WInput], transformers3w: Vector[Transformer3WInput], switches: Vector[SwitchInput], measurements: Vector[MeasurementUnitInput] ) extends ConverterMessage - - /** - * Report, with all nodes, that are not islanded - * - * @param nodes Non-islanded nodes - */ - final case class FilteredNodes(nodes: Map[Node, NodeInput]) - extends ConverterMessage - - /** - * Feedback, about converted node results - * - * @param nodeResults Converted node results - */ - final case class NodeResultsConverted(nodeResults: Vector[NodeResult]) - extends ConverterMessage } diff --git a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala index bded6146..1e09963c 100644 --- a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala @@ -50,9 +50,10 @@ case object GridConverter extends LazyLogging { def idle: Behaviors.Receive[GridConverterMessage] = Behaviors.receive { case ( ctx, - ConvertNodes( + ConvertGridStructure( simBenchCode, nodes, + nodeResults, externalNets, powerPlants, res, @@ -60,12 +61,13 @@ case object GridConverter extends LazyLogging { transformers3w, lines, switches, + measurements, removeSwitches, converter ) ) => ctx.log.debug( - s"Got asked to convert nodes of SimBench model '$simBenchCode'." + s"Got asked to convert the grid structure of SimBench model '$simBenchCode'." ) val nodeConversion = convertNodes( nodes, @@ -78,120 +80,55 @@ case object GridConverter extends LazyLogging { switches, removeSwitches ) - converter ! Converter.NodesConverted(nodeConversion) - Behaviors.same - case ( - ctx, - ConvertBranches( - simBenchCode, - nodes, - transformers2w, - transformers3w, - lines, - switches, - measurements, - converter - ) - ) => - ctx.log.debug( - s"Got asked to convert branches of SimBench model '$simBenchCode'." - ) - val convertedLines = convertLines(lines, nodes) + val convertedLines = convertLines(lines, nodeConversion) val convertedTransformers2w = - convertTransformers2w(transformers2w, nodes) + convertTransformers2w(transformers2w, nodeConversion) val convertedTransformers3w = Vector.empty[Transformer3WInput] /* Currently, no conversion strategy is known */ if (transformers3w.nonEmpty) ctx.log.debug( "Creation of three winding transformers is not yet implemented." ) - val convertedSwitches = SwitchConverter.convert(switches, nodes) + val convertedSwitches = SwitchConverter.convert(switches, nodeConversion) val convertedMeasurements = MeasurementConverter - .convert(measurements, nodes) + .convert(measurements, nodeConversion) - /* We have to hand back all results and cannot pass them to the mutator, because operators and types have to be - * harmonized across the entirety of models */ - converter ! Converter.BranchesConverted( + val nodes = filterIsolatedNodes( + nodeConversion, + convertedLines.toSet.asJava, + convertedTransformers2w.toSet.asJava, + convertedTransformers3w.toSet.asJava, + convertedSwitches.toSet.asJava + ) + + val convertedResults = convertNodeResults(nodeResults, nodes) + + converter ! Converter.GridStructureConverted( + nodes, + convertedResults, convertedLines, convertedTransformers2w, convertedTransformers3w, convertedSwitches, convertedMeasurements ) - - Behaviors.same - - case ( - ctx, - FilterIsolatedNodes( - simBenchCode, - nodeConversion, - lines, - transformers2w, - transformers3w, - switches, - converter - ) - ) => - ctx.log.debug(s"Filtering islanded nodes in '$simBenchCode'.") - val nodes = filterIsolatedNodes( - nodeConversion, - lines.toSet.asJava, - transformers2w.toSet.asJava, - transformers3w.toSet.asJava, - switches.toSet.asJava - ) - converter ! Converter.FilteredNodes(nodes) - Behaviors.same - case ( - ctx, - ConvertNodeResults(simBenchCode, nodeConversion, results, replyTo) - ) => - ctx.log.debug(s"Converting node results for '$simBenchCode'.") - val convertedResults = convertNodeResults(results, nodeConversion) Behaviors.same } sealed trait GridConverterMessage - final case class ConvertNodes( + final case class ConvertGridStructure( simBenchCode: String, nodes: Vector[Node], + results: Vector[NodePFResult], externalNets: Vector[ExternalNet], powerPlants: Vector[PowerPlant], res: Vector[RES], transformers2w: Vector[Transformer2W], transformers3w: Vector[Transformer3W], - lines: Vector[Line[_]], - switches: Vector[Switch], - removeSwitches: Boolean = false, - replyTo: ActorRef[Converter.ConverterMessage] - ) extends GridConverterMessage - - final case class ConvertBranches( - simBenchCode: String, - nodes: Map[Node, NodeInput], - transformers2w: Vector[Transformer2W], - transformers3w: Vector[Transformer3W], lines: Vector[Line[_ <: LineType]], switches: Vector[Switch], measurements: Vector[Measurement], - replyTo: ActorRef[Converter.ConverterMessage] - ) extends GridConverterMessage - - final case class FilterIsolatedNodes( - simBenchCode: String, - nodeConversion: Map[Node, NodeInput], - lines: Vector[LineInput], - transformers2w: Vector[Transformer2WInput], - transformers3w: Vector[Transformer3WInput], - switches: Vector[SwitchInput], - replyTo: ActorRef[Converter.ConverterMessage] - ) extends GridConverterMessage - - final case class ConvertNodeResults( - simBenchCode: String, - nodeConversion: Map[Node, NodeInput], - results: Vector[NodePFResult], + removeSwitches: Boolean = false, replyTo: ActorRef[Converter.ConverterMessage] ) extends GridConverterMessage From 6f9ef084575cc307dc498213cf2b479fb585de6f Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 27 Aug 2021 14:03:42 +0200 Subject: [PATCH 15/32] Spawn a ResConverter and it's worker pool --- docs/diagrams/protocol/protocol.puml | 29 ++-- src/main/resources/config-template.conf | 1 + .../edu/ie3/simbench/actor/Converter.scala | 36 +++- .../edu/ie3/simbench/actor/Coordinator.scala | 6 + .../ie3/simbench/actor/GridConverter.scala | 34 +--- .../edu/ie3/simbench/actor/ResConverter.scala | 158 ++++++++++++++++++ .../ie3/simbench/config/ConfigValidator.scala | 16 ++ .../ie3/simbench/config/SimbenchConfig.scala | 7 +- .../ie3/simbench/convert/ResConverter.scala | 85 ---------- .../simbench/convert/ResConverterSpec.scala | 4 +- .../edu/ie3/test/common/ConfigTestData.scala | 3 +- 11 files changed, 251 insertions(+), 128 deletions(-) create mode 100644 src/main/scala/edu/ie3/simbench/actor/ResConverter.scala delete mode 100644 src/main/scala/edu/ie3/simbench/convert/ResConverter.scala diff --git a/docs/diagrams/protocol/protocol.puml b/docs/diagrams/protocol/protocol.puml index 4f6f1aad..628459ff 100644 --- a/docs/diagrams/protocol/protocol.puml +++ b/docs/diagrams/protocol/protocol.puml @@ -4,9 +4,9 @@ participant Main participant Coordinator participant Converter participant GridConverter -participant RESConverter +participant ResConverter +collections ResConverterWorker participant Mutator -collections RESConverterWorker Main -> Coordinator: Start(Config) Coordinator --[#blue]> Converter: spawn & watch @@ -21,18 +21,21 @@ Converter -> GridConverter: ConvertGridStructure(...) Converter <- GridConverter: GridStructureConverted(Map[Node,NodeInput]) note left: Now all other entities\ncan be converted (not)\nconsidering islanded\nnodes -Converter --[#blue]> RESConverter: spawn & watch -Converter -> RESConverter: Convert(List[RES], Map[Node,NodeInput]) -RESConverter -> RESConverterWorker: Convert(RES, Map[Node,NodeInput]) -RESConverterWorker -> Mutator: Mutate(RES) +Converter --[#blue]> ResConverter: spawn & watch +Converter -> ResConverter: Init(...) +ResConverter --[#blue]> ResConverterWorker: spawn & watch +Converter <- ResConverter: ResConverterReady +Converter -> ResConverter: Convert(...) +ResConverter -> ResConverterWorker: Convert(Res, Map[Node,NodeInput]) +ResConverterWorker -> Mutator: Mutate(Res) activate Mutator -RESConverterWorker <- Mutator: MutatedEntity(UUID) -RESConverterWorker -> Mutator: Mutate(TimeSeries) -RESConverterWorker <- Mutator: MutatedTimeSeries(UUID) -RESConverter <- RESConverterWorker: Converted(UUID, UUID) -RESConverter --[#blue]> RESConverterWorker: terminate -RESConverter <--[#blue] RESConverterWorker -Converter <- RESConverter: Done +ResConverterWorker <- Mutator: MutatedEntity(UUID) +ResConverterWorker -> Mutator: Mutate(TimeSeries) +ResConverterWorker <- Mutator: MutatedTimeSeries(UUID) +ResConverter <- ResConverterWorker: Converted(UUID, UUID) +ResConverter --[#blue]> ResConverterWorker: terminate +ResConverter <--[#blue] ResConverterWorker +Converter <- ResConverter: Done GridConverter -> Mutator: Mutate(...) GridConverter <- Mutator: Mutated(...) diff --git a/src/main/resources/config-template.conf b/src/main/resources/config-template.conf index 55a408d4..aa851f30 100644 --- a/src/main/resources/config-template.conf +++ b/src/main/resources/config-template.conf @@ -22,4 +22,5 @@ io { } conversion { removeSwitches = "Boolean" | false + participantWorkersPerType = "Int" | 20 } \ No newline at end of file diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index 42233e09..2a794eca 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -9,12 +9,11 @@ import edu.ie3.datamodel.models.input.connector.{ Transformer2WInput, Transformer3WInput } -import edu.ie3.datamodel.models.input.system.{FixedFeedInInput, LoadInput} import edu.ie3.datamodel.models.result.NodeResult import edu.ie3.simbench.exception.CodeValidationException import edu.ie3.simbench.io.{Downloader, SimbenchReader, Zipper} import edu.ie3.simbench.model.SimbenchCode -import edu.ie3.simbench.model.datamodel.{GridModel, Node, Switch} +import edu.ie3.simbench.model.datamodel.{GridModel, Node} import java.nio.file.Path @@ -38,6 +37,7 @@ object Converter { inputFileEncoding, inputFileColumnSeparator, removeSwitches, + amountOfWorkers, useDirectoryHierarchy, targetDirectory, csvColumnSeparator, @@ -65,6 +65,7 @@ object Converter { inputFileEncoding, inputFileColumnSeparator, removeSwitches, + amountOfWorkers, converter ) } @@ -80,6 +81,7 @@ object Converter { * @param inputFileEncoding Encoding of the input files * @param inputFileColumnSeparator Column separator of the input data * @param removeSwitches Whether or not to remove switches in final model + * @param amountOfWorkers Amount of workers to convert participants * @param coordinator Reference to the coordinator * @return Behavior to do so */ @@ -92,6 +94,7 @@ object Converter { inputFileEncoding: String, inputFileColumnSeparator: String, removeSwitches: Boolean, + amountOfWorkers: Int, coordinator: ActorRef[Coordinator.CoordinatorMessage] ): Behaviors.Receive[ConverterMessage] = Behaviors.receive { case (ctx, MutatorInitialized(mutator)) => @@ -107,6 +110,7 @@ object Converter { inputFileEncoding, inputFileColumnSeparator, removeSwitches, + amountOfWorkers, mutator ) ) @@ -176,6 +180,16 @@ object Converter { ctx.log.debug( s"Grid structure of model '${stateData.simBenchCode}' has been converted." ) + + val resConverter = + ctx.spawn(ResConverter(), s"resConverter_${stateData.simBenchCode}") + resConverter ! ResConverter.Init( + stateData.simBenchCode, + stateData.amountOfWorkers, + simBenchModel.resProfiles, + ctx.self + ) + // TODO: Issue conversion of participants that then also respect for islanded nodes converting( stateData, @@ -191,6 +205,18 @@ object Converter { measurements = Some(measurements) ) ) + + case (ctx, ResConverterReady(resConverter)) => + ctx.log.debug( + s"ResConverter for '${stateData.simBenchCode}' is ready. Request conversion." + ) + resConverter ! ResConverter.Convert( + stateData.simBenchCode, + simBenchModel.res, + awaitedResults.nodeConversion.getOrElse(Map.empty), + ctx.self + ) + Behaviors.same } // TODO: Terminate mutator and await it's termination [[MutatorTerminated]] when terminating this actor @@ -288,6 +314,7 @@ object Converter { inputFileEncoding: String, inputFileColumnSeparator: String, removeSwitches: Boolean, + amountOfWorkers: Int, mutator: ActorRef[Mutator.MutatorMessage] ) @@ -324,6 +351,7 @@ object Converter { inputFileEncoding: String, inputFileColumnSeparator: String, removeSwitches: Boolean, + amountOfWorkers: Int, useDirectoryHierarchy: Boolean, targetDirectory: String, csvColumnSeparator: String, @@ -371,4 +399,8 @@ object Converter { switches: Vector[SwitchInput], measurements: Vector[MeasurementUnitInput] ) extends ConverterMessage + + final case class ResConverterReady( + replyTo: ActorRef[ResConverter.ResConverterMessage] + ) extends ConverterMessage } diff --git a/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala b/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala index 2952f19a..fd1287db 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala @@ -26,6 +26,7 @@ object Coordinator { config.io.input.csv.fileEncoding, config.io.input.csv.separator, config.conversion.removeSwitches, + config.conversion.participantWorkersPerType, config.io.output.csv.directoryHierarchy, config.io.output.targetFolder, config.io.output.csv.separator, @@ -44,6 +45,7 @@ object Coordinator { config.io.input.csv.fileEncoding, config.io.input.csv.separator, config.conversion.removeSwitches, + config.conversion.participantWorkersPerType, config.io.output.csv.directoryHierarchy, config.io.output.targetFolder, config.io.output.csv.separator, @@ -95,6 +97,7 @@ object Coordinator { stateData.inputFileEncoding, stateData.inputFileColumnSeparator, stateData.removeSwitches, + stateData.amountOfWorkers, stateData.useDirectoryHierarchy, stateData.targetDirectory, stateData.csvColumnSeparator, @@ -128,6 +131,7 @@ object Coordinator { inputFileEncoding: String, inputFileColumnSeparator: String, removeSwitches: Boolean, + amountOfWorkers: Int, useDirectoryHierarchy: Boolean, targetDirectory: String, csvColumnSeparator: String, @@ -143,6 +147,7 @@ object Coordinator { inputFileEncoding, inputFileColumnSeparator, removeSwitches, + amountOfWorkers, useDirectoryHierarchy, targetDirectory, csvColumnSeparator, @@ -162,6 +167,7 @@ object Coordinator { inputFileEncoding: String, inputFileColumnSeparator: String, removeSwitches: Boolean, + amountOfWorkers: Int, useDirectoryHierarchy: Boolean, targetDirectory: String, csvColumnSeparator: String, diff --git a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala index 1e09963c..d99cc080 100644 --- a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala @@ -93,7 +93,7 @@ case object GridConverter extends LazyLogging { val convertedMeasurements = MeasurementConverter .convert(measurements, nodeConversion) - val nodes = filterIsolatedNodes( + val nonIslandedNodes = filterIsolatedNodes( nodeConversion, convertedLines.toSet.asJava, convertedTransformers2w.toSet.asJava, @@ -101,10 +101,10 @@ case object GridConverter extends LazyLogging { convertedSwitches.toSet.asJava ) - val convertedResults = convertNodeResults(nodeResults, nodes) + val convertedResults = convertNodeResults(nodeResults, nonIslandedNodes) converter ! Converter.GridStructureConverted( - nodes, + nonIslandedNodes, convertedResults, convertedLines, convertedTransformers2w, @@ -703,13 +703,13 @@ case object GridConverter extends LazyLogging { logger.debug( s"Done converting ${gridInput.powerPlants.size} power plants including time series" ) - val resToTimeSeries = convertRes(gridInput, nodeConversion) - logger.debug( - s"Done converting ${gridInput.res.size} RES including time series" - ) +// val resToTimeSeries = convertRes(gridInput, nodeConversion) +// logger.debug( +// s"Done converting ${gridInput.res.size} RES including time series" +// ) /* Map participant uuid onto time series */ - val participantsToTimeSeries = loadsToTimeSeries ++ powerPlantsToTimeSeries ++ resToTimeSeries + val participantsToTimeSeries = loadsToTimeSeries ++ powerPlantsToTimeSeries // ++ resToTimeSeries val mapping = participantsToTimeSeries.map { case (model, timeSeries) => new TimeSeriesMappingSource.MappingEntry( @@ -727,7 +727,7 @@ case object GridConverter extends LazyLogging { Set.empty[ChpInput].asJava, Set.empty[EvcsInput].asJava, Set.empty[EvInput].asJava, - (powerPlantsToTimeSeries.keySet ++ resToTimeSeries.keySet).asJava, + (powerPlantsToTimeSeries.keySet).asJava, Set.empty[HpInput].asJava, loadsToTimeSeries.keySet.asJava, Set.empty[PvInput].asJava, @@ -776,20 +776,4 @@ case object GridConverter extends LazyLogging { powerPlantProfiles ) } - - /** - * Converting all renewable energy source system. - * - * @param gridInput Total grid input model to convert - * @param nodeConversion Already known conversion mapping of nodes - * @return A mapping from renewable energy source system to their assigned, specific time series - */ - def convertRes( - gridInput: GridModel, - nodeConversion: Map[Node, NodeInput] - ): Map[FixedFeedInInput, IndividualTimeSeries[PValue]] = { - val resProfiles = - gridInput.resProfiles.map(profile => profile.profileType -> profile).toMap - ResConverter.convert(gridInput.res, nodeConversion, resProfiles) - } } diff --git a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala new file mode 100644 index 00000000..2561f8ad --- /dev/null +++ b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala @@ -0,0 +1,158 @@ +package edu.ie3.simbench.actor + +import akka.actor.typed.{ActorRef, SupervisorStrategy} +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import edu.ie3.datamodel.models.OperationTime +import edu.ie3.datamodel.models.input.system.FixedFeedInInput +import edu.ie3.datamodel.models.input.system.characteristic.CosPhiFixed +import edu.ie3.datamodel.models.input.{NodeInput, OperatorInput} +import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries +import edu.ie3.datamodel.models.value.PValue +import edu.ie3.simbench.convert.profiles.PowerProfileConverter +import edu.ie3.simbench.convert.{NodeConverter, ShuntConverter} +import edu.ie3.simbench.model.datamodel.profiles.{ResProfile, ResProfileType} +import edu.ie3.simbench.model.datamodel.{Node, RES} +import edu.ie3.util.quantities.PowerSystemUnits.{ + MEGAVAR, + MEGAVOLTAMPERE, + MEGAWATT +} +import tech.units.indriya.quantity.Quantities + +import java.util.{Locale, UUID} + +case object ResConverter extends ShuntConverter { + def apply(): Behaviors.Receive[ResConverterMessage] = uninitialized + + def uninitialized: Behaviors.Receive[ResConverterMessage] = + Behaviors.receive { + case (ctx, Init(simBenchCode, amountOfWorkers, profiles, converter)) => + /* Prepare information */ + val typeToProfile = + profiles.map(profile => profile.profileType -> profile).toMap + + /* Set up a worker pool */ + val workerPool = Routers + .pool(poolSize = amountOfWorkers) { + Behaviors + .supervise(ResConverter.Worker()) + .onFailure(SupervisorStrategy.restart) + } + .withRoundRobinRouting() + val workerPoolProxy = + ctx.spawn( + workerPool, + s"ResConverterWorkerPool_$simBenchCode" + ) + + converter ! Converter.ResConverterReady(ctx.self) + + idle(typeToProfile, workerPoolProxy) + } + + def idle( + typeToProfile: Map[ResProfileType, ResProfile], + workerPool: ActorRef[ResConverter.Worker.WorkerMessage] + ): Behaviors.Receive[ResConverterMessage] = Behaviors.receive { + case (ctx, Convert(simBenchCode, res, nodes, converter)) => + /* + * TODO + * 1) Prepare needed information + * 2) Issue conversion + * 3) Collect converted models + * 4) Report back + */ + Behaviors.same + } + + sealed trait ResConverterMessage + + final case class Init( + simBenchCode: String, + amountOfWorkers: Int, + profiles: Vector[ResProfile], + replyTo: ActorRef[Converter.ConverterMessage] + ) extends ResConverterMessage + + /** + * Request to convert all given RES + * + * @param simBenchCode Code of the referencing model + * @param res Input models to convert + * @param nodes Mapping from SimBench to power system data model node + * @param replyTo Converter to report to + */ + final case class Convert( + simBenchCode: String, + res: Vector[RES], + nodes: Map[Node, NodeInput], + replyTo: ActorRef[Converter.ConverterMessage] + ) extends ResConverterMessage + + object Worker { + def apply(): Behaviors.Receive[WorkerMessage] = idle + def idle: Behaviors.Receive[WorkerMessage] = Behaviors.receive { + case (ctx, _) => Behaviors.same + } + + sealed trait WorkerMessage + + /** + * Convert a full set of renewable energy source system + * + * @param res Input models to convert + * @param nodes Mapping from Simbench to power system data model node + * @param profiles Collection of [[ResProfile]]s + * @return A mapping from converted renewable energy source system to equivalent individual time series + */ + def convert( + res: Vector[RES], + nodes: Map[Node, NodeInput], + profiles: Map[ResProfileType, ResProfile] + ): Map[FixedFeedInInput, IndividualTimeSeries[PValue]] = + res.map { plant => + val node = NodeConverter.getNode(plant.node, nodes) + val profile = + PowerProfileConverter.getProfile(plant.profile, profiles) + convert(plant, node, profile) + }.toMap + + /** + * Converts a single renewable energy source system to a fixed feed in model due to lacking information to + * sophistically guess typical types of assets. Different voltage regulation strategies are not covered, yet. + * + * @param input Input model + * @param node Node, the renewable energy source system is connected to + * @param profile SimBench renewable energy source system profile + * @param uuid Option to a specific uuid + * @return A pair of [[FixedFeedInInput]] and matching active power time series + */ + def convert( + input: RES, + node: NodeInput, + profile: ResProfile, + uuid: Option[UUID] = None + ): (FixedFeedInInput, IndividualTimeSeries[PValue]) = { + val p = Quantities.getQuantity(input.p, MEGAWATT) + val q = Quantities.getQuantity(input.q, MEGAVAR) + val cosphi = cosPhi(p.getValue.doubleValue(), q.getValue.doubleValue()) + val varCharacteristicString = + "cosPhiFixed:{(0.0,%#.2f)}".formatLocal(Locale.ENGLISH, cosphi) + val sRated = Quantities.getQuantity(input.sR, MEGAVOLTAMPERE) + + /* Flip the sign, as infeed is negative in PowerSystemDataModel */ + val timeSeries = PowerProfileConverter.convert(profile, p.multiply(-1)) + + new FixedFeedInInput( + uuid.getOrElse(UUID.randomUUID()), + input.id + "_" + input.resType.toString, + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + node, + new CosPhiFixed(varCharacteristicString), + sRated, + cosphi + ) -> timeSeries + } + } +} diff --git a/src/main/scala/edu/ie3/simbench/config/ConfigValidator.scala b/src/main/scala/edu/ie3/simbench/config/ConfigValidator.scala index 9ed27336..705a9efb 100644 --- a/src/main/scala/edu/ie3/simbench/config/ConfigValidator.scala +++ b/src/main/scala/edu/ie3/simbench/config/ConfigValidator.scala @@ -22,6 +22,7 @@ case object ConfigValidator { @throws[SimbenchException] def checkValidity(config: SimbenchConfig): Unit = { checkValidity(config.io) + checkValidity(config.conversion) } /** @@ -36,6 +37,21 @@ case object ConfigValidator { checkSimbenchCodes(io.simbenchCodes) } + /** + * Checks the validity of an conversion-config part. If any content is not valid, a + * [[SimbenchConfigException]] is thrown + * + * @param conversion The conversion config to validate + * @throws SimbenchException If any of the content is not as it is expected + */ + @throws[SimbenchException] + private def checkValidity(conversion: SimbenchConfig.Conversion): Unit = { + if (conversion.participantWorkersPerType <= 0) + throw new SimbenchException( + "The amount of participant workers has to be positive!" + ) + } + /** * Checks the validity of the provided codes with the help of the permissible ones in [[edu.ie3.simbench.model.SimbenchCode]] * @param codes The list of codes to check diff --git a/src/main/scala/edu/ie3/simbench/config/SimbenchConfig.scala b/src/main/scala/edu/ie3/simbench/config/SimbenchConfig.scala index 8f9caba7..e38fd541 100644 --- a/src/main/scala/edu/ie3/simbench/config/SimbenchConfig.scala +++ b/src/main/scala/edu/ie3/simbench/config/SimbenchConfig.scala @@ -1,4 +1,4 @@ -// generated by tscfg 0.9.986 on Mon Aug 09 20:12:24 CEST 2021 +// generated by tscfg 0.9.993 on Fri Aug 27 13:41:17 CEST 2021 // source: src/main/resources/config-template.conf package edu.ie3.simbench.config @@ -38,6 +38,7 @@ object SimbenchConfig { } final case class Conversion( + participantWorkersPerType: scala.Int, removeSwitches: scala.Boolean ) object Conversion { @@ -47,6 +48,10 @@ object SimbenchConfig { $tsCfgValidator: $TsCfgValidator ): SimbenchConfig.Conversion = { SimbenchConfig.Conversion( + participantWorkersPerType = + if (c.hasPathOrNull("participantWorkersPerType")) + c.getInt("participantWorkersPerType") + else 20, removeSwitches = c.hasPathOrNull("removeSwitches") && c.getBoolean( "removeSwitches" ) diff --git a/src/main/scala/edu/ie3/simbench/convert/ResConverter.scala b/src/main/scala/edu/ie3/simbench/convert/ResConverter.scala deleted file mode 100644 index 23937022..00000000 --- a/src/main/scala/edu/ie3/simbench/convert/ResConverter.scala +++ /dev/null @@ -1,85 +0,0 @@ -package edu.ie3.simbench.convert - -import java.util.{Locale, UUID} - -import edu.ie3.datamodel.models.OperationTime -import edu.ie3.datamodel.models.input.system.FixedFeedInInput -import edu.ie3.datamodel.models.input.system.characteristic.CosPhiFixed -import edu.ie3.datamodel.models.input.{NodeInput, OperatorInput} -import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries -import edu.ie3.datamodel.models.value.PValue -import edu.ie3.simbench.convert.profiles.PowerProfileConverter -import edu.ie3.simbench.model.datamodel.profiles.{ResProfile, ResProfileType} -import edu.ie3.simbench.model.datamodel.{Node, RES} -import edu.ie3.util.quantities.PowerSystemUnits.{ - MEGAVAR, - MEGAVOLTAMPERE, - MEGAWATT -} -import tech.units.indriya.quantity.Quantities - -import scala.collection.parallel.CollectionConverters._ - -case object ResConverter extends ShuntConverter { - - /** - * Convert a full set of renewable energy source system - * - * @param res Input models to convert - * @param nodes Mapping from Simbench to power system data model node - * @param profiles Collection of [[ResProfile]]s - * @return A mapping from converted renewable energy source system to equivalent individual time series - */ - def convert( - res: Vector[RES], - nodes: Map[Node, NodeInput], - profiles: Map[ResProfileType, ResProfile] - ): Map[FixedFeedInInput, IndividualTimeSeries[PValue]] = - res.par - .map { plant => - val node = NodeConverter.getNode(plant.node, nodes) - val profile = - PowerProfileConverter.getProfile(plant.profile, profiles) - convert(plant, node, profile) - } - .seq - .toMap - - /** - * Converts a single renewable energy source system to a fixed feed in model due to lacking information to - * sophistically guess typical types of assets. Different voltage regulation strategies are not covered, yet. - * - * @param input Input model - * @param node Node, the renewable energy source system is connected to - * @param profile SimBench renewable energy source system profile - * @param uuid Option to a specific uuid - * @return A pair of [[FixedFeedInInput]] and matching active power time series - */ - def convert( - input: RES, - node: NodeInput, - profile: ResProfile, - uuid: Option[UUID] = None - ): (FixedFeedInInput, IndividualTimeSeries[PValue]) = { - val p = Quantities.getQuantity(input.p, MEGAWATT) - val q = Quantities.getQuantity(input.q, MEGAVAR) - val cosphi = cosPhi(p.getValue.doubleValue(), q.getValue.doubleValue()) - val varCharacteristicString = - "cosPhiFixed:{(0.0,%#.2f)}".formatLocal(Locale.ENGLISH, cosphi) - val sRated = Quantities.getQuantity(input.sR, MEGAVOLTAMPERE) - - /* Flip the sign, as infeed is negative in PowerSystemDataModel */ - val timeSeries = PowerProfileConverter.convert(profile, p.multiply(-1)) - - new FixedFeedInInput( - uuid.getOrElse(UUID.randomUUID()), - input.id + "_" + input.resType.toString, - OperatorInput.NO_OPERATOR_ASSIGNED, - OperationTime.notLimited(), - node, - new CosPhiFixed(varCharacteristicString), - sRated, - cosphi - ) -> timeSeries - } -} diff --git a/src/test/scala/edu/ie3/simbench/convert/ResConverterSpec.scala b/src/test/scala/edu/ie3/simbench/convert/ResConverterSpec.scala index 52c690d1..301bbef3 100644 --- a/src/test/scala/edu/ie3/simbench/convert/ResConverterSpec.scala +++ b/src/test/scala/edu/ie3/simbench/convert/ResConverterSpec.scala @@ -1,6 +1,7 @@ package edu.ie3.simbench.convert import edu.ie3.datamodel.models.StandardUnits +import edu.ie3.simbench.actor.ResConverter import java.util.Objects import edu.ie3.simbench.model.datamodel.profiles.{ResProfile, ResProfileType} @@ -37,7 +38,8 @@ class ResConverterSpec ) ) ) - val (actual, actualTimeSeries) = ResConverter.convert(input, node, pProfile) + val (actual, actualTimeSeries) = + ResConverter.Worker.convert(input, node, pProfile) "bring up the correct input model" in { Objects.nonNull(actual.getUuid) shouldBe true diff --git a/src/test/scala/edu/ie3/test/common/ConfigTestData.scala b/src/test/scala/edu/ie3/test/common/ConfigTestData.scala index ae9f0162..73100596 100644 --- a/src/test/scala/edu/ie3/test/common/ConfigTestData.scala +++ b/src/test/scala/edu/ie3/test/common/ConfigTestData.scala @@ -32,7 +32,8 @@ trait ConfigTestData { List("1-LV-urban6--0-sw", "blabla", "1-EHVHV-mixed-2-0-sw") ) - val validConversionConfig: Conversion = Conversion(removeSwitches = false) + val validConversionConfig: Conversion = + Conversion(removeSwitches = false, participantWorkersPerType = 20) val validConfig = new SimbenchConfig(validConversionConfig, validIo) } From f6a734fe878bea960023018a00cb06fb7229412d Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 27 Aug 2021 14:27:54 +0200 Subject: [PATCH 16/32] Ping pong between converter and ResConverter --- .../edu/ie3/simbench/actor/Converter.scala | 21 ++++++-- .../edu/ie3/simbench/actor/ResConverter.scala | 51 ++++++++++++++++--- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index 2a794eca..302f1788 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -9,6 +9,7 @@ import edu.ie3.datamodel.models.input.connector.{ Transformer2WInput, Transformer3WInput } +import edu.ie3.datamodel.models.input.system.FixedFeedInInput import edu.ie3.datamodel.models.result.NodeResult import edu.ie3.simbench.exception.CodeValidationException import edu.ie3.simbench.io.{Downloader, SimbenchReader, Zipper} @@ -217,6 +218,13 @@ object Converter { ctx.self ) Behaviors.same + + case (ctx, ResConverted(converted)) => + ctx.log.debug( + s"All RES of model '${stateData.simBenchCode}' are converted." + ) + val updatedAwaitedResults = awaitedResults.copy(res = Some(converted)) + converting(stateData, simBenchModel, gridConverter, updatedAwaitedResults) } // TODO: Terminate mutator and await it's termination [[MutatorTerminated]] when terminating this actor @@ -295,14 +303,14 @@ object Converter { fileEnding: String, fileEncoding: String ): GridModel = { - val simbenchReader = SimbenchReader( + val simBenchReader = SimbenchReader( simBenchCode, downloadDirectory, csvColumnSeparator, fileEnding, fileEncoding ) - simbenchReader.readGrid() + simBenchReader.readGrid() } final case class StateData( @@ -325,7 +333,9 @@ object Converter { transformers2w: Option[Vector[Transformer2WInput]], transformers3w: Option[Vector[Transformer3WInput]], switches: Option[Vector[SwitchInput]], - measurements: Option[Vector[MeasurementUnitInput]] + measurements: Option[Vector[MeasurementUnitInput]], + res: Option[Vector[FixedFeedInInput]], + powerPlants: Option[Vector[FixedFeedInInput]] ) object AwaitedResults { def empty = @@ -336,6 +346,8 @@ object Converter { None, None, None, + None, + None, None ) } @@ -403,4 +415,7 @@ object Converter { final case class ResConverterReady( replyTo: ActorRef[ResConverter.ResConverterMessage] ) extends ConverterMessage + + final case class ResConverted(converted: Vector[FixedFeedInInput]) + extends ConverterMessage } diff --git a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala index 2561f8ad..6b92061a 100644 --- a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala @@ -55,14 +55,37 @@ case object ResConverter extends ShuntConverter { workerPool: ActorRef[ResConverter.Worker.WorkerMessage] ): Behaviors.Receive[ResConverterMessage] = Behaviors.receive { case (ctx, Convert(simBenchCode, res, nodes, converter)) => - /* - * TODO - * 1) Prepare needed information - * 2) Issue conversion - * 3) Collect converted models - * 4) Report back - */ - Behaviors.same + ctx.log.debug(s"Got request to convert res from '$simBenchCode'.") + val activeConversions = res.map { plant => + val node = NodeConverter.getNode(plant.node, nodes) + val profile = + PowerProfileConverter.getProfile(plant.profile, typeToProfile) + + workerPool ! Worker.Convert(plant, node, profile, ctx.self) + (plant.id, plant.node.getKey) + } + converting(activeConversions, Vector.empty, workerPool, converter) + } + + def converting( + activeConversions: Vector[(String, Node.NodeKey)], + converted: Vector[FixedFeedInInput], + workerPool: ActorRef[Worker.WorkerMessage], + converter: ActorRef[Converter.ConverterMessage] + ): Behaviors.Receive[ResConverterMessage] = Behaviors.receive { + case (ctx, Converted(id, node, fixedFeedInInput)) => + val remainingConversions = activeConversions.filterNot(_ == (id, node)) + val updatedConverted = converted :+ fixedFeedInInput + ctx.log.debug( + s"Model '$id' at node '$node' is converted. ${remainingConversions.size} active conversions remaining." + ) + /* Stop the children and myself, if all conversions are done. */ + if (remainingConversions.isEmpty) { + ctx.stop(workerPool) + converter ! Converter.ResConverted(converted) + Behaviors.stopped + } + converting(remainingConversions, updatedConverted, workerPool, converter) } sealed trait ResConverterMessage @@ -74,6 +97,12 @@ case object ResConverter extends ShuntConverter { replyTo: ActorRef[Converter.ConverterMessage] ) extends ResConverterMessage + final case class Converted( + id: String, + node: Node.NodeKey, + fixedFeedInInput: FixedFeedInInput + ) extends ResConverterMessage + /** * Request to convert all given RES * @@ -96,6 +125,12 @@ case object ResConverter extends ShuntConverter { } sealed trait WorkerMessage + final case class Convert( + res: RES, + node: NodeInput, + profile: ResProfile, + replyTo: ActorRef[ResConverterMessage] + ) extends WorkerMessage /** * Convert a full set of renewable energy source system From eaf3eed77baeb8bc173a9fe57d09edb0ae0f4c8f Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 27 Aug 2021 17:40:17 +0200 Subject: [PATCH 17/32] Convert and write time series --- docs/diagrams/protocol/protocol.puml | 18 +-- .../edu/ie3/simbench/actor/Converter.scala | 8 +- .../edu/ie3/simbench/actor/Mutator.scala | 11 ++ .../edu/ie3/simbench/actor/ResConverter.scala | 116 ++++++++++++------ .../simbench/convert/ResConverterSpec.scala | 55 +-------- 5 files changed, 104 insertions(+), 104 deletions(-) diff --git a/docs/diagrams/protocol/protocol.puml b/docs/diagrams/protocol/protocol.puml index 628459ff..4fec9332 100644 --- a/docs/diagrams/protocol/protocol.puml +++ b/docs/diagrams/protocol/protocol.puml @@ -9,10 +9,13 @@ collections ResConverterWorker participant Mutator Main -> Coordinator: Start(Config) +== Init == Coordinator --[#blue]> Converter: spawn & watch Coordinator -> Converter: Init(String) Coordinator <- Converter: ConverterInitialized Coordinator -> Converter: Convert(String) + +== Converting grid structure == Converter --[#blue]> Mutator: spawn & watch Converter -> Mutator: Init(Config) Converter <- Mutator: Ready @@ -21,26 +24,27 @@ Converter -> GridConverter: ConvertGridStructure(...) Converter <- GridConverter: GridStructureConverted(Map[Node,NodeInput]) note left: Now all other entities\ncan be converted (not)\nconsidering islanded\nnodes +== Converting participants == Converter --[#blue]> ResConverter: spawn & watch Converter -> ResConverter: Init(...) ResConverter --[#blue]> ResConverterWorker: spawn & watch Converter <- ResConverter: ResConverterReady Converter -> ResConverter: Convert(...) -ResConverter -> ResConverterWorker: Convert(Res, Map[Node,NodeInput]) -ResConverterWorker -> Mutator: Mutate(Res) +ResConverter -> ResConverterWorker: Convert(...) +ResConverterWorker -> Mutator: Persist(...) activate Mutator -ResConverterWorker <- Mutator: MutatedEntity(UUID) -ResConverterWorker -> Mutator: Mutate(TimeSeries) -ResConverterWorker <- Mutator: MutatedTimeSeries(UUID) -ResConverter <- ResConverterWorker: Converted(UUID, UUID) +ResConverterWorker <- Mutator: TimeSeriesPersisted(...) +ResConverter <- ResConverterWorker: Converted(...) ResConverter --[#blue]> ResConverterWorker: terminate ResConverter <--[#blue] ResConverterWorker -Converter <- ResConverter: Done +Converter <- ResConverter: ResConverted(...) +== Mutate Grid structure == GridConverter -> Mutator: Mutate(...) GridConverter <- Mutator: Mutated(...) Converter <- GridConverter: Done +== Shutdown == Converter -> Mutator: Shutdown Converter <- Mutator: Done deactivate Mutator diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index 302f1788..71bb7ebb 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -17,6 +17,7 @@ import edu.ie3.simbench.model.SimbenchCode import edu.ie3.simbench.model.datamodel.{GridModel, Node} import java.nio.file.Path +import java.util.UUID object Converter { def apply(): Behaviors.Receive[ConverterMessage] = uninitialized @@ -188,6 +189,7 @@ object Converter { stateData.simBenchCode, stateData.amountOfWorkers, simBenchModel.resProfiles, + stateData.mutator, ctx.self ) @@ -334,8 +336,8 @@ object Converter { transformers3w: Option[Vector[Transformer3WInput]], switches: Option[Vector[SwitchInput]], measurements: Option[Vector[MeasurementUnitInput]], - res: Option[Vector[FixedFeedInInput]], - powerPlants: Option[Vector[FixedFeedInInput]] + res: Option[Map[FixedFeedInInput, UUID]], + powerPlants: Option[Map[FixedFeedInInput, UUID]] ) object AwaitedResults { def empty = @@ -416,6 +418,6 @@ object Converter { replyTo: ActorRef[ResConverter.ResConverterMessage] ) extends ConverterMessage - final case class ResConverted(converted: Vector[FixedFeedInInput]) + final case class ResConverted(converted: Map[FixedFeedInInput, UUID]) extends ConverterMessage } diff --git a/src/main/scala/edu/ie3/simbench/actor/Mutator.scala b/src/main/scala/edu/ie3/simbench/actor/Mutator.scala index 1a8df4e1..a7f65965 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Mutator.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Mutator.scala @@ -8,6 +8,7 @@ import edu.ie3.datamodel.io.naming.{ FileNamingStrategy } import edu.ie3.datamodel.io.sink.CsvFileSink +import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries import edu.ie3.simbench.io.IoUtils import edu.ie3.util.io.FileIOUtils import org.apache.commons.io.FilenameUtils @@ -72,6 +73,13 @@ object Mutator { compress: Boolean ): Behaviors.Receive[MutatorMessage] = Behaviors.receive { + case (ctx, PersistTimeSeries(timeSeries)) => + ctx.log.debug( + s"Got request to persist time series '${timeSeries.getUuid}'." + ) + sink.persistTimeSeries(timeSeries) + Behaviors.same + case (ctx, Terminate) => ctx.log.debug("Got termination request") @@ -112,5 +120,8 @@ object Mutator { replyTo: ActorRef[Converter.ConverterMessage] ) extends MutatorMessage + final case class PersistTimeSeries(timeSeries: IndividualTimeSeries[_]) + extends MutatorMessage + object Terminate extends MutatorMessage } diff --git a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala index 6b92061a..54102459 100644 --- a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala @@ -6,8 +6,6 @@ import edu.ie3.datamodel.models.OperationTime import edu.ie3.datamodel.models.input.system.FixedFeedInInput import edu.ie3.datamodel.models.input.system.characteristic.CosPhiFixed import edu.ie3.datamodel.models.input.{NodeInput, OperatorInput} -import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries -import edu.ie3.datamodel.models.value.PValue import edu.ie3.simbench.convert.profiles.PowerProfileConverter import edu.ie3.simbench.convert.{NodeConverter, ShuntConverter} import edu.ie3.simbench.model.datamodel.profiles.{ResProfile, ResProfileType} @@ -26,7 +24,10 @@ case object ResConverter extends ShuntConverter { def uninitialized: Behaviors.Receive[ResConverterMessage] = Behaviors.receive { - case (ctx, Init(simBenchCode, amountOfWorkers, profiles, converter)) => + case ( + ctx, + Init(simBenchCode, amountOfWorkers, profiles, mutator, converter) + ) => /* Prepare information */ val typeToProfile = profiles.map(profile => profile.profileType -> profile).toMap @@ -38,6 +39,11 @@ case object ResConverter extends ShuntConverter { .supervise(ResConverter.Worker()) .onFailure(SupervisorStrategy.restart) } + /* Allow broadcast messages to init all workers */ + .withBroadcastPredicate { + case _: Worker.Init => true + case _ => false + } .withRoundRobinRouting() val workerPoolProxy = ctx.spawn( @@ -45,6 +51,7 @@ case object ResConverter extends ShuntConverter { s"ResConverterWorkerPool_$simBenchCode" ) + workerPoolProxy ! Worker.Init(mutator) converter ! Converter.ResConverterReady(ctx.self) idle(typeToProfile, workerPoolProxy) @@ -64,18 +71,18 @@ case object ResConverter extends ShuntConverter { workerPool ! Worker.Convert(plant, node, profile, ctx.self) (plant.id, plant.node.getKey) } - converting(activeConversions, Vector.empty, workerPool, converter) + converting(activeConversions, Map.empty, workerPool, converter) } def converting( activeConversions: Vector[(String, Node.NodeKey)], - converted: Vector[FixedFeedInInput], + converted: Map[FixedFeedInInput, UUID], workerPool: ActorRef[Worker.WorkerMessage], converter: ActorRef[Converter.ConverterMessage] ): Behaviors.Receive[ResConverterMessage] = Behaviors.receive { - case (ctx, Converted(id, node, fixedFeedInInput)) => + case (ctx, Converted(id, node, fixedFeedInInput, timeSeriesUuid)) => val remainingConversions = activeConversions.filterNot(_ == (id, node)) - val updatedConverted = converted :+ fixedFeedInInput + val updatedConverted = converted + (fixedFeedInInput -> timeSeriesUuid) ctx.log.debug( s"Model '$id' at node '$node' is converted. ${remainingConversions.size} active conversions remaining." ) @@ -94,13 +101,15 @@ case object ResConverter extends ShuntConverter { simBenchCode: String, amountOfWorkers: Int, profiles: Vector[ResProfile], + mutator: ActorRef[Mutator.MutatorMessage], replyTo: ActorRef[Converter.ConverterMessage] ) extends ResConverterMessage final case class Converted( id: String, node: Node.NodeKey, - fixedFeedInInput: FixedFeedInInput + fixedFeedInInput: FixedFeedInInput, + timeSeriesUuid: UUID ) extends ResConverterMessage /** @@ -119,55 +128,85 @@ case object ResConverter extends ShuntConverter { ) extends ResConverterMessage object Worker { - def apply(): Behaviors.Receive[WorkerMessage] = idle - def idle: Behaviors.Receive[WorkerMessage] = Behaviors.receive { - case (ctx, _) => Behaviors.same + def apply(): Behaviors.Receive[WorkerMessage] = uninitialized + + def uninitialized: Behaviors.Receive[WorkerMessage] = Behaviors.receive { + case (_, Init(mutator)) => + idle(mutator) + } + + def idle( + mutator: ActorRef[Mutator.MutatorMessage], + awaitTimeSeriesPersistence: Map[ + UUID, + ( + String, + Node.NodeKey, + FixedFeedInInput, + ActorRef[ResConverterMessage] + ) + ] = Map.empty + ): Behaviors.Receive[WorkerMessage] = Behaviors.receive { + case (ctx, Convert(res, node, profile, replyTo)) => + Behaviors.same + ctx.log.debug( + s"Got request to convert RES '${res.id} at node '${res.node.getKey} / ${node.getUuid}'." + ) + + val model = convertModel(res, node) + /* Flip the sign, as infeed is negative in PowerSystemDataModel */ + val timeSeries = PowerProfileConverter.convert( + profile, + Quantities.getQuantity(res.p, MEGAWATT).multiply(-1) + ) + + mutator ! Mutator.PersistTimeSeries(timeSeries) + val updatedAwaitedTimeSeriesPersistence = awaitTimeSeriesPersistence + (timeSeries.getUuid -> (res.id, res.node.getKey, model, replyTo)) + idle(mutator, updatedAwaitedTimeSeriesPersistence) + + case (ctx, TimeSeriesPersisted(uuid)) => + ctx.log.debug(s"Time series '$uuid' is fully persisted.") + awaitTimeSeriesPersistence.get(uuid) match { + case Some((id, nodeKey, model, replyTo)) => + val remainingPersistence = + awaitTimeSeriesPersistence.filterNot(_._1 == uuid) + + replyTo ! ResConverter.Converted(id, nodeKey, model, uuid) + + idle(mutator, remainingPersistence) + case None => + ctx.log.warn( + s"Got informed, that the time series with uuid '$uuid' is persisted. But I didn't expect that to happen." + ) + Behaviors.same + } } sealed trait WorkerMessage + final case class Init(mutator: ActorRef[Mutator.MutatorMessage]) + extends WorkerMessage final case class Convert( res: RES, node: NodeInput, profile: ResProfile, replyTo: ActorRef[ResConverterMessage] ) extends WorkerMessage - - /** - * Convert a full set of renewable energy source system - * - * @param res Input models to convert - * @param nodes Mapping from Simbench to power system data model node - * @param profiles Collection of [[ResProfile]]s - * @return A mapping from converted renewable energy source system to equivalent individual time series - */ - def convert( - res: Vector[RES], - nodes: Map[Node, NodeInput], - profiles: Map[ResProfileType, ResProfile] - ): Map[FixedFeedInInput, IndividualTimeSeries[PValue]] = - res.map { plant => - val node = NodeConverter.getNode(plant.node, nodes) - val profile = - PowerProfileConverter.getProfile(plant.profile, profiles) - convert(plant, node, profile) - }.toMap + final case class TimeSeriesPersisted(uuid: UUID) extends WorkerMessage /** * Converts a single renewable energy source system to a fixed feed in model due to lacking information to - * sophistically guess typical types of assets. Different voltage regulation strategies are not covered, yet. + * sophistical guess typical types of assets. Different voltage regulation strategies are not covered, yet. * * @param input Input model * @param node Node, the renewable energy source system is connected to - * @param profile SimBench renewable energy source system profile * @param uuid Option to a specific uuid * @return A pair of [[FixedFeedInInput]] and matching active power time series */ - def convert( + def convertModel( input: RES, node: NodeInput, - profile: ResProfile, uuid: Option[UUID] = None - ): (FixedFeedInInput, IndividualTimeSeries[PValue]) = { + ): FixedFeedInInput = { val p = Quantities.getQuantity(input.p, MEGAWATT) val q = Quantities.getQuantity(input.q, MEGAVAR) val cosphi = cosPhi(p.getValue.doubleValue(), q.getValue.doubleValue()) @@ -175,9 +214,6 @@ case object ResConverter extends ShuntConverter { "cosPhiFixed:{(0.0,%#.2f)}".formatLocal(Locale.ENGLISH, cosphi) val sRated = Quantities.getQuantity(input.sR, MEGAVOLTAMPERE) - /* Flip the sign, as infeed is negative in PowerSystemDataModel */ - val timeSeries = PowerProfileConverter.convert(profile, p.multiply(-1)) - new FixedFeedInInput( uuid.getOrElse(UUID.randomUUID()), input.id + "_" + input.resType.toString, @@ -187,7 +223,7 @@ case object ResConverter extends ShuntConverter { new CosPhiFixed(varCharacteristicString), sRated, cosphi - ) -> timeSeries + ) } } } diff --git a/src/test/scala/edu/ie3/simbench/convert/ResConverterSpec.scala b/src/test/scala/edu/ie3/simbench/convert/ResConverterSpec.scala index 301bbef3..b5cd89af 100644 --- a/src/test/scala/edu/ie3/simbench/convert/ResConverterSpec.scala +++ b/src/test/scala/edu/ie3/simbench/convert/ResConverterSpec.scala @@ -1,15 +1,10 @@ package edu.ie3.simbench.convert -import edu.ie3.datamodel.models.StandardUnits import edu.ie3.simbench.actor.ResConverter import java.util.Objects -import edu.ie3.simbench.model.datamodel.profiles.{ResProfile, ResProfileType} import edu.ie3.test.common.{ConverterTestData, UnitSpec} import edu.ie3.test.matchers.QuantityMatchers -import tech.units.indriya.quantity.Quantities - -import scala.jdk.OptionConverters.RichOptional class ResConverterSpec extends UnitSpec @@ -20,26 +15,7 @@ class ResConverterSpec "The RES converter" should { val (_, node) = getNodePair("MV1.101 Bus 4") val (input, expected) = getResPair("MV1.101 SGen 2") - val pProfile: ResProfile = ResProfile( - "test profile", - ResProfileType.LvRural1, - Map( - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:00") -> BigDecimal( - "0.75" - ), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:15") -> BigDecimal( - "0.55" - ), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:30") -> BigDecimal( - "0.35" - ), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:45") -> BigDecimal( - "0.15" - ) - ) - ) - val (actual, actualTimeSeries) = - ResConverter.Worker.convert(input, node, pProfile) + val actual = ResConverter.Worker.convertModel(input, node) "bring up the correct input model" in { Objects.nonNull(actual.getUuid) shouldBe true @@ -51,34 +27,5 @@ class ResConverterSpec actual.getsRated shouldBe expected.getsRated actual.getCosPhiRated shouldBe expected.getCosPhiRated } - - "lead to the correct time series" in { - val expected = Map( - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:00") -> Quantities - .getQuantity(-120.0, StandardUnits.ACTIVE_POWER_IN), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:15") -> Quantities - .getQuantity(-88.0, StandardUnits.ACTIVE_POWER_IN), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:30") -> Quantities - .getQuantity(-56.0, StandardUnits.ACTIVE_POWER_IN), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:45") -> Quantities - .getQuantity(-24.0, StandardUnits.ACTIVE_POWER_IN) - ) - - actualTimeSeries.getEntries.forEach { timeBasedValue => - val time = timeBasedValue.getTime - val value = timeBasedValue.getValue - - expected.get(time) match { - case Some(expectedValue) => - value.getP.toScala match { - case Some(p) => p should equalWithTolerance(expectedValue) - case None => - fail(s"Unable to get expected active power for time '$time'") - } - case None => - fail(s"Unable to get expected time series entry for time '$time'") - } - } - } } } From 58349d76c53c8611cf9e9f9886b07e7214e30cf2 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 27 Aug 2021 17:47:34 +0200 Subject: [PATCH 18/32] Shutdown converter after res have been converted (for testing purposes) --- .../scala/edu/ie3/simbench/actor/Converter.scala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index 71bb7ebb..fa897648 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -1,6 +1,8 @@ package edu.ie3.simbench.actor +import akka.actor.PoisonPill import akka.actor.typed.ActorRef +import akka.actor.typed.internal.Terminate import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import edu.ie3.datamodel.models.input.{MeasurementUnitInput, NodeInput} import edu.ie3.datamodel.models.input.connector.{ @@ -226,10 +228,18 @@ object Converter { s"All RES of model '${stateData.simBenchCode}' are converted." ) val updatedAwaitedResults = awaitedResults.copy(res = Some(converted)) + + // TODO: Move the termination of the mutator to another place, when all entities are converted + stateData.mutator ! Mutator.Terminate + converting(stateData, simBenchModel, gridConverter, updatedAwaitedResults) - } - // TODO: Terminate mutator and await it's termination [[MutatorTerminated]] when terminating this actor + case (ctx, MutatorTerminated) => + ctx.log.debug( + s"Mutator has terminated, shut down converter for SimBench model '${stateData.simBenchCode}'." + ) + Behaviors.stopped + } private def spawnMutator( simBenchCode: String, From cb43568675d8c210822ad6a4417af43d5d518fcc Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 27 Aug 2021 18:13:46 +0200 Subject: [PATCH 19/32] Some minor fixes --- .../edu/ie3/simbench/actor/Converter.scala | 32 +++++++++++-------- .../edu/ie3/simbench/actor/Coordinator.scala | 4 +-- .../edu/ie3/simbench/actor/Mutator.scala | 9 ++++-- .../edu/ie3/simbench/actor/ResConverter.scala | 2 +- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index fa897648..1bb69cd6 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -1,8 +1,6 @@ package edu.ie3.simbench.actor -import akka.actor.PoisonPill import akka.actor.typed.ActorRef -import akka.actor.typed.internal.Terminate import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import edu.ie3.datamodel.models.input.{MeasurementUnitInput, NodeInput} import edu.ie3.datamodel.models.input.connector.{ @@ -49,7 +47,7 @@ object Converter { converter ) ) => - ctx.log.info(s"Converter for '$simBenchCode' started.") + ctx.log.info(s"$simBenchCode - Converter started.") /* Initialize a mutator for this conversion and wait for it's reply */ spawnMutator( @@ -102,7 +100,7 @@ object Converter { coordinator: ActorRef[Coordinator.CoordinatorMessage] ): Behaviors.Receive[ConverterMessage] = Behaviors.receive { case (ctx, MutatorInitialized(mutator)) => - ctx.log.debug(s"Mutator for model '$simBenchCode' is ready.") + ctx.log.debug(s"$simBenchCode - Mutator is ready.") coordinator ! Coordinator.ConverterInitialized(simBenchCode, ctx.self) idle( StateData( @@ -115,7 +113,8 @@ object Converter { inputFileColumnSeparator, removeSwitches, amountOfWorkers, - mutator + mutator, + coordinator ) ) } @@ -133,7 +132,7 @@ object Converter { stateData.baseUrl, stateData.failOnExistingFiles ) - ctx.log.info(s"$simBenchCode - Reading in the SimBench data set") + ctx.log.debug(s"$simBenchCode - Reading in the SimBench data set") val simBenchModel = readModel( simBenchCode, downloadDirectory, @@ -143,6 +142,7 @@ object Converter { ) /* Spawning a grid converter and ask it to do some first conversions */ + ctx.log.debug(s"$simBenchCode - Start conversion of grid structure") val gridConverter = ctx.spawn(GridConverter(), s"gridConverter_${stateData.simBenchCode}") gridConverter ! GridConverter.ConvertGridStructure( @@ -182,9 +182,12 @@ object Converter { ) ) => ctx.log.debug( - s"Grid structure of model '${stateData.simBenchCode}' has been converted." + s"${stateData.simBenchCode} - Grid structure has been converted." ) + ctx.log.info( + s"${stateData.simBenchCode} - Starting conversion of participant models" + ) val resConverter = ctx.spawn(ResConverter(), s"resConverter_${stateData.simBenchCode}") resConverter ! ResConverter.Init( @@ -213,7 +216,7 @@ object Converter { case (ctx, ResConverterReady(resConverter)) => ctx.log.debug( - s"ResConverter for '${stateData.simBenchCode}' is ready. Request conversion." + s"${stateData.simBenchCode} - ResConverter is ready. Request conversion." ) resConverter ! ResConverter.Convert( stateData.simBenchCode, @@ -224,8 +227,8 @@ object Converter { Behaviors.same case (ctx, ResConverted(converted)) => - ctx.log.debug( - s"All RES of model '${stateData.simBenchCode}' are converted." + ctx.log.info( + s"${stateData.simBenchCode} - All RES are converted." ) val updatedAwaitedResults = awaitedResults.copy(res = Some(converted)) @@ -235,9 +238,11 @@ object Converter { converting(stateData, simBenchModel, gridConverter, updatedAwaitedResults) case (ctx, MutatorTerminated) => - ctx.log.debug( - s"Mutator has terminated, shut down converter for SimBench model '${stateData.simBenchCode}'." + ctx.log.debug(s"${stateData.simBenchCode} - Mutator has terminated.") + ctx.log.info( + s"${stateData.simBenchCode} - Shut down converter." ) + stateData.coordinator ! Coordinator.Converted(stateData.simBenchCode) Behaviors.stopped } @@ -335,7 +340,8 @@ object Converter { inputFileColumnSeparator: String, removeSwitches: Boolean, amountOfWorkers: Int, - mutator: ActorRef[Mutator.MutatorMessage] + mutator: ActorRef[Mutator.MutatorMessage], + coordinator: ActorRef[Coordinator.CoordinatorMessage] ) final case class AwaitedResults( diff --git a/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala b/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala index fd1287db..c623cd12 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala @@ -75,10 +75,10 @@ object Coordinator { activeConverters = yetActiveConverters ) ) - case (ctx, Converted(simBenchCode)) if stateData.simBenchCodes.nonEmpty => + case (ctx, Converted(simBenchCode)) => /* A converter has completed. Report that and start a new converter. */ ctx.log.info( - s"SimBench model with code '$simBenchCode' is completely converted." + s"$simBenchCode - Completely converted." ) val stillActiveConverters = stateData.activeConverters.filterNot(_ == simBenchCode) diff --git a/src/main/scala/edu/ie3/simbench/actor/Mutator.scala b/src/main/scala/edu/ie3/simbench/actor/Mutator.scala index a7f65965..0d6ae0dc 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Mutator.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Mutator.scala @@ -73,11 +73,12 @@ object Mutator { compress: Boolean ): Behaviors.Receive[MutatorMessage] = Behaviors.receive { - case (ctx, PersistTimeSeries(timeSeries)) => + case (ctx, PersistTimeSeries(timeSeries, replyTo)) => ctx.log.debug( s"Got request to persist time series '${timeSeries.getUuid}'." ) sink.persistTimeSeries(timeSeries) + replyTo ! ResConverter.Worker.TimeSeriesPersisted(timeSeries.getUuid) Behaviors.same case (ctx, Terminate) => @@ -120,8 +121,10 @@ object Mutator { replyTo: ActorRef[Converter.ConverterMessage] ) extends MutatorMessage - final case class PersistTimeSeries(timeSeries: IndividualTimeSeries[_]) - extends MutatorMessage + final case class PersistTimeSeries( + timeSeries: IndividualTimeSeries[_], + replyTo: ActorRef[ResConverter.Worker.WorkerMessage] + ) extends MutatorMessage object Terminate extends MutatorMessage } diff --git a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala index 54102459..62943134 100644 --- a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala @@ -160,7 +160,7 @@ case object ResConverter extends ShuntConverter { Quantities.getQuantity(res.p, MEGAWATT).multiply(-1) ) - mutator ! Mutator.PersistTimeSeries(timeSeries) + mutator ! Mutator.PersistTimeSeries(timeSeries, ctx.self) val updatedAwaitedTimeSeriesPersistence = awaitTimeSeriesPersistence + (timeSeries.getUuid -> (res.id, res.node.getKey, model, replyTo)) idle(mutator, updatedAwaitedTimeSeriesPersistence) From 07718c2e0c584966f0cddb48a958d2089f4136e4 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 27 Aug 2021 19:30:23 +0200 Subject: [PATCH 20/32] Persist all the remaining things --- .../edu/ie3/simbench/actor/Converter.scala | 215 +++++++++++++++++- .../edu/ie3/simbench/actor/Mutator.scala | 125 ++++++++++ 2 files changed, 330 insertions(+), 10 deletions(-) diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index 1bb69cd6..c500749e 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -9,12 +9,13 @@ import edu.ie3.datamodel.models.input.connector.{ Transformer2WInput, Transformer3WInput } -import edu.ie3.datamodel.models.input.system.FixedFeedInInput +import edu.ie3.datamodel.models.input.system.{FixedFeedInInput, LoadInput} import edu.ie3.datamodel.models.result.NodeResult import edu.ie3.simbench.exception.CodeValidationException import edu.ie3.simbench.io.{Downloader, SimbenchReader, Zipper} import edu.ie3.simbench.model.SimbenchCode import edu.ie3.simbench.model.datamodel.{GridModel, Node} +import org.slf4j.Logger import java.nio.file.Path import java.util.UUID @@ -142,7 +143,7 @@ object Converter { ) /* Spawning a grid converter and ask it to do some first conversions */ - ctx.log.debug(s"$simBenchCode - Start conversion of grid structure") + ctx.log.info(s"$simBenchCode - Start conversion of grid structure") val gridConverter = ctx.spawn(GridConverter(), s"gridConverter_${stateData.simBenchCode}") gridConverter ! GridConverter.ConvertGridStructure( @@ -232,18 +233,91 @@ object Converter { ) val updatedAwaitedResults = awaitedResults.copy(res = Some(converted)) - // TODO: Move the termination of the mutator to another place, when all entities are converted - stateData.mutator ! Mutator.Terminate - - converting(stateData, simBenchModel, gridConverter, updatedAwaitedResults) + if (updatedAwaitedResults.isReady) + finalizeConversion( + stateData.simBenchCode, + updatedAwaitedResults, + stateData.mutator, + stateData.coordinator, + ctx.self, + ctx.log + ) + else + converting( + stateData, + simBenchModel, + gridConverter, + updatedAwaitedResults + ) + } + def finalizing( + simBenchCode: String, + mutator: ActorRef[Mutator.MutatorMessage], + coordinator: ActorRef[Coordinator.CoordinatorMessage], + gridStructurePersisted: Boolean = false, + nodeResultsPersisted: Boolean = false, + timeSeriesMappingPersisted: Boolean = false + ): Behaviors.Receive[ConverterMessage] = Behaviors.receive { case (ctx, MutatorTerminated) => - ctx.log.debug(s"${stateData.simBenchCode} - Mutator has terminated.") + ctx.log.debug(s"$simBenchCode - Mutator has terminated.") ctx.log.info( - s"${stateData.simBenchCode} - Shut down converter." + s"$simBenchCode - Shut down converter." ) - stateData.coordinator ! Coordinator.Converted(stateData.simBenchCode) + coordinator ! Coordinator.Converted(simBenchCode) Behaviors.stopped + + case (ctx, message) => + val ( + updatedGridStructurePersisted, + updatedNodeResultsPersisted, + updatedTimeSeriesMappingPersisted + ) = message match { + case GridStructurePersisted => + ctx.log.debug(s"$simBenchCode - Grid structure is persisted") + ( + true, + nodeResultsPersisted, + timeSeriesMappingPersisted + ) + case NodalResultsPersisted => + ctx.log.debug(s"$simBenchCode - Node results are persisted") + ( + gridStructurePersisted, + true, + timeSeriesMappingPersisted + ) + case TimeSeriesMappingPersisted => + ctx.log.debug(s"$simBenchCode - Time series mapping is persisted") + ( + gridStructurePersisted, + nodeResultsPersisted, + true + ) + case unexpected => + ctx.log.warn( + s"$simBenchCode - Received unexpected message '$unexpected'." + ) + ( + gridStructurePersisted, + nodeResultsPersisted, + timeSeriesMappingPersisted + ) + } + if (updatedGridStructurePersisted && updatedNodeResultsPersisted && updatedTimeSeriesMappingPersisted) { + ctx.log.debug( + s"$simBenchCode - All models are persisted. Shut down mutator." + ) + mutator ! Mutator.Terminate + } + finalizing( + simBenchCode, + mutator, + coordinator, + updatedGridStructurePersisted, + updatedNodeResultsPersisted, + updatedTimeSeriesMappingPersisted + ) } private def spawnMutator( @@ -330,6 +404,101 @@ object Converter { simBenchReader.readGrid() } + private def finalizeConversion( + simBenchCode: String, + awaitedResults: AwaitedResults, + mutator: ActorRef[Mutator.MutatorMessage], + coordinator: ActorRef[Coordinator.CoordinatorMessage], + self: ActorRef[ConverterMessage], + logger: Logger + ): Behaviors.Receive[ConverterMessage] = { + logger.info( + s"$simBenchCode - Persisting grid structure and associated particicpants." + ) + /* Bring together all results and send them to the mutator */ + mutator ! Mutator.PersistGridStructure( + simBenchCode, + awaitedResults.nodeConversion.map(_.values.toSet).getOrElse(Set.empty), + awaitedResults.lines.map(_.toSet).getOrElse { + logger.warn("Model does not contain lines.") + Set.empty + }, + awaitedResults.transformers2w.map(_.toSet).getOrElse { + logger.warn("Model does not contain two winding transformers.") + Set.empty + }, + awaitedResults.transformers3w.map(_.toSet).getOrElse { + logger.debug("Model does not contain three winding transformers.") + Set.empty + }, + awaitedResults.switches.map(_.toSet).getOrElse { + logger.debug("Model does not contain switches.") + Set.empty + }, + awaitedResults.measurements.map(_.toSet).getOrElse { + logger.debug("Model does not contain measurements.") + Set.empty + }, + awaitedResults.loads.map(_.keys.toSet).getOrElse { + logger.debug("Model does not contain loads.") + Set.empty + }, + (awaitedResults.res.map(_.keys).getOrElse { + logger.debug("Model does not contain renewable energy sources.") + Seq.empty[FixedFeedInInput] + } ++ awaitedResults.powerPlants.map(_.keys).getOrElse { + logger.debug("Model does not contain power plants.") + Seq.empty[FixedFeedInInput] + }).toSet, + self + ) + + /* Build the time series mapping and send it to the mutator */ + val timeSeriesMapping: Map[UUID, UUID] = awaitedResults.loads + .map( + modelToTimeSeries => + modelToTimeSeries.map { + case (model, uuid) => model.getUuid -> uuid + } + ) + .getOrElse { + logger.warn("The model does not contain time series for loads.") + Map.empty[UUID, UUID] + } ++ awaitedResults.res + .map( + modelToTimeSeries => + modelToTimeSeries.map { + case (model, uuid) => model.getUuid -> uuid + } + ) + .getOrElse { + logger.warn( + "The model does not contain time series for renewable energy sources." + ) + Map.empty[UUID, UUID] + } ++ awaitedResults.powerPlants + .map( + modelToTimeSeries => + modelToTimeSeries.map { + case (model, uuid) => model.getUuid -> uuid + } + ) + .getOrElse { + logger.warn("The model does not contain time series for power plants.") + Map.empty[UUID, UUID] + } + + mutator ! Mutator.PersistTimeSeriesMapping(timeSeriesMapping, self) + + /* Persist the nodal results */ + mutator ! Mutator.PersistNodalResults(awaitedResults.nodeResults.getOrElse { + logger.warn("The model does not contain nodal results.") + Set.empty[NodeResult] + }.toSet, self) + + finalizing(simBenchCode, mutator, coordinator) + } + final case class StateData( simBenchCode: String, downloadDirectory: String, @@ -352,9 +521,30 @@ object Converter { transformers3w: Option[Vector[Transformer3WInput]], switches: Option[Vector[SwitchInput]], measurements: Option[Vector[MeasurementUnitInput]], + loads: Option[Map[LoadInput, UUID]], res: Option[Map[FixedFeedInInput, UUID]], powerPlants: Option[Map[FixedFeedInInput, UUID]] - ) + ) { + + /** + * Check, if all awaited results are there + * + * @return true, if nothing is missing + */ + def isReady: Boolean = + Seq( + nodeConversion, + nodeResults, + lines, + transformers2w, + transformers3w, + switches, + measurements, + //loads, TODO + res + //powerPlants TODO + ).forall(_.nonEmpty) + } object AwaitedResults { def empty = new AwaitedResults( @@ -366,6 +556,7 @@ object Converter { None, None, None, + None, None ) } @@ -436,4 +627,8 @@ object Converter { final case class ResConverted(converted: Map[FixedFeedInInput, UUID]) extends ConverterMessage + + object GridStructurePersisted extends ConverterMessage + object TimeSeriesMappingPersisted extends ConverterMessage + object NodalResultsPersisted extends ConverterMessage } diff --git a/src/main/scala/edu/ie3/simbench/actor/Mutator.scala b/src/main/scala/edu/ie3/simbench/actor/Mutator.scala index 0d6ae0dc..bc4a75e9 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Mutator.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Mutator.scala @@ -8,17 +8,48 @@ import edu.ie3.datamodel.io.naming.{ FileNamingStrategy } import edu.ie3.datamodel.io.sink.CsvFileSink +import edu.ie3.datamodel.io.source.TimeSeriesMappingSource.MappingEntry +import edu.ie3.datamodel.models.input.{MeasurementUnitInput, NodeInput} +import edu.ie3.datamodel.models.input.connector.{ + LineInput, + SwitchInput, + Transformer2WInput, + Transformer3WInput +} +import edu.ie3.datamodel.models.input.container.{ + GraphicElements, + JointGridContainer, + RawGridElements, + SystemParticipants +} +import edu.ie3.datamodel.models.input.system.{ + BmInput, + ChpInput, + EvInput, + EvcsInput, + FixedFeedInInput, + HpInput, + LoadInput, + PvInput, + StorageInput, + SystemParticipantInput, + WecInput +} +import edu.ie3.datamodel.models.result.NodeResult import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries import edu.ie3.simbench.io.IoUtils import edu.ie3.util.io.FileIOUtils import org.apache.commons.io.FilenameUtils import java.nio.file.Paths +import java.util +import java.util.UUID import scala.concurrent.Await import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration.Duration import scala.jdk.FutureConverters.CompletionStageOps import scala.util.{Failure, Success} +import scala.jdk.CollectionConverters._ object Mutator { def apply(): Behaviors.Receive[MutatorMessage] = uninitialized @@ -81,6 +112,77 @@ object Mutator { replyTo ! ResConverter.Worker.TimeSeriesPersisted(timeSeries.getUuid) Behaviors.same + case ( + ctx, + PersistGridStructure( + simBenchCode, + nodes, + lines, + transformers2w, + transformers3w, + switches, + measurements, + loads, + fixedFeedIns, + replyTo + ) + ) => + ctx.log.debug("Got request to persist grid structure.") + + val container = new JointGridContainer( + simBenchCode, + new RawGridElements( + nodes.asJava, + lines.asJava, + transformers2w.asJava, + transformers3w.asJava, + switches.asJava, + measurements.asJava + ), + new SystemParticipants( + Set.empty[BmInput].asJava, + Set.empty[ChpInput].asJava, + Set.empty[EvcsInput].asJava, + Set.empty[EvInput].asJava, + fixedFeedIns.asJava, + Set.empty[HpInput].asJava, + loads.asJava, + Set.empty[PvInput].asJava, + Set.empty[StorageInput].asJava, + Set.empty[WecInput].asJava + ), + new GraphicElements( + Set.empty[GraphicElements].asJava + ) + ) + + sink.persistJointGrid(container) + + replyTo ! Converter.GridStructurePersisted + Behaviors.same + + case (ctx, PersistNodalResults(results, replyTo)) => + ctx.log.debug("Got request to persist nodal results.") + + sink.persistAll(results.asJava) + replyTo ! Converter.NodalResultsPersisted + Behaviors.same + + case (ctx, PersistTimeSeriesMapping(mapping, replyTo)) => + ctx.log.debug("Got request to persist time series mapping") + + sink.persistAllIgnoreNested( + mapping + .map { + case (modelUuid, timeSeriesUuid) => + new MappingEntry(UUID.randomUUID(), modelUuid, timeSeriesUuid) + } + .toList + .asJava + ) + replyTo ! Converter.TimeSeriesMappingPersisted + Behaviors.same + case (ctx, Terminate) => ctx.log.debug("Got termination request") @@ -121,6 +223,29 @@ object Mutator { replyTo: ActorRef[Converter.ConverterMessage] ) extends MutatorMessage + final case class PersistGridStructure( + simBenchCode: String, + nodes: Set[NodeInput], + lines: Set[LineInput], + transformers2w: Set[Transformer2WInput], + transformers3w: Set[Transformer3WInput], + switches: Set[SwitchInput], + measurements: Set[MeasurementUnitInput], + loads: Set[LoadInput], + fixedFeedIns: Set[FixedFeedInInput], + replyTo: ActorRef[Converter.ConverterMessage] + ) extends MutatorMessage + + final case class PersistTimeSeriesMapping( + mapping: Map[UUID, UUID], + replyTo: ActorRef[Converter.ConverterMessage] + ) extends MutatorMessage + + final case class PersistNodalResults( + results: Set[NodeResult], + replyTo: ActorRef[Converter.ConverterMessage] + ) extends MutatorMessage + final case class PersistTimeSeries( timeSeries: IndividualTimeSeries[_], replyTo: ActorRef[ResConverter.Worker.WorkerMessage] From 908aaa73650081bb05e364efa00e561f88314103 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Sun, 29 Aug 2021 11:34:25 +0200 Subject: [PATCH 21/32] Generify the messages received by the worker --- .../edu/ie3/simbench/actor/Mutator.scala | 6 ++-- .../edu/ie3/simbench/actor/ResConverter.scala | 36 ++++++++++--------- .../ie3/simbench/actor/WorkerMessage.scala | 27 ++++++++++++++ 3 files changed, 49 insertions(+), 20 deletions(-) create mode 100644 src/main/scala/edu/ie3/simbench/actor/WorkerMessage.scala diff --git a/src/main/scala/edu/ie3/simbench/actor/Mutator.scala b/src/main/scala/edu/ie3/simbench/actor/Mutator.scala index bc4a75e9..7bae952b 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Mutator.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Mutator.scala @@ -32,7 +32,6 @@ import edu.ie3.datamodel.models.input.system.{ LoadInput, PvInput, StorageInput, - SystemParticipantInput, WecInput } import edu.ie3.datamodel.models.result.NodeResult @@ -42,7 +41,6 @@ import edu.ie3.util.io.FileIOUtils import org.apache.commons.io.FilenameUtils import java.nio.file.Paths -import java.util import java.util.UUID import scala.concurrent.Await import scala.concurrent.ExecutionContext.Implicits.global @@ -109,7 +107,7 @@ object Mutator { s"Got request to persist time series '${timeSeries.getUuid}'." ) sink.persistTimeSeries(timeSeries) - replyTo ! ResConverter.Worker.TimeSeriesPersisted(timeSeries.getUuid) + replyTo ! WorkerMessage.TimeSeriesPersisted(timeSeries.getUuid) Behaviors.same case ( @@ -248,7 +246,7 @@ object Mutator { final case class PersistTimeSeries( timeSeries: IndividualTimeSeries[_], - replyTo: ActorRef[ResConverter.Worker.WorkerMessage] + replyTo: ActorRef[WorkerMessage] ) extends MutatorMessage object Terminate extends MutatorMessage diff --git a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala index 62943134..94b6043e 100644 --- a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala @@ -41,8 +41,8 @@ case object ResConverter extends ShuntConverter { } /* Allow broadcast messages to init all workers */ .withBroadcastPredicate { - case _: Worker.Init => true - case _ => false + case _: WorkerMessage.Init => true + case _ => false } .withRoundRobinRouting() val workerPoolProxy = @@ -51,7 +51,7 @@ case object ResConverter extends ShuntConverter { s"ResConverterWorkerPool_$simBenchCode" ) - workerPoolProxy ! Worker.Init(mutator) + workerPoolProxy ! WorkerMessage.Init(mutator) converter ! Converter.ResConverterReady(ctx.self) idle(typeToProfile, workerPoolProxy) @@ -59,7 +59,7 @@ case object ResConverter extends ShuntConverter { def idle( typeToProfile: Map[ResProfileType, ResProfile], - workerPool: ActorRef[ResConverter.Worker.WorkerMessage] + workerPool: ActorRef[WorkerMessage] ): Behaviors.Receive[ResConverterMessage] = Behaviors.receive { case (ctx, Convert(simBenchCode, res, nodes, converter)) => ctx.log.debug(s"Got request to convert res from '$simBenchCode'.") @@ -77,7 +77,7 @@ case object ResConverter extends ShuntConverter { def converting( activeConversions: Vector[(String, Node.NodeKey)], converted: Map[FixedFeedInInput, UUID], - workerPool: ActorRef[Worker.WorkerMessage], + workerPool: ActorRef[WorkerMessage], converter: ActorRef[Converter.ConverterMessage] ): Behaviors.Receive[ResConverterMessage] = Behaviors.receive { case (ctx, Converted(id, node, fixedFeedInInput, timeSeriesUuid)) => @@ -131,7 +131,7 @@ case object ResConverter extends ShuntConverter { def apply(): Behaviors.Receive[WorkerMessage] = uninitialized def uninitialized: Behaviors.Receive[WorkerMessage] = Behaviors.receive { - case (_, Init(mutator)) => + case (_, WorkerMessage.Init(mutator)) => idle(mutator) } @@ -164,7 +164,7 @@ case object ResConverter extends ShuntConverter { val updatedAwaitedTimeSeriesPersistence = awaitTimeSeriesPersistence + (timeSeries.getUuid -> (res.id, res.node.getKey, model, replyTo)) idle(mutator, updatedAwaitedTimeSeriesPersistence) - case (ctx, TimeSeriesPersisted(uuid)) => + case (ctx, WorkerMessage.TimeSeriesPersisted(uuid)) => ctx.log.debug(s"Time series '$uuid' is fully persisted.") awaitTimeSeriesPersistence.get(uuid) match { case Some((id, nodeKey, model, replyTo)) => @@ -182,16 +182,20 @@ case object ResConverter extends ShuntConverter { } } - sealed trait WorkerMessage - final case class Init(mutator: ActorRef[Mutator.MutatorMessage]) - extends WorkerMessage + /** + * Override the abstract Request message with parameters, that suit your needs. + * + * @param model Model itself + * @param node Node, the converted model will be connected to + * @param profile The profile, that belongs to the model + * @param replyTo Address to reply to + */ final case class Convert( - res: RES, - node: NodeInput, - profile: ResProfile, - replyTo: ActorRef[ResConverterMessage] - ) extends WorkerMessage - final case class TimeSeriesPersisted(uuid: UUID) extends WorkerMessage + override val model: RES, + override val node: NodeInput, + override val profile: ResProfile, + override val replyTo: ActorRef[ResConverterMessage] + ) extends WorkerMessage.Convert[RES, ResProfile] /** * Converts a single renewable energy source system to a fixed feed in model due to lacking information to diff --git a/src/main/scala/edu/ie3/simbench/actor/WorkerMessage.scala b/src/main/scala/edu/ie3/simbench/actor/WorkerMessage.scala new file mode 100644 index 00000000..c9c0f42d --- /dev/null +++ b/src/main/scala/edu/ie3/simbench/actor/WorkerMessage.scala @@ -0,0 +1,27 @@ +package edu.ie3.simbench.actor + +import akka.actor.typed.ActorRef +import edu.ie3.datamodel.models.input.NodeInput +import edu.ie3.simbench.actor.ResConverter.ResConverterMessage +import edu.ie3.simbench.model.datamodel.ShuntModel +import edu.ie3.simbench.model.datamodel.profiles.ProfileModel + +import java.util.UUID + +sealed trait WorkerMessage +object WorkerMessage { + final case class Init(mutator: ActorRef[Mutator.MutatorMessage]) + extends WorkerMessage + + abstract protected[actor] class Convert[M <: ShuntModel, P <: ProfileModel[ + _, + _ + ]] extends WorkerMessage { + val model: M + val node: NodeInput + val profile: P + val replyTo: ActorRef[ResConverterMessage] + } + + final case class TimeSeriesPersisted(uuid: UUID) extends WorkerMessage +} From b8a477cd1256e876bc0710cf020d66b9febe1445 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Sun, 29 Aug 2021 12:07:21 +0200 Subject: [PATCH 22/32] Generifying ShuntConverterMessage --- .../edu/ie3/simbench/actor/Converter.scala | 2 +- .../edu/ie3/simbench/actor/ResConverter.scala | 47 +++++++++---------- .../actor/ShuntConverterMessageSupport.scala | 38 +++++++++++++++ .../ie3/simbench/actor/WorkerMessage.scala | 6 +-- 4 files changed, 63 insertions(+), 30 deletions(-) create mode 100644 src/main/scala/edu/ie3/simbench/actor/ShuntConverterMessageSupport.scala diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index c500749e..fd98fb37 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -622,7 +622,7 @@ object Converter { ) extends ConverterMessage final case class ResConverterReady( - replyTo: ActorRef[ResConverter.ResConverterMessage] + replyTo: ActorRef[ResConverter.ShuntConverterMessage] ) extends ConverterMessage final case class ResConverted(converted: Map[FixedFeedInInput, UUID]) diff --git a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala index 94b6043e..136d40b3 100644 --- a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala @@ -19,10 +19,12 @@ import tech.units.indriya.quantity.Quantities import java.util.{Locale, UUID} -case object ResConverter extends ShuntConverter { - def apply(): Behaviors.Receive[ResConverterMessage] = uninitialized +case object ResConverter + extends ShuntConverter + with ShuntConverterMessageSupport[RES, ResProfile, FixedFeedInInput] { + def apply(): Behaviors.Receive[ShuntConverterMessage] = uninitialized - def uninitialized: Behaviors.Receive[ResConverterMessage] = + def uninitialized: Behaviors.Receive[ShuntConverterMessage] = Behaviors.receive { case ( ctx, @@ -60,7 +62,7 @@ case object ResConverter extends ShuntConverter { def idle( typeToProfile: Map[ResProfileType, ResProfile], workerPool: ActorRef[WorkerMessage] - ): Behaviors.Receive[ResConverterMessage] = Behaviors.receive { + ): Behaviors.Receive[ShuntConverterMessage] = Behaviors.receive { case (ctx, Convert(simBenchCode, res, nodes, converter)) => ctx.log.debug(s"Got request to convert res from '$simBenchCode'.") val activeConversions = res.map { plant => @@ -79,7 +81,7 @@ case object ResConverter extends ShuntConverter { converted: Map[FixedFeedInInput, UUID], workerPool: ActorRef[WorkerMessage], converter: ActorRef[Converter.ConverterMessage] - ): Behaviors.Receive[ResConverterMessage] = Behaviors.receive { + ): Behaviors.Receive[ShuntConverterMessage] = Behaviors.receive { case (ctx, Converted(id, node, fixedFeedInInput, timeSeriesUuid)) => val remainingConversions = activeConversions.filterNot(_ == (id, node)) val updatedConverted = converted + (fixedFeedInInput -> timeSeriesUuid) @@ -95,37 +97,30 @@ case object ResConverter extends ShuntConverter { converting(remainingConversions, updatedConverted, workerPool, converter) } - sealed trait ResConverterMessage - final case class Init( simBenchCode: String, amountOfWorkers: Int, profiles: Vector[ResProfile], mutator: ActorRef[Mutator.MutatorMessage], replyTo: ActorRef[Converter.ConverterMessage] - ) extends ResConverterMessage - - final case class Converted( - id: String, - node: Node.NodeKey, - fixedFeedInInput: FixedFeedInInput, - timeSeriesUuid: UUID - ) extends ResConverterMessage + ) extends super.Init /** - * Request to convert all given RES - * - * @param simBenchCode Code of the referencing model - * @param res Input models to convert - * @param nodes Mapping from SimBench to power system data model node - * @param replyTo Converter to report to + * Request to convert all given models */ final case class Convert( simBenchCode: String, - res: Vector[RES], + inputs: Vector[RES], nodes: Map[Node, NodeInput], replyTo: ActorRef[Converter.ConverterMessage] - ) extends ResConverterMessage + ) extends super.Convert + + final case class Converted( + id: String, + node: Node.NodeKey, + model: FixedFeedInInput, + timeSeriesUuid: UUID + ) extends super.Converted object Worker { def apply(): Behaviors.Receive[WorkerMessage] = uninitialized @@ -143,7 +138,7 @@ case object ResConverter extends ShuntConverter { String, Node.NodeKey, FixedFeedInInput, - ActorRef[ResConverterMessage] + ActorRef[ShuntConverterMessage] ) ] = Map.empty ): Behaviors.Receive[WorkerMessage] = Behaviors.receive { @@ -194,8 +189,8 @@ case object ResConverter extends ShuntConverter { override val model: RES, override val node: NodeInput, override val profile: ResProfile, - override val replyTo: ActorRef[ResConverterMessage] - ) extends WorkerMessage.Convert[RES, ResProfile] + override val replyTo: ActorRef[ShuntConverterMessage] + ) extends WorkerMessage.Convert[RES, ResProfile, ShuntConverterMessage] /** * Converts a single renewable energy source system to a fixed feed in model due to lacking information to diff --git a/src/main/scala/edu/ie3/simbench/actor/ShuntConverterMessageSupport.scala b/src/main/scala/edu/ie3/simbench/actor/ShuntConverterMessageSupport.scala new file mode 100644 index 00000000..47da832b --- /dev/null +++ b/src/main/scala/edu/ie3/simbench/actor/ShuntConverterMessageSupport.scala @@ -0,0 +1,38 @@ +package edu.ie3.simbench.actor + +import akka.actor.typed.ActorRef +import edu.ie3.datamodel.models.input.NodeInput +import edu.ie3.datamodel.models.input.system.SystemParticipantInput +import edu.ie3.simbench.model.datamodel.{Node, ShuntModel} +import edu.ie3.simbench.model.datamodel.profiles.ProfileModel + +import java.util.UUID + +trait ShuntConverterMessageSupport[I <: ShuntModel, P <: ProfileModel[_, _], R <: SystemParticipantInput] { + sealed trait ShuntConverterMessage + + protected[actor] abstract class Init extends ShuntConverterMessage { + val simBenchCode: String + val amountOfWorkers: Int + val profiles: Vector[P] + val mutator: ActorRef[Mutator.MutatorMessage] + val replyTo: ActorRef[Converter.ConverterMessage] + } + + protected[actor] abstract class Converted extends ShuntConverterMessage { + val id: String + val node: Node.NodeKey + val model: R + val timeSeriesUuid: UUID + } + + /** + * Request to convert all given models + */ + protected[actor] abstract class Convert extends ShuntConverterMessage { + val simBenchCode: String + val inputs: Vector[I] + val nodes: Map[Node, NodeInput] + val replyTo: ActorRef[Converter.ConverterMessage] + } +} diff --git a/src/main/scala/edu/ie3/simbench/actor/WorkerMessage.scala b/src/main/scala/edu/ie3/simbench/actor/WorkerMessage.scala index c9c0f42d..a592ab6d 100644 --- a/src/main/scala/edu/ie3/simbench/actor/WorkerMessage.scala +++ b/src/main/scala/edu/ie3/simbench/actor/WorkerMessage.scala @@ -2,7 +2,6 @@ package edu.ie3.simbench.actor import akka.actor.typed.ActorRef import edu.ie3.datamodel.models.input.NodeInput -import edu.ie3.simbench.actor.ResConverter.ResConverterMessage import edu.ie3.simbench.model.datamodel.ShuntModel import edu.ie3.simbench.model.datamodel.profiles.ProfileModel @@ -16,11 +15,12 @@ object WorkerMessage { abstract protected[actor] class Convert[M <: ShuntModel, P <: ProfileModel[ _, _ - ]] extends WorkerMessage { + ], R] + extends WorkerMessage { val model: M val node: NodeInput val profile: P - val replyTo: ActorRef[ResConverterMessage] + val replyTo: ActorRef[R] } final case class TimeSeriesPersisted(uuid: UUID) extends WorkerMessage From 26a4f0162d0b6350ff3c8e1fc714f642f0d447a0 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Sun, 29 Aug 2021 12:34:16 +0200 Subject: [PATCH 23/32] Converting loads --- .../edu/ie3/simbench/actor/Converter.scala | 52 +++- .../ie3/simbench/actor/GridConverter.scala | 33 +-- .../ie3/simbench/actor/LoadConverter.scala | 234 ++++++++++++++++++ .../edu/ie3/simbench/actor/ResConverter.scala | 2 +- .../ie3/simbench/convert/LoadConverter.scala | 83 ------- .../simbench/convert/LoadConverterSpec.scala | 3 +- 6 files changed, 296 insertions(+), 111 deletions(-) create mode 100644 src/main/scala/edu/ie3/simbench/actor/LoadConverter.scala delete mode 100644 src/main/scala/edu/ie3/simbench/convert/LoadConverter.scala diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index fd98fb37..c044ab59 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -189,6 +189,15 @@ object Converter { ctx.log.info( s"${stateData.simBenchCode} - Starting conversion of participant models" ) + val loadConverter = + ctx.spawn(LoadConverter(), s"loadConverter_${stateData.simBenchCode}") + loadConverter ! LoadConverter.Init( + stateData.simBenchCode, + stateData.amountOfWorkers, + simBenchModel.loadProfiles, + stateData.mutator, + ctx.self + ) val resConverter = ctx.spawn(ResConverter(), s"resConverter_${stateData.simBenchCode}") resConverter ! ResConverter.Init( @@ -215,6 +224,18 @@ object Converter { ) ) + case (ctx, LoadConverterReady(loadConverter)) => + ctx.log.debug( + s"${stateData.simBenchCode} - LoadConverter is ready. Request conversion." + ) + loadConverter ! LoadConverter.Convert( + stateData.simBenchCode, + simBenchModel.loads, + awaitedResults.nodeConversion.getOrElse(Map.empty), + ctx.self + ) + Behaviors.same + case (ctx, ResConverterReady(resConverter)) => ctx.log.debug( s"${stateData.simBenchCode} - ResConverter is ready. Request conversion." @@ -227,6 +248,29 @@ object Converter { ) Behaviors.same + case (ctx, LoadsConverted(converted)) => + ctx.log.info( + s"${stateData.simBenchCode} - All loads are converted." + ) + val updatedAwaitedResults = awaitedResults.copy(loads = Some(converted)) + + if (updatedAwaitedResults.isReady) + finalizeConversion( + stateData.simBenchCode, + updatedAwaitedResults, + stateData.mutator, + stateData.coordinator, + ctx.self, + ctx.log + ) + else + converting( + stateData, + simBenchModel, + gridConverter, + updatedAwaitedResults + ) + case (ctx, ResConverted(converted)) => ctx.log.info( s"${stateData.simBenchCode} - All RES are converted." @@ -540,7 +584,7 @@ object Converter { transformers3w, switches, measurements, - //loads, TODO + loads, res //powerPlants TODO ).forall(_.nonEmpty) @@ -624,10 +668,16 @@ object Converter { final case class ResConverterReady( replyTo: ActorRef[ResConverter.ShuntConverterMessage] ) extends ConverterMessage + final case class LoadConverterReady( + replyTo: ActorRef[LoadConverter.ShuntConverterMessage] + ) extends ConverterMessage final case class ResConverted(converted: Map[FixedFeedInInput, UUID]) extends ConverterMessage + final case class LoadsConverted(converted: Map[LoadInput, UUID]) + extends ConverterMessage + object GridStructurePersisted extends ConverterMessage object TimeSeriesMappingPersisted extends ConverterMessage object NodalResultsPersisted extends ConverterMessage diff --git a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala index d99cc080..8a1ce315 100644 --- a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala @@ -695,10 +695,10 @@ case object GridConverter extends LazyLogging { s"Participants to convert:\n\tLoads: ${gridInput.loads.size}" + s"\n\tPower Plants: ${gridInput.powerPlants.size}\n\tRES: ${gridInput.res.size}" ) - val loadsToTimeSeries = convertLoads(gridInput, nodeConversion) - logger.debug( - s"Done converting ${gridInput.loads.size} loads including time series" - ) +// val loadsToTimeSeries = convertLoads(gridInput, nodeConversion) +// logger.debug( +// s"Done converting ${gridInput.loads.size} loads including time series" +// ) val powerPlantsToTimeSeries = convertPowerPlants(gridInput, nodeConversion) logger.debug( s"Done converting ${gridInput.powerPlants.size} power plants including time series" @@ -709,7 +709,7 @@ case object GridConverter extends LazyLogging { // ) /* Map participant uuid onto time series */ - val participantsToTimeSeries = loadsToTimeSeries ++ powerPlantsToTimeSeries // ++ resToTimeSeries + val participantsToTimeSeries = powerPlantsToTimeSeries // ++ resToTimeSeries val mapping = participantsToTimeSeries.map { case (model, timeSeries) => new TimeSeriesMappingSource.MappingEntry( @@ -719,7 +719,7 @@ case object GridConverter extends LazyLogging { ) }.toSeq val timeSeries: Vector[IndividualTimeSeries[_ >: SValue <: PValue]] = - participantsToTimeSeries.map(_._2).toVector + participantsToTimeSeries.values.toVector ( new SystemParticipants( @@ -727,9 +727,9 @@ case object GridConverter extends LazyLogging { Set.empty[ChpInput].asJava, Set.empty[EvcsInput].asJava, Set.empty[EvInput].asJava, - (powerPlantsToTimeSeries.keySet).asJava, + powerPlantsToTimeSeries.keySet.asJava, Set.empty[HpInput].asJava, - loadsToTimeSeries.keySet.asJava, + Set.empty[LoadInput].asJava, Set.empty[PvInput].asJava, Set.empty[StorageInput].asJava, Set.empty[WecInput].asJava @@ -739,23 +739,6 @@ case object GridConverter extends LazyLogging { ) } - /** - * Converting all loads. - * - * @param gridInput Total grid input model to convert - * @param nodeConversion Already known conversion mapping of nodes - * @return A mapping from loads to their assigned, specific time series - */ - def convertLoads( - gridInput: GridModel, - nodeConversion: Map[Node, NodeInput] - ): Map[LoadInput, IndividualTimeSeries[SValue]] = { - val loadProfiles = gridInput.loadProfiles - .map(profile => profile.profileType -> profile) - .toMap - LoadConverter.convert(gridInput.loads, nodeConversion, loadProfiles) - } - /** * Converting all power plants. * diff --git a/src/main/scala/edu/ie3/simbench/actor/LoadConverter.scala b/src/main/scala/edu/ie3/simbench/actor/LoadConverter.scala new file mode 100644 index 00000000..31580c94 --- /dev/null +++ b/src/main/scala/edu/ie3/simbench/actor/LoadConverter.scala @@ -0,0 +1,234 @@ +package edu.ie3.simbench.actor + +import akka.actor.typed.{ActorRef, SupervisorStrategy} +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import edu.ie3.datamodel.models.OperationTime +import edu.ie3.datamodel.models.StandardLoadProfile.DefaultLoadProfiles +import edu.ie3.datamodel.models.input.system.LoadInput +import edu.ie3.datamodel.models.input.system.characteristic.CosPhiFixed +import edu.ie3.datamodel.models.input.{NodeInput, OperatorInput} +import edu.ie3.simbench.convert.profiles.PowerProfileConverter +import edu.ie3.simbench.convert.{NodeConverter, ShuntConverter} +import edu.ie3.simbench.model.datamodel.profiles.{LoadProfile, LoadProfileType} +import edu.ie3.simbench.model.datamodel.{Load, Node} +import edu.ie3.util.quantities.PowerSystemUnits.{ + KILOWATTHOUR, + MEGAVAR, + MEGAVOLTAMPERE, + MEGAWATT +} +import tech.units.indriya.quantity.Quantities + +import java.util.{Locale, UUID} + +case object LoadConverter + extends ShuntConverter + with ShuntConverterMessageSupport[Load, LoadProfile, LoadInput] { + def apply(): Behaviors.Receive[ShuntConverterMessage] = uninitialized + + def uninitialized: Behaviors.Receive[ShuntConverterMessage] = + Behaviors.receive { + case ( + ctx, + Init(simBenchCode, amountOfWorkers, profiles, mutator, converter) + ) => + /* Prepare information */ + val typeToProfile = + profiles.map(profile => profile.profileType -> profile).toMap + + /* Set up a worker pool */ + val workerPool = Routers + .pool(poolSize = amountOfWorkers) { + Behaviors + .supervise(LoadConverter.Worker()) + .onFailure(SupervisorStrategy.restart) + } + /* Allow broadcast messages to init all workers */ + .withBroadcastPredicate { + case _: WorkerMessage.Init => true + case _ => false + } + .withRoundRobinRouting() + val workerPoolProxy = + ctx.spawn( + workerPool, + s"LoadConverterWorkerPool_$simBenchCode" + ) + + workerPoolProxy ! WorkerMessage.Init(mutator) + converter ! Converter.LoadConverterReady(ctx.self) + + idle(typeToProfile, workerPoolProxy) + } + + def idle( + typeToProfile: Map[LoadProfileType, LoadProfile], + workerPool: ActorRef[WorkerMessage] + ): Behaviors.Receive[ShuntConverterMessage] = Behaviors.receive { + case (ctx, Convert(simBenchCode, input, nodes, converter)) => + ctx.log.debug(s"Got request to convert loads from '$simBenchCode'.") + val activeConversions = input.map { plant => + val node = NodeConverter.getNode(plant.node, nodes) + val profile = + PowerProfileConverter.getProfile(plant.profile, typeToProfile) + + workerPool ! Worker.Convert(plant, node, profile, ctx.self) + (plant.id, plant.node.getKey) + } + converting(activeConversions, Map.empty, workerPool, converter) + } + + def converting( + activeConversions: Vector[(String, Node.NodeKey)], + converted: Map[LoadInput, UUID], + workerPool: ActorRef[WorkerMessage], + converter: ActorRef[Converter.ConverterMessage] + ): Behaviors.Receive[ShuntConverterMessage] = Behaviors.receive { + case (ctx, Converted(id, node, fixedFeedInInput, timeSeriesUuid)) => + val remainingConversions = activeConversions.filterNot(_ == (id, node)) + val updatedConverted = converted + (fixedFeedInInput -> timeSeriesUuid) + ctx.log.debug( + s"Model '$id' at node '$node' is converted. ${remainingConversions.size} active conversions remaining." + ) + /* Stop the children and myself, if all conversions are done. */ + if (remainingConversions.isEmpty) { + ctx.stop(workerPool) + converter ! Converter.LoadsConverted(converted) + Behaviors.stopped + } + converting(remainingConversions, updatedConverted, workerPool, converter) + } + + final case class Init( + simBenchCode: String, + amountOfWorkers: Int, + profiles: Vector[LoadProfile], + mutator: ActorRef[Mutator.MutatorMessage], + replyTo: ActorRef[Converter.ConverterMessage] + ) extends super.Init + + /** + * Request to convert all given models + */ + final case class Convert( + simBenchCode: String, + inputs: Vector[Load], + nodes: Map[Node, NodeInput], + replyTo: ActorRef[Converter.ConverterMessage] + ) extends super.Convert + + final case class Converted( + id: String, + node: Node.NodeKey, + model: LoadInput, + timeSeriesUuid: UUID + ) extends super.Converted + + object Worker { + def apply(): Behaviors.Receive[WorkerMessage] = uninitialized + + def uninitialized: Behaviors.Receive[WorkerMessage] = Behaviors.receive { + case (_, WorkerMessage.Init(mutator)) => + idle(mutator) + } + + def idle( + mutator: ActorRef[Mutator.MutatorMessage], + awaitTimeSeriesPersistence: Map[ + UUID, + ( + String, + Node.NodeKey, + LoadInput, + ActorRef[ShuntConverterMessage] + ) + ] = Map.empty + ): Behaviors.Receive[WorkerMessage] = Behaviors.receive { + case (ctx, Convert(input, node, profile, replyTo)) => + Behaviors.same + ctx.log.debug( + s"Got request to convert load '${input.id} at node '${input.node.getKey} / ${node.getUuid}'." + ) + + val model = convertModel(input, node) + + val timeSeries = PowerProfileConverter.convert( + profile, + Quantities.getQuantity(input.pLoad, MEGAWATT), + Quantities.getQuantity(input.qLoad, MEGAVAR) + ) + + mutator ! Mutator.PersistTimeSeries(timeSeries, ctx.self) + val updatedAwaitedTimeSeriesPersistence = awaitTimeSeriesPersistence + (timeSeries.getUuid -> (input.id, input.node.getKey, model, replyTo)) + idle(mutator, updatedAwaitedTimeSeriesPersistence) + + case (ctx, WorkerMessage.TimeSeriesPersisted(uuid)) => + ctx.log.debug(s"Time series '$uuid' is fully persisted.") + awaitTimeSeriesPersistence.get(uuid) match { + case Some((id, nodeKey, model, replyTo)) => + val remainingPersistence = + awaitTimeSeriesPersistence.filterNot(_._1 == uuid) + + replyTo ! LoadConverter.Converted(id, nodeKey, model, uuid) + + idle(mutator, remainingPersistence) + case None => + ctx.log.warn( + s"Got informed, that the time series with uuid '$uuid' is persisted. But I didn't expect that to happen." + ) + Behaviors.same + } + } + + /** + * Override the abstract Request message with parameters, that suit your needs. + * + * @param model Model itself + * @param node Node, the converted model will be connected to + * @param profile The profile, that belongs to the model + * @param replyTo Address to reply to + */ + final case class Convert( + override val model: Load, + override val node: NodeInput, + override val profile: LoadProfile, + override val replyTo: ActorRef[ShuntConverterMessage] + ) extends WorkerMessage.Convert[Load, LoadProfile, ShuntConverterMessage] + + /** + * Converts a single renewable energy source system to a fixed feed in model due to lacking information to + * sophistical guess typical types of assets. Different voltage regulation strategies are not covered, yet. + * + * @param input Input model + * @param node Node, the renewable energy source system is connected to + * @param uuid Option to a specific uuid + * @return A [[LoadInput]] + */ + def convertModel( + input: Load, + node: NodeInput, + uuid: Option[UUID] = None + ): LoadInput = { + val id = input.id + val cosphi = cosPhi(input.pLoad, input.qLoad) + val varCharacteristicString = + "cosPhiFixed:{(0.0,%#.2f)}".formatLocal(Locale.ENGLISH, cosphi) + val eCons = Quantities.getQuantity(0d, KILOWATTHOUR) + val sRated = Quantities.getQuantity(input.sR, MEGAVOLTAMPERE) + + new LoadInput( + uuid.getOrElse(UUID.randomUUID()), + id, + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + node, + new CosPhiFixed(varCharacteristicString), + DefaultLoadProfiles.NO_STANDARD_LOAD_PROFILE, + false, + eCons, + sRated, + cosphi + ) + } + } +} diff --git a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala index 136d40b3..9982564d 100644 --- a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala @@ -199,7 +199,7 @@ case object ResConverter * @param input Input model * @param node Node, the renewable energy source system is connected to * @param uuid Option to a specific uuid - * @return A pair of [[FixedFeedInInput]] and matching active power time series + * @return A [[FixedFeedInInput]] */ def convertModel( input: RES, diff --git a/src/main/scala/edu/ie3/simbench/convert/LoadConverter.scala b/src/main/scala/edu/ie3/simbench/convert/LoadConverter.scala deleted file mode 100644 index 8aa9f6cb..00000000 --- a/src/main/scala/edu/ie3/simbench/convert/LoadConverter.scala +++ /dev/null @@ -1,83 +0,0 @@ -package edu.ie3.simbench.convert - -import java.util.{Locale, UUID} - -import edu.ie3.datamodel.models.OperationTime -import edu.ie3.datamodel.models.StandardLoadProfile.DefaultLoadProfiles -import edu.ie3.datamodel.models.input.system.LoadInput -import edu.ie3.datamodel.models.input.system.characteristic.CosPhiFixed -import edu.ie3.datamodel.models.input.{NodeInput, OperatorInput} -import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries -import edu.ie3.datamodel.models.value.SValue -import edu.ie3.simbench.convert.profiles.PowerProfileConverter -import edu.ie3.simbench.model.datamodel.profiles.{LoadProfile, LoadProfileType} -import edu.ie3.simbench.model.datamodel.{Load, Node} -import edu.ie3.util.quantities.PowerSystemUnits.{ - KILOWATTHOUR, - MEGAVAR, - MEGAVOLTAMPERE, - MEGAWATT -} -import tech.units.indriya.quantity.Quantities - -import scala.collection.parallel.CollectionConverters._ - -case object LoadConverter extends ShuntConverter { - def convert( - loads: Vector[Load], - nodes: Map[Node, NodeInput], - profiles: Map[LoadProfileType, LoadProfile] - ): Map[LoadInput, IndividualTimeSeries[SValue]] = - loads.par - .map { load => - val node = NodeConverter.getNode(load.node, nodes) - val profile = PowerProfileConverter.getProfile(load.profile, profiles) - convert(load, node, profile) - } - .seq - .toMap - - /** - * Converts a single SimBench [[Load]] to ie3's [[LoadInput]]. Currently not sufficiently covered: - * - Consumed energy throughout the year - * - different VAr characteristics - * - * @param input Input model - * @param node Node, the load is connected to - * @param profile SimBench load profile - * @param uuid UUID to use for the model generation (default: Random UUID) - * @return A [[LoadInput]] model - */ - def convert( - input: Load, - node: NodeInput, - profile: LoadProfile, - uuid: UUID = UUID.randomUUID() - ): (LoadInput, IndividualTimeSeries[SValue]) = { - val id = input.id - val cosphi = cosPhi(input.pLoad, input.qLoad) - val varCharacteristicString = - "cosPhiFixed:{(0.0,%#.2f)}".formatLocal(Locale.ENGLISH, cosphi) - val eCons = Quantities.getQuantity(0d, KILOWATTHOUR) - val sRated = Quantities.getQuantity(input.sR, MEGAVOLTAMPERE) - - val p = Quantities.getQuantity(input.pLoad, MEGAWATT) - val q = Quantities.getQuantity(input.qLoad, MEGAVAR) - val timeSeries = PowerProfileConverter.convert(profile, p, q) - - new LoadInput( - uuid, - id, - OperatorInput.NO_OPERATOR_ASSIGNED, - OperationTime.notLimited(), - node, - new CosPhiFixed(varCharacteristicString), - DefaultLoadProfiles.NO_STANDARD_LOAD_PROFILE, - false, - eCons, - sRated, - cosphi - ) -> - timeSeries - } -} diff --git a/src/test/scala/edu/ie3/simbench/convert/LoadConverterSpec.scala b/src/test/scala/edu/ie3/simbench/convert/LoadConverterSpec.scala index 1eb29e28..884cde2b 100644 --- a/src/test/scala/edu/ie3/simbench/convert/LoadConverterSpec.scala +++ b/src/test/scala/edu/ie3/simbench/convert/LoadConverterSpec.scala @@ -2,6 +2,7 @@ package edu.ie3.simbench.convert import edu.ie3.datamodel.models.StandardUnits import edu.ie3.datamodel.models.input.NodeInput +import edu.ie3.simbench.actor.LoadConverter import edu.ie3.simbench.model.datamodel.profiles.LoadProfile import edu.ie3.test.common.{ConverterTestData, UnitSpec} import edu.ie3.util.quantities.PowerSystemUnits.KILOWATTHOUR @@ -19,7 +20,7 @@ class LoadConverterSpec extends UnitSpec with ConverterTestData { "The load converter" should { "convert a single model correctly" in { /* Time series conversion is tested in a single test */ - val (actual, _) = LoadConverter.convert(input, node, inputProfile) + val actual = LoadConverter.Worker.convertModel(input, node) actual.getId shouldBe actual.getId actual.getNode shouldBe expected.getNode From 8991fd19a700c1b968d931511feb64a682d16af4 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Sun, 29 Aug 2021 13:31:03 +0200 Subject: [PATCH 24/32] Convert power plants --- .../edu/ie3/simbench/actor/Converter.scala | 61 ++++- .../ie3/simbench/actor/GridConverter.scala | 217 ---------------- .../ie3/simbench/actor/LoadConverter.scala | 3 +- .../simbench/actor/PowerPlantConverter.scala | 244 ++++++++++++++++++ .../convert/PowerPlantConverter.scala | 91 ------- .../simbench/convert/GridConverterSpec.scala | 96 +++---- .../convert/PowerPlantConverterSpec.scala | 107 +------- 7 files changed, 357 insertions(+), 462 deletions(-) create mode 100644 src/main/scala/edu/ie3/simbench/actor/PowerPlantConverter.scala delete mode 100644 src/main/scala/edu/ie3/simbench/convert/PowerPlantConverter.scala diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index c044ab59..b480ea39 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -207,8 +207,19 @@ object Converter { stateData.mutator, ctx.self ) + val powerPlantConverter = + ctx.spawn( + PowerPlantConverter(), + s"powerPlantConverter_${stateData.simBenchCode}" + ) + powerPlantConverter ! PowerPlantConverter.Init( + stateData.simBenchCode, + stateData.amountOfWorkers, + simBenchModel.powerPlantProfiles, + stateData.mutator, + ctx.self + ) - // TODO: Issue conversion of participants that then also respect for islanded nodes converting( stateData, simBenchModel, @@ -248,6 +259,18 @@ object Converter { ) Behaviors.same + case (ctx, PowerPlantConverterReady(powerPlantConverter)) => + ctx.log.debug( + s"${stateData.simBenchCode} - PowerPlantConverter is ready. Request conversion." + ) + powerPlantConverter ! PowerPlantConverter.Convert( + stateData.simBenchCode, + simBenchModel.powerPlants, + awaitedResults.nodeConversion.getOrElse(Map.empty), + ctx.self + ) + Behaviors.same + case (ctx, LoadsConverted(converted)) => ctx.log.info( s"${stateData.simBenchCode} - All loads are converted." @@ -293,6 +316,30 @@ object Converter { gridConverter, updatedAwaitedResults ) + + case (ctx, PowerPlantsConverted(converted)) => + ctx.log.info( + s"${stateData.simBenchCode} - All power plants are converted." + ) + val updatedAwaitedResults = + awaitedResults.copy(powerPlants = Some(converted)) + + if (updatedAwaitedResults.isReady) + finalizeConversion( + stateData.simBenchCode, + updatedAwaitedResults, + stateData.mutator, + stateData.coordinator, + ctx.self, + ctx.log + ) + else + converting( + stateData, + simBenchModel, + gridConverter, + updatedAwaitedResults + ) } def finalizing( @@ -585,8 +632,8 @@ object Converter { switches, measurements, loads, - res - //powerPlants TODO + res, + powerPlants ).forall(_.nonEmpty) } object AwaitedResults { @@ -668,16 +715,24 @@ object Converter { final case class ResConverterReady( replyTo: ActorRef[ResConverter.ShuntConverterMessage] ) extends ConverterMessage + final case class LoadConverterReady( replyTo: ActorRef[LoadConverter.ShuntConverterMessage] ) extends ConverterMessage + final case class PowerPlantConverterReady( + replyTo: ActorRef[PowerPlantConverter.ShuntConverterMessage] + ) extends ConverterMessage + final case class ResConverted(converted: Map[FixedFeedInInput, UUID]) extends ConverterMessage final case class LoadsConverted(converted: Map[LoadInput, UUID]) extends ConverterMessage + final case class PowerPlantsConverted(converted: Map[FixedFeedInInput, UUID]) + extends ConverterMessage + object GridStructurePersisted extends ConverterMessage object TimeSeriesMappingPersisted extends ConverterMessage object NodalResultsPersisted extends ConverterMessage diff --git a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala index 8a1ce315..e7da2b7c 100644 --- a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala @@ -3,8 +3,6 @@ package edu.ie3.simbench.actor import akka.actor.typed.ActorRef import akka.actor.typed.scaladsl.Behaviors import com.typesafe.scalalogging.LazyLogging -import edu.ie3.datamodel.io.source.TimeSeriesMappingSource -import edu.ie3.datamodel.io.source.TimeSeriesMappingSource.MappingEntry import edu.ie3.datamodel.models.input.NodeInput import edu.ie3.datamodel.models.input.connector.{ LineInput, @@ -12,20 +10,7 @@ import edu.ie3.datamodel.models.input.connector.{ Transformer2WInput, Transformer3WInput } -import edu.ie3.datamodel.models.input.container.{ - GraphicElements, - JointGridContainer, - RawGridElements, - SystemParticipants -} -import edu.ie3.datamodel.models.input.graphics.{ - LineGraphicInput, - NodeGraphicInput -} -import edu.ie3.datamodel.models.input.system._ import edu.ie3.datamodel.models.result.NodeResult -import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries -import edu.ie3.datamodel.models.value.{PValue, SValue} import edu.ie3.simbench.convert.NodeConverter.AttributeOverride.{ JoinOverride, SubnetOverride @@ -39,7 +24,6 @@ import edu.ie3.simbench.exception.ConversionException import edu.ie3.simbench.model.datamodel._ import edu.ie3.simbench.model.datamodel.types.LineType -import java.util.UUID import scala.annotation.tailrec import scala.jdk.CollectionConverters._ import scala.collection.parallel.CollectionConverters._ @@ -132,122 +116,6 @@ case object GridConverter extends LazyLogging { replyTo: ActorRef[Converter.ConverterMessage] ) extends GridConverterMessage - /* FIXME: ===== From here on, it's plain object code! ===== */ - - /** - * Converts a full simbench grid into power system data models [[JointGridContainer]]. Additionally, individual time - * series for all system participants are delivered as well. - * - * @param simbenchCode Simbench code, that is used as identifier for the grid - * @param gridInput Total grid input model to be converted - * @param removeSwitches Whether or not to remove switches from the grid structure - * @return A converted [[JointGridContainer]], a [[Vector]] of [[IndividualTimeSeries]] as well as a [[Vector]] of [[NodeResult]]s - */ - @deprecated("Use messages instead") - def convert( - simbenchCode: String, - gridInput: GridModel, - removeSwitches: Boolean - ): ( - JointGridContainer, - Vector[IndividualTimeSeries[_ <: PValue]], - Seq[MappingEntry], - Vector[NodeResult] - ) = { - logger.debug(s"Converting raw grid elements of '${gridInput.simbenchCode}'") - val (rawGridElements, nodeConversion) = - convertGridElements(gridInput, removeSwitches) - - logger.debug( - s"Converting system participants and their time series of '${gridInput.simbenchCode}'" - ) - val (systemParticipants, timeSeries, timeSeriesMapping) = - convertParticipants(gridInput, nodeConversion) - - logger.debug( - s"Converting power flow results of '${gridInput.simbenchCode}'" - ) - val powerFlowResults = - convertNodeResults(gridInput.nodePFResults, nodeConversion) - - ( - new JointGridContainer( - simbenchCode, - rawGridElements, - systemParticipants, - new GraphicElements( - Set.empty[NodeGraphicInput].asJava, - Set.empty[LineGraphicInput].asJava - ) - ), - timeSeries, - timeSeriesMapping, - powerFlowResults - ) - } - - /** - * Converts all elements that do form the grid itself. - * - * @param gridInput Total grid input model to convert - * @param removeSwitches Whether or not to remove switches from the grid structure - * @return All grid elements in converted form + a mapping from old to new node models - */ - @deprecated("Use messages instead") - def convertGridElements( - gridInput: GridModel, - removeSwitches: Boolean - ): (RawGridElements, Map[Node, NodeInput]) = { - val nodeConversion = convertNodes( - gridInput.nodes, - gridInput.externalNets, - gridInput.powerPlants, - gridInput.res, - gridInput.transformers2w, - gridInput.transformers3w, - gridInput.lines, - gridInput.switches, - removeSwitches - ) - - val lines = convertLines(gridInput.lines, nodeConversion).toSet.asJava - val transformers2w = - convertTransformers2w(gridInput.transformers2w, nodeConversion).toSet.asJava - val transformers3w = Set.empty[Transformer3WInput].asJava /* Currently, no conversion strategy is known */ - logger.debug( - "Creation of three winding transformers is not yet implemented." - ) - val switches = - if (!removeSwitches) - SwitchConverter.convert(gridInput.switches, nodeConversion).toSet.asJava - else - Set.empty[SwitchInput].asJava - val measurements = MeasurementConverter - .convert(gridInput.measurements, nodeConversion) - .toSet - .asJava - - val connectedNodes = filterIsolatedNodes( - nodeConversion, - lines, - transformers2w, - transformers3w, - switches - ) - - ( - new RawGridElements( - connectedNodes.values.toSet.asJava, - lines, - transformers2w, - transformers3w, - switches, - measurements - ), - connectedNodes - ) - } - /** * Convert the nodes with all needed preliminary steps. This is determination of target subnets, correction of subnet * number for high voltage switch gear and joining of nodes in case of closed switches. @@ -674,89 +542,4 @@ case object GridConverter extends LazyLogging { connectedNodes } } - - /** - * Converts all system participants and extracts their individual power time series - * - * @param gridInput Total grid input model to convert - * @param nodeConversion Already known conversion mapping of nodes - * @return A collection of converted system participants and their individual time series - */ - def convertParticipants( - gridInput: GridModel, - nodeConversion: Map[Node, NodeInput] - ): ( - SystemParticipants, - Vector[IndividualTimeSeries[_ <: PValue]], - Seq[MappingEntry] - ) = { - /* Convert all participant groups */ - logger.debug( - s"Participants to convert:\n\tLoads: ${gridInput.loads.size}" + - s"\n\tPower Plants: ${gridInput.powerPlants.size}\n\tRES: ${gridInput.res.size}" - ) -// val loadsToTimeSeries = convertLoads(gridInput, nodeConversion) -// logger.debug( -// s"Done converting ${gridInput.loads.size} loads including time series" -// ) - val powerPlantsToTimeSeries = convertPowerPlants(gridInput, nodeConversion) - logger.debug( - s"Done converting ${gridInput.powerPlants.size} power plants including time series" - ) -// val resToTimeSeries = convertRes(gridInput, nodeConversion) -// logger.debug( -// s"Done converting ${gridInput.res.size} RES including time series" -// ) - - /* Map participant uuid onto time series */ - val participantsToTimeSeries = powerPlantsToTimeSeries // ++ resToTimeSeries - val mapping = participantsToTimeSeries.map { - case (model, timeSeries) => - new TimeSeriesMappingSource.MappingEntry( - UUID.randomUUID(), - model.getUuid, - timeSeries.getUuid - ) - }.toSeq - val timeSeries: Vector[IndividualTimeSeries[_ >: SValue <: PValue]] = - participantsToTimeSeries.values.toVector - - ( - new SystemParticipants( - Set.empty[BmInput].asJava, - Set.empty[ChpInput].asJava, - Set.empty[EvcsInput].asJava, - Set.empty[EvInput].asJava, - powerPlantsToTimeSeries.keySet.asJava, - Set.empty[HpInput].asJava, - Set.empty[LoadInput].asJava, - Set.empty[PvInput].asJava, - Set.empty[StorageInput].asJava, - Set.empty[WecInput].asJava - ), - timeSeries, - mapping - ) - } - - /** - * Converting all power plants. - * - * @param gridInput Total grid input model to convert - * @param nodeConversion Already known conversion mapping of nodes - * @return A mapping from power plants to their assigned, specific time series - */ - def convertPowerPlants( - gridInput: GridModel, - nodeConversion: Map[Node, NodeInput] - ): Map[FixedFeedInInput, IndividualTimeSeries[PValue]] = { - val powerPlantProfiles = gridInput.powerPlantProfiles - .map(profile => profile.profileType -> profile) - .toMap - PowerPlantConverter.convert( - gridInput.powerPlants, - nodeConversion, - powerPlantProfiles - ) - } } diff --git a/src/main/scala/edu/ie3/simbench/actor/LoadConverter.scala b/src/main/scala/edu/ie3/simbench/actor/LoadConverter.scala index 31580c94..cb5b9321 100644 --- a/src/main/scala/edu/ie3/simbench/actor/LoadConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/LoadConverter.scala @@ -196,8 +196,7 @@ case object LoadConverter ) extends WorkerMessage.Convert[Load, LoadProfile, ShuntConverterMessage] /** - * Converts a single renewable energy source system to a fixed feed in model due to lacking information to - * sophistical guess typical types of assets. Different voltage regulation strategies are not covered, yet. + * Converts a load. Different voltage regulation strategies are not covered, yet. * * @param input Input model * @param node Node, the renewable energy source system is connected to diff --git a/src/main/scala/edu/ie3/simbench/actor/PowerPlantConverter.scala b/src/main/scala/edu/ie3/simbench/actor/PowerPlantConverter.scala new file mode 100644 index 00000000..2862b51f --- /dev/null +++ b/src/main/scala/edu/ie3/simbench/actor/PowerPlantConverter.scala @@ -0,0 +1,244 @@ +package edu.ie3.simbench.actor + +import akka.actor.typed.{ActorRef, SupervisorStrategy} +import akka.actor.typed.scaladsl.{Behaviors, Routers} +import edu.ie3.datamodel.models.OperationTime +import edu.ie3.datamodel.models.input.system.FixedFeedInInput +import edu.ie3.datamodel.models.input.system.characteristic.CosPhiFixed +import edu.ie3.datamodel.models.input.{NodeInput, OperatorInput} +import edu.ie3.simbench.convert.profiles.PowerProfileConverter +import edu.ie3.simbench.convert.{NodeConverter, ShuntConverter} +import edu.ie3.simbench.model.datamodel.profiles.{ + PowerPlantProfile, + PowerPlantProfileType +} +import edu.ie3.simbench.model.datamodel.{Node, PowerPlant} +import edu.ie3.util.quantities.PowerSystemUnits.{ + MEGAVAR, + MEGAVOLTAMPERE, + MEGAWATT +} +import tech.units.indriya.quantity.Quantities + +import java.util.{Locale, UUID} + +case object PowerPlantConverter + extends ShuntConverter + with ShuntConverterMessageSupport[ + PowerPlant, + PowerPlantProfile, + FixedFeedInInput + ] { + def apply(): Behaviors.Receive[ShuntConverterMessage] = uninitialized + + def uninitialized: Behaviors.Receive[ShuntConverterMessage] = + Behaviors.receive { + case ( + ctx, + Init(simBenchCode, amountOfWorkers, profiles, mutator, converter) + ) => + /* Prepare information */ + val typeToProfile = + profiles.map(profile => profile.profileType -> profile).toMap + + /* Set up a worker pool */ + val workerPool = Routers + .pool(poolSize = amountOfWorkers) { + Behaviors + .supervise(PowerPlantConverter.Worker()) + .onFailure(SupervisorStrategy.restart) + } + /* Allow broadcast messages to init all workers */ + .withBroadcastPredicate { + case _: WorkerMessage.Init => true + case _ => false + } + .withRoundRobinRouting() + val workerPoolProxy = + ctx.spawn( + workerPool, + s"PowerPlantConverterWorkerPool_$simBenchCode" + ) + + workerPoolProxy ! WorkerMessage.Init(mutator) + converter ! Converter.PowerPlantConverterReady(ctx.self) + + idle(typeToProfile, workerPoolProxy) + } + + def idle( + typeToProfile: Map[PowerPlantProfileType, PowerPlantProfile], + workerPool: ActorRef[WorkerMessage] + ): Behaviors.Receive[ShuntConverterMessage] = Behaviors.receive { + case (ctx, Convert(simBenchCode, input, nodes, converter)) => + ctx.log.debug( + s"Got request to convert power plants from '$simBenchCode'." + ) + val activeConversions = input.map { plant => + val node = NodeConverter.getNode(plant.node, nodes) + val profile = + PowerProfileConverter.getProfile(plant.profile, typeToProfile) + + workerPool ! Worker.Convert(plant, node, profile, ctx.self) + (plant.id, plant.node.getKey) + } + converting(activeConversions, Map.empty, workerPool, converter) + } + + def converting( + activeConversions: Vector[(String, Node.NodeKey)], + converted: Map[FixedFeedInInput, UUID], + workerPool: ActorRef[WorkerMessage], + converter: ActorRef[Converter.ConverterMessage] + ): Behaviors.Receive[ShuntConverterMessage] = Behaviors.receive { + case (ctx, Converted(id, node, fixedFeedInInput, timeSeriesUuid)) => + val remainingConversions = activeConversions.filterNot(_ == (id, node)) + val updatedConverted = converted + (fixedFeedInInput -> timeSeriesUuid) + ctx.log.debug( + s"Model '$id' at node '$node' is converted. ${remainingConversions.size} active conversions remaining." + ) + /* Stop the children and myself, if all conversions are done. */ + if (remainingConversions.isEmpty) { + ctx.stop(workerPool) + converter ! Converter.PowerPlantsConverted(converted) + Behaviors.stopped + } + converting(remainingConversions, updatedConverted, workerPool, converter) + } + + final case class Init( + simBenchCode: String, + amountOfWorkers: Int, + profiles: Vector[PowerPlantProfile], + mutator: ActorRef[Mutator.MutatorMessage], + replyTo: ActorRef[Converter.ConverterMessage] + ) extends super.Init + + /** + * Request to convert all given models + */ + final case class Convert( + simBenchCode: String, + inputs: Vector[PowerPlant], + nodes: Map[Node, NodeInput], + replyTo: ActorRef[Converter.ConverterMessage] + ) extends super.Convert + + final case class Converted( + id: String, + node: Node.NodeKey, + model: FixedFeedInInput, + timeSeriesUuid: UUID + ) extends super.Converted + + object Worker { + def apply(): Behaviors.Receive[WorkerMessage] = uninitialized + + def uninitialized: Behaviors.Receive[WorkerMessage] = Behaviors.receive { + case (_, WorkerMessage.Init(mutator)) => + idle(mutator) + } + + def idle( + mutator: ActorRef[Mutator.MutatorMessage], + awaitTimeSeriesPersistence: Map[ + UUID, + ( + String, + Node.NodeKey, + FixedFeedInInput, + ActorRef[ShuntConverterMessage] + ) + ] = Map.empty + ): Behaviors.Receive[WorkerMessage] = Behaviors.receive { + case (ctx, Convert(input, node, profile, replyTo)) => + Behaviors.same + ctx.log.debug( + s"Got request to convert power plant '${input.id} at node '${input.node.getKey} / ${node.getUuid}'." + ) + + val model = convertModel(input, node) + + val timeSeries = PowerProfileConverter.convert( + profile, + Quantities.getQuantity(input.p, MEGAWATT).multiply(-1) + ) + + mutator ! Mutator.PersistTimeSeries(timeSeries, ctx.self) + val updatedAwaitedTimeSeriesPersistence = awaitTimeSeriesPersistence + (timeSeries.getUuid -> (input.id, input.node.getKey, model, replyTo)) + idle(mutator, updatedAwaitedTimeSeriesPersistence) + + case (ctx, WorkerMessage.TimeSeriesPersisted(uuid)) => + ctx.log.debug(s"Time series '$uuid' is fully persisted.") + awaitTimeSeriesPersistence.get(uuid) match { + case Some((id, nodeKey, model, replyTo)) => + val remainingPersistence = + awaitTimeSeriesPersistence.filterNot(_._1 == uuid) + + replyTo ! PowerPlantConverter.Converted(id, nodeKey, model, uuid) + + idle(mutator, remainingPersistence) + case None => + ctx.log.warn( + s"Got informed, that the time series with uuid '$uuid' is persisted. But I didn't expect that to happen." + ) + Behaviors.same + } + } + + /** + * Override the abstract Request message with parameters, that suit your needs. + * + * @param model Model itself + * @param node Node, the converted model will be connected to + * @param profile The profile, that belongs to the model + * @param replyTo Address to reply to + */ + final case class Convert( + override val model: PowerPlant, + override val node: NodeInput, + override val profile: PowerPlantProfile, + override val replyTo: ActorRef[ShuntConverterMessage] + ) extends WorkerMessage.Convert[ + PowerPlant, + PowerPlantProfile, + ShuntConverterMessage + ] + + /** + * Converts a single power plant system to a fixed feed in model due to lacking information to sophistical guess + * typical types of assets. Different voltage regulation strategies are not covered, yet. + * + * @param input Input model + * @param node Node, the renewable energy source system is connected to + * @param uuid Option to a specific uuid + * @return A [[FixedFeedInInput]] + */ + def convertModel( + input: PowerPlant, + node: NodeInput, + uuid: Option[UUID] = None + ): FixedFeedInInput = { + val p = Quantities.getQuantity(input.p, MEGAWATT) + val q = input.q match { + case Some(value) => Quantities.getQuantity(value, MEGAVAR) + case None => Quantities.getQuantity(0d, MEGAVAR) + } + val cosphi = cosPhi(p.getValue.doubleValue(), q.getValue.doubleValue()) + val varCharacteristicString = + "cosPhiFixed:{(0.0,%#.2f)}".formatLocal(Locale.ENGLISH, cosphi) + val sRated = Quantities.getQuantity(input.sR, MEGAVOLTAMPERE) + + new FixedFeedInInput( + uuid.getOrElse(UUID.randomUUID()), + input.id, + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + node, + new CosPhiFixed(varCharacteristicString), + sRated, + cosphi + ) + } + } +} diff --git a/src/main/scala/edu/ie3/simbench/convert/PowerPlantConverter.scala b/src/main/scala/edu/ie3/simbench/convert/PowerPlantConverter.scala deleted file mode 100644 index 217050bd..00000000 --- a/src/main/scala/edu/ie3/simbench/convert/PowerPlantConverter.scala +++ /dev/null @@ -1,91 +0,0 @@ -package edu.ie3.simbench.convert - -import java.util.{Locale, UUID} - -import edu.ie3.datamodel.models.OperationTime -import edu.ie3.datamodel.models.input.system.FixedFeedInInput -import edu.ie3.datamodel.models.input.system.characteristic.CosPhiFixed -import edu.ie3.datamodel.models.input.{NodeInput, OperatorInput} -import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries -import edu.ie3.datamodel.models.value.PValue -import edu.ie3.simbench.convert.profiles.PowerProfileConverter -import edu.ie3.simbench.model.datamodel.profiles.{ - PowerPlantProfile, - PowerPlantProfileType -} -import edu.ie3.simbench.model.datamodel.{Node, PowerPlant} -import edu.ie3.util.quantities.PowerSystemUnits.{ - MEGAVAR, - MEGAVOLTAMPERE, - MEGAWATT -} -import tech.units.indriya.quantity.Quantities - -import scala.collection.parallel.CollectionConverters._ - -case object PowerPlantConverter extends ShuntConverter { - - /** - * Convert a full set of power plants - * - * @param powerPlants Input models to convert - * @param nodes Mapping from Simbench to power system data model node - * @param profiles Collection of [[PowerPlantProfile]]s - * @return A mapping from converted power plant to equivalent individual time series - */ - def convert( - powerPlants: Vector[PowerPlant], - nodes: Map[Node, NodeInput], - profiles: Map[PowerPlantProfileType, PowerPlantProfile] - ): Map[FixedFeedInInput, IndividualTimeSeries[PValue]] = - powerPlants.par - .map { powerPlant => - val node = NodeConverter.getNode(powerPlant.node, nodes) - val profile = - PowerProfileConverter.getProfile(powerPlant.profile, profiles) - convert(powerPlant, node, profile) - } - .seq - .toMap - - /** - * Converts a single power plant model to a fixed feed in model, as the power system data model does not reflect - * power plants, yet. Voltage regulation strategies are also not correctly accounted for. - * - * @param input Input model - * @param node Node, the power plant is connected to - * @param profile SimBench power plant profile - * @param uuid Option to a specific uuid - * @return A pair of [[FixedFeedInInput]] and matching active power time series - */ - def convert( - input: PowerPlant, - node: NodeInput, - profile: PowerPlantProfile, - uuid: Option[UUID] = None - ): (FixedFeedInInput, IndividualTimeSeries[PValue]) = { - val p = Quantities.getQuantity(input.p, MEGAWATT) - val q = input.q match { - case Some(value) => Quantities.getQuantity(value, MEGAVAR) - case None => Quantities.getQuantity(0d, MEGAVAR) - } - val cosphi = cosPhi(p.getValue.doubleValue(), q.getValue.doubleValue()) - val varCharacteristicString = - "cosPhiFixed:{(0.0,%#.2f)}".formatLocal(Locale.ENGLISH, cosphi) - val sRated = Quantities.getQuantity(input.sR, MEGAVOLTAMPERE) - - /* Flip the sign, as infeed is negative in PowerSystemDataModel */ - val timeSeries = PowerProfileConverter.convert(profile, p.multiply(-1)) - - new FixedFeedInInput( - uuid.getOrElse(UUID.randomUUID()), - input.id, - OperatorInput.NO_OPERATOR_ASSIGNED, - OperationTime.notLimited(), - node, - new CosPhiFixed(varCharacteristicString), - sRated, - cosphi - ) -> timeSeries - } -} diff --git a/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala b/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala index 47b6abc8..00d698ee 100644 --- a/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala +++ b/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala @@ -140,53 +140,55 @@ class GridConverterSpec extends UnitSpec with SwitchTestingData { "converting a full data set" should { "bring the correct amount of converted models" in { - val actual = GridConverter.convert( - "1-LV-rural1--0-no_sw", - input, - removeSwitches = false - ) - inside(actual) { - case ( - gridContainer, - timeSeries, - timeSeriesMapping, - powerFlowResults - ) => - /* Evaluate the correctness of the container by counting the occurrence of models (the correct conversion is - * tested in separate unit tests */ - gridContainer.getGridName shouldBe "1-LV-rural1--0-no_sw" - countClassOccurrences(gridContainer.getRawGrid.allEntitiesAsList()) shouldBe Map( - classOf[NodeInput] -> 15, - classOf[LineInput] -> 13, - classOf[Transformer2WInput] -> 1 - ) - countClassOccurrences( - gridContainer.getSystemParticipants.allEntitiesAsList() - ) shouldBe Map( - classOf[FixedFeedInInput] -> 4, - classOf[LoadInput] -> 13 - ) - countClassOccurrences(gridContainer.getGraphics.allEntitiesAsList()) shouldBe Map - .empty[Class[_ <: UniqueEntity], Int] - - /* Evaluate the correctness of the time series by counting the occurrence of models */ - timeSeries.size shouldBe 17 - - /* Evaluate the existence of time series mappings for all participants */ - timeSeriesMapping.size shouldBe 17 - val participantUuids = gridContainer.getSystemParticipants - .allEntitiesAsList() - .asScala - .map(_.getUuid) - .toVector - /* There is no participant uuid in mapping, that is not among participants */ - timeSeriesMapping.exists( - entry => !participantUuids.contains(entry.getParticipant) - ) shouldBe false - - /* Evaluate the amount of converted power flow results */ - powerFlowResults.size shouldBe 15 - } + // TODO: Fix test + succeed +// val actual = GridConverter.convert( +// "1-LV-rural1--0-no_sw", +// input, +// removeSwitches = false +// ) +// inside(actual) { +// case ( +// gridContainer, +// timeSeries, +// timeSeriesMapping, +// powerFlowResults +// ) => +// /* Evaluate the correctness of the container by counting the occurrence of models (the correct conversion is +// * tested in separate unit tests */ +// gridContainer.getGridName shouldBe "1-LV-rural1--0-no_sw" +// countClassOccurrences(gridContainer.getRawGrid.allEntitiesAsList()) shouldBe Map( +// classOf[NodeInput] -> 15, +// classOf[LineInput] -> 13, +// classOf[Transformer2WInput] -> 1 +// ) +// countClassOccurrences( +// gridContainer.getSystemParticipants.allEntitiesAsList() +// ) shouldBe Map( +// classOf[FixedFeedInInput] -> 4, +// classOf[LoadInput] -> 13 +// ) +// countClassOccurrences(gridContainer.getGraphics.allEntitiesAsList()) shouldBe Map +// .empty[Class[_ <: UniqueEntity], Int] +// +// /* Evaluate the correctness of the time series by counting the occurrence of models */ +// timeSeries.size shouldBe 17 +// +// /* Evaluate the existence of time series mappings for all participants */ +// timeSeriesMapping.size shouldBe 17 +// val participantUuids = gridContainer.getSystemParticipants +// .allEntitiesAsList() +// .asScala +// .map(_.getUuid) +// .toVector +// /* There is no participant uuid in mapping, that is not among participants */ +// timeSeriesMapping.exists( +// entry => !participantUuids.contains(entry.getParticipant) +// ) shouldBe false +// +// /* Evaluate the amount of converted power flow results */ +// powerFlowResults.size shouldBe 15 +// } } } } diff --git a/src/test/scala/edu/ie3/simbench/convert/PowerPlantConverterSpec.scala b/src/test/scala/edu/ie3/simbench/convert/PowerPlantConverterSpec.scala index d591c1d4..3ba79710 100644 --- a/src/test/scala/edu/ie3/simbench/convert/PowerPlantConverterSpec.scala +++ b/src/test/scala/edu/ie3/simbench/convert/PowerPlantConverterSpec.scala @@ -1,15 +1,12 @@ package edu.ie3.simbench.convert -import edu.ie3.datamodel.models.StandardUnits +import edu.ie3.simbench.actor.PowerPlantConverter import edu.ie3.simbench.model.datamodel.profiles.{ PowerPlantProfile, PowerPlantProfileType } import edu.ie3.test.common.{ConverterTestData, UnitSpec} import edu.ie3.test.matchers.QuantityMatchers -import tech.units.indriya.quantity.Quantities - -import scala.jdk.OptionConverters.RichOptional class PowerPlantConverterSpec extends UnitSpec @@ -19,26 +16,8 @@ class PowerPlantConverterSpec "converting a power plant without reactive power information" should { val (input, expected) = getPowerPlantPair("EHV Gen 1") val node = getNodePair("EHV Bus 177")._2 - val pProfile: PowerPlantProfile = PowerPlantProfile( - "test profile", - PowerPlantProfileType.PowerPlantProfile1, - Map( - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:00") -> BigDecimal( - "0.75" - ), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:15") -> BigDecimal( - "0.55" - ), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:30") -> BigDecimal( - "0.35" - ), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:45") -> BigDecimal( - "0.15" - ) - ) - ) - val (actual, actualTimeSeries) = - PowerPlantConverter.convert(input, node, pProfile) + val actual = + PowerPlantConverter.Worker.convertModel(input, node) "bring up the correct input model" in { actual.getId shouldBe expected.getId @@ -48,60 +27,13 @@ class PowerPlantConverterSpec actual.getsRated shouldBe expected.getsRated actual.getCosPhiRated shouldBe expected.getCosPhiRated } - - "lead to the correct time series" in { - val expected = Map( - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:00") -> Quantities - .getQuantity(-222750.0, StandardUnits.ACTIVE_POWER_IN), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:15") -> Quantities - .getQuantity(-163350.0, StandardUnits.ACTIVE_POWER_IN), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:30") -> Quantities - .getQuantity(-103950.0, StandardUnits.ACTIVE_POWER_IN), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:45") -> Quantities - .getQuantity(-44550.0, StandardUnits.ACTIVE_POWER_IN) - ) - - actualTimeSeries.getEntries.forEach { timeBasedValue => - val time = timeBasedValue.getTime - val value = timeBasedValue.getValue - - expected.get(time) match { - case Some(expectedValue) => - value.getP.toScala match { - case Some(p) => p should equalWithTolerance(expectedValue) - case None => - fail(s"Unable to get expected active power for time '$time'") - } - case None => - fail(s"Unable to get expected time series entry for time '$time'") - } - } - } } "converting a power plant with reactive power information" should { val (input, expected) = getPowerPlantPair("EHV Gen 1_withQ") val node = getNodePair("EHV Bus 177")._2 - val pProfile: PowerPlantProfile = PowerPlantProfile( - "test profile", - PowerPlantProfileType.PowerPlantProfile1, - Map( - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:00") -> BigDecimal( - "0.75" - ), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:15") -> BigDecimal( - "0.55" - ), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:30") -> BigDecimal( - "0.35" - ), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:45") -> BigDecimal( - "0.15" - ) - ) - ) - val (actual, actualTimeSeries) = - PowerPlantConverter.convert(input, node, pProfile) + val actual = + PowerPlantConverter.Worker.convertModel(input, node) "bring up the correct input model" in { actual.getId shouldBe expected.getId @@ -111,35 +43,6 @@ class PowerPlantConverterSpec actual.getsRated shouldBe expected.getsRated actual.getCosPhiRated shouldBe expected.getCosPhiRated } - - "lead to the correct time series" in { - val expected = Map( - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:00") -> Quantities - .getQuantity(-222750.0, StandardUnits.ACTIVE_POWER_IN), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:15") -> Quantities - .getQuantity(-163350.0, StandardUnits.ACTIVE_POWER_IN), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:30") -> Quantities - .getQuantity(-103950.0, StandardUnits.ACTIVE_POWER_IN), - simbenchTimeUtil.toZonedDateTime("01.01.1990 00:45") -> Quantities - .getQuantity(-44550.0, StandardUnits.ACTIVE_POWER_IN) - ) - - actualTimeSeries.getEntries.forEach { timeBasedValue => - val time = timeBasedValue.getTime - val value = timeBasedValue.getValue - - expected.get(time) match { - case Some(expectedP) => - value.getP.toScala match { - case Some(p) => p should equalWithTolerance(expectedP) - case None => - fail(s"Unable to get expected active power for time '$time'") - } - case None => - fail(s"Unable to get expected time series entry for time '$time'") - } - } - } } } } From 80a20d53a74fa48628c2205a1d86fdff9372df92 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Sun, 29 Aug 2021 13:36:05 +0200 Subject: [PATCH 25/32] Only start participant converters, if models are available --- .../edu/ie3/simbench/actor/Converter.scala | 70 +++++++++++-------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/src/main/scala/edu/ie3/simbench/actor/Converter.scala b/src/main/scala/edu/ie3/simbench/actor/Converter.scala index b480ea39..99040892 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Converter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Converter.scala @@ -189,36 +189,48 @@ object Converter { ctx.log.info( s"${stateData.simBenchCode} - Starting conversion of participant models" ) - val loadConverter = - ctx.spawn(LoadConverter(), s"loadConverter_${stateData.simBenchCode}") - loadConverter ! LoadConverter.Init( - stateData.simBenchCode, - stateData.amountOfWorkers, - simBenchModel.loadProfiles, - stateData.mutator, - ctx.self - ) - val resConverter = - ctx.spawn(ResConverter(), s"resConverter_${stateData.simBenchCode}") - resConverter ! ResConverter.Init( - stateData.simBenchCode, - stateData.amountOfWorkers, - simBenchModel.resProfiles, - stateData.mutator, - ctx.self - ) - val powerPlantConverter = - ctx.spawn( - PowerPlantConverter(), - s"powerPlantConverter_${stateData.simBenchCode}" + if (simBenchModel.loads.nonEmpty) { + val loadConverter = + ctx.spawn(LoadConverter(), s"loadConverter_${stateData.simBenchCode}") + loadConverter ! LoadConverter.Init( + stateData.simBenchCode, + stateData.amountOfWorkers, + simBenchModel.loadProfiles, + stateData.mutator, + ctx.self ) - powerPlantConverter ! PowerPlantConverter.Init( - stateData.simBenchCode, - stateData.amountOfWorkers, - simBenchModel.powerPlantProfiles, - stateData.mutator, - ctx.self - ) + } else { + ctx.self ! LoadsConverted(Map.empty[LoadInput, UUID]) + } + if (simBenchModel.res.nonEmpty) { + val resConverter = + ctx.spawn(ResConverter(), s"resConverter_${stateData.simBenchCode}") + resConverter ! ResConverter.Init( + stateData.simBenchCode, + stateData.amountOfWorkers, + simBenchModel.resProfiles, + stateData.mutator, + ctx.self + ) + } else { + ctx.self ! ResConverted(Map.empty[FixedFeedInInput, UUID]) + } + if (simBenchModel.powerPlants.nonEmpty) { + val powerPlantConverter = + ctx.spawn( + PowerPlantConverter(), + s"powerPlantConverter_${stateData.simBenchCode}" + ) + powerPlantConverter ! PowerPlantConverter.Init( + stateData.simBenchCode, + stateData.amountOfWorkers, + simBenchModel.powerPlantProfiles, + stateData.mutator, + ctx.self + ) + } else { + ctx.self ! PowerPlantsConverted(Map.empty[FixedFeedInInput, UUID]) + } converting( stateData, From fe41d5d1f96f17601bc83c524bf621c2d27c3cff Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Sun, 29 Aug 2021 14:13:15 +0200 Subject: [PATCH 26/32] Fix grid converter test --- .../simbench/convert/GridConverterSpec.scala | 133 +++++++++--------- 1 file changed, 65 insertions(+), 68 deletions(-) diff --git a/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala b/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala index 00d698ee..7f899d25 100644 --- a/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala +++ b/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala @@ -1,21 +1,32 @@ package edu.ie3.simbench.convert +import akka.actor.testkit.typed.scaladsl.ActorTestKit + import java.nio.file.Paths import java.util import edu.ie3.datamodel.models.UniqueEntity -import edu.ie3.datamodel.models.input.NodeInput -import edu.ie3.datamodel.models.input.connector.{LineInput, Transformer2WInput} -import edu.ie3.datamodel.models.input.system.{FixedFeedInInput, LoadInput} -import edu.ie3.simbench.actor.GridConverter +import edu.ie3.simbench.actor.GridConverter.ConvertGridStructure +import edu.ie3.simbench.actor.{Converter, GridConverter} import edu.ie3.simbench.convert.NodeConverter.AttributeOverride.JoinOverride import edu.ie3.simbench.io.SimbenchReader import edu.ie3.simbench.model.datamodel.{GridModel, Node, Switch} import edu.ie3.test.common.{SwitchTestingData, UnitSpec} -import org.scalatest.Inside._ +import org.scalatest.BeforeAndAfterAll +import scala.concurrent.duration.{Duration, FiniteDuration} import scala.jdk.CollectionConverters._ -class GridConverterSpec extends UnitSpec with SwitchTestingData { +class GridConverterSpec + extends UnitSpec + with BeforeAndAfterAll + with SwitchTestingData { + val akkaTestKit: ActorTestKit = ActorTestKit() + + override protected def afterAll(): Unit = { + akkaTestKit.shutdownTestKit() + super.afterAll() + } + val simbenchReader: SimbenchReader = SimbenchReader( "1-LV-rural1--0-no_sw", Paths.get("src/test/resources/gridData/1-LV-rural1--0-no_sw") @@ -140,69 +151,55 @@ class GridConverterSpec extends UnitSpec with SwitchTestingData { "converting a full data set" should { "bring the correct amount of converted models" in { - // TODO: Fix test - succeed -// val actual = GridConverter.convert( -// "1-LV-rural1--0-no_sw", -// input, -// removeSwitches = false -// ) -// inside(actual) { -// case ( -// gridContainer, -// timeSeries, -// timeSeriesMapping, -// powerFlowResults -// ) => -// /* Evaluate the correctness of the container by counting the occurrence of models (the correct conversion is -// * tested in separate unit tests */ -// gridContainer.getGridName shouldBe "1-LV-rural1--0-no_sw" -// countClassOccurrences(gridContainer.getRawGrid.allEntitiesAsList()) shouldBe Map( -// classOf[NodeInput] -> 15, -// classOf[LineInput] -> 13, -// classOf[Transformer2WInput] -> 1 -// ) -// countClassOccurrences( -// gridContainer.getSystemParticipants.allEntitiesAsList() -// ) shouldBe Map( -// classOf[FixedFeedInInput] -> 4, -// classOf[LoadInput] -> 13 -// ) -// countClassOccurrences(gridContainer.getGraphics.allEntitiesAsList()) shouldBe Map -// .empty[Class[_ <: UniqueEntity], Int] -// -// /* Evaluate the correctness of the time series by counting the occurrence of models */ -// timeSeries.size shouldBe 17 -// -// /* Evaluate the existence of time series mappings for all participants */ -// timeSeriesMapping.size shouldBe 17 -// val participantUuids = gridContainer.getSystemParticipants -// .allEntitiesAsList() -// .asScala -// .map(_.getUuid) -// .toVector -// /* There is no participant uuid in mapping, that is not among participants */ -// timeSeriesMapping.exists( -// entry => !participantUuids.contains(entry.getParticipant) -// ) shouldBe false -// -// /* Evaluate the amount of converted power flow results */ -// powerFlowResults.size shouldBe 15 -// } + /* Set up the actors */ + val gridConverter = akkaTestKit.spawn(GridConverter(), "gridConverter") + val converter = + akkaTestKit.createTestProbe[Converter.ConverterMessage]("converter") + + /* Issue the conversion */ + gridConverter ! ConvertGridStructure( + "1-LV-rural1--0-no_sw", + input.nodes, + input.nodePFResults, + input.externalNets, + input.powerPlants, + input.res, + input.transformers2w, + input.transformers3w, + input.lines, + input.switches, + input.measurements, + removeSwitches = false, + converter.ref + ) + + converter.expectMessageType[Converter.GridStructureConverted]( + FiniteDuration(60, "s") + ) match { + case Converter.GridStructureConverted( + nodeConversion, + nodeResults, + lines, + transformers2w, + transformers3w, + switches, + measurements + ) => + nodeConversion.size shouldBe 15 + nodeResults.size shouldBe 15 + lines.size shouldBe 13 + transformers2w.size shouldBe 1 + transformers3w.size shouldBe 0 + switches.size shouldBe 0 + measurements.size shouldBe 0 + } } + /* TODO: Test amount of converted participants + * 4 x FixedFeedInInput + * 13 x LoadInput + * 17 x Time Series and Mapping + * All participants have a corresponding time series + */ } } - - def countClassOccurrences( - entities: util.List[_ <: UniqueEntity] - ): Map[Class[_ <: UniqueEntity], Int] = - entities.asScala - .groupBy(_.getClass) - .map( - classToOccurrences => - classToOccurrences._1 -> classToOccurrences._2.size - ) - .toSeq - .sortBy(_._1.getName) - .toMap } From 443839b86c75b63cac833db1c79d345f8dae5cc7 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Sun, 29 Aug 2021 15:13:46 +0200 Subject: [PATCH 27/32] Test correct amount of converted models --- .../ie3/simbench/actor/LoadConverter.scala | 2 +- .../simbench/actor/PowerPlantConverter.scala | 2 +- .../edu/ie3/simbench/actor/ResConverter.scala | 2 +- .../edu/ie3/simbench/main/RunSimbench.scala | 22 ---- .../ie3/simbench/convert/ConverterSpec.scala | 106 ++++++++++++++++++ .../simbench/convert/GridConverterSpec.scala | 6 - 6 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 src/test/scala/edu/ie3/simbench/convert/ConverterSpec.scala diff --git a/src/main/scala/edu/ie3/simbench/actor/LoadConverter.scala b/src/main/scala/edu/ie3/simbench/actor/LoadConverter.scala index cb5b9321..9859bb38 100644 --- a/src/main/scala/edu/ie3/simbench/actor/LoadConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/LoadConverter.scala @@ -93,7 +93,7 @@ case object LoadConverter /* Stop the children and myself, if all conversions are done. */ if (remainingConversions.isEmpty) { ctx.stop(workerPool) - converter ! Converter.LoadsConverted(converted) + converter ! Converter.LoadsConverted(updatedConverted) Behaviors.stopped } converting(remainingConversions, updatedConverted, workerPool, converter) diff --git a/src/main/scala/edu/ie3/simbench/actor/PowerPlantConverter.scala b/src/main/scala/edu/ie3/simbench/actor/PowerPlantConverter.scala index 2862b51f..b4bc2cd8 100644 --- a/src/main/scala/edu/ie3/simbench/actor/PowerPlantConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/PowerPlantConverter.scala @@ -100,7 +100,7 @@ case object PowerPlantConverter /* Stop the children and myself, if all conversions are done. */ if (remainingConversions.isEmpty) { ctx.stop(workerPool) - converter ! Converter.PowerPlantsConverted(converted) + converter ! Converter.PowerPlantsConverted(updatedConverted) Behaviors.stopped } converting(remainingConversions, updatedConverted, workerPool, converter) diff --git a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala index 9982564d..686862df 100644 --- a/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/ResConverter.scala @@ -91,7 +91,7 @@ case object ResConverter /* Stop the children and myself, if all conversions are done. */ if (remainingConversions.isEmpty) { ctx.stop(workerPool) - converter ! Converter.ResConverted(converted) + converter ! Converter.ResConverted(updatedConverted) Behaviors.stopped } converting(remainingConversions, updatedConverted, workerPool, converter) diff --git a/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala b/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala index c3cce891..6b207682 100644 --- a/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala +++ b/src/main/scala/edu/ie3/simbench/main/RunSimbench.scala @@ -26,27 +26,5 @@ object RunSimbench extends SimbenchHelper { "Coordinator" ) actorSystem ! Start(simbenchConfig) - -// simbenchConfig.io.simbenchCodes.foreach { simbenchCode => -// logger.info(s"$simbenchCode - Converting to PowerSystemDataModel") -// val ( -// jointGridContainer, -// timeSeries, -// timeSeriesMapping, -// powerFlowResults -// ) = -// GridConverter.convert( -// simbenchCode, -// simbenchModel, -// simbenchConfig.conversion.removeSwitches -// ) -// -// logger.info(s"$simbenchCode - Writing converted data set to files") -// -// csvSink.persistJointGrid(jointGridContainer) -// timeSeries.foreach(csvSink.persistTimeSeries(_)) -// csvSink.persistAllIgnoreNested(timeSeriesMapping.asJava) -// csvSink.persistAll(powerFlowResults.asJava) -// } } } diff --git a/src/test/scala/edu/ie3/simbench/convert/ConverterSpec.scala b/src/test/scala/edu/ie3/simbench/convert/ConverterSpec.scala new file mode 100644 index 00000000..93dd771c --- /dev/null +++ b/src/test/scala/edu/ie3/simbench/convert/ConverterSpec.scala @@ -0,0 +1,106 @@ +package edu.ie3.simbench.convert + +import akka.actor.testkit.typed.scaladsl.{ActorTestKit, TestProbe} +import akka.actor.typed.ActorRef +import edu.ie3.simbench.actor.{Converter, Coordinator, Mutator, WorkerMessage} +import edu.ie3.simbench.actor.Mutator.{ + MutatorMessage, + PersistGridStructure, + PersistNodalResults, + PersistTimeSeriesMapping +} +import edu.ie3.test.common.UnitSpec +import org.scalatest.BeforeAndAfterAll + +import scala.concurrent.duration.FiniteDuration + +class ConverterSpec extends UnitSpec with BeforeAndAfterAll { + val akkaTestKit: ActorTestKit = ActorTestKit() + + override protected def afterAll(): Unit = { + akkaTestKit.shutdownTestKit() + super.afterAll() + } + + val coordinator: TestProbe[Coordinator.CoordinatorMessage] = + akkaTestKit.createTestProbe[Coordinator.CoordinatorMessage]("coordinator") + val mutator: TestProbe[MutatorMessage] = + akkaTestKit.createTestProbe[Mutator.MutatorMessage]("mutator") + val converter: ActorRef[Converter.ConverterMessage] = akkaTestKit.spawn( + Converter.idle( + Converter.StateData( + "1-LV-rural1--0-no_sw", + "src/test/resources/gridData/1-LV-rural1--0-no_sw", + "http://141.51.193.167/simbench/gui/usecase/download", + failOnExistingFiles = false, + ".csv", + "UTF-8", + ";", + removeSwitches = false, + 20, + mutator.ref, + coordinator.ref + ) + ), + "converter" + ) + + "Converting a full grid model" should { + converter ! Converter.Convert("1-LV-rural1--0-no_sw") + "provide correct amount of converted models" in { + + /* Receive 17 time series messages and reply completion */ + (1 to 17).foldLeft(List.empty[Mutator.PersistTimeSeries]) { + case (messages, _) => + val message = mutator.expectMessageType[Mutator.PersistTimeSeries]( + FiniteDuration(60, "s") + ) + message.replyTo ! WorkerMessage.TimeSeriesPersisted( + message.timeSeries.getUuid + ) + messages :+ message + } + + /* Receive all all other awaited messages */ + mutator.expectMessageType[Mutator.PersistGridStructure]( + FiniteDuration(60, "s") + ) match { + case Mutator.PersistGridStructure( + simBenchCode, + nodes, + lines, + transformers2w, + transformers3w, + switches, + measurements, + loads, + fixedFeedIns, + _ + ) => + simBenchCode shouldBe "1-LV-rural1--0-no_sw" + nodes.size shouldBe 15 + lines.size shouldBe 13 + transformers2w.size shouldBe 1 + transformers3w.size shouldBe 0 + switches.size shouldBe 0 + measurements.size shouldBe 0 + loads.size shouldBe 13 + fixedFeedIns.size shouldBe 4 + } + + mutator.expectMessageType[Mutator.PersistTimeSeriesMapping]( + FiniteDuration(60, "s") + ) match { + case Mutator.PersistTimeSeriesMapping(mapping, _) => + mapping.size shouldBe 17 + } + + mutator.expectMessageType[Mutator.PersistNodalResults]( + FiniteDuration(60, "s") + ) match { + case Mutator.PersistNodalResults(results, _) => + results.size shouldBe 15 + } + } + } +} diff --git a/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala b/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala index 7f899d25..0cc435fc 100644 --- a/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala +++ b/src/test/scala/edu/ie3/simbench/convert/GridConverterSpec.scala @@ -194,12 +194,6 @@ class GridConverterSpec measurements.size shouldBe 0 } } - /* TODO: Test amount of converted participants - * 4 x FixedFeedInInput - * 13 x LoadInput - * 17 x Time Series and Mapping - * All participants have a corresponding time series - */ } } } From d73dffbb9e817e4e1186ac4e7a88c7d2a2019108 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Mon, 30 Aug 2021 15:54:18 +0200 Subject: [PATCH 28/32] Adding to CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a7f37e5..67c2194b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- Actor-based implementation for better concurrency ## [1.0.0] - 2021-08-03 ### Added From 0743b931a3970330edaa3f0169b382d69f7e1b35 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Tue, 31 Aug 2021 17:31:36 +0200 Subject: [PATCH 29/32] Improve documentation --- docs/diagrams/protocol/protocol.puml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/diagrams/protocol/protocol.puml b/docs/diagrams/protocol/protocol.puml index 4fec9332..6a84d690 100644 --- a/docs/diagrams/protocol/protocol.puml +++ b/docs/diagrams/protocol/protocol.puml @@ -11,17 +11,17 @@ participant Mutator Main -> Coordinator: Start(Config) == Init == Coordinator --[#blue]> Converter: spawn & watch -Coordinator -> Converter: Init(String) +Coordinator -> Converter: Init(...) +Converter --[#blue]> Mutator: spawn & watch +Converter -> Mutator: Init(Config) +Converter <- Mutator: Ready Coordinator <- Converter: ConverterInitialized Coordinator -> Converter: Convert(String) == Converting grid structure == -Converter --[#blue]> Mutator: spawn & watch -Converter -> Mutator: Init(Config) -Converter <- Mutator: Ready Converter --[#blue]> GridConverter: spawn & watch Converter -> GridConverter: ConvertGridStructure(...) -Converter <- GridConverter: GridStructureConverted(Map[Node,NodeInput]) +Converter <- GridConverter: GridStructureConverted(...) note left: Now all other entities\ncan be converted (not)\nconsidering islanded\nnodes == Converting participants == @@ -31,7 +31,7 @@ ResConverter --[#blue]> ResConverterWorker: spawn & watch ResConverter: Convert(...) ResConverter -> ResConverterWorker: Convert(...) -ResConverterWorker -> Mutator: Persist(...) +ResConverterWorker -> Mutator: PersistTimeSeries(...) activate Mutator ResConverterWorker <- Mutator: TimeSeriesPersisted(...) ResConverter <- ResConverterWorker: Converted(...) @@ -40,9 +40,12 @@ ResConverter <--[#blue] ResConverterWorker Converter <- ResConverter: ResConverted(...) == Mutate Grid structure == -GridConverter -> Mutator: Mutate(...) -GridConverter <- Mutator: Mutated(...) -Converter <- GridConverter: Done +Converter -> Mutator: PersistGridStructure(...) +Converter -> Mutator: PersistTimeSeriesMapping(...) +Converter -> Mutator: PersistNodalResults(...) +Converter <- Mutator: GridStructurePersisted(...) +Converter <- Mutator: TimeSeriesMappingPersisted(...) +Converter <- Mutator: NodalResultsPersisted(...) == Shutdown == Converter -> Mutator: Shutdown From 53e42207df5fe0bdc67ebaf7b39eb03dbfa46328 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Tue, 31 Aug 2021 17:50:08 +0200 Subject: [PATCH 30/32] Improve logging in test --- build.gradle | 4 ++++ src/test/scala/edu/ie3/simbench/convert/ConverterSpec.scala | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 38d8b56c..686db311 100644 --- a/build.gradle +++ b/build.gradle @@ -106,3 +106,7 @@ wrapper { tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } + +tasks.withType(Javadoc){ + options.encoding = 'UTF-8' +} \ No newline at end of file diff --git a/src/test/scala/edu/ie3/simbench/convert/ConverterSpec.scala b/src/test/scala/edu/ie3/simbench/convert/ConverterSpec.scala index 93dd771c..721b71b6 100644 --- a/src/test/scala/edu/ie3/simbench/convert/ConverterSpec.scala +++ b/src/test/scala/edu/ie3/simbench/convert/ConverterSpec.scala @@ -51,7 +51,8 @@ class ConverterSpec extends UnitSpec with BeforeAndAfterAll { /* Receive 17 time series messages and reply completion */ (1 to 17).foldLeft(List.empty[Mutator.PersistTimeSeries]) { - case (messages, _) => + case (messages, idx) => + logger.debug("Received {} time series for persistence so far.", idx) val message = mutator.expectMessageType[Mutator.PersistTimeSeries]( FiniteDuration(60, "s") ) From fa39f20f82879d41e6ded1d01df235307a32332d Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Wed, 1 Sep 2021 07:56:47 +0200 Subject: [PATCH 31/32] Actually not providing switches if asked to omit them --- src/main/scala/edu/ie3/simbench/actor/GridConverter.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala index e7da2b7c..00f07fea 100644 --- a/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala +++ b/src/main/scala/edu/ie3/simbench/actor/GridConverter.scala @@ -73,7 +73,9 @@ case object GridConverter extends LazyLogging { ctx.log.debug( "Creation of three winding transformers is not yet implemented." ) - val convertedSwitches = SwitchConverter.convert(switches, nodeConversion) + val convertedSwitches = + if (!removeSwitches) SwitchConverter.convert(switches, nodeConversion) + else Vector.empty val convertedMeasurements = MeasurementConverter .convert(measurements, nodeConversion) From 232179e1f54932f06c7febcb60aa34d38a76be37 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Wed, 1 Sep 2021 08:09:02 +0200 Subject: [PATCH 32/32] Actually registering the completed runs --- src/main/scala/edu/ie3/simbench/actor/Coordinator.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala b/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala index c623cd12..00942182 100644 --- a/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala +++ b/src/main/scala/edu/ie3/simbench/actor/Coordinator.scala @@ -107,6 +107,7 @@ object Coordinator { idle( stateData.copy( simBenchCodes = remainingCodes, + activeConverters = stillActiveConverters, initializingConverters = yetInitializingConverters ) )