From 63bcadc5506a2de4826eba1c7d9a6ee6bd8add53 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 5 Feb 2025 10:38:13 +0100 Subject: [PATCH 01/22] Adding `VoltageLimits`. --- CHANGELOG.md | 1 + docs/readthedocs/config.md | 27 +++++ docs/readthedocs/models/voltage_limits.md | 61 ++++++++++ .../resources/config/config-template.conf | 13 +++ .../edu/ie3/simona/agent/grid/GridAgent.scala | 1 + .../ie3/simona/agent/grid/GridAgentData.scala | 7 +- .../ie3/simona/config/ConfigFailFast.scala | 59 ++++++++++ .../ie3/simona/config/GridConfigParser.scala | 102 +++++++++++++++++ .../ie3/simona/config/RefSystemParser.scala | 108 ++---------------- .../edu/ie3/simona/config/SimonaConfig.scala | 100 +++++++++++++++- .../simona/config/VoltageLimitsParser.scala | 63 ++++++++++ .../edu/ie3/simona/model/grid/GridModel.scala | 5 + .../ie3/simona/model/grid/VoltageLimits.scala | 41 +++++++ .../ie3/simona/sim/setup/SetupHelper.scala | 22 +++- .../sim/setup/SimonaStandaloneSetup.scala | 12 +- .../agent/grid/DBFSAlgorithmCenGridSpec.scala | 3 +- .../DBFSAlgorithmFailedPowerFlowSpec.scala | 4 +- .../grid/DBFSAlgorithmParticipantSpec.scala | 3 +- .../agent/grid/DBFSAlgorithmSupGridSpec.scala | 3 +- .../agent/grid/PowerFlowSupportSpec.scala | 3 +- .../edu/ie3/simona/model/grid/GridSpec.scala | 7 ++ .../model/grid/TransformerModelSpec.scala | 1 + .../simona/test/common/DefaultTestData.scala | 4 +- .../model/grid/BasicGridWithSwitches.scala | 1 + .../model/grid/TransformerTestData.scala | 7 +- 25 files changed, 547 insertions(+), 111 deletions(-) create mode 100644 docs/readthedocs/models/voltage_limits.md create mode 100644 src/main/scala/edu/ie3/simona/config/GridConfigParser.scala create mode 100644 src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala create mode 100644 src/main/scala/edu/ie3/simona/model/grid/VoltageLimits.scala diff --git a/CHANGELOG.md b/CHANGELOG.md index c2443db6ff..2dfe800fb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Throw exception if the slack node is not directly conected to a transformer. [#525](https://github.com/ie3-institute/simona/issues/525) - Added support for topologies without transformers and slack grids with multiple nodes [#1099](https://github.com/ie3-institute/simona/issues/1099) - Checking the number of slack nodes [#1122](https://github.com/ie3-institute/simona/issues/1122) +- Added `VoltageLimits` [#1133](https://github.com/ie3-institute/simona/issues/1133) ### Changed - Adapted to changed data source in PSDM [#435](https://github.com/ie3-institute/simona/issues/435) diff --git a/docs/readthedocs/config.md b/docs/readthedocs/config.md index f6e968659a..925c040239 100644 --- a/docs/readthedocs/config.md +++ b/docs/readthedocs/config.md @@ -245,6 +245,7 @@ Tba: ## Grid configuration +### Reference System The reference system contains a list of voltage levels. Each element includes the nominal apparent power, the nominal voltage and the separate configuration of each voltage level. The voltage level configuration is composed of the identifier and the nominal voltage. @@ -268,6 +269,32 @@ simona.gridConfig.refSystems = [ Further typical voltage levels which can be used in the simulation and the configuration of individual reference systems are described in the documentation of [reference system](models/reference_system). +### Voltage limits + +The voltage limits contains a list of voltage levels. Each element includes the minimal and maximal allowed voltage and +the separate configuration of each voltage level. The voltage level configuration is composed of the identifier and the +nominal voltage. + +The configuration of a voltage limits is optional. If no configuration is provided by the user, the default +[voltage limits](models/voltage_limits) that includes all common german voltage levels is used. For those users +who need other voltage levels than the common german voltage levels or different voltage limits, they can configure +their limits as shown below. + +The voltage limits can be configured as follows: + +``` +simona.gridConfig.voltageLimits = [ + {vMin = 0.9, vMax = 1.1, voltLvls = [{id = "Lv", vNom = "0.4 kV"}]}, + {vMin = 0.9, vMax = 1.1, voltLvls = [{id = "Mv", vNom = "20 kV"}]}, + {vMin = 0.9, vMax = 1.1, voltLvls = [{id = "Hv", vNom = "110 kV"}]}, + {vMin = 0.9, vMax = 1.05, voltLvls = [{id = "EHV", vNom = "380 kV"}]}, +] +``` + +Further typical voltage levels which can be used in the simulation and the configuration of individual voltage limits +are described in the documentation of [voltage limits](models/voltage_limits). + + ## Power flow configuration Maximum allowed deviation in power between two sweeps, before overall convergence is assumed: diff --git a/docs/readthedocs/models/voltage_limits.md b/docs/readthedocs/models/voltage_limits.md new file mode 100644 index 0000000000..c5fae5b90a --- /dev/null +++ b/docs/readthedocs/models/voltage_limits.md @@ -0,0 +1,61 @@ +(voltage_limits)= + +# Voltage Limits + +The voltage limits are built up by specifying the included voltage levels. The following table describes typical network +levels and the corresponding parameterization. They are primarily used for the optional congestion management. + +## Default voltage limits + +```{list-table} +:widths: auto +:header-rows: 1 + +* - Voltage level (id) + - Minimal voltage (vMin) + - Maximal voltage (vMax) + +* - LV + - 0.9 p.u. + - 1.1 p.u. + +* - MV (10 kV) + - 0.9 p.u. + - 1.1 p.u. + +* - MV (20 kV) + - 0.9 p.u. + - 1.1 p.u. + +* - MV (30 kV) + - 0.9 p.u. + - 1.1 p.u. + +* - HV + - 0.9 p.u. + - 1.1 p.u. + +* - EHV (220 kV) + - 0.9 p.u. + - 1.118 p.u. + +* - EHV (380 kV) + - 0.9 p.u. + - 1.05 p.u. +``` + + +## Configuration of the voltage limits + +To configure the voltage limits, the voltage levels listed in the table above must be selected and integrated into the +config. The individual voltage level configuration (voltLvls) according to the example below. Each voltage level is composed +of the identifier and the nominal voltage. + +``` +simona.gridConfig.voltageLimits = [ + {vMin = 0.9, vMax = 1.1, voltLvls = [{id = "Lv", vNom = "0.4 kV"}]}, + {vMin = 0.9, vMax = 1.1, voltLvls = [{id = "Mv", vNom = "20 kV"}]}, + {vMin = 0.9, vMax = 1.1, voltLvls = [{id = "Hv", vNom = "110 kV"}]}, + {vMin = 0.9, vMax = 1.05, voltLvls = [{id = "EHV", vNom = "380 kV"}]}, +] +``` diff --git a/src/main/resources/config/config-template.conf b/src/main/resources/config/config-template.conf index 6c9d886711..77bc7d4d8a 100644 --- a/src/main/resources/config/config-template.conf +++ b/src/main/resources/config/config-template.conf @@ -14,6 +14,16 @@ RefSystemConfig { gridIds: [string] # Sub grid numbers to apply to, expl.: 1,2,4..10 } +#@define +VoltageLimitsConfig { + vMin: double # minimal voltage + vMax: double # maximal voltage + #@optional + voltLvls: [VoltLvlConfig] # Voltage levels to apply to + #@optional + gridIds: [string] # Sub grid numbers to apply to, expl.: 1,2,4..10 +} + #@define abstract extends !java.io.Serializable BaseRuntimeConfig { uuids: [string] # Unique id to identify the system participant models this config applies for @@ -363,6 +373,9 @@ simona.powerflow.stopOnFailure = boolean | false #@optional simona.gridConfig.refSystems = [RefSystemConfig] +#@optional +simona.gridConfig.voltageLimits = [VoltageLimitsConfig] + ################################################################## # Event Configuration ################################################################## diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala index f7e82f854c..22c3f70889 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -132,6 +132,7 @@ object GridAgent extends DBFSAlgorithm { val gridModel = GridModel( subGridContainer, refSystem, + gridAgentInitData.voltageLimits, TimeUtil.withDefaults.toZonedDateTime( constantData.simonaConfig.simona.time.startDateTime ), diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala index 6b77430487..112428deca 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -16,7 +16,7 @@ import edu.ie3.simona.agent.grid.ReceivedValuesStore.NodeToReceivedPower import edu.ie3.simona.agent.participant.ParticipantAgent.ParticipantMessage import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.event.ResultEvent -import edu.ie3.simona.model.grid.{GridModel, RefSystem} +import edu.ie3.simona.model.grid.{GridModel, RefSystem, VoltageLimits} import edu.ie3.simona.ontology.messages.Activation import org.apache.pekko.actor.typed.ActorRef @@ -69,12 +69,17 @@ object GridAgentData { * @param subGridGateToActorRef * information on inferior and superior grid connections [[SubGridGate]] s * and [[ActorRef]] s of the corresponding [[GridAgent]]s + * @param refSystem + * of the grid + * @param voltageLimits + * of the grid */ final case class GridAgentInitData( subGridContainer: SubGridContainer, thermalIslandGrids: Seq[ThermalGrid], subGridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], refSystem: RefSystem, + voltageLimits: VoltageLimits, ) extends GridAgentData with GridAgentDataHelper { override protected val subgridGates: Vector[SubGridGate] = diff --git a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala index c3b16d2ca7..c16570ab9b 100644 --- a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala +++ b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala @@ -120,6 +120,9 @@ object ConfigFailFast extends LazyLogging { if (refSystems.isDefined) refSystems.foreach(refsys => checkRefSystem(refsys)) + // check if the provided combinations of voltageLimits provided are valid + simonaConfig.simona.gridConfig.voltageLimits.foreach(checkVoltageLimits) + /* Check all participant model configurations */ checkParticipantRuntimeConfiguration( simonaConfig.simona.runtime.participant @@ -522,6 +525,62 @@ object ConfigFailFast extends LazyLogging { } } + /** Sanity checks for a [[SimonaConfig.VoltageLimitsConfig]] + * + * @param voltageLimits + * the [[SimonaConfig.VoltageLimitsConfig]] that should be checked + */ + private def checkVoltageLimits( + voltageLimits: List[VoltageLimitsConfig] + ): Unit = { + voltageLimits.foreach { limit => + val voltLvls = + limit.voltLvls.getOrElse(List.empty[SimonaConfig.VoltLvlConfig]) + val gridIds = limit.gridIds.getOrElse(List.empty[String]) + + if (voltLvls.isEmpty && gridIds.isEmpty) + throw new InvalidConfigParameterException( + "The provided values for voltLvls and gridIds are empty! " + + s"At least one of these optional parameters has to be provided for valid voltage limits! " + + s"Provided voltage limits are: $voltageLimits." + ) + + voltLvls.foreach { voltLvl => + Try(Quantities.getQuantity(voltLvl.vNom)) match { + case Success(quantity) => + if (!quantity.getUnit.isCompatible(Units.VOLT)) + throw new InvalidConfigParameterException( + s"The given nominal voltage '${voltLvl.vNom}' cannot be parsed to electrical potential! Please provide the volt level with its unit, e.g. \"20 kV\"" + ) + case Failure(exception) => + throw new InvalidConfigParameterException( + s"The given nominal voltage '${voltLvl.vNom}' cannot be parsed to a quantity. Did you provide the volt level with it's unit (e.g. \"20 kV\")?", + exception, + ) + } + } + + gridIds.foreach { + case gridIdRange @ ConfigConventions.gridIdDotRange(from, to) => + rangeCheck(from.toInt, to.toInt, gridIdRange) + case gridIdRange @ ConfigConventions.gridIdMinusRange(from, to) => + rangeCheck(from.toInt, to.toInt, gridIdRange) + case ConfigConventions.singleGridId(_) => + case gridId => + throw new InvalidConfigParameterException( + s"The provided gridId $gridId is malformed!" + ) + } + + def rangeCheck(from: Int, to: Int, gridIdRange: String): Unit = { + if (from >= to) + throw new InvalidConfigParameterException( + s"Invalid gridId Range $gridIdRange. Start $from cannot be equals or bigger than end $to." + ) + } + } + } + private def checkGridDataSource( gridDataSource: SimonaConfig.Simona.Input.Grid.Datasource ): Unit = { diff --git a/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala b/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala new file mode 100644 index 0000000000..1799430e66 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala @@ -0,0 +1,102 @@ +/* + * © 2025. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.config + +import edu.ie3.datamodel.models.voltagelevels.VoltageLevel +import edu.ie3.simona.config.SimonaConfig.VoltLvlConfig +import edu.ie3.simona.exceptions.InvalidConfigParameterException +import edu.ie3.simona.util.CollectionUtils +import edu.ie3.util.quantities.PowerSystemUnits + +object GridConfigParser { + abstract class GridConfig[GC]( + protected val gridIdMap: Map[Int, GC], + protected val voltLvlMap: Map[VoltageLevel, GC], + ) { + + /** Returns a [[GC]] based on the provided gridId or the voltLvl as fallback + * if available + * + * @param gridId + * the gridId the refSystem is wanted for + * @param voltLvl + * the voltLvL that is valid for the grid that is wanted + * @return + * Some(refSystem) if available or None if unavailable + */ + final def find( + gridId: Int, + voltLvl: Option[VoltageLevel] = None, + ): Option[GC] = + gridIdMap + .get(gridId) + .orElse(voltLvl.flatMap(voltLvlMap.get)) + } + + def parseWithDefaults[C, E, GC]( + configs: Option[List[C]], + gridIds: C => Option[List[String]], + voltLvls: C => Option[List[VoltLvlConfig]], + elementFcn: C => E, + builder: (Map[Int, E], Map[VoltageLevel, E]) => GC, + defaults: GC, + )(implicit gridConfigType: String): GC = configs match { + case Some(configElements) if configElements.nonEmpty => + // units for parsing are not initialized by default + // hence we call them manually + new PowerSystemUnits + + val emptyLists = (List.empty[(Int, E)], List.empty[(VoltageLevel, E)]) + + val (parsedIdList, parsedVoltLvlList) = + configElements.foldLeft(emptyLists) { case ((ids, lvls), config) => + val element = elementFcn(config) + + val parsedGridIds = gridIds(config).getOrElse(List.empty).flatMap { + case ConfigConventions.gridIdDotRange(from, to) => + (from.toInt to to.toInt) + .map(gridId => (gridId, element)) + case ConfigConventions.gridIdMinusRange(from, to) => + (from.toInt to to.toInt) + .map(gridId => (gridId, element)) + case ConfigConventions.singleGridId(singleGridId) => + Seq((singleGridId.toInt, element)) + case unknownGridIdFormat => + throw new InvalidConfigParameterException( + s"Unknown gridId format $unknownGridIdFormat provided for $gridConfigType $config" + ) + } + + val parsedVoltLvls = + voltLvls(config).getOrElse(List.empty).map { voltLvlDef => + (VoltLvlParser.from(voltLvlDef), element) + } + + ( + ids ++ parsedGridIds, + lvls ++ parsedVoltLvls, + ) + } + + if (CollectionUtils.listHasDuplicates(parsedIdList)) { + throw new InvalidConfigParameterException( + s"The provided gridIds in simona.gridConfig.${gridConfigType}s contain duplicates. " + + "Please check if there are either duplicate entries or overlapping ranges!" + ) + } + + if (CollectionUtils.listHasDuplicates(parsedVoltLvlList)) + throw new InvalidConfigParameterException( + s"The provided voltLvls in simona.gridConfig.${gridConfigType}s contain duplicates. " + + "Please check your configuration for duplicates in voltLvl entries!" + ) + + builder(parsedIdList.toMap, parsedVoltLvlList.toMap) + case _ => + defaults + } +} diff --git a/src/main/scala/edu/ie3/simona/config/RefSystemParser.scala b/src/main/scala/edu/ie3/simona/config/RefSystemParser.scala index fbe2f83362..c0722b5e87 100644 --- a/src/main/scala/edu/ie3/simona/config/RefSystemParser.scala +++ b/src/main/scala/edu/ie3/simona/config/RefSystemParser.scala @@ -10,11 +10,10 @@ import edu.ie3.datamodel.models.voltagelevels.{ GermanVoltageLevelUtils, VoltageLevel, } -import edu.ie3.simona.exceptions.InvalidConfigParameterException +import edu.ie3.simona.config.GridConfigParser.{GridConfig, parseWithDefaults} +import edu.ie3.simona.config.SimonaConfig.RefSystemConfig import edu.ie3.simona.model.grid.RefSystem -import edu.ie3.simona.util.CollectionUtils -import edu.ie3.util.quantities.PowerSystemUnits -import squants.electro.{Kilovolts, Volts} +import squants.electro.Kilovolts import squants.energy.{Kilowatts, Megawatts} /** Parser to parse [[RefSystem]] provided via [[SimonaConfig]] @@ -23,27 +22,7 @@ object RefSystemParser { final case class ConfigRefSystems( private val gridIdRefSystems: Map[Int, RefSystem], private val voltLvLRefSystems: Map[VoltageLevel, RefSystem], - ) { - - /** Returns a [[RefSystem]] based on the provided gridId or the voltLvl as - * fallback if available - * - * @param gridId - * the gridId the refSystem is wanted for - * @param voltLvl - * the voltLvL that is valid for the grid that is wanted - * @return - * Some(refSystem) if available or None if unavailable - */ - def find( - gridId: Int, - voltLvl: Option[VoltageLevel] = None, - ): Option[RefSystem] = - gridIdRefSystems - .get(gridId) - .orElse(voltLvl.flatMap(voltLvLRefSystems.get)) - - } + ) extends GridConfig[RefSystem](gridIdRefSystems, voltLvLRefSystems) /** Parses the configuration based [[RefSystem]] information based on a list * of [[SimonaConfig.RefSystemConfig]] @@ -85,76 +64,13 @@ object RefSystemParser { ), ) - configRefSystems match { - case Some(refSystems) if refSystems.nonEmpty => - // units for parsing are not initialized by default - // hence we call them manually - new PowerSystemUnits - - val parsedRefSystems = refSystems.flatMap { configRefSystem => - val refSystem = RefSystem(configRefSystem.sNom, configRefSystem.vNom) - - configRefSystem.gridIds.getOrElse(Seq.empty).flatMap { - case ConfigConventions.gridIdDotRange(from, to) => - (from.toInt to to.toInt) - .map(gridId => (gridId, refSystem)) - case ConfigConventions.gridIdMinusRange(from, to) => - (from.toInt to to.toInt) - .map(gridId => (gridId, refSystem)) - case ConfigConventions.singleGridId(singleGridId) => - Seq((singleGridId.toInt, refSystem)) - case unknownGridIdFormat => - throw new InvalidConfigParameterException( - s"Unknown gridId format $unknownGridIdFormat provided for refSystem $configRefSystem" - ) - } ++ configRefSystem.voltLvls.getOrElse(Seq.empty).map { voltLvlDef => - (VoltLvlParser.from(voltLvlDef), refSystem) - } - } - - val gridIdRefSystemsList: List[(Int, RefSystem)] = - parsedRefSystems.flatMap { - case (gridId: Int, refSystems) => - refSystems match { - case refSystem: RefSystem => Some(gridId -> refSystem) - case _ => None - } - case _ => None - } - - val gridIdRefSystems: Map[Int, RefSystem] = - gridIdRefSystemsList.toMap - - if (CollectionUtils.listHasDuplicates(gridIdRefSystemsList)) { - throw new InvalidConfigParameterException( - "The provided gridIds in simona.gridConfig.refSystems contain duplicates. " + - "Please check if there are either duplicate entries or overlapping ranges!" - ) - } - - val voltLvLRefSystemsList: List[(VoltageLevel, RefSystem)] = - parsedRefSystems.flatMap { - case (voltLvl: VoltageLevel, refSystems) => - refSystems match { - case refSystem: RefSystem => Some(voltLvl -> refSystem) - case _ => None - } - case _ => None - } - - if (CollectionUtils.listHasDuplicates(voltLvLRefSystemsList)) - throw new InvalidConfigParameterException( - "The provided voltLvls in simona.gridConfig.refSystems contain duplicates. " + - "Please check your configuration for duplicates in voltLvl entries!" - ) - - val voltLvLRefSys: Map[VoltageLevel, RefSystem] = - parsedRefSystems.collect { case (voltLvl: VoltageLevel, values) => - (voltLvl, values) - }.toMap - - ConfigRefSystems(gridIdRefSystems, voltLvLRefSys) - case _ => defaultRefSystems - } + parseWithDefaults[RefSystemConfig, RefSystem, ConfigRefSystems]( + configRefSystems, + refSystem => refSystem.gridIds, + refSystem => refSystem.voltLvls, + refSystem => RefSystem(refSystem.sNom, refSystem.vNom), + (gridIds, voltLvls) => ConfigRefSystems(gridIds, voltLvls), + defaultRefSystems, + )("refSystem") } } diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index 0790a6efd9..309201cfc5 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -1010,6 +1010,73 @@ object SimonaConfig { } + final case class VoltageLimitsConfig( + gridIds: scala.Option[scala.List[java.lang.String]], + vMax: scala.Double, + vMin: scala.Double, + voltLvls: scala.Option[scala.List[SimonaConfig.VoltLvlConfig]], + ) + object VoltageLimitsConfig { + def apply( + c: com.typesafe.config.Config, + parentPath: java.lang.String, + $tsCfgValidator: $TsCfgValidator, + ): SimonaConfig.VoltageLimitsConfig = { + SimonaConfig.VoltageLimitsConfig( + gridIds = + if (c.hasPathOrNull("gridIds")) + scala.Some( + $_L$_str(c.getList("gridIds"), parentPath, $tsCfgValidator) + ) + else None, + vMax = $_reqDbl(parentPath, c, "vMax", $tsCfgValidator), + vMin = $_reqDbl(parentPath, c, "vMin", $tsCfgValidator), + voltLvls = + if (c.hasPathOrNull("voltLvls")) + scala.Some( + $_LSimonaConfig_VoltLvlConfig( + c.getList("voltLvls"), + parentPath, + $tsCfgValidator, + ) + ) + else None, + ) + } + private def $_LSimonaConfig_VoltLvlConfig( + cl: com.typesafe.config.ConfigList, + parentPath: java.lang.String, + $tsCfgValidator: $TsCfgValidator, + ): scala.List[SimonaConfig.VoltLvlConfig] = { + import scala.jdk.CollectionConverters._ + cl.asScala + .map(cv => + SimonaConfig.VoltLvlConfig( + cv.asInstanceOf[com.typesafe.config.ConfigObject].toConfig, + parentPath, + $tsCfgValidator, + ) + ) + .toList + } + private def $_reqDbl( + parentPath: java.lang.String, + c: com.typesafe.config.Config, + path: java.lang.String, + $tsCfgValidator: $TsCfgValidator, + ): scala.Double = { + if (c == null) 0 + else + try c.getDouble(path) + catch { + case e: com.typesafe.config.ConfigException => + $tsCfgValidator.addBadPath(parentPath + path, e) + 0 + } + } + + } + final case class WecRuntimeConfig( override val calculateMissingReactivePowerWithModel: scala.Boolean, override val scaling: scala.Double, @@ -1203,7 +1270,10 @@ object SimonaConfig { } final case class GridConfig( - refSystems: scala.Option[scala.List[SimonaConfig.RefSystemConfig]] + refSystems: scala.Option[scala.List[SimonaConfig.RefSystemConfig]], + voltageLimits: scala.Option[ + scala.List[SimonaConfig.VoltageLimitsConfig] + ], ) object GridConfig { def apply( @@ -1221,7 +1291,17 @@ object SimonaConfig { $tsCfgValidator, ) ) - else None + else None, + voltageLimits = + if (c.hasPathOrNull("voltageLimits")) + scala.Some( + $_LSimonaConfig_VoltageLimitsConfig( + c.getList("voltageLimits"), + parentPath, + $tsCfgValidator, + ) + ) + else None, ) } private def $_LSimonaConfig_RefSystemConfig( @@ -1240,6 +1320,22 @@ object SimonaConfig { ) .toList } + private def $_LSimonaConfig_VoltageLimitsConfig( + cl: com.typesafe.config.ConfigList, + parentPath: java.lang.String, + $tsCfgValidator: $TsCfgValidator, + ): scala.List[SimonaConfig.VoltageLimitsConfig] = { + import scala.jdk.CollectionConverters._ + cl.asScala + .map(cv => + SimonaConfig.VoltageLimitsConfig( + cv.asInstanceOf[com.typesafe.config.ConfigObject].toConfig, + parentPath, + $tsCfgValidator, + ) + ) + .toList + } } final case class Input( diff --git a/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala b/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala new file mode 100644 index 0000000000..4c1ce7c596 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala @@ -0,0 +1,63 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.config + +import edu.ie3.datamodel.models.voltagelevels.{ + GermanVoltageLevelUtils, + VoltageLevel, +} +import edu.ie3.simona.config.GridConfigParser.{GridConfig, parseWithDefaults} +import edu.ie3.simona.config.SimonaConfig.VoltageLimitsConfig +import edu.ie3.simona.model.grid.VoltageLimits + +/** Parser to parse [[VoltageLimits]] provided via [[SimonaConfig]] + */ +object VoltageLimitsParser { + + final case class ConfigVoltageLimits( + private val gridIdVoltageLimits: Map[Int, VoltageLimits], + private val voltLvLVoltageLimits: Map[VoltageLevel, VoltageLimits], + ) extends GridConfig[VoltageLimits](gridIdVoltageLimits, voltLvLVoltageLimits) + + /** Parses the configuration based [[VoltageLimits]] information based on a + * list of [[SimonaConfig.VoltageLimitsConfig]] + * + * @param configVoltageLimits + * the refSystems provided via configuration + * @return + * object that holds two maps with mappings of gridIds and voltLvls to + * RefSystems + */ + def parse( + configVoltageLimits: Option[List[SimonaConfig.VoltageLimitsConfig]] + ): ConfigVoltageLimits = { + val distributionVoltageLimits = VoltageLimits(0.9, 1.1) + + val defaultVoltageLimits = ConfigVoltageLimits( + Map.empty, + Map( + GermanVoltageLevelUtils.LV -> distributionVoltageLimits, + GermanVoltageLevelUtils.MV_10KV -> distributionVoltageLimits, + GermanVoltageLevelUtils.MV_20KV -> distributionVoltageLimits, + GermanVoltageLevelUtils.MV_30KV -> distributionVoltageLimits, + GermanVoltageLevelUtils.HV -> distributionVoltageLimits, + GermanVoltageLevelUtils.EHV_220KV -> VoltageLimits(0.9, 1.118), + GermanVoltageLevelUtils.EHV_380KV -> VoltageLimits(0.9, 1.05), + ), + ) + + parseWithDefaults[VoltageLimitsConfig, VoltageLimits, ConfigVoltageLimits]( + configVoltageLimits, + voltageLimit => voltageLimit.gridIds, + voltageLimit => voltageLimit.voltLvls, + voltageLimit => VoltageLimits(voltageLimit.vMin, voltageLimit.vMax), + (gridIds, voltLvls) => ConfigVoltageLimits(gridIds, voltLvls), + defaultVoltageLimits, + )("voltage limits") + } + +} diff --git a/src/main/scala/edu/ie3/simona/model/grid/GridModel.scala b/src/main/scala/edu/ie3/simona/model/grid/GridModel.scala index 16285b9317..c7f823a07d 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/GridModel.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/GridModel.scala @@ -39,6 +39,7 @@ final case class GridModel( subnetNo: Int, mainRefSystem: RefSystem, gridComponents: GridComponents, + voltageLimits: VoltageLimits, gridControls: GridControls, ) { @@ -66,12 +67,14 @@ object GridModel { def apply( subGridContainer: SubGridContainer, refSystem: RefSystem, + voltageLimits: VoltageLimits, startDate: ZonedDateTime, endDate: ZonedDateTime, simonaConfig: SimonaConfig, ): GridModel = buildAndValidate( subGridContainer, refSystem, + voltageLimits, startDate, endDate, simonaConfig, @@ -500,6 +503,7 @@ object GridModel { private def buildAndValidate( subGridContainer: SubGridContainer, refSystem: RefSystem, + voltageLimits: VoltageLimits, startDate: ZonedDateTime, endDate: ZonedDateTime, simonaConfig: SimonaConfig, @@ -600,6 +604,7 @@ object GridModel { subGridContainer.getSubnet, refSystem, gridComponents, + voltageLimits, GridControls(transformerControlGroups), ) diff --git a/src/main/scala/edu/ie3/simona/model/grid/VoltageLimits.scala b/src/main/scala/edu/ie3/simona/model/grid/VoltageLimits.scala new file mode 100644 index 0000000000..b03fce842d --- /dev/null +++ b/src/main/scala/edu/ie3/simona/model/grid/VoltageLimits.scala @@ -0,0 +1,41 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.model.grid + +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import tech.units.indriya.ComparableQuantity + +import javax.measure.quantity.Dimensionless + +/** Case class that holds information about the lower and upper voltage limit of + * the grid. + * @param vMin + * minimal allowed voltage in the grid + * @param vMax + * maximal allowed voltage in the grid + */ +final case class VoltageLimits( + vMin: ComparableQuantity[Dimensionless], + vMax: ComparableQuantity[Dimensionless], +) { + + /** Checks if a given voltage magnitude is within the given limits. + * @param voltage + * to check + * @return + * true if the given voltage magnitude is within the limits + */ + def isInLimits(voltage: ComparableQuantity[Dimensionless]): Boolean = + vMin.isLessThanOrEqualTo(voltage) && voltage.isLessThanOrEqualTo(vMax) +} + +object VoltageLimits { + def apply( + vMin: Double, + vMax: Double, + ): VoltageLimits = VoltageLimits(vMin.asPu, vMax.asPu) +} 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 9b06f604da..8939dcda43 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala @@ -17,11 +17,12 @@ import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData import edu.ie3.simona.config.RefSystemParser.ConfigRefSystems import edu.ie3.simona.config.SimonaConfig +import edu.ie3.simona.config.VoltageLimitsParser.ConfigVoltageLimits 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.model.grid.{RefSystem, VoltageLimits} import edu.ie3.simona.util.ConfigUtil.{GridOutputConfigUtil, OutputConfigUtil} import edu.ie3.simona.util.ResultFileHierarchy.ResultEntityPathConfig import edu.ie3.simona.util.{EntityMapperUtil, ResultFileHierarchy} @@ -62,6 +63,7 @@ trait SetupHelper extends LazyLogging { subGridToActorRef: Map[Int, ActorRef[GridAgent.Request]], gridGates: Set[SubGridGate], configRefSystems: ConfigRefSystems, + configVoltageLimits: ConfigVoltageLimits, thermalGrids: Seq[ThermalGrid], ): GridAgentInitData = { val subGridGateToActorRef = buildGateToActorRef( @@ -74,6 +76,8 @@ trait SetupHelper extends LazyLogging { val refSystem = getRefSystem(configRefSystems, subGridContainer) + val voltageLimits = getVoltageLimits(configVoltageLimits, subGridContainer) + /* Prepare the subgrid container for the agents by adapting the transformer high voltage nodes to be slacks */ val updatedSubGridContainer = ContainerUtils.withTrafoNodeAsSlack(subGridContainer) @@ -84,6 +88,7 @@ trait SetupHelper extends LazyLogging { thermalGrids, subGridGateToActorRef, refSystem, + voltageLimits, ) } @@ -200,6 +205,21 @@ trait SetupHelper extends LazyLogging { refSystem } + def getVoltageLimits( + configVoltageLimits: ConfigVoltageLimits, + subGridContainer: SubGridContainer, + ): VoltageLimits = configVoltageLimits + .find( + subGridContainer.getSubnet, + Some(subGridContainer.getPredominantVoltageLevel), + ) + .getOrElse( + throw new InitializationException( + s"Unable to determine voltage limits for grid with id ${subGridContainer.getSubnet} @ " + + s"volt level ${subGridContainer.getPredominantVoltageLevel}. Please either provide voltage limits for the grid id or the whole volt level!" + ) + ) + /** Build the result file hierarchy based on the provided configuration file. * The provided type safe config must be able to be parsed as * [[SimonaConfig]], otherwise an exception is thrown 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 0894ad3978..d790aa1b06 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -16,10 +16,14 @@ import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.agent.grid.GridAgentMessages.CreateGridAgent import edu.ie3.simona.api.ExtSimAdapter -import edu.ie3.simona.api.data.ExtDataConnection import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.simulation.ExtSimAdapterData -import edu.ie3.simona.config.{ArgsParser, RefSystemParser, SimonaConfig} +import edu.ie3.simona.config.{ + ArgsParser, + RefSystemParser, + SimonaConfig, + VoltageLimitsParser, +} import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.exceptions.agent.GridAgentInitializationException @@ -92,6 +96,9 @@ class SimonaStandaloneSetup( val configRefSystems = RefSystemParser.parse(simonaConfig.simona.gridConfig.refSystems) + val configVoltageLimits = + VoltageLimitsParser.parse(simonaConfig.simona.gridConfig.voltageLimits) + /* Create all agents and map the sub grid id to their actor references */ val subGridToActorRefMap = buildSubGridToActorRefMap( subGridTopologyGraph, @@ -141,6 +148,7 @@ class SimonaStandaloneSetup( subGridToActorRefMap, subGridGates, configRefSystems, + configVoltageLimits, thermalGrids, ) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala index 8d23af9c46..6ab5ee808e 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmCenGridSpec.scala @@ -16,7 +16,7 @@ import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} -import edu.ie3.simona.model.grid.RefSystem +import edu.ie3.simona.model.grid.{RefSystem, VoltageLimits} import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, @@ -116,6 +116,7 @@ class DBFSAlgorithmCenGridSpec Seq.empty[ThermalGrid], subGridGateToActorRef, RefSystem("2000 MVA", "110 kV"), + VoltageLimits(0.9, 1.1), ) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala index a1047edeff..d5ece47f75 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala @@ -15,7 +15,7 @@ import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ } import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} -import edu.ie3.simona.model.grid.RefSystem +import edu.ie3.simona.model.grid.{RefSystem, VoltageLimits} import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, @@ -100,6 +100,7 @@ class DBFSAlgorithmFailedPowerFlowSpec Seq.empty[ThermalGrid], subGridGateToActorRef, RefSystem("2000 MVA", "110 kV"), + VoltageLimits(0.9, 1.1), ) val key = @@ -312,6 +313,7 @@ class DBFSAlgorithmFailedPowerFlowSpec Seq.empty[ThermalGrid], subnetGatesToActorRef, RefSystem("5000 MVA", "380 kV"), + VoltageLimits(0.9, 1.1), ) val key = diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala index fa43ee2d1e..11d674b401 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmParticipantSpec.scala @@ -15,7 +15,7 @@ import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ } import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} -import edu.ie3.simona.model.grid.RefSystem +import edu.ie3.simona.model.grid.{RefSystem, VoltageLimits} import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, @@ -92,6 +92,7 @@ class DBFSAlgorithmParticipantSpec Seq.empty, subGridGateToActorRef, RefSystem("2000 MVA", "110 kV"), + VoltageLimits(0.9, 1.1), ) val key = diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala index beb1408270..f81b92ea51 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmSupGridSpec.scala @@ -14,7 +14,7 @@ import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.ExchangePower import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} -import edu.ie3.simona.model.grid.RefSystem +import edu.ie3.simona.model.grid.{RefSystem, VoltageLimits} import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, @@ -87,6 +87,7 @@ class DBFSAlgorithmSupGridSpec Seq.empty[ThermalGrid], subnetGatesToActorRef, RefSystem("5000 MVA", "380 kV"), + VoltageLimits(0.9, 1.1), ) val key = diff --git a/src/test/scala/edu/ie3/simona/agent/grid/PowerFlowSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/PowerFlowSupportSpec.scala index d909ec4d61..89614f68e2 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/PowerFlowSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/PowerFlowSupportSpec.scala @@ -23,7 +23,7 @@ import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ ExchangeVoltage, } import edu.ie3.simona.config.SimonaConfig.Simona -import edu.ie3.simona.model.grid.{GridModel, RefSystem} +import edu.ie3.simona.model.grid.{GridModel, RefSystem, VoltageLimits} import edu.ie3.simona.test.common.model.grid.{ BasicGridWithSwitches, DbfsTestGrid, @@ -469,6 +469,7 @@ class PowerFlowSupportSpec GridModel( subGridContainer, RefSystem("2000 MVA", "110 kV"), + VoltageLimits(0.9, 1.1), time.startDateTime, time.endDateTime, simonaConfig, diff --git a/src/test/scala/edu/ie3/simona/model/grid/GridSpec.scala b/src/test/scala/edu/ie3/simona/model/grid/GridSpec.scala index 45b0e5a837..3d7d81d6c8 100644 --- a/src/test/scala/edu/ie3/simona/model/grid/GridSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/grid/GridSpec.scala @@ -215,6 +215,7 @@ class GridSpec Set.empty[Transformer3wModel], switches, ), + defaultVoltageLimits, GridControls.empty, ) // get the private method for validation @@ -251,6 +252,7 @@ class GridSpec Set.empty[Transformer3wModel], switches, ), + defaultVoltageLimits, GridControls.empty, ) @@ -355,6 +357,7 @@ class GridSpec Set.empty[Transformer3wModel], switches, ), + defaultVoltageLimits, GridControls.empty, ) @@ -407,6 +410,7 @@ class GridSpec Set.empty[Transformer3wModel], Set.empty[SwitchModel], ), + defaultVoltageLimits, GridControls.empty, ) @@ -460,6 +464,7 @@ class GridSpec Set.empty[Transformer3wModel], switches, ), + defaultVoltageLimits, GridControls.empty, ) @@ -540,6 +545,7 @@ class GridSpec Set.empty, switches, ), + defaultVoltageLimits, GridControls.empty, ) @@ -643,6 +649,7 @@ class GridSpec GridModel( validTestGridInputModel, gridInputModelTestDataRefSystem, + defaultVoltageLimits, defaultSimulationStart, defaultSimulationEnd, simonaConfig, diff --git a/src/test/scala/edu/ie3/simona/model/grid/TransformerModelSpec.scala b/src/test/scala/edu/ie3/simona/model/grid/TransformerModelSpec.scala index 880ca12116..5f7becad11 100644 --- a/src/test/scala/edu/ie3/simona/model/grid/TransformerModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/grid/TransformerModelSpec.scala @@ -379,6 +379,7 @@ class TransformerModelSpec val gridModel = GridModel( grid, refSystem, + defaultVoltageLimits, defaultSimulationStart, defaultSimulationEnd, simonaConfig, diff --git a/src/test/scala/edu/ie3/simona/test/common/DefaultTestData.scala b/src/test/scala/edu/ie3/simona/test/common/DefaultTestData.scala index b2ddb887a7..0c026e2f66 100644 --- a/src/test/scala/edu/ie3/simona/test/common/DefaultTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/DefaultTestData.scala @@ -10,7 +10,7 @@ import com.typesafe.config.{Config, ConfigFactory} import edu.ie3.datamodel.models.OperationTime import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.model.SystemComponent -import edu.ie3.simona.model.grid.RefSystem +import edu.ie3.simona.model.grid.{RefSystem, VoltageLimits} import edu.ie3.simona.model.participant.load.{LoadModelBehaviour, LoadReference} import edu.ie3.util.scala.OperationInterval import org.locationtech.jts.geom.{Coordinate, GeometryFactory, Point} @@ -63,6 +63,8 @@ trait DefaultTestData { Kilovolts(10d), ) + protected val defaultVoltageLimits: VoltageLimits = VoltageLimits(0.9, 1.1) + /** Creates a [[SimonaConfig]], that provides the desired participant model * configurations * diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/BasicGridWithSwitches.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/BasicGridWithSwitches.scala index 5781848e1b..3b0af96454 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/BasicGridWithSwitches.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/BasicGridWithSwitches.scala @@ -231,6 +231,7 @@ trait BasicGridWithSwitches extends BasicGrid { Set.empty[Transformer3wModel], gridSwitches, ), + defaultVoltageLimits, GridControls.empty, ) } diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/TransformerTestData.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/TransformerTestData.scala index 26f26cc141..c4dddcf25e 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/TransformerTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/TransformerTestData.scala @@ -8,8 +8,7 @@ package edu.ie3.simona.test.common.model.grid import breeze.math.Complex import edu.ie3.datamodel.models.input.connector.ConnectorPort -import edu.ie3.simona.model.grid.RefSystem -import edu.ie3.util.quantities.PowerSystemUnits._ +import edu.ie3.simona.model.grid.{RefSystem, VoltageLimits} import org.scalatest.prop.TableDrivenPropertyChecks.Table import org.scalatest.prop.{TableFor5, TableFor9} import squants.electro.Kilovolts @@ -28,11 +27,13 @@ trait TransformerTestData extends TransformerTestGrid { /* Define some parameters for the power flow calculation */ val epsilon = 1e-10 val maxIterations = 50 - val refSystem: RefSystem = + val refSystem: RefSystem = { RefSystem( Kilowatts(400d), Kilovolts(0.4d), ) + } + val defaultVoltageLimits: VoltageLimits = VoltageLimits(0.9, 1.1) val nodeUuidToIndexMap: Map[UUID, Int] = gridTapHv.getRawGrid.getNodes.asScala .map(node => node.getUuid -> node.getSubnet) From 4e3b05e0dc6fd754157df3da470a04505933fca0 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 5 Feb 2025 12:34:43 +0100 Subject: [PATCH 02/22] Fixing duplicate lines. --- docs/readthedocs/models.md | 1 + .../ie3/simona/config/ConfigFailFast.scala | 172 ++++++++---------- 2 files changed, 80 insertions(+), 93 deletions(-) diff --git a/docs/readthedocs/models.md b/docs/readthedocs/models.md index c9a1f870f2..1218e2a66c 100644 --- a/docs/readthedocs/models.md +++ b/docs/readthedocs/models.md @@ -17,6 +17,7 @@ models/three_winding_transformer_model models/reference_system models/thermal_grid_model models/thermal_house_model +models/voltage_limits ``` ## System Participant Related Models diff --git a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala index c16570ab9b..baea5ca9e3 100644 --- a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala +++ b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala @@ -457,69 +457,30 @@ object ConfigFailFast extends LazyLogging { * @param refSystems * a list of [[SimonaConfig.RefSystemConfig]]s that should be checked */ - - private def checkRefSystem(refSystems: List[RefSystemConfig]): Unit = { + private def checkRefSystem( + refSystems: List[RefSystemConfig] + ): Unit = { refSystems.foreach { refSystem => - { - val voltLvls = - refSystem.voltLvls.getOrElse(List.empty[SimonaConfig.VoltLvlConfig]) - val gridIds = refSystem.gridIds.getOrElse(List.empty[String]) + checkVoltageLvlsAndGridIds( + refSystem.voltLvls, + refSystem.gridIds, + refSystem, + "refSystem", + ) - if (voltLvls.isEmpty && gridIds.isEmpty) + refSystem.sNom match { + case ConfigConventions.refSystemQuantRegex(_) => + case _ => throw new InvalidConfigParameterException( - "The provided values for voltLvls and gridIds are empty! " + - s"At least one of these optional parameters has to be provided for a valid refSystem! " + - s"Provided refSystem is: $refSystem." + s"Invalid value for sNom from provided refSystem $refSystem. Is a valid unit provided?" ) - - voltLvls.foreach { voltLvl => - Try(Quantities.getQuantity(voltLvl.vNom)) match { - case Success(quantity) => - if (!quantity.getUnit.isCompatible(Units.VOLT)) - throw new InvalidConfigParameterException( - s"The given nominal voltage '${voltLvl.vNom}' cannot be parsed to electrical potential! Please provide the volt level with its unit, e.g. \"20 kV\"" - ) - case Failure(exception) => - throw new InvalidConfigParameterException( - s"The given nominal voltage '${voltLvl.vNom}' cannot be parsed to a quantity. Did you provide the volt level with it's unit (e.g. \"20 kV\")?", - exception, - ) - } - } - - gridIds.foreach { - case gridIdRange @ ConfigConventions.gridIdDotRange(from, to) => - rangeCheck(from.toInt, to.toInt, gridIdRange) - case gridIdRange @ ConfigConventions.gridIdMinusRange(from, to) => - rangeCheck(from.toInt, to.toInt, gridIdRange) - case ConfigConventions.singleGridId(_) => - case gridId => - throw new InvalidConfigParameterException( - s"The provided gridId $gridId is malformed!" - ) - } - - refSystem.sNom match { - case ConfigConventions.refSystemQuantRegex(_) => - case _ => - throw new InvalidConfigParameterException( - s"Invalid value for sNom from provided refSystem $refSystem. Is a valid unit provided?" - ) - } - - refSystem.vNom match { - case ConfigConventions.refSystemQuantRegex(_) => - case _ => - throw new InvalidConfigParameterException( - s"Invalid value for vNom from provided refSystem $refSystem. Is a valid unit provided?" - ) - } } - def rangeCheck(from: Int, to: Int, gridIdRange: String): Unit = { - if (from >= to) + refSystem.vNom match { + case ConfigConventions.refSystemQuantRegex(_) => + case _ => throw new InvalidConfigParameterException( - s"Invalid gridId Range $gridIdRange. Start $from cannot be equals or bigger than end $to." + s"Invalid value for vNom from provided refSystem $refSystem. Is a valid unit provided?" ) } } @@ -532,52 +493,77 @@ object ConfigFailFast extends LazyLogging { */ private def checkVoltageLimits( voltageLimits: List[VoltageLimitsConfig] - ): Unit = { + ): Unit = voltageLimits.foreach { limit => - val voltLvls = - limit.voltLvls.getOrElse(List.empty[SimonaConfig.VoltLvlConfig]) - val gridIds = limit.gridIds.getOrElse(List.empty[String]) + checkVoltageLvlsAndGridIds( + limit.voltLvls, + limit.gridIds, + limit, + "voltage limit", + ) + } - if (voltLvls.isEmpty && gridIds.isEmpty) - throw new InvalidConfigParameterException( - "The provided values for voltLvls and gridIds are empty! " + - s"At least one of these optional parameters has to be provided for valid voltage limits! " + - s"Provided voltage limits are: $voltageLimits." - ) + /** Method to check the common elements of a + * [[SimonaConfig.Simona.GridConfig]]. + * @param voltLvlOption + * option for [[VoltLvlConfig]] + * @param gridIdOption + * option for grid ids + * @param config + * the individual config + * @param configType + * the type of config (e.g. refSystem) + * @tparam C + * type fo + */ + private def checkVoltageLvlsAndGridIds[C]( + voltLvlOption: Option[List[VoltLvlConfig]], + gridIdOption: Option[List[String]], + config: C, + configType: String, + ): Unit = { + val voltLvls = voltLvlOption.getOrElse(List.empty) + val gridIds = gridIdOption.getOrElse(List.empty) - voltLvls.foreach { voltLvl => - Try(Quantities.getQuantity(voltLvl.vNom)) match { - case Success(quantity) => - if (!quantity.getUnit.isCompatible(Units.VOLT)) - throw new InvalidConfigParameterException( - s"The given nominal voltage '${voltLvl.vNom}' cannot be parsed to electrical potential! Please provide the volt level with its unit, e.g. \"20 kV\"" - ) - case Failure(exception) => + if (voltLvls.isEmpty && gridIds.isEmpty) + throw new InvalidConfigParameterException( + "The provided values for voltLvls and gridIds are empty! " + + s"At least one of these optional parameters has to be provided for a valid $configType! " + + s"Provided $configType is: $config." + ) + + voltLvls.foreach { voltLvl => + Try(Quantities.getQuantity(voltLvl.vNom)) match { + case Success(quantity) => + if (!quantity.getUnit.isCompatible(Units.VOLT)) throw new InvalidConfigParameterException( - s"The given nominal voltage '${voltLvl.vNom}' cannot be parsed to a quantity. Did you provide the volt level with it's unit (e.g. \"20 kV\")?", - exception, + s"The given nominal voltage '${voltLvl.vNom}' cannot be parsed to electrical potential! Please provide the volt level with its unit, e.g. \"20 kV\"" ) - } - } - - gridIds.foreach { - case gridIdRange @ ConfigConventions.gridIdDotRange(from, to) => - rangeCheck(from.toInt, to.toInt, gridIdRange) - case gridIdRange @ ConfigConventions.gridIdMinusRange(from, to) => - rangeCheck(from.toInt, to.toInt, gridIdRange) - case ConfigConventions.singleGridId(_) => - case gridId => + case Failure(exception) => throw new InvalidConfigParameterException( - s"The provided gridId $gridId is malformed!" + s"The given nominal voltage '${voltLvl.vNom}' cannot be parsed to a quantity. Did you provide the volt level with it's unit (e.g. \"20 kV\")?", + exception, ) } + } - def rangeCheck(from: Int, to: Int, gridIdRange: String): Unit = { - if (from >= to) - throw new InvalidConfigParameterException( - s"Invalid gridId Range $gridIdRange. Start $from cannot be equals or bigger than end $to." - ) - } + gridIds.foreach { + case gridIdRange @ ConfigConventions.gridIdDotRange(from, to) => + rangeCheck(from.toInt, to.toInt, gridIdRange) + case gridIdRange @ ConfigConventions.gridIdMinusRange(from, to) => + rangeCheck(from.toInt, to.toInt, gridIdRange) + case ConfigConventions.singleGridId(_) => + case gridId => + throw new InvalidConfigParameterException( + s"The provided gridId $gridId is malformed!" + ) + } + + def rangeCheck(from: Int, to: Int, gridIdRange: String): Unit = { + if (from >= to) + throw new InvalidConfigParameterException( + s"Invalid gridId Range $gridIdRange. Start $from cannot be equals or bigger than end $to." + ) } } From f1df187eea542ab372ba21b04978dc8990634d01 Mon Sep 17 00:00:00 2001 From: Philipp Schmelter Date: Wed, 5 Feb 2025 17:39:53 +0100 Subject: [PATCH 03/22] Debug --- .../listener/ResultEventListenerSpec.scala | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) 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 68caa1706b..c80fb488ab 100644 --- a/src/test/scala/edu/ie3/simona/event/listener/ResultEventListenerSpec.scala +++ b/src/test/scala/edu/ie3/simona/event/listener/ResultEventListenerSpec.scala @@ -402,6 +402,9 @@ class ResultEventListenerSpec max = timeoutDuration, ) + // Debug: Check if the file exists + assert(outputFile.exists(), "Output file does not exist") + // stopping the actor should wait until existing messages within an actor are fully processed // otherwise it might happen, that the shutdown is triggered even before the just send ParticipantResultEvent // reached the listener @@ -425,18 +428,20 @@ class ResultEventListenerSpec timeoutDuration, ) + // Debug: Check if the compressed file exists + val compressedFile = specificOutputFileHierarchy.rawOutputDataFilePaths + .getOrElse( + classOf[PvResult], + fail( + s"Cannot get filepath for raw result file of class '${classOf[PvResult].getSimpleName}' from outputFileHierarchy!'" + ), + ) + .toFile + assert(compressedFile.exists(), "Compressed file does not exist") + 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!'" - ), - ) - .toFile - ) + new FileInputStream(compressedFile) ) ) From 92430aa687d497c723ee000bbbc70706c419e092 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 7 Feb 2025 10:47:56 +0100 Subject: [PATCH 04/22] Enhancing `ConfigFailFast`. --- .../ie3/simona/config/ConfigFailFast.scala | 9 +- .../simona/config/ConfigFailFastSpec.scala | 132 ++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala index baea5ca9e3..110eb7939b 100644 --- a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala +++ b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala @@ -493,7 +493,7 @@ object ConfigFailFast extends LazyLogging { */ private def checkVoltageLimits( voltageLimits: List[VoltageLimitsConfig] - ): Unit = + ): Unit = { voltageLimits.foreach { limit => checkVoltageLvlsAndGridIds( limit.voltLvls, @@ -501,7 +501,14 @@ object ConfigFailFast extends LazyLogging { limit, "voltage limit", ) + + if (limit.vMin > limit.vMax) { + throw new InvalidConfigParameterException( + s"Invalid value for vMin and vMax from provided voltage limit $limit. Is vMin smaller than vMax?" + ) + } } + } /** Method to check the common elements of a * [[SimonaConfig.Simona.GridConfig]]. diff --git a/src/test/scala/edu/ie3/simona/config/ConfigFailFastSpec.scala b/src/test/scala/edu/ie3/simona/config/ConfigFailFastSpec.scala index f86b0b47c0..7c803265e0 100644 --- a/src/test/scala/edu/ie3/simona/config/ConfigFailFastSpec.scala +++ b/src/test/scala/edu/ie3/simona/config/ConfigFailFastSpec.scala @@ -283,6 +283,138 @@ class ConfigFailFastSpec extends UnitSpec with ConfigTestData { } } + "A configuration with faulty voltage limit parameters" should { + val checkVoltageLimits = + PrivateMethod[Unit](Symbol("checkVoltageLimits")) + + "throw an InvalidConfigParametersException when gridIds and voltLvls are empty" in { + + val voltageLimitsConfigAllEmpty = ConfigFactory.parseString( + "simona.gridConfig.voltageLimits = [{vMin=\"0.9\", vMax=\"1.1\"}]" + ) + val faultyConfig = + voltageLimitsConfigAllEmpty.withFallback(typesafeConfig).resolve() + val faultySimonaConfig = SimonaConfig(faultyConfig) + + intercept[InvalidConfigParameterException] { + ConfigFailFast.check(faultySimonaConfig) + }.getMessage shouldBe "The provided values for voltLvls and gridIds are empty! " + + "At least one of these optional parameters has to be provided for a valid voltage limit! " + + "Provided voltage limit is: VoltageLimitsConfig(None,1.1,0.9,None)." + + } + + "throw an InvalidConfigParametersException when the gridId is malformed" in { + + val malformedGridIds = List("10--100", "MV", "10..100") + + malformedGridIds.foreach(malformedGridId => { + + val voltageLimitsConfig = + ConfigFactory.parseString(s"""simona.gridConfig.voltageLimits = [ + | { + | vMin="0.9", + | vMax="1.1", + | gridIds = [$malformedGridId] + | } + |]""".stripMargin) + val faultyConfig = + voltageLimitsConfig.withFallback(typesafeConfig).resolve() + val faultySimonaConfig: List[SimonaConfig] = + List(SimonaConfig(faultyConfig)) + + intercept[InvalidConfigParameterException] { + faultySimonaConfig.foreach(conf => + conf.simona.gridConfig.voltageLimits.foreach(refSystem => + ConfigFailFast invokePrivate checkVoltageLimits(refSystem) + ) + ) + }.getMessage shouldBe s"The provided gridId $malformedGridId is malformed!" + + }) + } + + "throw an InvalidConfigParameterException if the nominal voltage of the voltage level is malformed" in { + + val voltageLimitsConfig = + ConfigFactory.parseString("""simona.gridConfig.voltageLimits = [ + | { + | vMin="0.9", + | vMax="1.1", + | voltLvls = [{id = "1", vNom = "foo"}] + | } + |]""".stripMargin) + val faultyConfig = + voltageLimitsConfig.withFallback(typesafeConfig).resolve() + val faultySimonaConfig: List[SimonaConfig] = + List(SimonaConfig(faultyConfig)) + + intercept[InvalidConfigParameterException] { + faultySimonaConfig.foreach(conf => + conf.simona.gridConfig.voltageLimits.foreach(refSystem => + ConfigFailFast invokePrivate checkVoltageLimits(refSystem) + ) + ) + }.getMessage shouldBe "The given nominal voltage 'foo' cannot be parsed to a quantity. Did you provide the volt level with it's unit (e.g. \"20 kV\")?" + + } + + "throw an InvalidConfigParametersException when vMin is greater than vMax" in { + val voltageLimitsConfig = + ConfigFactory.parseString( + """simona.gridConfig.voltageLimits = [ + | { + | vMin="1.1", + | vMax="1.0", + | voltLvls = [{id = "MV", vNom = "10 kV"},{id = "HV", vNom = "110 kV"}] + | } + |]""".stripMargin + ) + val faultyConfig = + voltageLimitsConfig.withFallback(typesafeConfig).resolve() + val faultySimonaConfig: List[SimonaConfig] = + List(SimonaConfig(faultyConfig)) + + intercept[InvalidConfigParameterException] { + faultySimonaConfig.foreach(conf => + conf.simona.gridConfig.voltageLimits.foreach(refSystem => + ConfigFailFast invokePrivate checkVoltageLimits(refSystem) + ) + ) + }.getMessage shouldBe "Invalid value for vMin and vMax from provided voltage limit VoltageLimitsConfig(None,1.0,1.1,Some(List(VoltLvlConfig(MV,10 kV), VoltLvlConfig(HV,110 kV)))). Is vMin smaller than vMax?" + } + + "work as expected for correctly provided data" in { + val voltageLimitsConfig = + ConfigFactory.parseString( + """simona.gridConfig.voltageLimits = [ + | { + | vMin="0.9", + | vMax="1.1", + | voltLvls = [{id = "MV", vNom = "10 kV"},{id = "HV", vNom = "110 kV"}] + | gridIds = ["1","1-10","10...100"] + | }, + | { + | vMin="0.9", + | vMax="1.1", + | voltLvls = [{id = "HV", vNom = "110 kV"},{id = "EHV", vNom = "380 kV"}] + | gridIds = ["1-3","3...6","10...100"] + | } + |]""".stripMargin + ) + val config = + voltageLimitsConfig.withFallback(typesafeConfig).resolve() + val simonaConfig = List(SimonaConfig(config)) + + simonaConfig.foreach(conf => + conf.simona.gridConfig.voltageLimits.foreach(refSystem => { + ConfigFailFast invokePrivate checkVoltageLimits(refSystem) + }) + ) + + } + } + "Checking a participant model config" should { val checkParticipantRuntimeConfiguration = PrivateMethod[Unit](Symbol("checkParticipantRuntimeConfiguration")) From e2fffe81c954867b167df2fe0eec9cfe40b578df Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 10 Feb 2025 10:04:06 +0100 Subject: [PATCH 05/22] Addressing reviewer's comments. --- docs/readthedocs/models/voltage_limits.md | 6 +++--- .../edu/ie3/simona/agent/grid/GridAgentData.scala | 2 +- .../edu/ie3/simona/config/ConfigFailFast.scala | 2 +- .../edu/ie3/simona/config/GridConfigParser.scala | 14 +++++++------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/readthedocs/models/voltage_limits.md b/docs/readthedocs/models/voltage_limits.md index c5fae5b90a..27ebb8c61b 100644 --- a/docs/readthedocs/models/voltage_limits.md +++ b/docs/readthedocs/models/voltage_limits.md @@ -3,7 +3,7 @@ # Voltage Limits The voltage limits are built up by specifying the included voltage levels. The following table describes typical network -levels and the corresponding parameterization. They are primarily used for the optional congestion management. +levels and their parameterization. They are primarily used for the optional congestion management. ## Default voltage limits @@ -48,8 +48,8 @@ levels and the corresponding parameterization. They are primarily used for the o ## Configuration of the voltage limits To configure the voltage limits, the voltage levels listed in the table above must be selected and integrated into the -config. The individual voltage level configuration (voltLvls) according to the example below. Each voltage level is composed -of the identifier and the nominal voltage. +config. The individual voltage level configuration (voltLvls) according to the example below. Each voltage level consists +of an identifier and the nominal voltage. ``` simona.gridConfig.voltageLimits = [ diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala index 112428deca..4f725cfd81 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -72,7 +72,7 @@ object GridAgentData { * @param refSystem * of the grid * @param voltageLimits - * of the grid + * of the grid, used to evaluate voltage congestion */ final case class GridAgentInitData( subGridContainer: SubGridContainer, diff --git a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala index 110eb7939b..9528dd68aa 100644 --- a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala +++ b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala @@ -502,7 +502,7 @@ object ConfigFailFast extends LazyLogging { "voltage limit", ) - if (limit.vMin > limit.vMax) { + if (limit.vMin >= limit.vMax) { throw new InvalidConfigParameterException( s"Invalid value for vMin and vMax from provided voltage limit $limit. Is vMin smaller than vMax?" ) diff --git a/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala b/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala index 1799430e66..2c2fb7c4d1 100644 --- a/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala +++ b/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala @@ -13,25 +13,25 @@ import edu.ie3.simona.util.CollectionUtils import edu.ie3.util.quantities.PowerSystemUnits object GridConfigParser { - abstract class GridConfig[GC]( - protected val gridIdMap: Map[Int, GC], - protected val voltLvlMap: Map[VoltageLevel, GC], + abstract class GridConfig[GridConfig]( + protected val gridIdMap: Map[Int, GridConfig], + protected val voltLvlMap: Map[VoltageLevel, GridConfig], ) { - /** Returns a [[GC]] based on the provided gridId or the voltLvl as fallback - * if available + /** Returns a [[GridConfig]] based on the provided gridId or the voltLvl as + * fallback if available * * @param gridId * the gridId the refSystem is wanted for * @param voltLvl - * the voltLvL that is valid for the grid that is wanted + * the voltLvL that is valid for this grid * @return * Some(refSystem) if available or None if unavailable */ final def find( gridId: Int, voltLvl: Option[VoltageLevel] = None, - ): Option[GC] = + ): Option[GridConfig] = gridIdMap .get(gridId) .orElse(voltLvl.flatMap(voltLvlMap.get)) From a0a6030624f56a24bdb28f74e4105273017ee43f Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 10 Feb 2025 10:45:36 +0100 Subject: [PATCH 06/22] Addressing reviewer's comments. --- .../scala/edu/ie3/simona/config/GridConfigParser.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala b/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala index 2c2fb7c4d1..2f66e406cc 100644 --- a/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala +++ b/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala @@ -37,14 +37,14 @@ object GridConfigParser { .orElse(voltLvl.flatMap(voltLvlMap.get)) } - def parseWithDefaults[C, E, GC]( + def parseWithDefaults[C, E, GridConfig]( configs: Option[List[C]], gridIds: C => Option[List[String]], voltLvls: C => Option[List[VoltLvlConfig]], elementFcn: C => E, - builder: (Map[Int, E], Map[VoltageLevel, E]) => GC, - defaults: GC, - )(implicit gridConfigType: String): GC = configs match { + builder: (Map[Int, E], Map[VoltageLevel, E]) => GridConfig, + defaults: GridConfig, + )(implicit gridConfigType: String): GridConfig = configs match { case Some(configElements) if configElements.nonEmpty => // units for parsing are not initialized by default // hence we call them manually From 502eb6c026baa5790ab861d747da21f973c4cd2a Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 12 Feb 2025 13:00:48 +0100 Subject: [PATCH 07/22] Adding references for voltage limits. --- .../_static/bibliography/bibtexAll.bib | 17 ++++++++++++++++- docs/readthedocs/models/voltage_limits.md | 4 ++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/readthedocs/_static/bibliography/bibtexAll.bib b/docs/readthedocs/_static/bibliography/bibtexAll.bib index 8761d828b4..81c8cea91d 100644 --- a/docs/readthedocs/_static/bibliography/bibtexAll.bib +++ b/docs/readthedocs/_static/bibliography/bibtexAll.bib @@ -190,4 +190,19 @@ @MISC{Radiation_ECMWF author = {Robin Hogan}, title = {Radiation Quantities in the ECMWF model and MARS}, howpublished = {\url{https://www.ecmwf.int/sites/default/files/elibrary/2015/18490-radiation-quantities-ecmwf-model-and-mars.pdf}} -} \ No newline at end of file +} + +@misc{EN_50160, + title = {EN 50160:2020-11, Voltage characteristics of electricity supplied by public electricity networks}, + publisher = {CENELEC}, + type = {NORM}, + year = {2022}, + date = {2022-12-01}, +} + +@online{EU_2017/1485, + author = {{Die Europäische Kommission}}, + title = {COMMISSION REGULATION (EU) 2017/1485 of 2 August 2017 establishing a guideline on electricity transmission system operation}, + date = {2017-08-02}, + url = {https://eur-lex.europa.eu/legal-content/DE/TXT/?uri=CELEX%3A32017R1485}, +} diff --git a/docs/readthedocs/models/voltage_limits.md b/docs/readthedocs/models/voltage_limits.md index 27ebb8c61b..0b54bd5261 100644 --- a/docs/readthedocs/models/voltage_limits.md +++ b/docs/readthedocs/models/voltage_limits.md @@ -44,6 +44,10 @@ levels and their parameterization. They are primarily used for the optional cong - 1.05 p.u. ``` +**References:** + +* {cite:cts}`EN_50160` +* {cite:cts}`EU_2017/1485` ## Configuration of the voltage limits From dd3bd03bd5767695f5cef91fe9b3cf7215f579b2 Mon Sep 17 00:00:00 2001 From: Marvin Heintze Date: Thu, 13 Feb 2025 16:09:27 +0100 Subject: [PATCH 08/22] changed pvInput values and corresponding expected results --- .../edu/ie3/simona/agent/em/EmAgentIT.scala | 26 +++++++++---------- .../test/common/input/PvInputTestData.scala | 10 +++---- 2 files changed, 18 insertions(+), 18 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 599fe4aa4b..2fbc28335e 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -285,9 +285,9 @@ class EmAgentIT emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 0L.toDateTime emResult.getP should equalWithTolerance( - -0.00034885012.asMegaWatt + -0.03283549601246891.asMegaWatt ) - emResult.getQ should equalWithTolerance(0.00008828554.asMegaVar) + emResult.getQ should equalWithTolerance(0.0000882855367033582.asMegaVar) } scheduler.expectMessage(Completion(emAgentActivation, Some(7200))) @@ -318,13 +318,13 @@ class EmAgentIT case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 7200.toDateTime - emResult.getP should equalWithTolerance(0.asMegaWatt) + emResult.getP should equalWithTolerance(-0.020583738053224127.asMegaWatt) emResult.getQ should equalWithTolerance(0.0000882855367.asMegaVar) } - scheduler.expectMessage(Completion(emAgentActivation, Some(13362))) + scheduler.expectMessage(Completion(emAgentActivation, Some(11368))) - /* TICK 13362 + /* TICK 11368 LOAD: 0.269 kW (unchanged) PV: -3.651 kW (unchanged) STORAGE: SOC 100 % @@ -332,14 +332,14 @@ class EmAgentIT -> remaining -3.382 kW */ - emAgentActivation ! Activation(13362) + emAgentActivation ! Activation(11368) resultListener.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid - emResult.getTime shouldBe 13362L.toDateTime + emResult.getTime shouldBe 11368L.toDateTime emResult.getP should equalWithTolerance( - -0.003382375474.asMegaWatt + -0.025583738053224125.asMegaWatt ) emResult.getQ should equalWithTolerance(0.0000882855367.asMegaVar) } @@ -374,7 +374,7 @@ class EmAgentIT case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 14400L.toDateTime - emResult.getP should equalWithTolerance(0.asMegaWatt) + emResult.getP should equalWithTolerance(-0.0001779479755506383.asMegaWatt) emResult.getQ should equalWithTolerance(0.000088285537.asMegaVar) } @@ -586,7 +586,7 @@ class EmAgentIT emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 0.toDateTime emResult.getP should equalWithTolerance( - -0.000498850118.asMegaWatt + -0.032985496012468904.asMegaWatt ) emResult.getQ should equalWithTolerance(0.001073120041.asMegaVar) } @@ -621,7 +621,7 @@ class EmAgentIT case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 7200.toDateTime - emResult.getP should equalWithTolerance(0.001467624526.asMegaWatt) + emResult.getP should equalWithTolerance(-0.020733738053224125.asMegaWatt) emResult.getQ should equalWithTolerance(0.001073120041.asMegaVar) } @@ -656,7 +656,7 @@ class EmAgentIT case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 14400L.toDateTime - emResult.getP should equalWithTolerance(0.000202956264.asMegaWatt) + emResult.getP should equalWithTolerance(-0.0001779479755506383.asMegaWatt) emResult.getQ should equalWithTolerance(0.000088285537.asMegaVar) } @@ -691,7 +691,7 @@ class EmAgentIT case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 21600.toDateTime - emResult.getP should equalWithTolerance(0.000242284024.asMegaWatt) + emResult.getP should equalWithTolerance(0.0001305120025875211.asMegaWatt) emResult.getQ should equalWithTolerance(0.000088285537.asMegaVar) } diff --git a/src/test/scala/edu/ie3/simona/test/common/input/PvInputTestData.scala b/src/test/scala/edu/ie3/simona/test/common/input/PvInputTestData.scala index c7d18b2474..2acc0bdbdb 100644 --- a/src/test/scala/edu/ie3/simona/test/common/input/PvInputTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/input/PvInputTestData.scala @@ -44,12 +44,12 @@ trait PvInputTestData nodeInputNoSlackNs04KvA, CosPhiFixed.CONSTANT_CHARACTERISTIC, null, - 1, + 0.2, Quantities.getQuantity(12, StandardUnits.AZIMUTH), - Quantities.getQuantity(10, StandardUnits.EFFICIENCY), - Quantities.getQuantity(100, StandardUnits.SOLAR_ELEVATION_ANGLE), - 12, - 11, + Quantities.getQuantity(90, StandardUnits.EFFICIENCY), + Quantities.getQuantity(45, StandardUnits.SOLAR_ELEVATION_ANGLE), + 0.9, + 1.0, false, Quantities.getQuantity(10, StandardUnits.S_RATED), 0.95, From 6c77011af8f078332afb9a58965cada2e8eb2744 Mon Sep 17 00:00:00 2001 From: Marvin Heintze Date: Thu, 13 Feb 2025 16:20:01 +0100 Subject: [PATCH 09/22] spotless and changelog --- CHANGELOG.md | 1 + .../edu/ie3/simona/agent/em/EmAgentIT.scala | 24 ++++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9070d22b39..482df127ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refactored EM messages [#1138](https://github.com/ie3-institute/simona/issues/1138) - `OperationInterval` should extend `RightOpenInterval` [#1142](https://github.com/ie3-institute/simona/issues/1142) - Enhance EmAggregate of SelfOpt to cope with other targetLimits [#1131](https://github.com/ie3-institute/simona/issues/1131) +- Changed `pvInput` values in `PvInputTestData` to more realistic values [#1144](https://github.com/ie3-institute/simona/issues/1144) ### 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/agent/em/EmAgentIT.scala b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala index 2fbc28335e..3bacd23d10 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -287,7 +287,9 @@ class EmAgentIT emResult.getP should equalWithTolerance( -0.03283549601246891.asMegaWatt ) - emResult.getQ should equalWithTolerance(0.0000882855367033582.asMegaVar) + emResult.getQ should equalWithTolerance( + 0.0000882855367033582.asMegaVar + ) } scheduler.expectMessage(Completion(emAgentActivation, Some(7200))) @@ -318,7 +320,9 @@ class EmAgentIT case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 7200.toDateTime - emResult.getP should equalWithTolerance(-0.020583738053224127.asMegaWatt) + emResult.getP should equalWithTolerance( + -0.020583738053224127.asMegaWatt + ) emResult.getQ should equalWithTolerance(0.0000882855367.asMegaVar) } @@ -374,7 +378,9 @@ class EmAgentIT case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 14400L.toDateTime - emResult.getP should equalWithTolerance(-0.0001779479755506383.asMegaWatt) + emResult.getP should equalWithTolerance( + -0.0001779479755506383.asMegaWatt + ) emResult.getQ should equalWithTolerance(0.000088285537.asMegaVar) } @@ -621,7 +627,9 @@ class EmAgentIT case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 7200.toDateTime - emResult.getP should equalWithTolerance(-0.020733738053224125.asMegaWatt) + emResult.getP should equalWithTolerance( + -0.020733738053224125.asMegaWatt + ) emResult.getQ should equalWithTolerance(0.001073120041.asMegaVar) } @@ -656,7 +664,9 @@ class EmAgentIT case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 14400L.toDateTime - emResult.getP should equalWithTolerance(-0.0001779479755506383.asMegaWatt) + emResult.getP should equalWithTolerance( + -0.0001779479755506383.asMegaWatt + ) emResult.getQ should equalWithTolerance(0.000088285537.asMegaVar) } @@ -691,7 +701,9 @@ class EmAgentIT case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 21600.toDateTime - emResult.getP should equalWithTolerance(0.0001305120025875211.asMegaWatt) + emResult.getP should equalWithTolerance( + 0.0001305120025875211.asMegaWatt + ) emResult.getQ should equalWithTolerance(0.000088285537.asMegaVar) } From e84eceac71870f46a2b861b208ede6927a9adbb1 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Thu, 13 Feb 2025 18:36:16 +0100 Subject: [PATCH 10/22] use correct hp when applying for weather service within EmAgentIT --- src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 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 3bacd23d10..4c922b9718 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -549,8 +549,8 @@ class EmAgentIT weatherService.expectMessage( RegisterForWeatherMessage( - hpInputModel.getNode.getGeoPosition.getY, - hpInputModel.getNode.getGeoPosition.getX, + adaptedHpInputModel.getNode.getGeoPosition.getY, + adaptedHpInputModel.getNode.getGeoPosition.getX, ) ) From 55642060955760adf7cc295725191ebb3d6abb58 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Thu, 13 Feb 2025 18:48:19 +0100 Subject: [PATCH 11/22] removed scaling of pv within emAgentIT, adapted weather data for first test tick --- .../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 4c922b9718..1152c1a055 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -150,7 +150,7 @@ class EmAgentIT pvInput, PvRuntimeConfig( calculateMissingReactivePowerWithModel = true, - scaling = 2d, + scaling = 1d, uuids = List.empty, ), primaryServiceProxy.ref.toClassic, @@ -260,10 +260,10 @@ class EmAgentIT /* TICK 0 LOAD: 0.269 kW - PV: -5.617 kW + PV: -5.842 kW STORAGE: SOC 0 % -> charge with 5 kW - -> remaining -0.348 kW + -> remaining -0.723 kW */ emAgentActivation ! Activation(0) @@ -272,8 +272,8 @@ class EmAgentIT 0, weatherService.ref.toClassic, WeatherData( - WattsPerSquareMeter(540d), WattsPerSquareMeter(200d), + WattsPerSquareMeter(100d), Celsius(0d), MetersPerSecond(0d), ), @@ -285,10 +285,10 @@ class EmAgentIT emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 0L.toDateTime emResult.getP should equalWithTolerance( - -0.03283549601246891.asMegaWatt + -0.00057340027059.asMegaWatt ) emResult.getQ should equalWithTolerance( - 0.0000882855367033582.asMegaVar + 0.0000882855367033.asMegaVar ) } From c23116ac60699e0182193a7d1086ddc54272a85b Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 14 Feb 2025 10:39:27 +0100 Subject: [PATCH 12/22] Adapting to changes in `dev`. --- .../ie3/simona/config/ConfigFailFast.scala | 38 +-- .../ie3/simona/config/GridConfigParser.scala | 145 +++++++++++- .../ie3/simona/config/RefSystemParser.scala | 76 ------ .../edu/ie3/simona/config/SimonaConfig.scala | 17 +- .../simona/config/VoltageLimitsParser.scala | 63 ----- .../ie3/simona/sim/setup/SetupHelper.scala | 6 +- .../sim/setup/SimonaStandaloneSetup.scala | 14 +- ...rSpec.scala => GridConfigParserSpec.scala} | 222 ++++++++++++++++-- 8 files changed, 360 insertions(+), 221 deletions(-) delete mode 100644 src/main/scala/edu/ie3/simona/config/RefSystemParser.scala delete mode 100644 src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala rename src/test/scala/edu/ie3/simona/config/{RefSystemParserSpec.scala => GridConfigParserSpec.scala} (52%) diff --git a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala index 9528dd68aa..bd236d32cb 100644 --- a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala +++ b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala @@ -116,9 +116,7 @@ object ConfigFailFast extends LazyLogging { checkTimeConfig(simonaConfig.simona.time) // check if the provided combinations of refSystems provided are valid - val refSystems = simonaConfig.simona.gridConfig.refSystems - if (refSystems.isDefined) - refSystems.foreach(refsys => checkRefSystem(refsys)) + simonaConfig.simona.gridConfig.refSystems.foreach(checkRefSystem) // check if the provided combinations of voltageLimits provided are valid simonaConfig.simona.gridConfig.voltageLimits.foreach(checkVoltageLimits) @@ -461,12 +459,7 @@ object ConfigFailFast extends LazyLogging { refSystems: List[RefSystemConfig] ): Unit = { refSystems.foreach { refSystem => - checkVoltageLvlsAndGridIds( - refSystem.voltLvls, - refSystem.gridIds, - refSystem, - "refSystem", - ) + checkGridConfig(refSystem, "refSystem") refSystem.sNom match { case ConfigConventions.refSystemQuantRegex(_) => @@ -495,12 +488,7 @@ object ConfigFailFast extends LazyLogging { voltageLimits: List[VoltageLimitsConfig] ): Unit = { voltageLimits.foreach { limit => - checkVoltageLvlsAndGridIds( - limit.voltLvls, - limit.gridIds, - limit, - "voltage limit", - ) + checkGridConfig(limit, "voltage limit") if (limit.vMin >= limit.vMax) { throw new InvalidConfigParameterException( @@ -512,31 +500,23 @@ object ConfigFailFast extends LazyLogging { /** Method to check the common elements of a * [[SimonaConfig.Simona.GridConfig]]. - * @param voltLvlOption - * option for [[VoltLvlConfig]] - * @param gridIdOption - * option for grid ids - * @param config + * @param gridConfig * the individual config * @param configType * the type of config (e.g. refSystem) - * @tparam C - * type fo */ - private def checkVoltageLvlsAndGridIds[C]( - voltLvlOption: Option[List[VoltLvlConfig]], - gridIdOption: Option[List[String]], - config: C, + private def checkGridConfig( + gridConfig: GridConfigParams, configType: String, ): Unit = { - val voltLvls = voltLvlOption.getOrElse(List.empty) - val gridIds = gridIdOption.getOrElse(List.empty) + val voltLvls = gridConfig.voltLvls.getOrElse(List.empty) + val gridIds = gridConfig.gridIds.getOrElse(List.empty) if (voltLvls.isEmpty && gridIds.isEmpty) throw new InvalidConfigParameterException( "The provided values for voltLvls and gridIds are empty! " + s"At least one of these optional parameters has to be provided for a valid $configType! " + - s"Provided $configType is: $config." + s"Provided $configType is: $gridConfig." ) voltLvls.foreach { voltLvl => diff --git a/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala b/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala index 2f66e406cc..bb81bedf96 100644 --- a/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala +++ b/src/main/scala/edu/ie3/simona/config/GridConfigParser.scala @@ -6,16 +6,27 @@ package edu.ie3.simona.config -import edu.ie3.datamodel.models.voltagelevels.VoltageLevel -import edu.ie3.simona.config.SimonaConfig.VoltLvlConfig +import edu.ie3.datamodel.models.voltagelevels.{ + GermanVoltageLevelUtils, + VoltageLevel, +} +import edu.ie3.simona.config.SimonaConfig.Simona.GridConfig +import edu.ie3.simona.config.SimonaConfig.{ + RefSystemConfig, + VoltLvlConfig, + VoltageLimitsConfig, +} import edu.ie3.simona.exceptions.InvalidConfigParameterException +import edu.ie3.simona.model.grid.{RefSystem, VoltageLimits} import edu.ie3.simona.util.CollectionUtils import edu.ie3.util.quantities.PowerSystemUnits +import squants.electro.Kilovolts +import squants.energy.{Kilowatts, Megawatts} object GridConfigParser { - abstract class GridConfig[GridConfig]( - protected val gridIdMap: Map[Int, GridConfig], - protected val voltLvlMap: Map[VoltageLevel, GridConfig], + abstract class ParsedGridConfig[T]( + protected val gridIdMap: Map[Int, T], + protected val voltLvlMap: Map[VoltageLevel, T], ) { /** Returns a [[GridConfig]] based on the provided gridId or the voltLvl as @@ -31,20 +42,128 @@ object GridConfigParser { final def find( gridId: Int, voltLvl: Option[VoltageLevel] = None, - ): Option[GridConfig] = + ): Option[T] = gridIdMap .get(gridId) .orElse(voltLvl.flatMap(voltLvlMap.get)) } - def parseWithDefaults[C, E, GridConfig]( + final case class ConfigRefSystems( + private val gridIdRefSystems: Map[Int, RefSystem], + private val voltLvLRefSystems: Map[VoltageLevel, RefSystem], + ) extends ParsedGridConfig[RefSystem](gridIdRefSystems, voltLvLRefSystems) + + final case class ConfigVoltageLimits( + private val gridIdVoltageLimits: Map[Int, VoltageLimits], + private val voltLvLVoltageLimits: Map[VoltageLevel, VoltageLimits], + ) extends ParsedGridConfig[VoltageLimits]( + gridIdVoltageLimits, + voltLvLVoltageLimits, + ) + + def parse( + gridConfigs: GridConfig + ): (ConfigRefSystems, ConfigVoltageLimits) = + ( + parseRefSystems(gridConfigs.refSystems), + parseVoltageLimits(gridConfigs.voltageLimits), + ) + + /** Parses the configuration based [[RefSystem]] information based on a list + * of [[RefSystemConfig]] + * + * @param configRefSystems + * the refSystems provided via configuration + * @return + * object that holds two maps with mappings of gridIds and voltLvls to + * RefSystems + */ + def parseRefSystems( + configRefSystems: Option[List[RefSystemConfig]] + ): ConfigRefSystems = { + val defaultRefSystems = ConfigRefSystems( + Map.empty, + Map( + GermanVoltageLevelUtils.LV -> RefSystem(Kilowatts(100), Kilovolts(0.4)), + GermanVoltageLevelUtils.MV_10KV -> RefSystem( + Megawatts(40), + Kilovolts(10), + ), + GermanVoltageLevelUtils.MV_20KV -> RefSystem( + Megawatts(60), + Kilovolts(20), + ), + GermanVoltageLevelUtils.MV_30KV -> RefSystem( + Megawatts(150), + Kilovolts(30), + ), + GermanVoltageLevelUtils.HV -> RefSystem(Megawatts(600), Kilovolts(110)), + GermanVoltageLevelUtils.EHV_220KV -> RefSystem( + Megawatts(800), + Kilovolts(220), + ), + GermanVoltageLevelUtils.EHV_380KV -> RefSystem( + Megawatts(1000), + Kilovolts(380), + ), + ), + ) + + parseWithDefaults[RefSystemConfig, RefSystem, ConfigRefSystems]( + configRefSystems, + refSystem => refSystem.gridIds, + refSystem => refSystem.voltLvls, + refSystem => RefSystem(refSystem.sNom, refSystem.vNom), + (gridIds, voltLvls) => ConfigRefSystems(gridIds, voltLvls), + defaultRefSystems, + )("refSystems") + } + + /** Parses the configuration based [[VoltageLimits]] information based on a + * list of [[VoltageLimitsConfig]] + * + * @param configVoltageLimits + * the refSystems provided via configuration + * @return + * object that holds two maps with mappings of gridIds and voltLvls to + * RefSystems + */ + def parseVoltageLimits( + configVoltageLimits: Option[List[VoltageLimitsConfig]] + ): ConfigVoltageLimits = { + val distributionVoltageLimits = VoltageLimits(0.9, 1.1) + + val defaultVoltageLimits = ConfigVoltageLimits( + Map.empty, + Map( + GermanVoltageLevelUtils.LV -> distributionVoltageLimits, + GermanVoltageLevelUtils.MV_10KV -> distributionVoltageLimits, + GermanVoltageLevelUtils.MV_20KV -> distributionVoltageLimits, + GermanVoltageLevelUtils.MV_30KV -> distributionVoltageLimits, + GermanVoltageLevelUtils.HV -> distributionVoltageLimits, + GermanVoltageLevelUtils.EHV_220KV -> VoltageLimits(0.9, 1.118), + GermanVoltageLevelUtils.EHV_380KV -> VoltageLimits(0.9, 1.05), + ), + ) + + parseWithDefaults[VoltageLimitsConfig, VoltageLimits, ConfigVoltageLimits]( + configVoltageLimits, + voltageLimit => voltageLimit.gridIds, + voltageLimit => voltageLimit.voltLvls, + voltageLimit => VoltageLimits(voltageLimit.vMin, voltageLimit.vMax), + (gridIds, voltLvls) => ConfigVoltageLimits(gridIds, voltLvls), + defaultVoltageLimits, + )("voltageLimits") + } + + def parseWithDefaults[C, E, T <: ParsedGridConfig[_]]( configs: Option[List[C]], gridIds: C => Option[List[String]], voltLvls: C => Option[List[VoltLvlConfig]], elementFcn: C => E, - builder: (Map[Int, E], Map[VoltageLevel, E]) => GridConfig, - defaults: GridConfig, - )(implicit gridConfigType: String): GridConfig = configs match { + builder: (Map[Int, E], Map[VoltageLevel, E]) => T, + defaults: T, + )(implicit gridConfigType: String): T = configs match { case Some(configElements) if configElements.nonEmpty => // units for parsing are not initialized by default // hence we call them manually @@ -67,7 +186,7 @@ object GridConfigParser { Seq((singleGridId.toInt, element)) case unknownGridIdFormat => throw new InvalidConfigParameterException( - s"Unknown gridId format $unknownGridIdFormat provided for $gridConfigType $config" + s"Unknown gridId format $unknownGridIdFormat provided for grid config: $config" ) } @@ -84,14 +203,14 @@ object GridConfigParser { if (CollectionUtils.listHasDuplicates(parsedIdList)) { throw new InvalidConfigParameterException( - s"The provided gridIds in simona.gridConfig.${gridConfigType}s contain duplicates. " + + s"The provided gridIds in simona.gridConfig.$gridConfigType contain duplicates. " + "Please check if there are either duplicate entries or overlapping ranges!" ) } if (CollectionUtils.listHasDuplicates(parsedVoltLvlList)) throw new InvalidConfigParameterException( - s"The provided voltLvls in simona.gridConfig.${gridConfigType}s contain duplicates. " + + s"The provided voltLvls in simona.gridConfig.$gridConfigType contain duplicates. " + "Please check your configuration for duplicates in voltLvl entries!" ) diff --git a/src/main/scala/edu/ie3/simona/config/RefSystemParser.scala b/src/main/scala/edu/ie3/simona/config/RefSystemParser.scala deleted file mode 100644 index c0722b5e87..0000000000 --- a/src/main/scala/edu/ie3/simona/config/RefSystemParser.scala +++ /dev/null @@ -1,76 +0,0 @@ -/* - * © 2020. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.config - -import edu.ie3.datamodel.models.voltagelevels.{ - GermanVoltageLevelUtils, - VoltageLevel, -} -import edu.ie3.simona.config.GridConfigParser.{GridConfig, parseWithDefaults} -import edu.ie3.simona.config.SimonaConfig.RefSystemConfig -import edu.ie3.simona.model.grid.RefSystem -import squants.electro.Kilovolts -import squants.energy.{Kilowatts, Megawatts} - -/** Parser to parse [[RefSystem]] provided via [[SimonaConfig]] - */ -object RefSystemParser { - final case class ConfigRefSystems( - private val gridIdRefSystems: Map[Int, RefSystem], - private val voltLvLRefSystems: Map[VoltageLevel, RefSystem], - ) extends GridConfig[RefSystem](gridIdRefSystems, voltLvLRefSystems) - - /** Parses the configuration based [[RefSystem]] information based on a list - * of [[SimonaConfig.RefSystemConfig]] - * - * @param configRefSystems - * the refSystems provided via configuration - * @return - * object that holds two maps with mappings of gridIds and voltLvls to - * RefSystems - */ - def parse( - configRefSystems: Option[List[SimonaConfig.RefSystemConfig]] - ): ConfigRefSystems = { - val defaultRefSystems = ConfigRefSystems( - Map.empty, - Map( - GermanVoltageLevelUtils.LV -> RefSystem(Kilowatts(100), Kilovolts(0.4)), - GermanVoltageLevelUtils.MV_10KV -> RefSystem( - Megawatts(40), - Kilovolts(10), - ), - GermanVoltageLevelUtils.MV_20KV -> RefSystem( - Megawatts(60), - Kilovolts(20), - ), - GermanVoltageLevelUtils.MV_30KV -> RefSystem( - Megawatts(150), - Kilovolts(30), - ), - GermanVoltageLevelUtils.HV -> RefSystem(Megawatts(600), Kilovolts(110)), - GermanVoltageLevelUtils.EHV_220KV -> RefSystem( - Megawatts(800), - Kilovolts(220), - ), - GermanVoltageLevelUtils.EHV_380KV -> RefSystem( - Megawatts(1000), - Kilovolts(380), - ), - ), - ) - - parseWithDefaults[RefSystemConfig, RefSystem, ConfigRefSystems]( - configRefSystems, - refSystem => refSystem.gridIds, - refSystem => refSystem.voltLvls, - refSystem => RefSystem(refSystem.sNom, refSystem.vNom), - (gridIds, voltLvls) => ConfigRefSystems(gridIds, voltLvls), - defaultRefSystems, - )("refSystem") - } -} diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index 898ff46515..0d34c4e22b 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -201,12 +201,17 @@ object SimonaConfig { uuids, ) + sealed trait GridConfigParams { + val gridIds: Option[List[String]] + val voltLvls: Option[List[VoltLvlConfig]] + } + final case class RefSystemConfig( - gridIds: Option[List[String]] = None, + override val gridIds: Option[List[String]] = None, sNom: String, vNom: String, - voltLvls: Option[List[VoltLvlConfig]] = None, - ) + override val voltLvls: Option[List[VoltLvlConfig]] = None, + ) extends GridConfigParams final case class ResultKafkaParams( override val bootstrapServers: String, @@ -254,11 +259,11 @@ object SimonaConfig { ) final case class VoltageLimitsConfig( - gridIds: Option[List[String]] = None, + override val gridIds: Option[List[String]] = None, vMax: Double, vMin: Double, - voltLvls: Option[List[VoltLvlConfig]] = None, - ) + override val voltLvls: Option[List[VoltLvlConfig]] = None, + ) extends GridConfigParams final case class WecRuntimeConfig( override val calculateMissingReactivePowerWithModel: Boolean = false, diff --git a/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala b/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala deleted file mode 100644 index 4c1ce7c596..0000000000 --- a/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala +++ /dev/null @@ -1,63 +0,0 @@ -/* - * © 2024. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.config - -import edu.ie3.datamodel.models.voltagelevels.{ - GermanVoltageLevelUtils, - VoltageLevel, -} -import edu.ie3.simona.config.GridConfigParser.{GridConfig, parseWithDefaults} -import edu.ie3.simona.config.SimonaConfig.VoltageLimitsConfig -import edu.ie3.simona.model.grid.VoltageLimits - -/** Parser to parse [[VoltageLimits]] provided via [[SimonaConfig]] - */ -object VoltageLimitsParser { - - final case class ConfigVoltageLimits( - private val gridIdVoltageLimits: Map[Int, VoltageLimits], - private val voltLvLVoltageLimits: Map[VoltageLevel, VoltageLimits], - ) extends GridConfig[VoltageLimits](gridIdVoltageLimits, voltLvLVoltageLimits) - - /** Parses the configuration based [[VoltageLimits]] information based on a - * list of [[SimonaConfig.VoltageLimitsConfig]] - * - * @param configVoltageLimits - * the refSystems provided via configuration - * @return - * object that holds two maps with mappings of gridIds and voltLvls to - * RefSystems - */ - def parse( - configVoltageLimits: Option[List[SimonaConfig.VoltageLimitsConfig]] - ): ConfigVoltageLimits = { - val distributionVoltageLimits = VoltageLimits(0.9, 1.1) - - val defaultVoltageLimits = ConfigVoltageLimits( - Map.empty, - Map( - GermanVoltageLevelUtils.LV -> distributionVoltageLimits, - GermanVoltageLevelUtils.MV_10KV -> distributionVoltageLimits, - GermanVoltageLevelUtils.MV_20KV -> distributionVoltageLimits, - GermanVoltageLevelUtils.MV_30KV -> distributionVoltageLimits, - GermanVoltageLevelUtils.HV -> distributionVoltageLimits, - GermanVoltageLevelUtils.EHV_220KV -> VoltageLimits(0.9, 1.118), - GermanVoltageLevelUtils.EHV_380KV -> VoltageLimits(0.9, 1.05), - ), - ) - - parseWithDefaults[VoltageLimitsConfig, VoltageLimits, ConfigVoltageLimits]( - configVoltageLimits, - voltageLimit => voltageLimit.gridIds, - voltageLimit => voltageLimit.voltLvls, - voltageLimit => VoltageLimits(voltageLimit.vMin, voltageLimit.vMax), - (gridIds, voltLvls) => ConfigVoltageLimits(gridIds, voltLvls), - defaultVoltageLimits, - )("voltage limits") - } - -} 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 6067919b27..72df0e49fc 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala @@ -14,9 +14,11 @@ import edu.ie3.datamodel.models.result.system.FlexOptionsResult import edu.ie3.datamodel.utils.ContainerUtils import edu.ie3.simona.agent.grid.GridAgent import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData -import edu.ie3.simona.config.RefSystemParser.ConfigRefSystems +import edu.ie3.simona.config.GridConfigParser.{ + ConfigRefSystems, + ConfigVoltageLimits, +} import edu.ie3.simona.config.SimonaConfig -import edu.ie3.simona.config.VoltageLimitsParser.ConfigVoltageLimits import edu.ie3.simona.exceptions.InitializationException import edu.ie3.simona.exceptions.agent.GridAgentInitializationException import edu.ie3.simona.io.result.ResultSinkType 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 16d2332348..672a5969dd 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -18,12 +18,7 @@ import edu.ie3.simona.agent.grid.GridAgentMessages.CreateGridAgent import edu.ie3.simona.api.ExtSimAdapter import edu.ie3.simona.api.data.ev.ExtEvDataConnection import edu.ie3.simona.api.simulation.ExtSimAdapterData -import edu.ie3.simona.config.{ - ArgsParser, - RefSystemParser, - SimonaConfig, - VoltageLimitsParser, -} +import edu.ie3.simona.config.{ArgsParser, GridConfigParser, SimonaConfig} import edu.ie3.simona.event.listener.{ResultEventListener, RuntimeEventListener} import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.exceptions.agent.GridAgentInitializationException @@ -93,11 +88,8 @@ class SimonaStandaloneSetup( ) /* extract and prepare refSystem information from config */ - val configRefSystems = - RefSystemParser.parse(simonaConfig.simona.gridConfig.refSystems) - - val configVoltageLimits = - VoltageLimitsParser.parse(simonaConfig.simona.gridConfig.voltageLimits) + val (configRefSystems, configVoltageLimits) = + GridConfigParser.parse(simonaConfig.simona.gridConfig) /* Create all agents and map the sub grid id to their actor references */ val subGridToActorRefMap = buildSubGridToActorRefMap( diff --git a/src/test/scala/edu/ie3/simona/config/RefSystemParserSpec.scala b/src/test/scala/edu/ie3/simona/config/GridConfigParserSpec.scala similarity index 52% rename from src/test/scala/edu/ie3/simona/config/RefSystemParserSpec.scala rename to src/test/scala/edu/ie3/simona/config/GridConfigParserSpec.scala index 13ff81a1a3..9bac374c49 100644 --- a/src/test/scala/edu/ie3/simona/config/RefSystemParserSpec.scala +++ b/src/test/scala/edu/ie3/simona/config/GridConfigParserSpec.scala @@ -7,16 +7,20 @@ package edu.ie3.simona.config import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils -import edu.ie3.simona.config.SimonaConfig.{RefSystemConfig, VoltLvlConfig} +import edu.ie3.simona.config.SimonaConfig.{ + RefSystemConfig, + VoltLvlConfig, + VoltageLimitsConfig, +} import edu.ie3.simona.exceptions.InvalidConfigParameterException -import edu.ie3.simona.model.grid.RefSystem +import edu.ie3.simona.model.grid.{RefSystem, VoltageLimits} import edu.ie3.simona.test.common.UnitSpec import squants.electro.{Kilovolts, Volts} import squants.energy.{Kilowatts, Megawatts} -class RefSystemParserSpec extends UnitSpec { +class GridConfigParserSpec extends UnitSpec { - "A RefSystemParser" must { + "A GridConfigParser.parseRefSystems" must { // check internal gridIdRefSystems val gridIdRefSystems = PrivateMethod[Map[Int, RefSystem]](Symbol("gridIdRefSystems")) @@ -26,7 +30,7 @@ class RefSystemParserSpec extends UnitSpec { PrivateMethod[Map[String, RefSystem]](Symbol("voltLvLRefSystems")) "return the default ref systems if no config was provided" in { - val defaults = RefSystemParser.parse(Some(List.empty)) + val defaults = GridConfigParser.parseRefSystems(Some(List.empty)) defaults invokePrivate gridIdRefSystems() shouldBe Map.empty @@ -60,7 +64,7 @@ class RefSystemParserSpec extends UnitSpec { val validRefSystems: Option[List[SimonaConfig.RefSystemConfig]] = Some( List( - new RefSystemConfig( + RefSystemConfig( gridIds = Some(List("1", "2-10", "15...20")), sNom = "100 MVA", vNom = "10 kV", @@ -68,7 +72,7 @@ class RefSystemParserSpec extends UnitSpec { List(VoltLvlConfig("MV", "10 kV"), VoltLvlConfig("MV", "20 kV")) ), ), - new RefSystemConfig( + RefSystemConfig( gridIds = Some(List("100")), sNom = "5000 MVA", vNom = "110 kV", @@ -79,7 +83,7 @@ class RefSystemParserSpec extends UnitSpec { ) ), ), - new RefSystemConfig( + RefSystemConfig( gridIds = None, sNom = "5000 MVA", vNom = "110 kV", @@ -88,7 +92,7 @@ class RefSystemParserSpec extends UnitSpec { ) ) - val configRefSystems = RefSystemParser.parse(validRefSystems) + val configRefSystems = GridConfigParser.parseRefSystems(validRefSystems) // prepare internal value check val configRefSystemOne = RefSystem("100 MVA", "10 kV") @@ -128,7 +132,7 @@ class RefSystemParserSpec extends UnitSpec { val validRefSystems: Option[List[SimonaConfig.RefSystemConfig]] = Some( List( - new RefSystemConfig( + RefSystemConfig( gridIds = Some(List("1", "2", "2", "2-10", "15...20")), sNom = "100 MVA", vNom = "10 kV", @@ -139,7 +143,7 @@ class RefSystemParserSpec extends UnitSpec { ) ) intercept[InvalidConfigParameterException] { - RefSystemParser.parse(validRefSystems) + GridConfigParser.parseRefSystems(validRefSystems) }.getMessage shouldBe s"The provided gridIds in simona.gridConfig.refSystems contain duplicates. Please check if there are either duplicate entries or overlapping ranges!" } @@ -149,7 +153,7 @@ class RefSystemParserSpec extends UnitSpec { val validRefSystems: Option[List[SimonaConfig.RefSystemConfig]] = Some( List( - new RefSystemConfig( + RefSystemConfig( gridIds = None, sNom = "100 MVA", vNom = "10 kV", @@ -157,7 +161,7 @@ class RefSystemParserSpec extends UnitSpec { List(VoltLvlConfig("MV", "10 kV"), VoltLvlConfig("MV", "20 kV")) ), ), - new RefSystemConfig( + RefSystemConfig( gridIds = None, sNom = "100 MVA", vNom = "10 kV", @@ -168,7 +172,7 @@ class RefSystemParserSpec extends UnitSpec { ) ) intercept[InvalidConfigParameterException] { - RefSystemParser.parse(validRefSystems) + GridConfigParser.parseRefSystems(validRefSystems) }.getMessage shouldBe s"The provided voltLvls in simona.gridConfig.refSystems contain duplicates. Please check your configuration for duplicates in voltLvl entries!" } @@ -178,7 +182,7 @@ class RefSystemParserSpec extends UnitSpec { val validRefSystems: Option[List[SimonaConfig.RefSystemConfig]] = Some( List( - new RefSystemConfig( + RefSystemConfig( gridIds = Some(List("asd")), sNom = "100 MVA", vNom = "10 kV", @@ -186,7 +190,7 @@ class RefSystemParserSpec extends UnitSpec { List(VoltLvlConfig("MV", "10 kV"), VoltLvlConfig("MV", "20 kV")) ), ), - new RefSystemConfig( + RefSystemConfig( gridIds = None, sNom = "100 MVA", vNom = "10 kV", @@ -197,8 +201,184 @@ class RefSystemParserSpec extends UnitSpec { ) ) intercept[InvalidConfigParameterException] { - RefSystemParser.parse(validRefSystems) - }.getMessage shouldBe "Unknown gridId format asd provided for refSystem RefSystemConfig(Some(List(asd)),100 MVA,10 kV,Some(List(VoltLvlConfig(MV,10 kV), VoltLvlConfig(MV,20 kV))))" + GridConfigParser.parseRefSystems(validRefSystems) + }.getMessage shouldBe "Unknown gridId format asd provided for grid config: RefSystemConfig(Some(List(asd)),100 MVA,10 kV,Some(List(VoltLvlConfig(MV,10 kV), VoltLvlConfig(MV,20 kV))))" + + } + + } + + "A GridConfigParser.parseVoltageLimits" must { + // check internal gridIdRefSystems + val gridIdVoltageLimits = + PrivateMethod[Map[Int, RefSystem]](Symbol("gridIdVoltageLimits")) + + // check internal voltLvLRefSystems + val voltLvLVoltageLimits = + PrivateMethod[Map[String, RefSystem]](Symbol("voltLvLVoltageLimits")) + + "return the default voltage limits if no config was provided" in { + val defaults = GridConfigParser.parseVoltageLimits(Some(List.empty)) + + defaults invokePrivate gridIdVoltageLimits() shouldBe Map.empty + + val distributionVoltageLimits = VoltageLimits(0.9, 1.1) + + defaults invokePrivate voltLvLVoltageLimits() shouldBe Map( + GermanVoltageLevelUtils.LV -> distributionVoltageLimits, + GermanVoltageLevelUtils.MV_10KV -> distributionVoltageLimits, + GermanVoltageLevelUtils.MV_20KV -> distributionVoltageLimits, + GermanVoltageLevelUtils.MV_30KV -> distributionVoltageLimits, + GermanVoltageLevelUtils.HV -> distributionVoltageLimits, + GermanVoltageLevelUtils.EHV_220KV -> VoltageLimits(0.9, 1.118), + GermanVoltageLevelUtils.EHV_380KV -> VoltageLimits(0.9, 1.05), + ) + } + + "parse provided valid simona config voltage limits correctly" in { + val validVoltageLimits: Option[List[VoltageLimitsConfig]] = + Some( + List( + VoltageLimitsConfig( + gridIds = Some(List("1", "2-10", "15...20")), + vMax = 1.1, + vMin = 0.9, + voltLvls = Some( + List(VoltLvlConfig("MV", "10 kV"), VoltLvlConfig("MV", "20 kV")) + ), + ), + VoltageLimitsConfig( + gridIds = Some(List("100")), + vMax = 1.05, + vMin = 0.9, + voltLvls = Some( + List( + VoltLvlConfig("HV", "110 kV"), + VoltLvlConfig("EHV", "380 kV"), + ) + ), + ), + VoltageLimitsConfig( + gridIds = None, + vMax = 1.1, + vMin = 0.9, + voltLvls = None, + ), + ) + ) + + val configVoltageLimits = + GridConfigParser.parseVoltageLimits(validVoltageLimits) + + // prepare internal value check + val configVoltageLimitsOne = VoltageLimits(0.9, 1.1) + val configVoltageLimitsTwo = VoltageLimits(0.9, 1.05) + + configVoltageLimits invokePrivate gridIdVoltageLimits() shouldBe Map( + 1 -> configVoltageLimitsOne, + 2 -> configVoltageLimitsOne, + 3 -> configVoltageLimitsOne, + 4 -> configVoltageLimitsOne, + 5 -> configVoltageLimitsOne, + 6 -> configVoltageLimitsOne, + 7 -> configVoltageLimitsOne, + 8 -> configVoltageLimitsOne, + 9 -> configVoltageLimitsOne, + 10 -> configVoltageLimitsOne, + 15 -> configVoltageLimitsOne, + 16 -> configVoltageLimitsOne, + 17 -> configVoltageLimitsOne, + 18 -> configVoltageLimitsOne, + 19 -> configVoltageLimitsOne, + 20 -> configVoltageLimitsOne, + 100 -> configVoltageLimitsTwo, + ) + + configVoltageLimits invokePrivate voltLvLVoltageLimits() shouldBe Map( + GermanVoltageLevelUtils.MV_10KV -> configVoltageLimitsOne, + GermanVoltageLevelUtils.MV_20KV -> configVoltageLimitsOne, + GermanVoltageLevelUtils.HV -> configVoltageLimitsTwo, + GermanVoltageLevelUtils.EHV_380KV -> configVoltageLimitsTwo, + ) + + } + + "throw an InvalidConfigParameterException when provided gridIds contain duplicate entries" in { + + val validVoltageLimits: Option[List[VoltageLimitsConfig]] = + Some( + List( + VoltageLimitsConfig( + gridIds = Some(List("1", "2", "2", "2-10", "15...20")), + vMax = 1.1, + vMin = 0.9, + voltLvls = Some( + List(VoltLvlConfig("MV", "10 kV"), VoltLvlConfig("MV", "20 kV")) + ), + ) + ) + ) + intercept[InvalidConfigParameterException] { + GridConfigParser.parseVoltageLimits(validVoltageLimits) + }.getMessage shouldBe s"The provided gridIds in simona.gridConfig.voltageLimits contain duplicates. Please check if there are either duplicate entries or overlapping ranges!" + + } + + "throw an InvalidConfigParameterException when provided voltLvls contain duplicate entries" in { + + val validVoltageLimits: Option[List[VoltageLimitsConfig]] = + Some( + List( + VoltageLimitsConfig( + gridIds = None, + vMax = 1.1, + vMin = 0.9, + voltLvls = Some( + List(VoltLvlConfig("MV", "10 kV"), VoltLvlConfig("MV", "20 kV")) + ), + ), + VoltageLimitsConfig( + gridIds = None, + vMax = 1.1, + vMin = 0.9, + voltLvls = Some( + List(VoltLvlConfig("MV", "10 kV"), VoltLvlConfig("MV", "20 kV")) + ), + ), + ) + ) + intercept[InvalidConfigParameterException] { + GridConfigParser.parseVoltageLimits(validVoltageLimits) + }.getMessage shouldBe s"The provided voltLvls in simona.gridConfig.voltageLimits contain duplicates. Please check your configuration for duplicates in voltLvl entries!" + + } + + "throw an InvalidConfigParameterException when the provided gridId format is unknown" in { + + val validVoltageLimits: Option[List[VoltageLimitsConfig]] = + Some( + List( + VoltageLimitsConfig( + gridIds = Some(List("asd")), + vMax = 1.1, + vMin = 0.9, + voltLvls = Some( + List(VoltLvlConfig("MV", "10 kV"), VoltLvlConfig("MV", "20 kV")) + ), + ), + VoltageLimitsConfig( + gridIds = None, + vMax = 1.1, + vMin = 0.9, + voltLvls = Some( + List(VoltLvlConfig("MV", "10 kV"), VoltLvlConfig("MV", "20 kV")) + ), + ), + ) + ) + intercept[InvalidConfigParameterException] { + GridConfigParser.parseVoltageLimits(validVoltageLimits) + }.getMessage shouldBe "Unknown gridId format asd provided for grid config: VoltageLimitsConfig(Some(List(asd)),1.1,0.9,Some(List(VoltLvlConfig(MV,10 kV), VoltLvlConfig(MV,20 kV))))" } @@ -209,7 +389,7 @@ class RefSystemParserSpec extends UnitSpec { val validRefSystems: Option[List[SimonaConfig.RefSystemConfig]] = Some( List( - new RefSystemConfig( + RefSystemConfig( gridIds = Some(List("1", "2-10", "15...20")), sNom = "100 MVA", vNom = "10 kV", @@ -217,7 +397,7 @@ class RefSystemParserSpec extends UnitSpec { List(VoltLvlConfig("MV", "10 kV"), VoltLvlConfig("MV", "20 kV")) ), ), - new RefSystemConfig( + RefSystemConfig( gridIds = Some(List("100")), sNom = "5000 MVA", vNom = "110 kV", @@ -231,7 +411,7 @@ class RefSystemParserSpec extends UnitSpec { ) ) - val configRefSystems = RefSystemParser.parse(validRefSystems) + val configRefSystems = GridConfigParser.parseRefSystems(validRefSystems) // prepare expected RefSystems val configRefSystemOne = RefSystem("100 MVA", "10 kV") From 43b1869eeee9e2684a4157a109eb2d43d4a16327 Mon Sep 17 00:00:00 2001 From: Marvin Heintze Date: Sat, 15 Feb 2025 14:04:39 +0100 Subject: [PATCH 13/22] values storage test --- .../edu/ie3/simona/agent/em/EmAgentIT.scala | 76 ++++++++----------- 1 file changed, 32 insertions(+), 44 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 1152c1a055..3dd95081c8 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -17,21 +17,11 @@ import edu.ie3.simona.config.SimonaConfig._ import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.event.ResultEvent.ParticipantResultEvent import edu.ie3.simona.event.notifier.NotifierConfig -import edu.ie3.simona.ontology.messages.SchedulerMessage.{ - Completion, - ScheduleActivation, -} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.PrimaryServiceRegistrationMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegistrationResponseMessage.{ - RegistrationFailedMessage, - RegistrationSuccessfulMessage, -} -import edu.ie3.simona.ontology.messages.services.WeatherMessage.{ - ProvideWeatherMessage, - RegisterForWeatherMessage, - WeatherData, -} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegistrationResponseMessage.{RegistrationFailedMessage, RegistrationSuccessfulMessage} +import edu.ie3.simona.ontology.messages.services.WeatherMessage.{ProvideWeatherMessage, RegisterForWeatherMessage, WeatherData} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.test.common.input.EmInputTestData import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK @@ -41,10 +31,7 @@ import edu.ie3.util.quantities.QuantityMatchers.equalWithTolerance import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import edu.ie3.util.scala.quantities.WattsPerSquareMeter import org.apache.pekko.actor.ActorSystem -import org.apache.pekko.actor.testkit.typed.scaladsl.{ - ScalaTestWithActorTestKit, - TestProbe, -} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} import org.apache.pekko.actor.typed.scaladsl.adapter._ import org.apache.pekko.testkit.TestActorRef import org.scalatest.OptionValues._ @@ -55,6 +42,7 @@ import squants.motion.MetersPerSecond import squants.thermal.Celsius import java.time.ZonedDateTime +import scala.concurrent.duration.{FiniteDuration, SECONDS} class EmAgentIT extends ScalaTestWithActorTestKit @@ -62,7 +50,7 @@ class EmAgentIT with should.Matchers with EmInputTestData with MockitoSugar { - + val timeout2: FiniteDuration = FiniteDuration(200, SECONDS) // start a bit later so the sun is up protected implicit val simulationStartDate: ZonedDateTime = TimeUtil.withDefaults.toZonedDateTime("2020-01-01T10:00:00Z") @@ -263,7 +251,7 @@ class EmAgentIT PV: -5.842 kW STORAGE: SOC 0 % -> charge with 5 kW - -> remaining -0.723 kW + -> remaining -0.573 kW */ emAgentActivation ! Activation(0) @@ -280,7 +268,7 @@ class EmAgentIT Some(7200), ) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultListener.expectMessageType[ParticipantResultEvent](timeout2) match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 0L.toDateTime @@ -296,9 +284,9 @@ class EmAgentIT /* TICK 7200 LOAD: 0.269 kW (unchanged) - PV: -3.651 kW - STORAGE: SOC 63.3 % - -> charge with 3.382 kW + PV: -3.791 kW + STORAGE: SOC 63.3 % fixme: reminder check this at the end + -> charge with 3.522 kW -> remaining 0 kW */ @@ -308,42 +296,42 @@ class EmAgentIT 7200, weatherService.ref.toClassic, WeatherData( - WattsPerSquareMeter(300d), - WattsPerSquareMeter(500d), + WattsPerSquareMeter(50d), + WattsPerSquareMeter(150d), Celsius(0d), MetersPerSecond(0d), ), Some(14400), ) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultListener.expectMessageType[ParticipantResultEvent](timeout2) match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 7200.toDateTime emResult.getP should equalWithTolerance( - -0.020583738053224127.asMegaWatt + 0.0.asMegaWatt ) emResult.getQ should equalWithTolerance(0.0000882855367.asMegaVar) } - scheduler.expectMessage(Completion(emAgentActivation, Some(11368))) + scheduler.expectMessage(Completion(emAgentActivation, Some(13115))) - /* TICK 11368 + /* TICK 13115 LOAD: 0.269 kW (unchanged) - PV: -3.651 kW (unchanged) + PV: -3.791 kW (unchanged) STORAGE: SOC 100 % -> charge with 0 kW - -> remaining -3.382 kW + -> remaining -3.522 kW */ - emAgentActivation ! Activation(11368) + emAgentActivation ! Activation(13115) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultListener.expectMessageType[ParticipantResultEvent](timeout2) match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid - emResult.getTime shouldBe 11368L.toDateTime + emResult.getTime shouldBe 13115L.toDateTime emResult.getP should equalWithTolerance( - -0.025583738053224125.asMegaWatt + -0.0035233186089842434.asMegaWatt ) emResult.getQ should equalWithTolerance(0.0000882855367.asMegaVar) } @@ -352,10 +340,10 @@ class EmAgentIT /* TICK 14400 LOAD: 0.269 kW (unchanged) - PV: -0.066 kW + PV: -0.069 kW STORAGE: SOC 100 % - -> charge with -0.202956 kW - -> remaining 0 kW + -> charge with 0 kW + -> remaining 0.2 kW */ // send weather data before activation, which can happen @@ -364,8 +352,8 @@ class EmAgentIT 14400, weatherService.ref.toClassic, WeatherData( - WattsPerSquareMeter(5d), - WattsPerSquareMeter(5d), + WattsPerSquareMeter(0.5d), + WattsPerSquareMeter(2d), Celsius(0d), MetersPerSecond(0d), ), @@ -374,14 +362,14 @@ class EmAgentIT emAgentActivation ! Activation(14400) - resultListener.expectMessageType[ParticipantResultEvent] match { + resultListener.expectMessageType[ParticipantResultEvent](timeout2) match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 14400L.toDateTime emResult.getP should equalWithTolerance( - -0.0001779479755506383.asMegaWatt + -0.0.asMegaWatt ) - emResult.getQ should equalWithTolerance(0.000088285537.asMegaVar) + emResult.getQ should equalWithTolerance(0.000088285536.asMegaVar) } scheduler.expectMessage(Completion(emAgentActivation, Some(21600))) @@ -442,7 +430,7 @@ class EmAgentIT pvInput, PvRuntimeConfig( calculateMissingReactivePowerWithModel = true, - scaling = 2d, + scaling = 1d, uuids = List.empty, ), primaryServiceProxy.ref.toClassic, From 965881ca4167ad7a8a8dddb48cba1eb536283d20 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Sun, 16 Feb 2025 15:25:12 +0100 Subject: [PATCH 14/22] use english name of author --- docs/readthedocs/_static/bibliography/bibtexAll.bib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readthedocs/_static/bibliography/bibtexAll.bib b/docs/readthedocs/_static/bibliography/bibtexAll.bib index 81c8cea91d..521736c76c 100644 --- a/docs/readthedocs/_static/bibliography/bibtexAll.bib +++ b/docs/readthedocs/_static/bibliography/bibtexAll.bib @@ -201,7 +201,7 @@ @misc{EN_50160 } @online{EU_2017/1485, - author = {{Die Europäische Kommission}}, + author = {{The European Commission}}, title = {COMMISSION REGULATION (EU) 2017/1485 of 2 August 2017 establishing a guideline on electricity transmission system operation}, date = {2017-08-02}, url = {https://eur-lex.europa.eu/legal-content/DE/TXT/?uri=CELEX%3A32017R1485}, From 64cc6651fbe636e9c1bd47550b6ff203e8f2312f Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 17 Feb 2025 10:30:52 +0100 Subject: [PATCH 15/22] Removing debug comments. --- .../edu/ie3/simona/event/listener/ResultEventListenerSpec.scala | 2 -- 1 file changed, 2 deletions(-) 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 c80fb488ab..33ebe9caaa 100644 --- a/src/test/scala/edu/ie3/simona/event/listener/ResultEventListenerSpec.scala +++ b/src/test/scala/edu/ie3/simona/event/listener/ResultEventListenerSpec.scala @@ -402,7 +402,6 @@ class ResultEventListenerSpec max = timeoutDuration, ) - // Debug: Check if the file exists assert(outputFile.exists(), "Output file does not exist") // stopping the actor should wait until existing messages within an actor are fully processed @@ -428,7 +427,6 @@ class ResultEventListenerSpec timeoutDuration, ) - // Debug: Check if the compressed file exists val compressedFile = specificOutputFileHierarchy.rawOutputDataFilePaths .getOrElse( classOf[PvResult], From 0c53934358e761bfe6c0f6a0439f262cde895d32 Mon Sep 17 00:00:00 2001 From: Marvin Heintze Date: Mon, 17 Feb 2025 11:26:17 +0100 Subject: [PATCH 16/22] values heat pump tests and timeout removal --- .../edu/ie3/simona/agent/em/EmAgentIT.scala | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 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 3dd95081c8..b5c2409307 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -50,7 +50,6 @@ class EmAgentIT with should.Matchers with EmInputTestData with MockitoSugar { - val timeout2: FiniteDuration = FiniteDuration(200, SECONDS) // start a bit later so the sun is up protected implicit val simulationStartDate: ZonedDateTime = TimeUtil.withDefaults.toZonedDateTime("2020-01-01T10:00:00Z") @@ -268,7 +267,7 @@ class EmAgentIT Some(7200), ) - resultListener.expectMessageType[ParticipantResultEvent](timeout2) match { + resultListener.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 0L.toDateTime @@ -304,7 +303,7 @@ class EmAgentIT Some(14400), ) - resultListener.expectMessageType[ParticipantResultEvent](timeout2) match { + resultListener.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 7200.toDateTime @@ -326,7 +325,7 @@ class EmAgentIT emAgentActivation ! Activation(13115) - resultListener.expectMessageType[ParticipantResultEvent](timeout2) match { + resultListener.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 13115L.toDateTime @@ -362,12 +361,12 @@ class EmAgentIT emAgentActivation ! Activation(14400) - resultListener.expectMessageType[ParticipantResultEvent](timeout2) match { + resultListener.expectMessageType[ParticipantResultEvent] match { case ParticipantResultEvent(emResult: EmResult) => emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 14400L.toDateTime emResult.getP should equalWithTolerance( - -0.0.asMegaWatt + 0.0.asMegaWatt ) emResult.getQ should equalWithTolerance(0.000088285536.asMegaVar) } @@ -553,7 +552,7 @@ class EmAgentIT /* TICK 0 LOAD: 0.269 kW - PV: -5.617 kW + PV: -5.842 kW Heat pump: off, can be turned on or stay off -> set point ~3.5 kW (bigger than 50 % rated apparent power): turned on -> remaining -0.499 kW @@ -566,8 +565,8 @@ class EmAgentIT 0, weatherService.ref.toClassic, WeatherData( - WattsPerSquareMeter(540d), WattsPerSquareMeter(200d), + WattsPerSquareMeter(100d), Celsius(0d), MetersPerSecond(0d), ), @@ -580,16 +579,16 @@ class EmAgentIT emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 0.toDateTime emResult.getP should equalWithTolerance( - -0.032985496012468904.asMegaWatt + -0.0007234002705905523.asMegaWatt ) - emResult.getQ should equalWithTolerance(0.001073120041.asMegaVar) + emResult.getQ should equalWithTolerance(0.0010731200407782782.asMegaVar) } scheduler.expectMessage(Completion(emAgentActivation, Some(7200))) /* TICK 7200 LOAD: 0.269 kW (unchanged) - PV: -3.651 kW + PV: -3.791 kW Heat pump: running (turned on from last request), can also be turned off -> set point ~3.5 kW (bigger than 50 % rated apparent power): stays turned on with unchanged state -> remaining 1.468 kW @@ -602,8 +601,8 @@ class EmAgentIT 7200, weatherService.ref.toClassic, WeatherData( - WattsPerSquareMeter(300d), - WattsPerSquareMeter(500d), + WattsPerSquareMeter(50d), + WattsPerSquareMeter(150d), Celsius(0d), MetersPerSecond(0d), ), @@ -616,19 +615,19 @@ class EmAgentIT emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 7200.toDateTime emResult.getP should equalWithTolerance( - -0.020733738053224125.asMegaWatt + 0.0013266813910157566.asMegaWatt ) - emResult.getQ should equalWithTolerance(0.001073120041.asMegaVar) + emResult.getQ should equalWithTolerance(0.0010731200407782782.asMegaVar) } scheduler.expectMessage(Completion(emAgentActivation, Some(14400))) /* TICK 14400 LOAD: 0.269 kW (unchanged) - PV: -0.066 kW + PV: -0.069 kW Heat pump: Is still running, can still be turned off -> flex signal is 0 MW: Heat pump is turned off - -> remaining 0.203 kW + -> remaining 0.2 kW */ emAgentActivation ! Activation(14400) @@ -639,8 +638,8 @@ class EmAgentIT 14400, weatherService.ref.toClassic, WeatherData( - WattsPerSquareMeter(5d), - WattsPerSquareMeter(5d), + WattsPerSquareMeter(0.5d), + WattsPerSquareMeter(2d), Celsius(0d), MetersPerSecond(0d), ), @@ -653,19 +652,19 @@ class EmAgentIT emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 14400L.toDateTime emResult.getP should equalWithTolerance( - -0.0001779479755506383.asMegaWatt + 0.00019892577822992104.asMegaWatt ) - emResult.getQ should equalWithTolerance(0.000088285537.asMegaVar) + emResult.getQ should equalWithTolerance(0.0000882855367033582.asMegaVar) } scheduler.expectMessage(Completion(emAgentActivation, Some(21600))) /* TICK 21600 LOAD: 0.269 kW (unchanged) - PV: -0.026 kW + PV: -0.023 kW Heat pump: Is not running, can run or stay off -> flex signal is 0 MW: Heat pump is turned off - -> remaining 0.242 kW + -> remaining 0.246 kW */ emAgentActivation ! Activation(21600) @@ -676,8 +675,8 @@ class EmAgentIT weatherService.ref.toClassic, WeatherData( // Same irradiation, but different angle of the sun - WattsPerSquareMeter(5d), - WattsPerSquareMeter(5d), + WattsPerSquareMeter(2d), + WattsPerSquareMeter(4d), Celsius(0d), MetersPerSecond(0d), ), @@ -690,9 +689,9 @@ class EmAgentIT emResult.getInputModel shouldBe emInput.getUuid emResult.getTime shouldBe 21600.toDateTime emResult.getP should equalWithTolerance( - 0.0001305120025875211.asMegaWatt + 0.0002450436827011999.asMegaWatt ) - emResult.getQ should equalWithTolerance(0.000088285537.asMegaVar) + emResult.getQ should equalWithTolerance(0.0000882855367033582.asMegaVar) } scheduler.expectMessage(Completion(emAgentActivation, Some(28800))) From ea94de05469b6897a530114de5a51f406b1e7ebf Mon Sep 17 00:00:00 2001 From: Marvin Heintze Date: Mon, 17 Feb 2025 11:27:56 +0100 Subject: [PATCH 17/22] spotless --- .../edu/ie3/simona/agent/em/EmAgentIT.scala | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 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 b5c2409307..a9c002b6b5 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -17,11 +17,21 @@ import edu.ie3.simona.config.SimonaConfig._ import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.event.ResultEvent.ParticipantResultEvent import edu.ie3.simona.event.notifier.NotifierConfig -import edu.ie3.simona.ontology.messages.SchedulerMessage.{Completion, ScheduleActivation} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} import edu.ie3.simona.ontology.messages.services.ServiceMessage import edu.ie3.simona.ontology.messages.services.ServiceMessage.PrimaryServiceRegistrationMessage -import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegistrationResponseMessage.{RegistrationFailedMessage, RegistrationSuccessfulMessage} -import edu.ie3.simona.ontology.messages.services.WeatherMessage.{ProvideWeatherMessage, RegisterForWeatherMessage, WeatherData} +import edu.ie3.simona.ontology.messages.services.ServiceMessage.RegistrationResponseMessage.{ + RegistrationFailedMessage, + RegistrationSuccessfulMessage, +} +import edu.ie3.simona.ontology.messages.services.WeatherMessage.{ + ProvideWeatherMessage, + RegisterForWeatherMessage, + WeatherData, +} import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.test.common.input.EmInputTestData import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK @@ -31,7 +41,10 @@ import edu.ie3.util.quantities.QuantityMatchers.equalWithTolerance import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import edu.ie3.util.scala.quantities.WattsPerSquareMeter import org.apache.pekko.actor.ActorSystem -import org.apache.pekko.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} import org.apache.pekko.actor.typed.scaladsl.adapter._ import org.apache.pekko.testkit.TestActorRef import org.scalatest.OptionValues._ @@ -581,7 +594,9 @@ class EmAgentIT emResult.getP should equalWithTolerance( -0.0007234002705905523.asMegaWatt ) - emResult.getQ should equalWithTolerance(0.0010731200407782782.asMegaVar) + emResult.getQ should equalWithTolerance( + 0.0010731200407782782.asMegaVar + ) } scheduler.expectMessage(Completion(emAgentActivation, Some(7200))) @@ -617,7 +632,9 @@ class EmAgentIT emResult.getP should equalWithTolerance( 0.0013266813910157566.asMegaWatt ) - emResult.getQ should equalWithTolerance(0.0010731200407782782.asMegaVar) + emResult.getQ should equalWithTolerance( + 0.0010731200407782782.asMegaVar + ) } scheduler.expectMessage(Completion(emAgentActivation, Some(14400))) @@ -654,7 +671,9 @@ class EmAgentIT emResult.getP should equalWithTolerance( 0.00019892577822992104.asMegaWatt ) - emResult.getQ should equalWithTolerance(0.0000882855367033582.asMegaVar) + emResult.getQ should equalWithTolerance( + 0.0000882855367033582.asMegaVar + ) } scheduler.expectMessage(Completion(emAgentActivation, Some(21600))) @@ -691,7 +710,9 @@ class EmAgentIT emResult.getP should equalWithTolerance( 0.0002450436827011999.asMegaWatt ) - emResult.getQ should equalWithTolerance(0.0000882855367033582.asMegaVar) + emResult.getQ should equalWithTolerance( + 0.0000882855367033582.asMegaVar + ) } scheduler.expectMessage(Completion(emAgentActivation, Some(28800))) From 74ca2215db5bf5a4611405cf9394bdfa3e9ddb22 Mon Sep 17 00:00:00 2001 From: Marvin Heintze Date: Mon, 17 Feb 2025 11:57:15 +0100 Subject: [PATCH 18/22] remaining values --- src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 a9c002b6b5..b6bdcda098 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -568,7 +568,7 @@ class EmAgentIT PV: -5.842 kW Heat pump: off, can be turned on or stay off -> set point ~3.5 kW (bigger than 50 % rated apparent power): turned on - -> remaining -0.499 kW + -> remaining -0.072 kW */ emAgentActivation ! Activation(0) @@ -606,7 +606,7 @@ class EmAgentIT PV: -3.791 kW Heat pump: running (turned on from last request), can also be turned off -> set point ~3.5 kW (bigger than 50 % rated apparent power): stays turned on with unchanged state - -> remaining 1.468 kW + -> remaining 1.327 kW */ emAgentActivation ! Activation(7200) @@ -640,11 +640,11 @@ class EmAgentIT scheduler.expectMessage(Completion(emAgentActivation, Some(14400))) /* TICK 14400 - LOAD: 0.269 kW (unchanged) + LOAD: 0.27 kW (unchanged) PV: -0.069 kW Heat pump: Is still running, can still be turned off -> flex signal is 0 MW: Heat pump is turned off - -> remaining 0.2 kW + -> remaining 0.199 kW */ emAgentActivation ! Activation(14400) @@ -683,7 +683,7 @@ class EmAgentIT PV: -0.023 kW Heat pump: Is not running, can run or stay off -> flex signal is 0 MW: Heat pump is turned off - -> remaining 0.246 kW + -> remaining 0.245 kW */ emAgentActivation ! Activation(21600) From eb0de326e61fad4cac60a602fd6ee89139ec51c3 Mon Sep 17 00:00:00 2001 From: Marvin Heintze Date: Mon, 17 Feb 2025 11:59:55 +0100 Subject: [PATCH 19/22] remaing value fix --- src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 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 b6bdcda098..d371ac77c8 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -640,8 +640,8 @@ class EmAgentIT scheduler.expectMessage(Completion(emAgentActivation, Some(14400))) /* TICK 14400 - LOAD: 0.27 kW (unchanged) - PV: -0.069 kW + LOAD: 0.269 kW (unchanged) + PV: -0.07 kW Heat pump: Is still running, can still be turned off -> flex signal is 0 MW: Heat pump is turned off -> remaining 0.199 kW From 526fb7875e00ef9f004548a99f9b1249b56a360f Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Mon, 17 Feb 2025 13:05:35 +0100 Subject: [PATCH 20/22] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd30d04150..3102301024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -176,7 +176,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Rename `PrimaryDataWithApparentPower` to `PrimaryDataWithComplexPower` [#1140](https://github.com/ie3-institute/simona/issues/1140) - Refactoring of `ThermalGrid.handleInfeed` to fix thermal storage recharge correctly when empty [#930](https://github.com/ie3-institute/simona/issues/930) - Move `ScheduleServiceActivation` out of `RegistrationResponseMessage` [#1143](https://github.com/ie3-institute/simona/issues/1143) -- Handle edge cases of thermal grid [#1167](https://github.com/ie3-institute/simona/issues/1167) +- Check for runningHp when handling infeed to thermalGrid [#1167](https://github.com/ie3-institute/simona/issues/1167) ## [3.0.0] - 2023-08-07 From 1f1f39d1bec2737e2d0251e818d455415bc163f6 Mon Sep 17 00:00:00 2001 From: Marvin Heintze Date: Mon, 17 Feb 2025 14:25:34 +0100 Subject: [PATCH 21/22] redundant default values + value changes in comments --- .../edu/ie3/simona/agent/em/EmAgentIT.scala | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 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 d371ac77c8..4ddb339adb 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -55,7 +55,6 @@ import squants.motion.MetersPerSecond import squants.thermal.Celsius import java.time.ZonedDateTime -import scala.concurrent.duration.{FiniteDuration, SECONDS} class EmAgentIT extends ScalaTestWithActorTestKit @@ -85,11 +84,8 @@ class EmAgentIT ) override protected val modelConfig: EmRuntimeConfig = EmRuntimeConfig( - calculateMissingReactivePowerWithModel = false, - scaling = 1d, uuids = List("default"), aggregateFlex = "SELF_OPT", - curtailRegenerative = false, ) private implicit val quantityTolerance: Double = 1e-10d @@ -125,7 +121,6 @@ class EmAgentIT loadInput, LoadRuntimeConfig( calculateMissingReactivePowerWithModel = true, - scaling = 1d, modelBehaviour = "fix", reference = "power", uuids = List.empty, @@ -150,7 +145,6 @@ class EmAgentIT pvInput, PvRuntimeConfig( calculateMissingReactivePowerWithModel = true, - scaling = 1d, uuids = List.empty, ), primaryServiceProxy.ref.toClassic, @@ -173,9 +167,7 @@ class EmAgentIT householdStorageInput, StorageRuntimeConfig( calculateMissingReactivePowerWithModel = true, - scaling = 1d, uuids = List.empty, - initialSoc = 0d, targetSoc = None, ), primaryServiceProxy.ref.toClassic, @@ -297,7 +289,7 @@ class EmAgentIT /* TICK 7200 LOAD: 0.269 kW (unchanged) PV: -3.791 kW - STORAGE: SOC 63.3 % fixme: reminder check this at the end + STORAGE: SOC 63.3 % -> charge with 3.522 kW -> remaining 0 kW */ @@ -354,8 +346,8 @@ class EmAgentIT LOAD: 0.269 kW (unchanged) PV: -0.069 kW STORAGE: SOC 100 % - -> charge with 0 kW - -> remaining 0.2 kW + -> charge with 0.2 kW + -> remaining 0.0 kW */ // send weather data before activation, which can happen @@ -417,7 +409,6 @@ class EmAgentIT loadInput, LoadRuntimeConfig( calculateMissingReactivePowerWithModel = true, - scaling = 1d, modelBehaviour = "fix", reference = "power", uuids = List.empty, @@ -442,7 +433,6 @@ class EmAgentIT pvInput, PvRuntimeConfig( calculateMissingReactivePowerWithModel = true, - scaling = 1d, uuids = List.empty, ), primaryServiceProxy.ref.toClassic, @@ -568,7 +558,7 @@ class EmAgentIT PV: -5.842 kW Heat pump: off, can be turned on or stay off -> set point ~3.5 kW (bigger than 50 % rated apparent power): turned on - -> remaining -0.072 kW + -> remaining -0.723 kW */ emAgentActivation ! Activation(0) From cd34c1c61c246e4ead15676e51336331310e35e1 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Mon, 17 Feb 2025 15:21:05 +0100 Subject: [PATCH 22/22] fix comment --- src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4ddb339adb..2f99b9d5ca 100644 --- a/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala +++ b/src/test/scala/edu/ie3/simona/agent/em/EmAgentIT.scala @@ -346,7 +346,7 @@ class EmAgentIT LOAD: 0.269 kW (unchanged) PV: -0.069 kW STORAGE: SOC 100 % - -> charge with 0.2 kW + -> discharge with 0.2 kW -> remaining 0.0 kW */