From 7341e10ac9e99eb80fb765ce6424be51557a13d5 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Fri, 15 Nov 2024 14:09:46 +0100 Subject: [PATCH 01/14] remove unnecessary value --- src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala b/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala index b74c8ffa04..f50b493b9c 100644 --- a/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala +++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala @@ -369,8 +369,7 @@ final case class ThermalGrid( qDotHouse: Power, qDotHeatStorage: Power, ): (ThermalGridState, Option[ThermalThreshold]) = { - // FIXME: Is there any case where we get back some remainingQDotHouse? - val (updatedHouseState, thermalHouseThreshold, remainingQDotHouse) = + val (updatedHouseState, thermalHouseThreshold, _) = handleInfeedHouse( tick, lastAmbientTemperature, From fa36523ce73f8c0074a5ed9bb7fa03f4917690d0 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 15 Nov 2024 15:08:22 +0100 Subject: [PATCH 02/14] Removing logs in `logs/simona`. --- CHANGELOG.md | 1 + src/main/resources/logback.xml | 8 -------- .../simona/logging/logback/LogbackConfiguration.scala | 11 +++++------ 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a2cb7b066..cb4a719cb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated `ExtEvSimulationClasses` [#898](https://github.com/ie3-institute/simona/issues/898) - Refactoring of `ThermalGrid.energyGrid` to distinguish between demand of house and storage [#928](https://github.com/ie3-institute/simona/issues/928) - Refactoring to use zeroKW and zeroKWH in thermal grid unit tests [#1023](https://github.com/ie3-institute/simona/issues/1023) +- Removing logs in `logs/simona` [#1017](https://github.com/ie3-institute/simona/issues/1017) ### Fixed - Fix rendering of references in documentation [#505](https://github.com/ie3-institute/simona/issues/505) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index e5abb4e391..3448ebab83 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -18,14 +18,6 @@ - logs/simona/simona.log - - - logs/simona/archive/simona-%d{yyyyMMdd'T'HHmmss}-${bySecond}.log - - 10 - 3GB - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n diff --git a/src/main/scala/edu/ie3/simona/logging/logback/LogbackConfiguration.scala b/src/main/scala/edu/ie3/simona/logging/logback/LogbackConfiguration.scala index 3d236a1f41..58f982c2c7 100644 --- a/src/main/scala/edu/ie3/simona/logging/logback/LogbackConfiguration.scala +++ b/src/main/scala/edu/ie3/simona/logging/logback/LogbackConfiguration.scala @@ -43,12 +43,11 @@ object LogbackConfiguration extends LazyLogging { ) ) - rootLogger.iteratorForAppenders().asScala.foreach { - case rf: RollingFileAppender[_] => - rf.getTriggeringPolicy.start() - rf.start() - case appender => appender.start() - } + rootLogger + .iteratorForAppenders() + .asScala + .foreach(appender => appender.start()) + case factory => logger.error( s"Cannot configure simulation run logger! Invalid factory: $factory" From 30c4c95a9fa0842b0250e8251b518fee786a469f Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 18 Nov 2024 21:06:15 +0100 Subject: [PATCH 03/14] Configurable log level Completely refactored ResultFileHierarchy Signed-off-by: Sebastian Peter --- .../resources/config/config-template.conf | 2 + src/main/resources/logback.xml | 10 +- .../edu/ie3/simona/config/SimonaConfig.scala | 23 ++ .../event/listener/ResultEventListener.scala | 2 +- .../logback/LogbackConfiguration.scala | 59 ++-- .../scala/edu/ie3/simona/main/RunSimona.scala | 4 +- .../ie3/simona/sim/setup/SetupHelper.scala | 9 +- .../ie3/simona/sim/setup/SimonaSetup.scala | 4 +- .../sim/setup/SimonaStandaloneSetup.scala | 3 +- .../ie3/simona/util/ResultFileHierarchy.scala | 283 +++++++++--------- .../listener/ResultEventListenerSpec.scala | 85 +++--- .../integration/RunSimonaStandaloneIT.scala | 12 +- .../io/file/ResultFileHierarchySpec.scala | 64 +--- .../edu/ie3/simona/sim/SimonaSimSpec.scala | 3 +- .../simona/sim/setup/SimonaSetupSpec.scala | 3 +- 15 files changed, 271 insertions(+), 295 deletions(-) diff --git a/src/main/resources/config/config-template.conf b/src/main/resources/config/config-template.conf index 2d6c5c9194..6c9d886711 100644 --- a/src/main/resources/config/config-template.conf +++ b/src/main/resources/config/config-template.conf @@ -292,6 +292,8 @@ simona.output.thermal = { } simona.output.flex = Boolean | false +simona.output.log.level = String | "INFO" + ################################################################## # Runtime Configuration // todo refactor as this naming is misleading ################################################################## diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 3448ebab83..909dc6bda8 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -17,16 +17,8 @@ INFO - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - INFO - - + - \ No newline at end of file diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index e31ccce104..0790a6efd9 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -1957,6 +1957,7 @@ object SimonaConfig { base: SimonaConfig.Simona.Output.Base, flex: scala.Boolean, grid: SimonaConfig.GridOutputConfig, + log: SimonaConfig.Simona.Output.Log, participant: SimonaConfig.Simona.Output.Participant, sink: SimonaConfig.Simona.Output.Sink, thermal: SimonaConfig.Simona.Output.Thermal, @@ -1997,6 +1998,22 @@ object SimonaConfig { } + final case class Log( + level: java.lang.String + ) + object Log { + def apply( + c: com.typesafe.config.Config, + parentPath: java.lang.String, + $tsCfgValidator: $TsCfgValidator, + ): SimonaConfig.Simona.Output.Log = { + SimonaConfig.Simona.Output.Log( + level = + if (c.hasPathOrNull("level")) c.getString("level") else "INFO" + ) + } + } + final case class Participant( defaultConfig: SimonaConfig.ParticipantBaseOutputConfig, individualConfigs: scala.List[ @@ -2236,6 +2253,12 @@ object SimonaConfig { parentPath + "grid.", $tsCfgValidator, ), + log = SimonaConfig.Simona.Output.Log( + if (c.hasPathOrNull("log")) c.getConfig("log") + else com.typesafe.config.ConfigFactory.parseString("log{}"), + parentPath + "log.", + $tsCfgValidator, + ), participant = SimonaConfig.Simona.Output.Participant( if (c.hasPathOrNull("participant")) c.getConfig("participant") else com.typesafe.config.ConfigFactory.parseString("participant{}"), diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index 8e7dd12abd..d9c910ef1a 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -88,7 +88,7 @@ object ResultEventListener extends Transformer3wResultSupport { filePathFuture.map { fileName => val finalFileName = - fileName match { + fileName.toString match { case name if name.endsWith(".csv.gz") && enableCompression => name.replace(".gz", "") case name if name.endsWith(".csv") => name diff --git a/src/main/scala/edu/ie3/simona/logging/logback/LogbackConfiguration.scala b/src/main/scala/edu/ie3/simona/logging/logback/LogbackConfiguration.scala index 58f982c2c7..31c23e4568 100644 --- a/src/main/scala/edu/ie3/simona/logging/logback/LogbackConfiguration.scala +++ b/src/main/scala/edu/ie3/simona/logging/logback/LogbackConfiguration.scala @@ -10,56 +10,44 @@ import ch.qos.logback.classic.LoggerContext import ch.qos.logback.classic.encoder.PatternLayoutEncoder import ch.qos.logback.classic.filter.ThresholdFilter import ch.qos.logback.classic.spi.ILoggingEvent -import ch.qos.logback.core.rolling.RollingFileAppender import ch.qos.logback.core.FileAppender -import ch.qos.logback.core.filter.Filter import com.typesafe.scalalogging.LazyLogging import org.slf4j.LoggerFactory -import java.io.File -import scala.jdk.CollectionConverters._ +import java.nio.file.Path object LogbackConfiguration extends LazyLogging { - def default(logPath: String): Unit = { + def default(logPath: Path, logLevel: String): Unit = { LoggerFactory.getILoggerFactory match { case loggerContext: LoggerContext => val rootLogger = loggerContext.getLogger("root") - val log = logPath.concat(File.separator).concat("simona.log") - // stop all appenders - rootLogger.iteratorForAppenders().asScala.foreach(_.stop()) - /* Identify the filters of existing rolling file appender */ - val fileLoggerFilterList = rootLogger - .iteratorForAppenders() - .asScala - .find(_.getName == "RF") - .map(_.getCopyOfAttachedFiltersList.asScala.toSeq) + + // Since logback configuration is static + val logFile = logPath.resolve("simona.log") + rootLogger.addAppender( - fileAppender( - log, + createFileAppender( + logFile, + logLevel, "simona-default", - fileLoggerFilterList, loggerContext, ) ) - rootLogger - .iteratorForAppenders() - .asScala - .foreach(appender => appender.start()) - case factory => logger.error( s"Cannot configure simulation run logger! Invalid factory: $factory" ) } - } - private def fileAppender( - logPath: String, + /** Creates a FileAppender that logs to given path + */ + private def createFileAppender( + logPath: Path, + logLevel: String, appenderName: String, - maybeFilterList: Option[Seq[Filter[ILoggingEvent]]], loggerContext: LoggerContext, ): FileAppender[ILoggingEvent] = { @@ -68,23 +56,16 @@ object LogbackConfiguration extends LazyLogging { layoutEncoder.setContext(loggerContext) layoutEncoder.start() + val filter = new ThresholdFilter() + filter.setLevel(logLevel) + filter.start() + val fileAppender = new FileAppender[ILoggingEvent] - fileAppender.setFile(logPath) + fileAppender.setFile(logPath.toString) fileAppender.setEncoder(layoutEncoder) fileAppender.setContext(loggerContext) fileAppender.setName(appenderName) - /* If applicable, apply the filters from existing file logger else log with "INFO"-Level */ - maybeFilterList match { - case Some(filterList) => - if (filterList.isEmpty) { // No filters in appenders -> Empty List - val filter = new ThresholdFilter() - filter.setLevel("INFO") - filter.start() - fileAppender.addFilter(filter) - } else { - filterList.foreach(fileAppender.addFilter) - } - } + fileAppender.addFilter(filter) fileAppender.start() fileAppender diff --git a/src/main/scala/edu/ie3/simona/main/RunSimona.scala b/src/main/scala/edu/ie3/simona/main/RunSimona.scala index c255990a05..9a4e26fdb2 100644 --- a/src/main/scala/edu/ie3/simona/main/RunSimona.scala +++ b/src/main/scala/edu/ie3/simona/main/RunSimona.scala @@ -57,7 +57,7 @@ trait RunSimona[T <: SimonaSetup] extends LazyLogging { private def printGoodbye( successful: Boolean, - outputPath: String = "", + logOutputDir: Path, ): Unit = { val myWords = Array( "\"Vielleicht ist heute ein besonders guter Tag zum Sterben.\" - Worf (in Star Trek: Der erste Kontakt)", @@ -78,7 +78,7 @@ trait RunSimona[T <: SimonaSetup] extends LazyLogging { // to ensure that the link to the log is printed last Thread.sleep(1000) - val path = Path.of(outputPath).resolve("simona.log").toUri + val path = logOutputDir.resolve("simona.log").toUri logger.error( s"Simulation stopped due to the occurrence of an error! The full log can be found here: $path" diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala b/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala index 535b140798..835a6f2599 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala @@ -220,7 +220,7 @@ trait SetupHelper extends LazyLogging { val modelsToWrite = SetupHelper.allResultEntitiesToWrite(simonaConfig.simona.output) - val resultFileHierarchy = ResultFileHierarchy( + ResultFileHierarchy( simonaConfig.simona.output.base.dir, simonaConfig.simona.simulationName, ResultEntityPathConfig( @@ -230,15 +230,10 @@ trait SetupHelper extends LazyLogging { simonaConfig.simona.simulationName, ), ), + config = Some(config), addTimeStampToOutputDir = simonaConfig.simona.output.base.addTimestampToOutputDir, - createDirs = createDirs, ) - - // copy config data to output directory - ResultFileHierarchy.prepareDirectories(config, resultFileHierarchy) - - resultFileHierarchy } } diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala index 9c6effa859..4a03b2b905 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaSetup.scala @@ -21,6 +21,8 @@ import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext import org.apache.pekko.actor.{ActorRef => ClassicRef} +import java.nio.file.Path + /** Trait that can be used to setup a customized simona simulation by providing * implementations for all setup information required by a * [[edu.ie3.simona.sim.SimonaSim]]. Most of the time, using or extending @@ -40,7 +42,7 @@ trait SimonaSetup { /** Directory of the log output. */ - def logOutputDir: String + def logOutputDir: Path /** Creates the runtime event listener * diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala index 65d47863cf..55848d2ced 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -50,6 +50,7 @@ import org.apache.pekko.actor.typed.scaladsl.adapter.{ } import org.apache.pekko.actor.{ActorRef => ClassicRef} +import java.nio.file.Path import java.util.UUID import java.util.concurrent.LinkedBlockingQueue import scala.jdk.CollectionConverters._ @@ -68,7 +69,7 @@ class SimonaStandaloneSetup( override val args: Array[String], ) extends SimonaSetup { - override def logOutputDir: String = resultFileHierarchy.logOutputDir + override def logOutputDir: Path = resultFileHierarchy.logOutputDir override def gridAgents( context: ActorContext[_], diff --git a/src/main/scala/edu/ie3/simona/util/ResultFileHierarchy.scala b/src/main/scala/edu/ie3/simona/util/ResultFileHierarchy.scala index 171c5835a2..aceab43822 100644 --- a/src/main/scala/edu/ie3/simona/util/ResultFileHierarchy.scala +++ b/src/main/scala/edu/ie3/simona/util/ResultFileHierarchy.scala @@ -7,7 +7,7 @@ package edu.ie3.simona.util import java.io.{BufferedWriter, File, FileWriter} -import java.nio.file.{Files, Paths} +import java.nio.file.{Files, Path, Paths} import java.text.SimpleDateFormat import com.typesafe.config.{ConfigRenderOptions, Config => TypesafeConfig} import com.typesafe.scalalogging.LazyLogging @@ -20,130 +20,135 @@ import edu.ie3.simona.exceptions.FileHierarchyException import edu.ie3.simona.io.result.ResultSinkType import edu.ie3.simona.io.result.ResultSinkType.Csv import edu.ie3.simona.logging.logback.LogbackConfiguration -import edu.ie3.simona.util.ResultFileHierarchy.ResultEntityPathConfig import edu.ie3.util.io.FileIOUtils import org.apache.commons.io.FilenameUtils._ import scala.jdk.OptionConverters.RichOptional -/** Represents the output directory where the results will be materialized. If - * new directories are added please remember to add them to the dirsToBeCreated - * Vector if they should be created. Otherwise they will not be created! - * - * @version 0.1 - * @since 12.01.20 +/** Represents the output directory where the results will be materialized. */ -final case class ResultFileHierarchy( - private val outputDir: String, - private val simulationName: String, - private val resultEntityPathConfig: ResultEntityPathConfig, - private val configureLogger: String => Unit = LogbackConfiguration.default, - private val addTimeStampToOutputDir: Boolean = true, - private val createDirs: Boolean = false, -) extends LazyLogging { +final case class ResultFileHierarchy private ( + runOutputDir: Path, + rawOutputDataFilePaths: Map[Class[_ <: ResultEntity], Path], + configOutputDir: Path, + logOutputDir: Path, + tmpDir: Path, + resultSinkType: ResultSinkType, + resultEntitiesToConsider: Set[Class[_ <: ResultEntity]], +) - private val fileSeparator: String = File.separator - - val runStartTimeUTC: String = - new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(new java.util.Date()) - - val baseOutputDir: String = buildBaseOutputDir - - val runOutputDir: String = buildRunOutputDir +object ResultFileHierarchy extends LazyLogging { - val configOutputDir: String = - runOutputDir.concat(fileSeparator).concat("configs") + /** Creates the [[ResultFileHierarchy]] and relevant directories + */ + def apply( + outputDir: String, + simulationName: String, + resultEntityPathConfig: ResultEntityPathConfig, + configureLogger: (Path, String) => Unit = LogbackConfiguration.default, + config: Option[TypesafeConfig] = None, + addTimeStampToOutputDir: Boolean = true, + ): ResultFileHierarchy = { + + val runStartTimeUTC = Option.when(addTimeStampToOutputDir)( + new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(new java.util.Date()) + ) - val rawOutputDataDir: String = - runOutputDir.concat(fileSeparator).concat("rawOutputData") + val baseOutputDir = buildBaseOutputDir(outputDir) - val logOutputDir: String = - runOutputDir.concat(fileSeparator).concat("log") + val runOutputDir = buildRunOutputDir( + baseOutputDir, + simulationName, + runStartTimeUTC, + ) - val resultSinkType: ResultSinkType = resultEntityPathConfig.resultSinkType + val configOutputDir = runOutputDir.resolve("configs") + val rawOutputDataDir = runOutputDir.resolve("rawOutputData") + val logOutputDir = runOutputDir.resolve("log") + val tmpDir = runOutputDir.resolve("tmp") - val resultEntitiesToConsider: Set[Class[_ <: ResultEntity]] = - resultEntityPathConfig.resultEntitiesToConsider + val resultSinkType: ResultSinkType = resultEntityPathConfig.resultSinkType - val rawOutputDataFilePaths: Map[Class[_ <: ResultEntity], String] = { - resultSinkType match { - case csv: Csv => - resultEntityPathConfig.resultEntitiesToConsider - .map(resultEntityClass => - ( - resultEntityClass, - ResultFileHierarchy.buildRawOutputFilePath( + val rawOutputDataFilePaths: Map[Class[_ <: ResultEntity], Path] = { + resultSinkType match { + case csv: Csv => + resultEntityPathConfig.resultEntitiesToConsider + .map(resultEntityClass => + ( resultEntityClass, - csv, - rawOutputDataDir, - fileSeparator, - ), + ResultFileHierarchy.buildRawOutputFilePath( + resultEntityClass, + csv, + rawOutputDataDir, + ), + ) ) - ) - .toMap - case _ => - Map.empty + .toMap + case _ => + Map.empty + } } - } - - val graphOutputDir: String = - runOutputDir.concat(fileSeparator).concat("graphs") - val kpiOutputDir: String = runOutputDir.concat(fileSeparator).concat("kpi") - - val tmpDir: String = runOutputDir.concat(fileSeparator).concat("tmp") + val dirsToBeCreated = Seq( + baseOutputDir, + runOutputDir, + configOutputDir, + rawOutputDataDir, + logOutputDir, + tmpDir, + ) - private val dirsToBeCreated: Vector[String] = Vector( - baseOutputDir, - runOutputDir, - configOutputDir, - rawOutputDataDir, - graphOutputDir, - kpiOutputDir, - tmpDir, - logOutputDir, - ) + val resultFileHierarchy = ResultFileHierarchy( + runOutputDir, + rawOutputDataFilePaths, + configOutputDir, + logOutputDir, + tmpDir, + resultSinkType, + resultEntityPathConfig.resultEntitiesToConsider, + ) + prepareDirectories( + baseOutputDir, + dirsToBeCreated, + resultFileHierarchy, + config, + ) - // needs to be the latest call because otherwise the values are null as they are not initialized yet - if (createDirs) - ResultFileHierarchy.createOutputDirectories(this) + // needs to be done after dir creation + configureLogger(logOutputDir, "INFO") // todo - // needs to be done after dir creation - configureLogger(logOutputDir) + resultFileHierarchy + } - /** Builds the base output directory string + /** Builds the base output directory * * @return - * the filepath string to the directory + * the filepath of the directory */ - private def buildBaseOutputDir: String = { - + private def buildBaseOutputDir( + outputDir: String + ): Path = { // clean file string if necessary val cleanedBaseOutputDir = { val normalizedOutputDir = normalize(outputDir) - (fileSeparator + "$").r.replaceAllIn(normalizedOutputDir, "") + (File.separator + "$").r.replaceAllIn(normalizedOutputDir, "") } - // create base output dir if non-existent - Paths.get(cleanedBaseOutputDir).toFile.getAbsolutePath + Paths.get(cleanedBaseOutputDir) } - /** Builds the output directory string for this specific run - * - * @return + /** Builds the output directory for this specific run */ - private def buildRunOutputDir: String = { + private def buildRunOutputDir( + baseOutputDir: Path, + simulationName: String, + runStartTimeUTC: Option[String], + ): Path = { val optionalSuffix = - if (addTimeStampToOutputDir) s"_$runStartTimeUTC" else "" - baseOutputDir - .concat(fileSeparator) - .concat(simulationName) - .concat(optionalSuffix) - } + runStartTimeUTC.map(pattern => s"_$pattern").getOrElse("") -} - -object ResultFileHierarchy extends LazyLogging { + baseOutputDir.resolve(s"$simulationName$optionalSuffix") + } /** @param resultEntitiesToConsider * [[ResultEntity]] s to consider to be written out @@ -161,8 +166,6 @@ object ResultFileHierarchy extends LazyLogging { * the csv sink type parameters * @param rawOutputDataDir * the directory of the raw output data - * @param fileSeparator - * the file separator to be used * @return * an absolute file path as string for the provided model class incl. file * name + extension @@ -170,9 +173,8 @@ object ResultFileHierarchy extends LazyLogging { private def buildRawOutputFilePath( modelClass: Class[_ <: ResultEntity], csvSink: Csv, - rawOutputDataDir: String, - fileSeparator: String, - ): String = { + rawOutputDataDir: Path, + ): Path = { val fileEnding = if (csvSink.fileFormat.startsWith(".")) csvSink.fileFormat @@ -192,64 +194,72 @@ object ResultFileHierarchy extends LazyLogging { ) } - rawOutputDataDir - .concat(fileSeparator) - .concat(filename.toString) - .concat(fileEnding) + rawOutputDataDir.resolve(s"${filename.toString}$fileEnding") } /** Prepares the output directories to be ready to hold the output data. This * includes creating the run directory with all subsequent directories as * well as copying the simulation configuration to the output dir * - * @param config + * @param baseOutputDir + * The base output directory + * @param dirsToBeCreated + * The directories that need to be created + * @param maybeConfig * the config of the current simulation * @param resultFileHierarchy * the output file hierarchy of the current simulation */ - def prepareDirectories( - config: TypesafeConfig, + private def prepareDirectories( + baseOutputDir: Path, + dirsToBeCreated: Seq[Path], resultFileHierarchy: ResultFileHierarchy, + maybeConfig: Option[TypesafeConfig], ): Unit = { // create output directories if they are not present yet if (!runOutputDirExists(resultFileHierarchy)) - ResultFileHierarchy.createOutputDirectories(resultFileHierarchy) + createOutputDirectories( + baseOutputDir, + dirsToBeCreated, + resultFileHierarchy, + ) - logger.info( - "Processing configs for simulation: {}.", - config.getString("simona.simulationName"), - ) + maybeConfig.foreach { config => + logger.info( + "Processing configs for simulation: {}.", + config.getString("simona.simulationName"), + ) - val outFile = - Paths.get(resultFileHierarchy.configOutputDir, "vn_simona.conf").toFile - val bw = new BufferedWriter(new FileWriter(outFile)) - bw.write( - config - .root() - .render( - ConfigRenderOptions - .defaults() - .setOriginComments(false) - .setComments(false) - ) - ) - bw.close() - logger.info("Config '{}' written to '{}'.", outFile.getPath, outFile) + val outFile = + resultFileHierarchy.configOutputDir.resolve("vn_simona.conf").toFile + val bw = new BufferedWriter(new FileWriter(outFile)) + bw.write( + config + .root() + .render( + ConfigRenderOptions + .defaults() + .setOriginComments(false) + .setComments(false) + ) + ) + bw.close() + logger.info("Config '{}' written to '{}'.", outFile.getPath, outFile) + } } /** Checks if the directory of the current run already exists * - * @param outputFileHierarchy + * @param fileHierarchy * the [[ResultFileHierarchy]] that holds information on the run directory * path * @return * true if it exists, false if not */ - def runOutputDirExists(outputFileHierarchy: ResultFileHierarchy): Boolean = { - new File(outputFileHierarchy.runOutputDir).exists() && new File( - outputFileHierarchy.runOutputDir - ).listFiles().length > 0 + def runOutputDirExists(fileHierarchy: ResultFileHierarchy): Boolean = { + val outputDir = fileHierarchy.runOutputDir.toFile + outputDir.exists() && outputDir.listFiles().length > 0 } /** Creates all output directories of the provided [[ResultFileHierarchy]] @@ -257,11 +267,12 @@ object ResultFileHierarchy extends LazyLogging { * @param outputFileHierarchy * the [[ResultFileHierarchy]] the directories should be created for */ - def createOutputDirectories( - outputFileHierarchy: ResultFileHierarchy + private def createOutputDirectories( + baseOutputDir: Path, + dirsToBeCreated: Seq[Path], + outputFileHierarchy: ResultFileHierarchy, ): Unit = { // try to create base output dir - val baseOutputDir = Paths.get(outputFileHierarchy.baseOutputDir) // / check for existence of the provided baseOutputDir, if not create it if (Files.exists(baseOutputDir) && baseOutputDir.toFile.isFile) { throw new FileHierarchyException( @@ -270,17 +281,17 @@ object ResultFileHierarchy extends LazyLogging { } // check if there is data inside the runOutputDir taking into account the provided FileHandling - val runOutputDir = new File(outputFileHierarchy.runOutputDir) + val runOutputDir = outputFileHierarchy.runOutputDir.toFile if (runOutputDir.exists() && runOutputDir.listFiles().length > 0) { // files inside the runOutputDir -> fail throw new FileHierarchyException( - s"The runOutputDir ${outputFileHierarchy.runOutputDir} already exists and is NOT empty! " + + s"The runOutputDir ${outputFileHierarchy.runOutputDir.toString} already exists and is NOT empty! " + s"Please either delete or empty the directory." ) } // create the output directories for the specific run - outputFileHierarchy.dirsToBeCreated.foreach(createDir) + dirsToBeCreated.foreach(createDir) } @@ -289,8 +300,8 @@ object ResultFileHierarchy extends LazyLogging { * @param dir * the full path where the directory should be created (incl. it's name) */ - private def createDir(dir: String): Unit = { - val dirFile = new File(dir) + private def createDir(dir: Path): Unit = { + val dirFile = dir.toFile if (!dirFile.mkdirs() && !dirFile.exists()) throw new FileHierarchyException( "The output directory path " + dir diff --git a/src/test/scala/edu/ie3/simona/event/listener/ResultEventListenerSpec.scala b/src/test/scala/edu/ie3/simona/event/listener/ResultEventListenerSpec.scala index f110feca8e..7498e270dc 100644 --- a/src/test/scala/edu/ie3/simona/event/listener/ResultEventListenerSpec.scala +++ b/src/test/scala/edu/ie3/simona/event/listener/ResultEventListenerSpec.scala @@ -84,7 +84,6 @@ class ResultEventListenerSpec classes, resultSinkType, ), - createDirs = true, ) } @@ -122,14 +121,14 @@ class ResultEventListenerSpec ) // after the creation of the listener, it is expected that a corresponding raw result data file is present - val outputFile = new File( - fileHierarchy.rawOutputDataFilePaths.getOrElse( + val outputFile = fileHierarchy.rawOutputDataFilePaths + .getOrElse( classOf[PvResult], fail( s"Cannot get filepath for raw result file of class '${classOf[PvResult].getSimpleName}' from outputFileHierarchy!'" ), ) - ) + .toFile assert(outputFile.exists) assert(outputFile.isFile) @@ -162,14 +161,14 @@ class ResultEventListenerSpec listenerRef ! ParticipantResultEvent(dummyPvResult) - val outputFile = new File( - specificOutputFileHierarchy.rawOutputDataFilePaths.getOrElse( + val outputFile = specificOutputFileHierarchy.rawOutputDataFilePaths + .getOrElse( classOf[PvResult], fail( s"Cannot get filepath for raw result file of class '${classOf[PvResult].getSimpleName}' from outputFileHierarchy!'" ), ) - ) + .toFile // wait until output file exists (headers are flushed out immediately): awaitCond( @@ -219,39 +218,37 @@ class ResultEventListenerSpec ) val outputFiles = Map( - dummyNodeResultString -> new File( - specificOutputFileHierarchy.rawOutputDataFilePaths.getOrElse( + dummyNodeResultString -> specificOutputFileHierarchy.rawOutputDataFilePaths + .getOrElse( classOf[NodeResult], fail( s"Cannot get filepath for raw result file of class '${classOf[NodeResult].getSimpleName}' from outputFileHierarchy!'" ), - ) - ), - dummySwitchResultString -> new File( + ), + dummySwitchResultString -> specificOutputFileHierarchy.rawOutputDataFilePaths.getOrElse( classOf[SwitchResult], fail( s"Cannot get filepath for raw result file of class '${classOf[SwitchResult].getSimpleName}' from outputFileHierarchy!'" ), - ) - ), - dummyLineResultDataString -> new File( - specificOutputFileHierarchy.rawOutputDataFilePaths.getOrElse( + ), + dummyLineResultDataString -> specificOutputFileHierarchy.rawOutputDataFilePaths + .getOrElse( classOf[LineResult], fail( s"Cannot get filepath for raw result file of class '${classOf[LineResult].getSimpleName}' from outputFileHierarchy!'" ), - ) - ), - dummyTrafo2wResultDataString -> new File( - specificOutputFileHierarchy.rawOutputDataFilePaths.getOrElse( + ), + dummyTrafo2wResultDataString -> specificOutputFileHierarchy.rawOutputDataFilePaths + .getOrElse( classOf[Transformer2WResult], fail( s"Cannot get filepath for raw result file of class '${classOf[Transformer2WResult].getSimpleName}' from outputFileHierarchy!'" ), - ) - ), - ) + ), + ).map { case (dummyString, path) => + (dummyString, path.toFile) + } // wait until all output files exist (headers are flushed out immediately): awaitCond( @@ -308,14 +305,15 @@ class ResultEventListenerSpec ) ) - val outputFile = new File( - fileHierarchy.rawOutputDataFilePaths.getOrElse( + val outputFile = fileHierarchy.rawOutputDataFilePaths + .getOrElse( classOf[Transformer3WResult], fail( s"Cannot get filepath for raw result file of class '${classOf[Transformer3WResult].getSimpleName}' from outputFileHierarchy!'" ), ) - ) + .toFile + /* The result file is created at start up and only contains a head line. */ awaitCond( outputFile.exists(), @@ -386,12 +384,14 @@ class ResultEventListenerSpec val outputFile = new File( ".gz$".r.replaceAllIn( - specificOutputFileHierarchy.rawOutputDataFilePaths.getOrElse( - classOf[PvResult], - fail( - s"Cannot get filepath for raw result file of class '${classOf[PvResult].getSimpleName}' from outputFileHierarchy!'" - ), - ), + specificOutputFileHierarchy.rawOutputDataFilePaths + .getOrElse( + classOf[PvResult], + fail( + s"Cannot get filepath for raw result file of class '${classOf[PvResult].getSimpleName}' from outputFileHierarchy!'" + ), + ) + .toString, "", ) ) @@ -413,26 +413,29 @@ class ResultEventListenerSpec // wait until file exists awaitCond( - new File( - specificOutputFileHierarchy.rawOutputDataFilePaths.getOrElse( + specificOutputFileHierarchy.rawOutputDataFilePaths + .getOrElse( classOf[PvResult], fail( s"Cannot get filepath for raw result file of class '${classOf[PvResult].getSimpleName}' from outputFileHierarchy!'" ), ) - ).exists, + .toFile + .exists, timeoutDuration, ) val resultFileSource = Source.fromInputStream( new GZIPInputStream( new FileInputStream( - specificOutputFileHierarchy.rawOutputDataFilePaths.getOrElse( - classOf[PvResult], - fail( - s"Cannot get filepath for raw result file of class '${classOf[PvResult].getSimpleName}' from outputFileHierarchy!'" - ), - ) + specificOutputFileHierarchy.rawOutputDataFilePaths + .getOrElse( + classOf[PvResult], + fail( + s"Cannot get filepath for raw result file of class '${classOf[PvResult].getSimpleName}' from outputFileHierarchy!'" + ), + ) + .toFile ) ) ) diff --git a/src/test/scala/edu/ie3/simona/integration/RunSimonaStandaloneIT.scala b/src/test/scala/edu/ie3/simona/integration/RunSimonaStandaloneIT.scala index 4e53f0e078..083b9681e3 100644 --- a/src/test/scala/edu/ie3/simona/integration/RunSimonaStandaloneIT.scala +++ b/src/test/scala/edu/ie3/simona/integration/RunSimonaStandaloneIT.scala @@ -90,7 +90,7 @@ class RunSimonaStandaloneIT /* check the results */ // check configs - val configOutputDir = new File(resultFileHierarchy.configOutputDir) + val configOutputDir = resultFileHierarchy.configOutputDir.toFile configOutputDir.isDirectory shouldBe true configOutputDir.listFiles.toVector.size shouldBe 1 @@ -118,10 +118,12 @@ class RunSimonaStandaloneIT entityClass: Class[_ <: ResultEntity], ): BufferedSource = { Source.fromFile( - resultFileHierarchy.rawOutputDataFilePaths.getOrElse( - entityClass, - fail(s"Unable to get output path for result entity: $entityClass"), - ) + resultFileHierarchy.rawOutputDataFilePaths + .getOrElse( + entityClass, + fail(s"Unable to get output path for result entity: $entityClass"), + ) + .toFile ) } diff --git a/src/test/scala/edu/ie3/simona/io/file/ResultFileHierarchySpec.scala b/src/test/scala/edu/ie3/simona/io/file/ResultFileHierarchySpec.scala index 3b89d418f2..c273634fbe 100644 --- a/src/test/scala/edu/ie3/simona/io/file/ResultFileHierarchySpec.scala +++ b/src/test/scala/edu/ie3/simona/io/file/ResultFileHierarchySpec.scala @@ -51,37 +51,15 @@ class ResultFileHierarchySpec ), ) - val runOutputDirWithDate = - "vn_simona_".concat(validOutputFileHierarchy.runStartTimeUTC) - - relativizePath( - validOutputFileHierarchy.baseOutputDir - ).toString shouldBe baseOutputDir - relativizePath( - validOutputFileHierarchy.runOutputDir - ).toString shouldBe baseOutputDir + fileSeparator + runOutputDirWithDate - relativizePath( - validOutputFileHierarchy.tmpDir - ).toString shouldBe baseOutputDir + fileSeparator + runOutputDirWithDate + fileSeparator + "tmp" - relativizePath( - validOutputFileHierarchy.configOutputDir - ).toString shouldBe baseOutputDir + fileSeparator + runOutputDirWithDate + fileSeparator + "configs" - relativizePath( - validOutputFileHierarchy.rawOutputDataDir - ).toString shouldBe baseOutputDir + fileSeparator + runOutputDirWithDate + fileSeparator + "rawOutputData" - relativizePath( - validOutputFileHierarchy.graphOutputDir - ).toString shouldBe baseOutputDir + fileSeparator + runOutputDirWithDate + fileSeparator + "graphs" - relativizePath( - validOutputFileHierarchy.kpiOutputDir - ).toString shouldBe baseOutputDir + fileSeparator + runOutputDirWithDate + fileSeparator + "kpi" - - relativizePath( - validOutputFileHierarchy.rawOutputDataFilePaths(classOf[PvResult]) - ).toString shouldBe baseOutputDir + fileSeparator + runOutputDirWithDate + fileSeparator + "rawOutputData" + fileSeparator + "pref_pv_res_suff.csv" + validOutputFileHierarchy.tmpDir.toString shouldBe validOutputFileHierarchy.runOutputDir.toString + fileSeparator + "tmp" + validOutputFileHierarchy.configOutputDir.toString shouldBe validOutputFileHierarchy.runOutputDir.toString + fileSeparator + "configs" + validOutputFileHierarchy.logOutputDir.toString shouldBe validOutputFileHierarchy.runOutputDir.toString + fileSeparator + "log" + + validOutputFileHierarchy + .rawOutputDataFilePaths(classOf[PvResult]) + .toString shouldBe validOutputFileHierarchy.runOutputDir.toString + fileSeparator + "rawOutputData" + fileSeparator + "pref_pv_res_suff.csv" } - "not write directories automatically on instantiation" in {} // todo "write directories automatically on instantiation when requested so" in { // delete file if they exist @@ -97,7 +75,6 @@ class ResultFileHierarchySpec Set(classOf[PvResult]), ResultSinkType.Csv("csv", "pref", "suff"), ), - createDirs = true, ) // check for existence of run output dir @@ -106,26 +83,13 @@ class ResultFileHierarchySpec ) shouldBe true // check for existence of other folders - assert( - Files.exists(new File(validOutputFileHierarchy.baseOutputDir).toPath) - ) - assert(Files.exists(new File(validOutputFileHierarchy.tmpDir).toPath)) - assert( - Files.exists(new File(validOutputFileHierarchy.configOutputDir).toPath) - ) - assert( - Files.exists(new File(validOutputFileHierarchy.rawOutputDataDir).toPath) - ) - assert( - Files.exists(new File(validOutputFileHierarchy.graphOutputDir).toPath) - ) - assert( - Files.exists(new File(validOutputFileHierarchy.kpiOutputDir).toPath) - ) + assert(Files.exists(validOutputFileHierarchy.configOutputDir)) + assert(Files.exists(validOutputFileHierarchy.logOutputDir)) + assert(Files.exists(validOutputFileHierarchy.tmpDir)) // check if tmp directory can be deleted by output file hierarchy ResultFileHierarchy.deleteTmpDir(validOutputFileHierarchy) - assert(!Files.exists(new File(validOutputFileHierarchy.tmpDir).toPath)) + assert(!Files.exists(validOutputFileHierarchy.tmpDir)) } @@ -133,10 +97,8 @@ class ResultFileHierarchySpec } - private def relativizePath(fullPath: String): Path = { - new File(new File("").getAbsolutePath).toPath - .relativize(new File(fullPath).toPath) - } + private def relativizePath(fullPath: Path): Path = + new File(new File("").getAbsolutePath).toPath.relativize(fullPath) // todo output model path config compression should always be disabled -> test for this diff --git a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala index 63be126c0f..9d1140be17 100644 --- a/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/SimonaSimSpec.scala @@ -34,6 +34,7 @@ import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors} import org.apache.pekko.actor.typed.{ActorRef, Behavior} import org.apache.pekko.actor.{ActorRef => ClassicRef} +import java.nio.file.Path import java.util.UUID class SimonaSimSpec extends ScalaTestWithActorTestKit with UnitSpec { @@ -398,7 +399,7 @@ object SimonaSimSpec { override val args: Array[String] = Array.empty[String] - override def logOutputDir: String = throw new NotImplementedError() + override def logOutputDir: Path = throw new NotImplementedError() override def runtimeEventListener( context: ActorContext[_] diff --git a/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala b/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala index 7fa3ab3469..0428ed765b 100644 --- a/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala +++ b/src/test/scala/edu/ie3/simona/sim/setup/SimonaSetupSpec.scala @@ -26,13 +26,14 @@ import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.ActorContext import org.apache.pekko.actor.{ActorRef => ClassicRef} +import java.nio.file.Path import java.util.UUID class SimonaSetupSpec extends UnitSpec with SimonaSetup with SubGridGateMokka { override val args: Array[String] = Array.empty[String] - override def logOutputDir: String = throw new NotImplementedError() + override def logOutputDir: Path = throw new NotImplementedError() override def runtimeEventListener( context: ActorContext[_] From adbb3f557b92ecf549e297b9ba8120fe6116d5ff Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Tue, 19 Nov 2024 12:02:57 +0100 Subject: [PATCH 04/14] Validation of log level config Actually using the configuration Signed-off-by: Sebastian Peter --- .../ie3/simona/config/ConfigFailFast.scala | 52 ++++++++++++++----- .../logback/LogbackConfiguration.scala | 2 +- .../ie3/simona/sim/setup/SetupHelper.scala | 3 ++ .../ie3/simona/util/ResultFileHierarchy.scala | 4 +- .../simona/config/ConfigFailFastSpec.scala | 22 ++++++++ 5 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala index 0eaa7e30ba..c3b16d2ca7 100644 --- a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala +++ b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala @@ -47,7 +47,7 @@ import scala.util.{Failure, Success, Try} * missing config parameters where at least one is needed or check for invalid * or contradicting parameters */ -case object ConfigFailFast extends LazyLogging { +object ConfigFailFast extends LazyLogging { def check(typeSafeConfig: Config, simonaConfig: SimonaConfig): Unit = { check(typeSafeConfig) @@ -139,18 +139,7 @@ case object ConfigFailFast extends LazyLogging { /* Check if the provided combination of data source and parameters are valid */ checkWeatherDataSource(simonaConfig.simona.input.weather.datasource) - /* check if at least one data sink is defined */ - checkDataSink(simonaConfig.simona.output.sink) - - /* Check all output configurations for participant models */ - checkParticipantsOutputConfig( - simonaConfig.simona.output.participant - ) - - /* Check all output configurations for thermal models */ - checkThermalOutputConfig( - simonaConfig.simona.output.thermal - ) + checkOutputConfig(simonaConfig.simona.output) /* Check power flow resolution configuration */ checkPowerFlowResolutionConfiguration(simonaConfig.simona.powerflow) @@ -162,6 +151,28 @@ case object ConfigFailFast extends LazyLogging { checkStoragesConfig(simonaConfig.simona.runtime.participant.storage) } + /** Checks for valid output configuration + * + * @param outputConfig + * the output configuration that should be checked + */ + private def checkOutputConfig( + outputConfig: SimonaConfig.Simona.Output + ): Unit = { + + /* check if at least one data sink is defined */ + checkDataSink(outputConfig.sink) + + /* Check all output configurations for participant models */ + checkParticipantsOutputConfig(outputConfig.participant) + + /* Check all output configurations for thermal models */ + checkThermalOutputConfig(outputConfig.thermal) + + /* Check output configurations for log */ + checkLogOutputConfig(outputConfig.log) + } + /** Checks for valid sink configuration * * @param sink @@ -698,6 +709,21 @@ case object ConfigFailFast extends LazyLogging { checkIndividualOutputConfigs(subConfig.individualConfigs) } + /** Check the config subtree for log output parameterization + * + * @param subConfig + * Output sub config tree for log + */ + private def checkLogOutputConfig( + subConfig: SimonaConfig.Simona.Output.Log + ): Unit = { + val validLogLevels = Seq("TRACE", "DEBUG", "INFO", "WARN", "ERROR") + if (!validLogLevels.contains(subConfig.level)) + throw new InvalidConfigParameterException( + s"Invalid log level \"${subConfig.level}\". Valid log levels: ${validLogLevels.mkString(", ")}" + ) + } + /** Checks resolution of power flow calculation * * @param powerFlow diff --git a/src/main/scala/edu/ie3/simona/logging/logback/LogbackConfiguration.scala b/src/main/scala/edu/ie3/simona/logging/logback/LogbackConfiguration.scala index 31c23e4568..b28499074f 100644 --- a/src/main/scala/edu/ie3/simona/logging/logback/LogbackConfiguration.scala +++ b/src/main/scala/edu/ie3/simona/logging/logback/LogbackConfiguration.scala @@ -18,7 +18,7 @@ import java.nio.file.Path object LogbackConfiguration extends LazyLogging { - def default(logPath: Path, logLevel: String): Unit = { + def default(logLevel: String)(logPath: Path): Unit = { LoggerFactory.getILoggerFactory match { case loggerContext: LoggerContext => val rootLogger = loggerContext.getLogger("root") diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala b/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala index 1b2d2df7e7..9b06f604da 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala @@ -20,6 +20,7 @@ import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.exceptions.InitializationException import edu.ie3.simona.exceptions.agent.GridAgentInitializationException import edu.ie3.simona.io.result.ResultSinkType +import edu.ie3.simona.logging.logback.LogbackConfiguration import edu.ie3.simona.model.grid.RefSystem import edu.ie3.simona.util.ConfigUtil.{GridOutputConfigUtil, OutputConfigUtil} import edu.ie3.simona.util.ResultFileHierarchy.ResultEntityPathConfig @@ -226,6 +227,8 @@ trait SetupHelper extends LazyLogging { simonaConfig.simona.simulationName, ), ), + configureLogger = + LogbackConfiguration.default(simonaConfig.simona.output.log.level), config = Some(config), addTimeStampToOutputDir = simonaConfig.simona.output.base.addTimestampToOutputDir, diff --git a/src/main/scala/edu/ie3/simona/util/ResultFileHierarchy.scala b/src/main/scala/edu/ie3/simona/util/ResultFileHierarchy.scala index 2020d74743..bb371308a6 100644 --- a/src/main/scala/edu/ie3/simona/util/ResultFileHierarchy.scala +++ b/src/main/scala/edu/ie3/simona/util/ResultFileHierarchy.scala @@ -45,7 +45,7 @@ object ResultFileHierarchy extends LazyLogging { outputDir: String, simulationName: String, resultEntityPathConfig: ResultEntityPathConfig, - configureLogger: (Path, String) => Unit = LogbackConfiguration.default, + configureLogger: Path => Unit = LogbackConfiguration.default("INFO"), config: Option[TypesafeConfig] = None, addTimeStampToOutputDir: Boolean = true, ): ResultFileHierarchy = { @@ -115,7 +115,7 @@ object ResultFileHierarchy extends LazyLogging { ) // needs to be done after dir creation - configureLogger(logOutputDir, "INFO") // todo + configureLogger(logOutputDir) resultFileHierarchy } diff --git a/src/test/scala/edu/ie3/simona/config/ConfigFailFastSpec.scala b/src/test/scala/edu/ie3/simona/config/ConfigFailFastSpec.scala index ae7bfae423..f86b0b47c0 100644 --- a/src/test/scala/edu/ie3/simona/config/ConfigFailFastSpec.scala +++ b/src/test/scala/edu/ie3/simona/config/ConfigFailFastSpec.scala @@ -897,6 +897,28 @@ class ConfigFailFastSpec extends UnitSpec with ConfigTestData { } } + "Checking log config" should { + val checkLogOutputConfig = + PrivateMethod[Unit](Symbol("checkLogOutputConfig")) + + "identify an unknown log level" in { + val invalidLogConfig = SimonaConfig.Simona.Output.Log("INVALID") + + intercept[InvalidConfigParameterException] { + ConfigFailFast invokePrivate checkLogOutputConfig(invalidLogConfig) + }.getMessage shouldBe "Invalid log level \"INVALID\". Valid log levels: TRACE, DEBUG, INFO, WARN, ERROR" + } + + "let valid log output configuration pass" in { + val validLogConfig = SimonaConfig.Simona.Output.Log("WARN") + + noException shouldBe thrownBy { + ConfigFailFast invokePrivate checkLogOutputConfig(validLogConfig) + } + } + + } + "Checking grid data sources" should { "identify a faulty csv separator" in { val csvParams = From 5c0750c58e8fd29e4d45b2f3e8590e670fb86060 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Tue, 19 Nov 2024 12:05:32 +0100 Subject: [PATCH 05/14] Also setting the log level in example grid, because most people will look there Signed-off-by: Sebastian Peter --- input/samples/vn_simona/vn_simona.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/input/samples/vn_simona/vn_simona.conf b/input/samples/vn_simona/vn_simona.conf index 3a795e7cb5..b911acf2c1 100644 --- a/input/samples/vn_simona/vn_simona.conf +++ b/input/samples/vn_simona/vn_simona.conf @@ -101,6 +101,8 @@ simona.output.thermal = { ] } +simona.output.log.level = "INFO" + ################################################################## # Runtime Configuration // todo refactor as this naming is misleading and partly unneeded ################################################################## From 28d23371cdb8a7a71e85e73a5227642e9e408ec6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:45:03 +0000 Subject: [PATCH 06/14] Bump org.sonarqube from 5.1.0.4882 to 6.0.0.5145 (#1037) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f611c25268..2b7b87ffe4 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ plugins { id "de.undercouch.download" version "5.6.0" // downloads plugin id "kr.motd.sphinx" version "2.10.1" // documentation generation id "com.github.johnrengelman.shadow" version "8.1.1" // fat jar - id "org.sonarqube" version "5.1.0.4882" // sonarqube + id "org.sonarqube" version "6.0.0.5145" // sonarqube id "org.scoverage" version "8.1" // scala code coverage scoverage id "com.github.maiflai.scalatest" version "0.32" // run scalatest without specific spec task id 'org.hidetake.ssh' version '2.11.2' From 741a2b2b5a7c54b7e59c1a9c71931f1eedb02e33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Nov 2024 07:27:31 +0100 Subject: [PATCH 07/14] Bump tech.units:indriya from 2.2 to 2.2.1 (#1039) Bumps [tech.units:indriya](https://github.com/unitsofmeasurement/indriya) from 2.2 to 2.2.1. - [Release notes](https://github.com/unitsofmeasurement/indriya/releases) - [Commits](https://github.com/unitsofmeasurement/indriya/compare/2.2...2.2.1) --- updated-dependencies: - dependency-name: tech.units:indriya dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2b7b87ffe4..a69e01e63d 100644 --- a/build.gradle +++ b/build.gradle @@ -145,7 +145,7 @@ dependencies { implementation 'org.apache.commons:commons-math3:3.6.1' // apache commons math3 implementation 'org.apache.poi:poi-ooxml:5.3.0' // used for FilenameUtils implementation 'javax.measure:unit-api:2.2' - implementation 'tech.units:indriya:2.2' // quantities + implementation 'tech.units:indriya:2.2.1' // quantities implementation "org.typelevel:squants_${scalaVersion}:1.8.3" implementation 'org.apache.commons:commons-csv:1.12.0' implementation 'org.scalanlp:breeze_2.13:2.1.0' // scientific calculations (http://www.scalanlp.org/) From ec0c36069be58383981fb164cf57435a777ec687 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Fri, 22 Nov 2024 10:01:01 +0100 Subject: [PATCH 08/14] Fix implausible test cases of HpModelSpec --- CHANGELOG.md | 1 + .../model/participant/HpModelSpec.scala | 56 +++++++++---------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c42a0b055..1bd8545b67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -102,6 +102,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refactoring to use zeroKW and zeroKWH in thermal grid unit tests [#1023](https://github.com/ie3-institute/simona/issues/1023) - Refactor `ResultFileHierarchy` [#1031](https://github.com/ie3-institute/simona/issues/1031) - Removing logs in `logs/simona` [#1017](https://github.com/ie3-institute/simona/issues/1017) +- Fix implausible test cases of HpModelSpec [#1042](https://github.com/ie3-institute/simona/issues/1042) ### Fixed - Fix rendering of references in documentation [#505](https://github.com/ie3-institute/simona/issues/505) diff --git a/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala index 81c62e549d..0479798610 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala @@ -55,7 +55,7 @@ class HpModelSpec true, 95, 15.6, - Some(HouseTemperatureUpperBoundaryReached(31711L)), + Some(HouseTemperatureUpperBoundaryReached(31711)), ), ( HpState( @@ -70,7 +70,7 @@ class HpModelSpec true, 95, 16.4, - Some(HouseTemperatureUpperBoundaryReached(30642L)), + Some(HouseTemperatureUpperBoundaryReached(30642)), ), ( HpState( @@ -85,7 +85,7 @@ class HpModelSpec true, 95, 18.0, - Some(HouseTemperatureUpperBoundaryReached(27771L)), + Some(HouseTemperatureUpperBoundaryReached(27771)), ), ( HpState( @@ -100,7 +100,7 @@ class HpModelSpec false, 0, 19.6, - Some(HouseTemperatureLowerBoundaryReached(13200L)), + Some(HouseTemperatureLowerBoundaryReached(13200)), ), ( HpState( @@ -115,7 +115,7 @@ class HpModelSpec false, 0, 20.4, - Some(HouseTemperatureLowerBoundaryReached(15508L)), + Some(HouseTemperatureLowerBoundaryReached(15508)), ), ( HpState( @@ -124,13 +124,13 @@ class HpModelSpec Some(hpData.ambientTemperature), Kilowatts(95d), Kilowatts(80d), - thermalState(Celsius(17)), + thermalState(Celsius(17), Kilowatts(80d)), None, ), - true, - 95, - 15.6, - Some(HouseTemperatureUpperBoundaryReached(31711L)), + false, + 0, + 31.6, + Some(HouseTemperatureLowerBoundaryReached(29867)), ), ( HpState( @@ -139,13 +139,13 @@ class HpModelSpec Some(hpData.ambientTemperature), Kilowatts(95d), Kilowatts(80d), - thermalState(Celsius(18)), + thermalState(Celsius(18), Kilowatts(80d)), None, ), - true, - 95, - 16.4, - Some(HouseTemperatureUpperBoundaryReached(30642L)), + false, + 0, + 32.4, + Some(HouseTemperatureLowerBoundaryReached(30343)), ), ( HpState( @@ -154,13 +154,13 @@ class HpModelSpec Some(hpData.ambientTemperature), Kilowatts(95d), Kilowatts(80d), - thermalState(Celsius(20)), + thermalState(Celsius(20), Kilowatts(80d)), None, ), - true, - 95, - 18.0, - Some(HouseTemperatureUpperBoundaryReached(27771L)), + false, + 0, + 34.0, + Some(HouseTemperatureLowerBoundaryReached(31200)), ), ( HpState( @@ -169,13 +169,13 @@ class HpModelSpec Some(hpData.ambientTemperature), Kilowatts(95d), Kilowatts(80d), - thermalState(Celsius(22)), + thermalState(Celsius(22), Kilowatts(80d)), None, ), - true, - 95, - 19.6, - Some(HouseTemperatureUpperBoundaryReached(23200L)), + false, + 0, + 35.6, + Some(HouseTemperatureLowerBoundaryReached(31950)), ), ( HpState( @@ -184,13 +184,13 @@ class HpModelSpec Some(hpData.ambientTemperature), Kilowatts(95d), Kilowatts(80d), - thermalState(Celsius(25)), + thermalState(Celsius(25), Kilowatts(80d)), None, ), false, 0, - 22.0, - Some(HouseTemperatureLowerBoundaryReached(19200L)), + 38.0, + Some(HouseTemperatureLowerBoundaryReached(32914)), ), ) From b4044496f61b1b6086d6e4c9f0d4c7e5a5dda69c Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Fri, 22 Nov 2024 12:13:14 +0100 Subject: [PATCH 09/14] Unlimited fetch depth Signed-off-by: Sebastian Peter --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 182819f0a1..e09485924c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: - name: Checkout Source uses: actions/checkout@v4 with: - fetch-depth: 1 + fetch-depth: 0 ref: ${{ github.event.pull_request.head.ref || github.ref }} repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} From a728fa211a8f80d0edf52a3cc7012438693e73b3 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Fri, 22 Nov 2024 12:13:37 +0100 Subject: [PATCH 10/14] Test: removing ref and repository Signed-off-by: Sebastian Peter --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e09485924c..6d58167b6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,8 +29,6 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - ref: ${{ github.event.pull_request.head.ref || github.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - name: Setup Java uses: actions/setup-java@v4 From 823d5e0b0d65b28355e821a8f640f5dfdb338669 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Fri, 22 Nov 2024 12:30:56 +0100 Subject: [PATCH 11/14] Adding to changelog Signed-off-by: Sebastian Peter --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c42a0b055..c5c51c6c00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -140,6 +140,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix grammar and spelling in docs and comments [#1022](https://github.com/ie3-institute/simona/issues/1022) - Fix some minor issues and findings from inspections [#1019](https://github.com/ie3-institute/simona/issues/1019) - Fix initialisation freezing on empty primary data [#981](https://github.com/ie3-institute/simona/issues/981) +- Shallow fetch in CI [#1041](https://github.com/ie3-institute/simona/issues/1041) ## [3.0.0] - 2023-08-07 From debae480c15738ce24e34f4f5d72c82e0c96742c Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Fri, 22 Nov 2024 17:01:34 +0100 Subject: [PATCH 12/14] refactoring thermal demand indicators --- .../simona/model/participant/HpModel.scala | 88 ++++++------------- .../simona/model/thermal/ThermalGrid.scala | 62 ++++++------- .../model/participant/HpModelSpec.scala | 2 +- .../model/thermal/ThermalGridTestData.scala | 29 ++++-- .../ThermalGridWithHouseAndStorageSpec.scala | 11 ++- .../ThermalGridWithHouseOnlySpec.scala | 7 +- .../ThermalGridWithStorageOnlySpec.scala | 8 +- 7 files changed, 100 insertions(+), 107 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala b/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala index 3400fa15c8..76e9f24651 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala @@ -12,8 +12,7 @@ import edu.ie3.simona.model.SystemComponent import edu.ie3.simona.model.participant.HpModel.{HpRelevantData, HpState} import edu.ie3.simona.model.participant.control.QControl import edu.ie3.simona.model.thermal.ThermalGrid.{ - ThermalDemandIndicator, - ThermalEnergyDemand, + ThermalDemandWrapper, ThermalGridState, } import edu.ie3.simona.model.thermal.{ThermalGrid, ThermalThreshold} @@ -133,7 +132,7 @@ final case class HpModel( ): (Boolean, Boolean, HpState) = { // Use lastHpState and relevantData to update state of thermalGrid to the current tick - val (demandHouse, demandThermalStorage, currentThermalGridState) = + val (thermalDemandWrapper, currentThermalGridState) = thermalGrid.energyDemandAndUpdatedState( relevantData.currentTick, lastHpState.ambientTemperature.getOrElse( @@ -144,18 +143,17 @@ final case class HpModel( ) // Determining the operation point and limitations at this tick - val (turnOn, canOperate, canBeOutOfOperation, demandIndicator) = + val (turnOn, canOperate, canBeOutOfOperation) = operatesInNextState( lastHpState, currentThermalGridState, relevantData, - demandHouse, - demandThermalStorage, + thermalDemandWrapper, ) // Updating the HpState val updatedState = - calcState(lastHpState, relevantData, turnOn, demandIndicator) + calcState(lastHpState, relevantData, turnOn, thermalDemandWrapper) (canOperate, canBeOutOfOperation, updatedState) } @@ -171,35 +169,30 @@ final case class HpModel( * to current tick updated state of the thermalGrid * @param relevantData * Relevant (external) data - * @param demandHouse - * ThermalEnergyDemand of the house - * @param demandThermalStorage - * ThermalEnergyDemand of the thermal storage + * @param thermalDemands + * ThermalEnergyDemand of the house and the thermal storage * @return * boolean defining if heat pump runs in next time step, if it can be in - * operation and can be out of operation plus the - * [[ThermalDemandIndicator]] of the thermal units + * operation and can be out of operation */ private def operatesInNextState( lastState: HpState, currentThermalGridState: ThermalGridState, relevantData: HpRelevantData, - demandHouse: ThermalEnergyDemand, - demandThermalStorage: ThermalEnergyDemand, - ): (Boolean, Boolean, Boolean, ThermalDemandIndicator) = { + thermalDemands: ThermalDemandWrapper, + ): (Boolean, Boolean, Boolean) = { - val ( - demandIndicator, - noThermalStorageOrThermalStorageIsEmpty, - ) = determineDemandBooleans( + val demandHouse = thermalDemands.houseDemand + val demandThermalStorage = thermalDemands.heatStorageDemand + + val noThermalStorageOrThermalStorageIsEmpty = determineThermalStorageStatus( lastState, currentThermalGridState, - demandHouse, - demandThermalStorage, ) val turnHpOn = - demandIndicator.houseDemand || demandIndicator.heatStorageDemand + (demandHouse.hasRequiredDemand && noThermalStorageOrThermalStorageIsEmpty) || + demandThermalStorage.hasRequiredDemand || (demandThermalStorage.hasAdditionalDemand && lastState.isRunning) val canOperate = demandHouse.hasRequiredDemand || demandHouse.hasAdditionalDemand || @@ -211,7 +204,6 @@ final case class HpModel( turnHpOn, canOperate, canBeOutOfOperation, - demandIndicator, ) } @@ -223,22 +215,14 @@ final case class HpModel( * Current state of the heat pump * @param updatedGridState * The updated state of the [[ThermalGrid]] - * @param demandHouse - * heat demand of the thermal house - * @param demandThermalStorage - * heat demand of the thermal storage * @return - * First boolean is true, if house has heat demand. Second boolean is true, - * if thermalStorage has heat demand. Third boolean is true, if there is no - * thermalStorage, or it's empty. + * boolean which is true, if there is no thermalStorage, or it's empty. */ - private def determineDemandBooleans( + private def determineThermalStorageStatus( lastHpState: HpState, updatedGridState: ThermalGridState, - demandHouse: ThermalEnergyDemand, - demandThermalStorage: ThermalEnergyDemand, - ): (ThermalDemandIndicator, Boolean) = { + ): Boolean = { implicit val tolerance: Energy = KilowattHours(1e-3) val noThermalStorageOrThermalStorageIsEmpty: Boolean = updatedGridState.storageState.isEmpty || updatedGridState.storageState @@ -246,13 +230,7 @@ final case class HpModel( _.storedEnergy =~ zeroKWh ) - val houseDemand = - (demandHouse.hasRequiredDemand && noThermalStorageOrThermalStorageIsEmpty) || (lastHpState.isRunning && demandHouse.hasAdditionalDemand) - val heatStorageDemand = - demandThermalStorage.hasRequiredDemand || (lastHpState.isRunning && demandThermalStorage.hasAdditionalDemand) - - val demandIndicator = ThermalDemandIndicator(houseDemand, heatStorageDemand) - (demandIndicator, noThermalStorageOrThermalStorageIsEmpty) + noThermalStorageOrThermalStorageIsEmpty } /** Calculate state depending on whether heat pump is needed or not. Also @@ -265,9 +243,8 @@ final case class HpModel( * data of heat pump including state of the heat pump * @param isRunning * determines whether the heat pump is running or not - * @param demandIndicator - * determines if the thermal units (house, storage) having some heat demand - * or not + * @param demandWrapper + * holds the thermal demands of the thermal units (house, storage) * @return * next [[HpState]] */ @@ -275,7 +252,7 @@ final case class HpModel( lastState: HpState, relevantData: HpRelevantData, isRunning: Boolean, - demandIndicator: ThermalDemandIndicator, + demandWrapper: ThermalDemandWrapper, ): HpState = { val lastStateStorageQDot = lastState.thermalGridState.storageState .map(_.qDot) @@ -297,7 +274,7 @@ final case class HpModel( relevantData.ambientTemperature, isRunning, newThermalPower, - demandIndicator, + demandWrapper, ) HpState( @@ -364,9 +341,8 @@ final case class HpModel( val turnOn = setPower > (sRated.toActivePower(cosPhiRated) * 0.5) val ( - thermalEnergyDemandHouse, - thermalEnergyDemandStorage, - updatedThermalGridState, + thermalDemandWrapper, + _, ) = thermalGrid.energyDemandAndUpdatedState( data.currentTick, @@ -375,21 +351,11 @@ final case class HpModel( lastState.thermalGridState, ) - val ( - demandIndicator, - _, - ) = determineDemandBooleans( - lastState, - updatedThermalGridState, - thermalEnergyDemandHouse, - thermalEnergyDemandStorage, - ) - val updatedHpState = calcState( lastState, data, turnOn, - demandIndicator, + thermalDemandWrapper, ) ( diff --git a/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala b/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala index 033f84e532..4e7b99ddc1 100644 --- a/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala +++ b/src/main/scala/edu/ie3/simona/model/thermal/ThermalGrid.scala @@ -15,7 +15,7 @@ import edu.ie3.datamodel.models.result.thermal.{ } import edu.ie3.simona.exceptions.agent.InconsistentStateException import edu.ie3.simona.model.thermal.ThermalGrid.{ - ThermalDemandIndicator, + ThermalDemandWrapper, ThermalEnergyDemand, ThermalGridState, } @@ -64,7 +64,7 @@ final case class ThermalGrid( lastAmbientTemperature: Temperature, ambientTemperature: Temperature, state: ThermalGridState, - ): (ThermalEnergyDemand, ThermalEnergyDemand, ThermalGridState) = { + ): (ThermalDemandWrapper, ThermalGridState) = { /* First get the energy demand of the houses but only if inner temperature is below target temperature */ val (houseDemand, updatedHouseState) = @@ -135,13 +135,15 @@ final case class ThermalGrid( } ( - ThermalEnergyDemand( - houseDemand.required, - houseDemand.possible, - ), - ThermalEnergyDemand( - storageDemand.required, - storageDemand.possible, + ThermalDemandWrapper( + ThermalEnergyDemand( + houseDemand.required, + houseDemand.possible, + ), + ThermalEnergyDemand( + storageDemand.required, + storageDemand.possible, + ), ), ThermalGridState(updatedHouseState, updatedStorageState), ) @@ -161,9 +163,8 @@ final case class ThermalGrid( * determines whether the heat pump is running or not * @param qDot * Thermal energy balance - * @param demandIndicator - * determines if the thermal units (house, storage) having some heat demand - * or not + * @param thermalDemands + * holds the thermal demands of the thermal units (house, storage) * @return * The updated state of the grid */ @@ -174,7 +175,7 @@ final case class ThermalGrid( ambientTemperature: Temperature, isRunning: Boolean, qDot: Power, - demandIndicator: ThermalDemandIndicator, + thermalDemands: ThermalDemandWrapper, ): (ThermalGridState, Option[ThermalThreshold]) = if (qDot > zeroKW) handleInfeed( tick, @@ -183,7 +184,7 @@ final case class ThermalGrid( state, isRunning, qDot, - demandIndicator, + thermalDemands, ) else handleConsumption( @@ -209,9 +210,8 @@ final case class ThermalGrid( * determines whether the heat pump is running or not * @param qDot * Infeed to the grid - * @param demandIndicator - * determines if the thermal units (house, storage) having some heat demand - * or not + * @param thermalDemands + * holds the thermal demands of the thermal units (house, storage) * @return * Updated thermal grid state */ @@ -222,7 +222,7 @@ final case class ThermalGrid( state: ThermalGridState, isRunning: Boolean, qDot: Power, - demandIndicator: ThermalDemandIndicator, + thermalDemands: ThermalDemandWrapper, ): (ThermalGridState, Option[ThermalThreshold]) = { // TODO: We would need to issue a storage result model here... @@ -240,7 +240,8 @@ final case class ThermalGrid( } if ( - (qDotHouseLastState > zeroKW && (qDotStorageLastState >= zeroKW)) | (qDotStorageLastState > zeroKW & demandIndicator.heatStorageDemand) + // todo: Check if it has to be hasRequiredDemand or hasAdditionalDemand + (qDotHouseLastState > zeroKW && (qDotStorageLastState >= zeroKW)) | (qDotStorageLastState > zeroKW & thermalDemands.heatStorageDemand.hasAdditionalDemand) ) { val (updatedHouseState, thermalHouseThreshold, remainingQDotHouse) = handleInfeedHouse( @@ -302,8 +303,11 @@ final case class ThermalGrid( ) } } else { - - (demandIndicator.houseDemand, demandIndicator.heatStorageDemand) match { + // todo: Check if it has to be hasRequiredDemand or hasAdditionalDemand + ( + thermalDemands.houseDemand.hasRequiredDemand, + thermalDemands.heatStorageDemand.hasRequiredDemand, + ) match { case (true, _) => // house first then heatStorage after heating House @@ -737,17 +741,16 @@ object ThermalGrid { thermalGrid.storage.map(_.startingState), ) - /** Wraps booleans indicating the demand of thermal units (thermal house, - * thermal storage). + /** Wraps the demand of thermal units (thermal house, thermal storage). * * @param houseDemand - * Boolean indicating the demand of the thermal house + * the demand of the thermal house * @param heatStorageDemand - * Boolean indicating the demand of the thermal heat storage + * the demand of the thermal heat storage */ - final case class ThermalDemandIndicator private ( - houseDemand: Boolean, - heatStorageDemand: Boolean, + final case class ThermalDemandWrapper private ( + houseDemand: ThermalEnergyDemand, + heatStorageDemand: ThermalEnergyDemand, ) /** Defines the thermal energy demand of a thermal grid. It comprises the @@ -776,8 +779,7 @@ object ThermalGrid { object ThermalEnergyDemand { /** Builds a new instance of [[ThermalEnergyDemand]]. If the possible energy - * is less than the required energy, this is considered to be a bad state - * and the required energy is curtailed to the possible energy. + * is less than the required energy, this is considered to be a bad state. * @param required * The absolutely required energy to reach target state * @param possible diff --git a/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala b/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala index 81c62e549d..7080cac327 100644 --- a/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/participant/HpModelSpec.scala @@ -265,7 +265,7 @@ class HpModelSpec (95.0, 95.0, 95.0), ), // 2. Same as before but heat storage is NOT empty - // should be possible to keep hp off + // should be possible to turn hp on ( ThermalGridState( Some(ThermalHouseState(0L, Celsius(15), Kilowatts(0))), diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala index 21f27b1892..df410786c5 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridTestData.scala @@ -9,8 +9,12 @@ package edu.ie3.simona.model.thermal import edu.ie3.datamodel.models.OperationTime import edu.ie3.datamodel.models.input.OperatorInput import edu.ie3.datamodel.models.input.thermal.ThermalBusInput -import edu.ie3.simona.model.thermal.ThermalGrid.ThermalDemandIndicator -import squants.energy.{Kilowatts, Power} +import edu.ie3.simona.model.thermal.ThermalGrid.{ + ThermalDemandWrapper, + ThermalEnergyDemand, +} +import edu.ie3.util.scala.quantities.DefaultQuantities.zeroKWh +import squants.energy.{KilowattHours, Kilowatts, Power} import squants.thermal.{Celsius, Temperature} import java.util.UUID @@ -26,12 +30,21 @@ trait ThermalGridTestData { protected val testGridQDotInfeed: Power = Kilowatts(15d) protected val testGridQDotConsumption: Power = Kilowatts(-42d) protected val testGridQDotConsumptionHigh: Power = Kilowatts(-200d) - protected val noThermalDemand: ThermalDemandIndicator = - ThermalDemandIndicator(false, false) - protected val onlyThermalDemandOfHouse: ThermalDemandIndicator = - ThermalDemandIndicator(true, false) - protected val onlyThermalDemandOfHeatStorage: ThermalDemandIndicator = - ThermalDemandIndicator(false, true) + protected val noThermalDemand: ThermalDemandWrapper = + ThermalDemandWrapper( + ThermalEnergyDemand(zeroKWh, zeroKWh), + ThermalEnergyDemand(zeroKWh, zeroKWh), + ) + protected val onlyThermalDemandOfHouse: ThermalDemandWrapper = + ThermalDemandWrapper( + ThermalEnergyDemand(KilowattHours(1), KilowattHours(2)), + ThermalEnergyDemand(zeroKWh, zeroKWh), + ) + protected val onlyThermalDemandOfHeatStorage: ThermalDemandWrapper = + ThermalDemandWrapper( + ThermalEnergyDemand(zeroKWh, zeroKWh), + ThermalEnergyDemand(KilowattHours(1), KilowattHours(2)), + ) protected val isRunning: Boolean = true protected val isNotRunning: Boolean = false } diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala index 2351774f20..6cd7558e3d 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseAndStorageSpec.scala @@ -13,13 +13,13 @@ import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseThreshold.{ HouseTemperatureLowerBoundaryReached, HouseTemperatureUpperBoundaryReached, } -import edu.ie3.util.scala.quantities.DefaultQuantities.{zeroKW, zeroKWh} import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageState import edu.ie3.simona.model.thermal.ThermalStorage.ThermalStorageThreshold.{ StorageEmpty, StorageFull, } import edu.ie3.simona.test.common.UnitSpec +import edu.ie3.util.scala.quantities.DefaultQuantities.{zeroKW, zeroKWh} import squants.energy._ import squants.thermal.Celsius import squants.{Energy, Kelvin, Power, Temperature} @@ -97,13 +97,16 @@ class ThermalGridWithHouseAndStorageSpec "deliver the house demand (no demand) with added flexibility by storage" in { val tick = 10800 // after three hours - val (houseDemand, storageDemand, updatedThermalGridState) = + val (thermalDemands, updatedThermalGridState) = thermalGrid.energyDemandAndUpdatedState( tick, testGridAmbientTemperature, testGridAmbientTemperature, ThermalGrid.startingState(thermalGrid), ) + val houseDemand = thermalDemands.houseDemand + val storageDemand = thermalDemands.heatStorageDemand + houseDemand.required should approximate(zeroKWh) houseDemand.possible should approximate(KilowattHours(31.05009722d)) storageDemand.required should approximate(KilowattHours(1150d)) @@ -120,7 +123,7 @@ class ThermalGridWithHouseAndStorageSpec val tick = 10800 // after three hours val startingState = ThermalGrid.startingState(thermalGrid) - val (houseDemand, storageDemand, updatedThermalGridState) = + val (thermalDemands, updatedThermalGridState) = thermalGrid.energyDemandAndUpdatedState( tick, testGridAmbientTemperature, @@ -131,6 +134,8 @@ class ThermalGridWithHouseAndStorageSpec ) ), ) + val houseDemand = thermalDemands.houseDemand + val storageDemand = thermalDemands.heatStorageDemand houseDemand.required should approximate(KilowattHours(45.6000555)) houseDemand.possible should approximate(KilowattHours(75.600055555)) diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala index 0c61b79ba8..34b37c2e03 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithHouseOnlySpec.scala @@ -13,8 +13,8 @@ import edu.ie3.simona.model.thermal.ThermalHouse.ThermalHouseThreshold.{ HouseTemperatureLowerBoundaryReached, HouseTemperatureUpperBoundaryReached, } -import edu.ie3.util.scala.quantities.DefaultQuantities.{zeroKW, zeroKWh} import edu.ie3.simona.test.common.UnitSpec +import edu.ie3.util.scala.quantities.DefaultQuantities.{zeroKW, zeroKWh} import squants.energy._ import squants.thermal.Celsius import squants.{Energy, Kelvin, Power, Temperature} @@ -81,7 +81,7 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { expectedHouseStartingState, ) - val (houseDemand, storageDemand, updatedThermalGridState) = + val (thermalDemands, updatedThermalGridState) = thermalGrid.energyDemandAndUpdatedState( tick, testGridAmbientTemperature, @@ -89,6 +89,9 @@ class ThermalGridWithHouseOnlySpec extends UnitSpec with ThermalHouseTestData { ThermalGrid.startingState(thermalGrid), ) + val houseDemand = thermalDemands.houseDemand + val storageDemand = thermalDemands.heatStorageDemand + houseDemand.required should approximate(expectedHouseDemand.required) houseDemand.possible should approximate(expectedHouseDemand.possible) storageDemand.required should approximate(zeroKWh) diff --git a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala index d8b622e78c..c4e5339a54 100644 --- a/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala +++ b/src/test/scala/edu/ie3/simona/model/thermal/ThermalGridWithStorageOnlySpec.scala @@ -81,13 +81,15 @@ class ThermalGridWithStorageOnlySpec "deliver the capabilities of the storage" in { val tick = 10800 // after three hours - val (houseDemand, storageDemand, updatedThermalGridState) = + val (thermalDemands, updatedThermalGridState) = thermalGrid.energyDemandAndUpdatedState( tick, testGridAmbientTemperature, testGridAmbientTemperature, ThermalGrid.startingState(thermalGrid), ) + val houseDemand = thermalDemands.houseDemand + val storageDemand = thermalDemands.heatStorageDemand houseDemand.required should approximate(zeroKWh) houseDemand.possible should approximate(zeroKWh) @@ -102,7 +104,7 @@ class ThermalGridWithStorageOnlySpec "deliver the capabilities of a half full storage" in { val tick = 10800 // after three hours - val (houseDemand, storageDemand, updatedThermalGridState) = + val (thermalDemands, updatedThermalGridState) = thermalGrid.energyDemandAndUpdatedState( tick, testGridAmbientTemperature, @@ -112,6 +114,8 @@ class ThermalGridWithStorageOnlySpec Some(ThermalStorageState(0L, KilowattHours(575d), zeroKW)), ), ) + val houseDemand = thermalDemands.houseDemand + val storageDemand = thermalDemands.heatStorageDemand houseDemand.required should approximate(zeroKWh) houseDemand.possible should approximate(zeroKWh) From 5c00a896c5ca9e086fa82e5384073e96a519482f Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Fri, 22 Nov 2024 17:02:32 +0100 Subject: [PATCH 13/14] fix thermalPower determination in calcState of HpModel --- .../edu/ie3/simona/model/participant/HpModel.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala b/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala index 76e9f24651..3e5c8f4cd1 100644 --- a/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala +++ b/src/main/scala/edu/ie3/simona/model/participant/HpModel.scala @@ -258,12 +258,20 @@ final case class HpModel( .map(_.qDot) .getOrElse(zeroKW) - val (newActivePower, newThermalPower) = + val (newActivePower, newThermalPower) = { if (isRunning) (pRated, pThermal) else if (lastStateStorageQDot < zeroKW) (zeroKW, lastStateStorageQDot * -1) + else if ( + lastStateStorageQDot == zeroKW && (demandWrapper.houseDemand.hasRequiredDemand || demandWrapper.heatStorageDemand.hasRequiredDemand) + ) + ( + zeroKW, + thermalGrid.storage.map(_.getChargingPower: squants.Power).get * -1, + ) else (zeroKW, zeroKW) + } /* Push thermal energy to the thermal grid and get its updated state in return */ val (thermalGridState, maybeThreshold) = From cbd997ebc8ef6f86d87e5653b08c528710d29d56 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Fri, 22 Nov 2024 23:39:58 +0100 Subject: [PATCH 14/14] fix EmAgentIT --- .../scala/edu/ie3/simona/agent/em/EmAgentIT.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala index be1cee377f..b43b73f129 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -680,7 +680,7 @@ class EmAgentIT Celsius(0d), MetersPerSecond(0d), ), - Some(56000), + Some(28800), ) } @@ -692,26 +692,26 @@ class EmAgentIT emResult.getQ should equalWithTolerance(0.000088285537.asMegaVar) } - scheduler.expectMessage(Completion(emAgentActivation, Some(44398))) + scheduler.expectMessage(Completion(emAgentActivation, Some(26748))) - /* TICK 44398 + /* TICK 26748 LOAD: 0.000269 MW (unchanged) PV: -0.000032 MW (unchanged) Heat pump: Is turned on again and cannot be turned off -> flex signal is no control -> 0.00485 MW */ - emAgentActivation ! Activation(44398) + emAgentActivation ! Activation(26748) resultListener.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid - emResult.getTime shouldBe 44398.toDateTime + emResult.getTime shouldBe 26748.toDateTime emResult.getP should equalWithTolerance(0.005086768.asMegaWatt) emResult.getQ should equalWithTolerance(0.00107312004.asMegaVar) } - scheduler.expectMessage(Completion(emAgentActivation, Some(56000))) + scheduler.expectMessage(Completion(emAgentActivation, Some(28800))) } }