From f180b73ac98018038d907c1d134e7cde890c0a42 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Sat, 6 Apr 2024 14:13:43 +0200 Subject: [PATCH 01/55] MS: Master Thesis Dev --- .../agent/grid/CongestionManagementData.scala | 63 +++++ .../ie3/simona/agent/grid/DBFSAlgorithm.scala | 20 +- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 261 ++++++++++++++++++ .../edu/ie3/simona/agent/grid/GridAgent.scala | 28 +- 4 files changed, 352 insertions(+), 20 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementData.scala create mode 100644 src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementData.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementData.scala new file mode 100644 index 0000000000..a7b715a9c3 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementData.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.agent.grid + +import edu.ie3.simona.agent.grid.CongestionManagementData.Congestions +import edu.ie3.simona.agent.grid.GridAgentData.GridAgentBaseData +import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent +import org.apache.pekko.actor.typed.ActorRef + +case class CongestionManagementData( + gridAgentBaseData: GridAgentBaseData, + currentTick: Long, + powerFlowData: PowerFlowResultEvent, + estimatedData: PowerFlowResultEvent, + congestions: Congestions, + inferiorCongestions: Map[ActorRef[GridAgentMessage], Option[Congestions]], +) { + + /** Returns true if congestion data from inferior grids is expected and no + * data was received yet. + */ + def awaitingInferiorData: Boolean = + inferiorCongestions.values.exists(_.isEmpty) + + /** Method for updating the data with the received data. + * @param receivedData + * data that was received + * @return + * a updated copy of this data + */ + def handleReceivingData( + receivedData: Map[ActorRef[GridAgentMessage], Option[Congestions]] + ): CongestionManagementData = { + copy(inferiorCongestions = inferiorCongestions ++ receivedData) + } + + def inferiorRefs: Set[ActorRef[GridAgentMessage]] = + gridAgentBaseData.inferiorGridGates + .map(gridAgentBaseData.gridEnv.subgridGateToActorRef(_)) + .distinct + .toSet +} + +object CongestionManagementData { + case class Congestions( + voltageCongestions: Boolean, + lineCongestions: Boolean, + transformerCongestions: Boolean, + ) { + + def combine(options: Iterable[Congestions]): Congestions = + Congestions( + voltageCongestions || options.exists(_.voltageCongestions), + lineCongestions || options.exists(_.lineCongestions), + transformerCongestions || options.exists(_.transformerCongestions), + ) + + } +} diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala index 039331b2a1..982fb99b80 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala @@ -15,7 +15,7 @@ import edu.ie3.powerflow.model.PowerFlowResult import edu.ie3.powerflow.model.PowerFlowResult.FailedPowerFlowResult.FailedNewtonRaphsonPFResult import edu.ie3.powerflow.model.PowerFlowResult.SuccessFullPowerFlowResult.ValidNewtonRaphsonPFResult import edu.ie3.powerflow.model.enums.NodeType -import edu.ie3.simona.agent.grid.GridAgent.idle +import edu.ie3.simona.agent.grid.GridAgent.{idle, pipeToSelf} import edu.ie3.simona.agent.grid.GridAgentData.{ GridAgentBaseData, GridAgentConstantData, @@ -57,7 +57,6 @@ import squants.Each import java.time.{Duration, ZonedDateTime} import java.util.UUID import scala.concurrent.{ExecutionContext, Future} -import scala.util.{Failure, Success} /** Trait that is normally mixed into every [[GridAgent]] to enable distributed * forward backward sweep (DBFS) algorithm execution. It is considered to be @@ -1414,21 +1413,4 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { } } - /** This method uses [[ActorContext.pipeToSelf()]] to send a future message to - * itself. If the future is a [[Success]] the message is send, else a - * [[ReceivedFailure]] with the thrown error is send. - * @param future - * future message that should be send to the agent after it was processed - * @param ctx - * [[ActorContext]] of the receiving actor - */ - private def pipeToSelf( - future: Future[GridAgentMessage], - ctx: ActorContext[GridAgentMessage], - ): Unit = { - ctx.pipeToSelf[GridAgentMessage](future) { - case Success(value) => value - case Failure(exception) => ReceivedFailure(exception) - } - } } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala new file mode 100644 index 0000000000..3a876f9c1b --- /dev/null +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -0,0 +1,261 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.agent.grid + +import edu.ie3.simona.agent.grid.CongestionManagementData.Congestions +import edu.ie3.simona.agent.grid.GridAgent.pipeToSelf +import edu.ie3.simona.agent.grid.GridAgentData.GridAgentConstantData +import edu.ie3.simona.agent.grid.GridAgentMessage.{ + InternalMessage, + WrappedActivation, +} +import edu.ie3.simona.ontology.messages.Activation +import org.apache.pekko.actor.typed.scaladsl.AskPattern.Askable +import org.apache.pekko.actor.typed.scaladsl.{ + ActorContext, + Behaviors, + StashBuffer, +} +import org.apache.pekko.actor.typed.{ActorRef, Behavior, Scheduler} +import org.apache.pekko.util.Timeout + +import scala.concurrent.duration.SECONDS +import scala.concurrent.{ExecutionContext, Future} + +/** Trait that is normally mixed into every [[GridAgent]] to enable distributed + * congestion management (DCM) algorithm execution. It is considered to be the + * standard behaviour of a [[GridAgent]]. + */ +trait DCMAlgorithm { + case object Check extends InternalMessage + case class CongestionRequest(sender: ActorRef[GridAgentMessage]) + extends InternalMessage + case class CongestionResponse( + congestions: Congestions, + sender: ActorRef[GridAgentMessage], + ) extends InternalMessage + + case class ReceivedCongestions(congestions: Vector[CongestionResponse]) + extends InternalMessage + + case class NextStepRequest( + next: (CongestionManagementData, Int) => Behavior[GridAgentMessage] + ) extends InternalMessage + + case object StartStep extends InternalMessage + case object FinishStep extends InternalMessage + + case object FinishCongestionManagement extends InternalMessage + + /** Method that defines the [[Behavior]] for checking if there are any + * congestion in the grid. + * @param stateData + * of the actor + * @param step + * the number of the next congestion management step + * @param constantData + * constant data of the [[GridAgent]] + * @param buffer + * for stashed messages + * @return + * a [[Behavior]] + */ + def checkForCongestion( + stateData: CongestionManagementData, + step: Int, + )(implicit + constantData: GridAgentConstantData, + buffer: StashBuffer[GridAgentMessage], + ): Behavior[GridAgentMessage] = Behaviors.receivePartial { + + case (ctx, Check) => + // ask all inferior grids for a congestion check + askInferiorGridsForCongestionCheck( + stateData.inferiorRefs + )(ctx) + + checkForCongestion(stateData, step) + case (ctx, congestionRequest @ CongestionRequest(sender)) => + // check if waiting for inferior data is needed + if (stateData.awaitingInferiorData) { + ctx.log.debug( + s"Received request for congestions before all data from inferior grids were received. Stashing away." + ) + + // stash away the message, because we need to wait for data from inferior grids + buffer.stash(congestionRequest) + } else { + // check if there are any congestions in the grid + // sends the results to the superior grid + sender ! CongestionResponse( + stateData.congestions.combine( + stateData.inferiorCongestions.values.flatten + ), + ctx.self, + ) + } + + Behaviors.same + case (ctx, ReceivedCongestions(congestions)) => + // updating the state data with received data from inferior grids + val updatedStateData = stateData.handleReceivingData( + congestions.map { msg => msg.sender -> Some(msg.congestions) }.toMap + ) + + if (updatedStateData.gridAgentBaseData.isSuperior) { + // if we are the superior grid, we find the next behavior + + val congestions = updatedStateData.congestions.combine( + updatedStateData.inferiorCongestions.values.flatten + ) + + findNextStep(congestions, updatedStateData, step, ctx) + } else { + // un-stash all messages + buffer.unstashAll(checkForCongestion(updatedStateData, step)) + } + + case (_, NextStepRequest(next)) => + // inform my inferior grids about the next behavior + stateData.inferiorRefs.foreach( + _ ! NextStepRequest(next) + ) + + // switching to the next behavior + next.apply(stateData, step) + + case (ctx, FinishCongestionManagement) => + // inform my inferior grids about the end of the congestion management + stateData.inferiorRefs.foreach( + _ ! FinishCongestionManagement + ) + + // switching to simulating grid + val currentTick = stateData.currentTick + + ctx.self ! WrappedActivation(Activation(currentTick)) + GridAgent.simulateGrid(stateData.gridAgentBaseData, currentTick) + } + + /** Method that defines the [[Behavior]] for changing the tapping for + * transformers. + * + * @param stateData + * of the actor + * @param step + * the number of the current congestion management step + * @param constantData + * constant data of the [[GridAgent]] + * @param buffer + * for stashed messages + * @return + * a [[Behavior]] + */ + private def updateTransformerTapping( + stateData: CongestionManagementData, + step: Int, + )(implicit + constantData: GridAgentConstantData, + buffer: StashBuffer[GridAgentMessage], + ): Behavior[GridAgentMessage] = Behaviors.receivePartial { + case (ctx, StartStep) => + Behaviors.same + case (ctx, FinishStep) => + // inform my inferior grids about the end of this step + stateData.inferiorRefs.foreach(_ ! FinishStep) + + ctx.self ! Check + checkForCongestion(stateData, step + 1) + } + + /** Triggers an execution of the pekko `ask` pattern for all congestions in + * inferior grids (if any) of this [[GridAgent]]. + * + * @param subGridActorRefs + * a set of [[ActorRef]]s to all inferior grids + * @param ctx + * actor context + * @param askTimeout + * a timeout for the request + */ + private def askInferiorGridsForCongestionCheck( + subGridActorRefs: Set[ActorRef[GridAgentMessage]] + )(implicit + ctx: ActorContext[GridAgentMessage], + askTimeout: Timeout = Timeout(10, SECONDS), + ): Unit = { + + // request congestion check if we have inferior grids + if (subGridActorRefs.nonEmpty) { + implicit val ec: ExecutionContext = ctx.executionContext + implicit val scheduler: Scheduler = ctx.system.scheduler + + val future = Future + .sequence( + subGridActorRefs.map { inferiorGridAgentRef => + inferiorGridAgentRef + .ask(ref => CongestionRequest(ref)) + .map { case response: CongestionResponse => response } + }.toVector + ) + .map(res => ReceivedCongestions(res)) + pipeToSelf(future, ctx) + } + } + + /** Method to determine the next congestion management step. + * @param congestions + * information if there is any congestion in the grid + * @param stateData + * current state data + * @param step + * the number of the next step + * @param ctx + * actor context + * @param constantData + * constant data of the [[GridAgent]] + * @param buffer + * for stashed messages + * @return + * a [[Behavior]] + */ + private def findNextStep( + congestions: Congestions, + stateData: CongestionManagementData, + step: Int, + ctx: ActorContext[GridAgentMessage], + )(implicit + constantData: GridAgentConstantData, + buffer: StashBuffer[GridAgentMessage], + ): Behavior[GridAgentMessage] = { + + // checking for any congestion in the complete grid + if ( + !congestions.voltageCongestions && !congestions.lineCongestions && !congestions.transformerCongestions + ) { + ctx.log.debug( + s"No congestions found. Finishing the congestion management." + ) + + ctx.self ! FinishCongestionManagement + checkForCongestion(stateData, step) + } else { + step match { + case 0 => + ctx.self ! NextStepRequest(updateTransformerTapping) + checkForCongestion(stateData, step) + + // TODO: Add more congestion management steps + + case _ => + ctx.self ! FinishCongestionManagement + checkForCongestion(stateData, step) + } + } + } + +} 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 8cd2a6b1f6..af50fdb06e 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -14,6 +14,7 @@ import edu.ie3.simona.agent.grid.GridAgentData.{ GridAgentInitData, } import edu.ie3.simona.agent.grid.GridAgentMessage._ +import edu.ie3.simona.agent.grid.ReceivedValues.ReceivedFailure import edu.ie3.simona.agent.participant.ParticipantAgent.ParticipantMessage import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.event.ResultEvent @@ -26,13 +27,19 @@ import edu.ie3.simona.ontology.messages.SchedulerMessage.{ } import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK import edu.ie3.util.TimeUtil -import org.apache.pekko.actor.typed.scaladsl.{Behaviors, StashBuffer} +import org.apache.pekko.actor.typed.scaladsl.{ + ActorContext, + Behaviors, + StashBuffer, +} import org.apache.pekko.actor.typed.{ActorRef, Behavior} import java.time.ZonedDateTime import java.time.temporal.ChronoUnit import java.util.UUID +import scala.concurrent.Future import scala.language.postfixOps +import scala.util.{Failure, Success} object GridAgent extends DBFSAlgorithm { @@ -228,4 +235,23 @@ object GridAgent extends DBFSAlgorithm { s"be cause by wrong subnetGate information or invalid parametrization of the simulation!" ) } + + /** This method uses [[ActorContext.pipeToSelf()]] to send a future message to + * itself. If the future is a [[Success]] the message is send, else a + * [[ReceivedFailure]] with the thrown error is send. + * + * @param future + * future message that should be send to the agent after it was processed + * @param ctx + * [[ActorContext]] of the receiving actor + */ + private[grid] def pipeToSelf( + future: Future[GridAgentMessage], + ctx: ActorContext[GridAgentMessage], + ): Unit = { + ctx.pipeToSelf[GridAgentMessage](future) { + case Success(value) => value + case Failure(exception) => ReceivedFailure(exception) + } + } } From f1dfe94db72e8f4648af8dc9012472b602dd7ff5 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 12 Apr 2024 17:48:33 +0200 Subject: [PATCH 02/55] Adding config parameters. --- .../resources/config/config-template.conf | 20 +++ .../agent/grid/CongestionManagementData.scala | 63 -------- .../grid/CongestionManagementParams.scala | 36 +++++ .../ie3/simona/agent/grid/DBFSAlgorithm.scala | 94 ++++++------ .../ie3/simona/agent/grid/DCMAlgorithm.scala | 135 ++++++++---------- .../edu/ie3/simona/agent/grid/GridAgent.scala | 52 +++++-- .../ie3/simona/agent/grid/GridAgentData.scala | 119 ++++++++++++++- .../simona/agent/grid/GridAgentMessages.scala | 34 ++++- .../ie3/simona/config/ConfigFailFast.scala | 55 +++++++ .../edu/ie3/simona/config/SimonaConfig.scala | 130 ++++++++++++++++- .../simona/config/VoltageLimitsParser.scala | 32 +++++ .../edu/ie3/simona/model/grid/GridModel.scala | 23 +-- .../ie3/simona/model/grid/VoltageLimits.scala | 15 ++ .../ie3/simona/sim/setup/SetupHelper.scala | 22 ++- .../sim/setup/SimonaStandaloneSetup.scala | 11 +- 15 files changed, 627 insertions(+), 214 deletions(-) delete mode 100644 src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementData.scala create mode 100644 src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.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/src/main/resources/config/config-template.conf b/src/main/resources/config/config-template.conf index cb446ae680..fcbe0b631f 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 @@ -345,6 +355,7 @@ simona.powerflow.stopOnFailure = boolean | false ################################################################## simona.gridConfig.refSystems = [RefSystemConfig] +simona.gridConfig.voltageLimits = [VoltageLimitsConfig] ################################################################## # Event Configuration @@ -358,6 +369,15 @@ simona.event.listener = [ } ] +################################################################## +# Congestion Management Configuration +################################################################## + +simona.congestionManagement.enable = boolean | false +simona.congestionManagement.enableTransformerTapping = boolean | true +simona.congestionManagement.enableTopologyChanges = boolean | false +simona.congestionManagement.useFlexOptions = boolean | false + ################################################################## # Configuration of Control Schemes ################################################################## diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementData.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementData.scala deleted file mode 100644 index a7b715a9c3..0000000000 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementData.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.agent.grid - -import edu.ie3.simona.agent.grid.CongestionManagementData.Congestions -import edu.ie3.simona.agent.grid.GridAgentData.GridAgentBaseData -import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent -import org.apache.pekko.actor.typed.ActorRef - -case class CongestionManagementData( - gridAgentBaseData: GridAgentBaseData, - currentTick: Long, - powerFlowData: PowerFlowResultEvent, - estimatedData: PowerFlowResultEvent, - congestions: Congestions, - inferiorCongestions: Map[ActorRef[GridAgentMessage], Option[Congestions]], -) { - - /** Returns true if congestion data from inferior grids is expected and no - * data was received yet. - */ - def awaitingInferiorData: Boolean = - inferiorCongestions.values.exists(_.isEmpty) - - /** Method for updating the data with the received data. - * @param receivedData - * data that was received - * @return - * a updated copy of this data - */ - def handleReceivingData( - receivedData: Map[ActorRef[GridAgentMessage], Option[Congestions]] - ): CongestionManagementData = { - copy(inferiorCongestions = inferiorCongestions ++ receivedData) - } - - def inferiorRefs: Set[ActorRef[GridAgentMessage]] = - gridAgentBaseData.inferiorGridGates - .map(gridAgentBaseData.gridEnv.subgridGateToActorRef(_)) - .distinct - .toSet -} - -object CongestionManagementData { - case class Congestions( - voltageCongestions: Boolean, - lineCongestions: Boolean, - transformerCongestions: Boolean, - ) { - - def combine(options: Iterable[Congestions]): Congestions = - Congestions( - voltageCongestions || options.exists(_.voltageCongestions), - lineCongestions || options.exists(_.lineCongestions), - transformerCongestions || options.exists(_.transformerCongestions), - ) - - } -} diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala new file mode 100644 index 0000000000..d269ca88b3 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala @@ -0,0 +1,36 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.agent.grid + +/** Holds all congestion management configuration parameters used in + * [[edu.ie3.simona.agent.grid]]. If the parameter [[runCongestionManagement]] + * is set to false, no congestion management is run and all the other + * parameters are ignored + * + * @param runCongestionManagement + * defines if the congestion managements should run at all + * @param runTransformerTapping + * defines if the transformer tapping should be used for tappable + * transformers + * @param runTopologyChanges + * defines if switches should be used to change the topology of the grid + * @param useFlexOptions + * defines if available [[edu.ie3.simona.agent.em.EmAgent]] should be used to + * resolve congestions + */ +final case class CongestionManagementParams( + runCongestionManagement: Boolean, + runTransformerTapping: Boolean, + runTopologyChanges: Boolean, + useFlexOptions: Boolean, + hasRunTransformerTapping: Boolean = false, + hasRunTopologyChanges: Boolean = false, +) { + def clean: CongestionManagementParams = { + copy(hasRunTransformerTapping = false, hasRunTopologyChanges = false) + } +} diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala index 299abbfe66..f43883939f 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala @@ -17,6 +17,7 @@ import edu.ie3.powerflow.model.PowerFlowResult.SuccessFullPowerFlowResult.ValidN import edu.ie3.powerflow.model.enums.NodeType import edu.ie3.simona.agent.grid.GridAgent.{idle, pipeToSelf} import edu.ie3.simona.agent.grid.GridAgentData.{ + CongestionManagementData, GridAgentBaseData, GridAgentConstantData, PowerFlowDoneData, @@ -28,6 +29,7 @@ import edu.ie3.simona.agent.participant.ParticipantAgent.{ ParticipantMessage, RequestAssetPowerMessage, } +import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.event.RuntimeEvent.PowerFlowFailed import edu.ie3.simona.exceptions.agent.DBFSAlgorithmException import edu.ie3.simona.model.grid.{NodeModel, RefSystem} @@ -461,33 +463,59 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { } } - // notify listener about the results ctx.log.debug( - "Calculate results and sending the results to the listener ..." - ) - createAndSendPowerFlowResults( - gridAgentBaseData, - currentTick.toDateTime(constantData.simStartTime), - )(ctx.log, constantData) - - // do my cleanup stuff - ctx.log.debug("Doing my cleanup stuff") - - // / clean copy of the gridAgentBaseData - val cleanedGridAgentBaseData = GridAgentBaseData.clean( - gridAgentBaseData, - gridAgentBaseData.superiorGridNodeUuids, - gridAgentBaseData.inferiorGridGates, + "Calculate results ..." ) + val results: Option[PowerFlowResultEvent] = + gridAgentBaseData.sweepValueStores.lastOption.map { + case (_, valueStore) => + createResultModels( + gridAgentBaseData.gridEnv.gridModel, + valueStore, + )(currentTick.toDateTime(constantData.simStartTime), ctx.log) + } - // / inform scheduler that we are done with the whole simulation and request new trigger for next time step - constantData.environmentRefs.scheduler ! Completion( - constantData.activationAdapter, - Some(currentTick + constantData.resolution), - ) + if ( + gridAgentBaseData.congestionManagementParams.runCongestionManagement && results.isDefined + ) { + // go to congestion management, if enabled + + val congestionManagementData = CongestionManagementData( + gridAgentBaseData, + currentTick, + results.getOrElse( + throw new DBFSAlgorithmException( + s"No results received for tick $currentTick!" + ) + ), + ) + + ctx.self ! Check + GridAgent.checkForCongestion(congestionManagementData) + } else { + + // notify listener about the results + results.foreach(constantData.notifyListeners) - // return to Idle - idle(cleanedGridAgentBaseData) + // do my cleanup stuff + ctx.log.debug("Doing my cleanup stuff") + + // / clean copy of the gridAgentBaseData + val cleanedGridAgentBaseData = GridAgentBaseData.clean( + gridAgentBaseData, + gridAgentBaseData.superiorGridNodeUuids, + gridAgentBaseData.inferiorGridGates, + ) + + // / inform scheduler that we are done with the whole simulation and request new trigger for next time step + constantData.environmentRefs.scheduler ! Completion( + constantData.activationAdapter, + Some(currentTick + constantData.resolution), + ) + + // return to Idle + idle(cleanedGridAgentBaseData) + } case _ => // preventing "match may not be exhaustive" @@ -1371,6 +1399,7 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { * @param currentTimestamp * the current time stamp */ + @deprecated private def createAndSendPowerFlowResults( gridAgentBaseData: GridAgentBaseData, currentTimestamp: ZonedDateTime, @@ -1391,23 +1420,4 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { ) } } - - /** This method uses [[ActorContext.pipeToSelf()]] to send a future message to - * itself. If the future is a [[Success]] the message is send, else a - * [[WrappedFailure]] with the thrown error is send. - * - * @param future - * future message that should be send to the agent after it was processed - * @param ctx - * [[ActorContext]] of the receiving actor - */ - private def pipeToSelf( - future: Future[GridAgent.Request], - ctx: ActorContext[GridAgent.Request], - ): Unit = { - ctx.pipeToSelf[GridAgent.Request](future) { - case Success(value) => value - case Failure(exception) => WrappedFailure(exception) - } - } } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 3a876f9c1b..01dfd74231 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -6,14 +6,16 @@ package edu.ie3.simona.agent.grid -import edu.ie3.simona.agent.grid.CongestionManagementData.Congestions -import edu.ie3.simona.agent.grid.GridAgent.pipeToSelf -import edu.ie3.simona.agent.grid.GridAgentData.GridAgentConstantData -import edu.ie3.simona.agent.grid.GridAgentMessage.{ - InternalMessage, - WrappedActivation, +import edu.ie3.simona.agent.grid.GridAgent.{idle, pipeToSelf} +import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions +import edu.ie3.simona.agent.grid.GridAgentData.{ + CongestionManagementData, + GridAgentBaseData, + GridAgentConstantData, } +import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.ontology.messages.Activation +import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion import org.apache.pekko.actor.typed.scaladsl.AskPattern.Askable import org.apache.pekko.actor.typed.scaladsl.{ ActorContext, @@ -31,32 +33,11 @@ import scala.concurrent.{ExecutionContext, Future} * standard behaviour of a [[GridAgent]]. */ trait DCMAlgorithm { - case object Check extends InternalMessage - case class CongestionRequest(sender: ActorRef[GridAgentMessage]) - extends InternalMessage - case class CongestionResponse( - congestions: Congestions, - sender: ActorRef[GridAgentMessage], - ) extends InternalMessage - - case class ReceivedCongestions(congestions: Vector[CongestionResponse]) - extends InternalMessage - - case class NextStepRequest( - next: (CongestionManagementData, Int) => Behavior[GridAgentMessage] - ) extends InternalMessage - - case object StartStep extends InternalMessage - case object FinishStep extends InternalMessage - - case object FinishCongestionManagement extends InternalMessage /** Method that defines the [[Behavior]] for checking if there are any * congestion in the grid. * @param stateData * of the actor - * @param step - * the number of the next congestion management step * @param constantData * constant data of the [[GridAgent]] * @param buffer @@ -64,13 +45,12 @@ trait DCMAlgorithm { * @return * a [[Behavior]] */ - def checkForCongestion( - stateData: CongestionManagementData, - step: Int, + private[grid] def checkForCongestion( + stateData: CongestionManagementData )(implicit constantData: GridAgentConstantData, - buffer: StashBuffer[GridAgentMessage], - ): Behavior[GridAgentMessage] = Behaviors.receivePartial { + buffer: StashBuffer[GridAgent.Request], + ): Behavior[GridAgent.Request] = Behaviors.receivePartial { case (ctx, Check) => // ask all inferior grids for a congestion check @@ -78,8 +58,8 @@ trait DCMAlgorithm { stateData.inferiorRefs )(ctx) - checkForCongestion(stateData, step) - case (ctx, congestionRequest @ CongestionRequest(sender)) => + Behaviors.same + case (ctx, congestionRequest @ CongestionCheckRequest(sender)) => // check if waiting for inferior data is needed if (stateData.awaitingInferiorData) { ctx.log.debug( @@ -113,10 +93,10 @@ trait DCMAlgorithm { updatedStateData.inferiorCongestions.values.flatten ) - findNextStep(congestions, updatedStateData, step, ctx) + findNextStep(congestions, updatedStateData, ctx) } else { // un-stash all messages - buffer.unstashAll(checkForCongestion(updatedStateData, step)) + buffer.unstashAll(checkForCongestion(updatedStateData)) } case (_, NextStepRequest(next)) => @@ -126,19 +106,32 @@ trait DCMAlgorithm { ) // switching to the next behavior - next.apply(stateData, step) + next(stateData) - case (ctx, FinishCongestionManagement) => + case (ctx, GotoIdle) => // inform my inferior grids about the end of the congestion management stateData.inferiorRefs.foreach( - _ ! FinishCongestionManagement + _ ! GotoIdle ) - // switching to simulating grid - val currentTick = stateData.currentTick + // do my cleanup stuff + ctx.log.debug("Doing my cleanup stuff") - ctx.self ! WrappedActivation(Activation(currentTick)) - GridAgent.simulateGrid(stateData.gridAgentBaseData, currentTick) + // / clean copy of the gridAgentBaseData + val cleanedGridAgentBaseData = GridAgentBaseData.clean( + stateData.gridAgentBaseData, + stateData.gridAgentBaseData.superiorGridNodeUuids, + stateData.gridAgentBaseData.inferiorGridGates, + ) + + // / inform scheduler that we are done with the whole simulation and request new trigger for next time step + constantData.environmentRefs.scheduler ! Completion( + constantData.activationAdapter, + Some(stateData.currentTick + constantData.resolution), + ) + + // return to Idle + idle(cleanedGridAgentBaseData) } /** Method that defines the [[Behavior]] for changing the tapping for @@ -146,8 +139,6 @@ trait DCMAlgorithm { * * @param stateData * of the actor - * @param step - * the number of the current congestion management step * @param constantData * constant data of the [[GridAgent]] * @param buffer @@ -156,20 +147,29 @@ trait DCMAlgorithm { * a [[Behavior]] */ private def updateTransformerTapping( - stateData: CongestionManagementData, - step: Int, + stateData: CongestionManagementData )(implicit constantData: GridAgentConstantData, - buffer: StashBuffer[GridAgentMessage], - ): Behavior[GridAgentMessage] = Behaviors.receivePartial { - case (ctx, StartStep) => + buffer: StashBuffer[GridAgent.Request], + ): Behavior[GridAgent.Request] = Behaviors.receivePartial { + case (_, StartStep) => Behaviors.same case (ctx, FinishStep) => // inform my inferior grids about the end of this step stateData.inferiorRefs.foreach(_ ! FinishStep) - ctx.self ! Check - checkForCongestion(stateData, step + 1) + // switching to simulating grid + val currentTick = stateData.currentTick + + ctx.self ! WrappedActivation(Activation(currentTick)) + + val updatedData = stateData.gridAgentBaseData.copy( + congestionManagementParams = + stateData.gridAgentBaseData.congestionManagementParams + .copy(hasRunTransformerTapping = true) + ) + + GridAgent.simulateGrid(updatedData, currentTick) } /** Triggers an execution of the pekko `ask` pattern for all congestions in @@ -183,9 +183,9 @@ trait DCMAlgorithm { * a timeout for the request */ private def askInferiorGridsForCongestionCheck( - subGridActorRefs: Set[ActorRef[GridAgentMessage]] + subGridActorRefs: Set[ActorRef[GridAgent.Request]] )(implicit - ctx: ActorContext[GridAgentMessage], + ctx: ActorContext[GridAgent.Request], askTimeout: Timeout = Timeout(10, SECONDS), ): Unit = { @@ -198,7 +198,7 @@ trait DCMAlgorithm { .sequence( subGridActorRefs.map { inferiorGridAgentRef => inferiorGridAgentRef - .ask(ref => CongestionRequest(ref)) + .ask(ref => CongestionCheckRequest(ref)) .map { case response: CongestionResponse => response } }.toVector ) @@ -212,8 +212,6 @@ trait DCMAlgorithm { * information if there is any congestion in the grid * @param stateData * current state data - * @param step - * the number of the next step * @param ctx * actor context * @param constantData @@ -226,12 +224,11 @@ trait DCMAlgorithm { private def findNextStep( congestions: Congestions, stateData: CongestionManagementData, - step: Int, - ctx: ActorContext[GridAgentMessage], + ctx: ActorContext[GridAgent.Request], )(implicit constantData: GridAgentConstantData, - buffer: StashBuffer[GridAgentMessage], - ): Behavior[GridAgentMessage] = { + buffer: StashBuffer[GridAgent.Request], + ): Behavior[GridAgent.Request] = { // checking for any congestion in the complete grid if ( @@ -241,20 +238,14 @@ trait DCMAlgorithm { s"No congestions found. Finishing the congestion management." ) - ctx.self ! FinishCongestionManagement - checkForCongestion(stateData, step) + ctx.self ! GotoIdle + checkForCongestion(stateData) } else { - step match { - case 0 => - ctx.self ! NextStepRequest(updateTransformerTapping) - checkForCongestion(stateData, step) - // TODO: Add more congestion management steps - case _ => - ctx.self ! FinishCongestionManagement - checkForCongestion(stateData, step) - } + + + ??? } } 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 c0559bfb10..81f035f227 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -14,8 +14,6 @@ import edu.ie3.simona.agent.grid.GridAgentData.{ GridAgentInitData, } import edu.ie3.simona.agent.grid.GridAgentMessages._ -import edu.ie3.simona.agent.grid.GridAgentMessage._ -import edu.ie3.simona.agent.grid.ReceivedValues.ReceivedFailure import edu.ie3.simona.agent.participant.ParticipantAgent.ParticipantMessage import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.event.ResultEvent @@ -42,7 +40,7 @@ import scala.concurrent.Future import scala.language.postfixOps import scala.util.{Failure, Success} -object GridAgent extends DBFSAlgorithm { +object GridAgent extends DBFSAlgorithm with DCMAlgorithm { /** Trait for requests made to the [[GridAgent]] */ sealed trait Request @@ -123,6 +121,8 @@ object GridAgent extends DBFSAlgorithm { ctx.log.debug("Received InitializeTrigger.") + val cfg = constantData.simonaConfig.simona + // build the assets concurrently val subGridContainer = gridAgentInitData.subGridContainer val refSystem = gridAgentInitData.refSystem @@ -135,11 +135,12 @@ object GridAgent extends DBFSAlgorithm { val gridModel = GridModel( subGridContainer, refSystem, + gridAgentInitData.voltageLimits, TimeUtil.withDefaults.toZonedDateTime( - constantData.simonaConfig.simona.time.startDateTime + cfg.time.startDateTime ), TimeUtil.withDefaults.toZonedDateTime( - constantData.simonaConfig.simona.time.endDateTime + cfg.time.endDateTime ), simonaConfig, ) @@ -150,9 +151,9 @@ object GridAgent extends DBFSAlgorithm { constantData.environmentRefs, constantData.simStartTime, TimeUtil.withDefaults - .toZonedDateTime(constantData.simonaConfig.simona.time.endDateTime), - constantData.simonaConfig.simona.runtime.participant, - constantData.simonaConfig.simona.output.participant, + .toZonedDateTime(cfg.time.endDateTime), + cfg.runtime.participant, + cfg.output.participant, constantData.resolution, constantData.listener, ctx.log, @@ -182,11 +183,17 @@ object GridAgent extends DBFSAlgorithm { gridAgentInitData.superiorGridNodeUuids, gridAgentInitData.inferiorGridGates, PowerFlowParams( - constantData.simonaConfig.simona.powerflow.maxSweepPowerDeviation, - constantData.simonaConfig.simona.powerflow.newtonraphson.epsilon.toVector.sorted, - constantData.simonaConfig.simona.powerflow.newtonraphson.iterations, - constantData.simonaConfig.simona.powerflow.sweepTimeout, - constantData.simonaConfig.simona.powerflow.stopOnFailure, + cfg.powerflow.maxSweepPowerDeviation, + cfg.powerflow.newtonraphson.epsilon.toVector.sorted, + cfg.powerflow.newtonraphson.iterations, + cfg.powerflow.sweepTimeout, + cfg.powerflow.stopOnFailure, + ), + CongestionManagementParams( + cfg.congestionManagement.enable, + cfg.congestionManagement.enableTransformerTapping, + cfg.congestionManagement.enableTopologyChanges, + cfg.congestionManagement.useFlexOptions, ), SimonaActorNaming.actorName(ctx.self), ) @@ -244,4 +251,23 @@ object GridAgent extends DBFSAlgorithm { s"be cause by wrong subnetGate information or invalid parametrization of the simulation!" ) } + + /** This method uses [[ActorContext.pipeToSelf()]] to send a future message to + * itself. If the future is a [[Success]] the message is send, else a + * [[WrappedFailure]] with the thrown error is send. + * + * @param future + * future message that should be send to the agent after it was processed + * @param ctx + * [[ActorContext]] of the receiving actor + */ + private[grid] def pipeToSelf( + future: Future[GridAgent.Request], + ctx: ActorContext[GridAgent.Request], + ): Unit = { + ctx.pipeToSelf[GridAgent.Request](future) { + case Success(value) => value + case Failure(exception) => WrappedFailure(exception) + } + } } 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 636baca646..54ef383b0b 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -11,12 +11,14 @@ import edu.ie3.datamodel.models.input.container.{SubGridContainer, ThermalGrid} import edu.ie3.powerflow.model.PowerFlowResult import edu.ie3.powerflow.model.PowerFlowResult.SuccessFullPowerFlowResult.ValidNewtonRaphsonPFResult import edu.ie3.simona.agent.EnvironmentRefs +import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions import edu.ie3.simona.agent.grid.GridAgentMessages._ 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.event.ResultEvent.PowerFlowResultEvent +import edu.ie3.simona.model.grid.{GridModel, RefSystem, VoltageLimits} import edu.ie3.simona.ontology.messages.Activation import org.apache.pekko.actor.typed.ActorRef @@ -75,6 +77,7 @@ object GridAgentData { thermalIslandGrids: Seq[ThermalGrid], subGridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], refSystem: RefSystem, + voltageLimits: VoltageLimits, ) extends GridAgentData with GridAgentDataHelper { override protected val subgridGates: Vector[SubGridGate] = @@ -125,6 +128,7 @@ object GridAgentData { superiorGridNodeUuids: Vector[UUID], inferiorGridGates: Vector[SubGridGate], powerFlowParams: PowerFlowParams, + congestionManagementParams: CongestionManagementParams, actorName: String, ): GridAgentBaseData = { @@ -140,6 +144,7 @@ object GridAgentData { GridAgentBaseData( GridEnvironment(gridModel, subgridGateToActorRef, nodeToAssetAgents), powerFlowParams, + congestionManagementParams, currentSweepNo, ReceivedValuesStore.empty( nodeToAssetAgents, @@ -182,6 +187,8 @@ object GridAgentData { ), currentSweepNo = 0, sweepValueStores = Map.empty[Int, SweepValueStore], + congestionManagementParams = + gridAgentBaseData.congestionManagementParams.clean, ) } @@ -208,6 +215,7 @@ object GridAgentData { final case class GridAgentBaseData private ( gridEnv: GridEnvironment, powerFlowParams: PowerFlowParams, + congestionManagementParams: CongestionManagementParams, currentSweepNo: Int, receivedValueStore: ReceivedValuesStore, sweepValueStores: Map[Int, SweepValueStore], @@ -464,4 +472,113 @@ object GridAgentData { } } + final case class CongestionManagementData private ( + gridAgentBaseData: GridAgentBaseData, + currentTick: Long, + powerFlowResults: PowerFlowResultEvent, + congestions: Congestions, + inferiorCongestions: Map[ActorRef[GridAgent.Request], Option[Congestions]], + ) extends GridAgentData { + + /** Returns true if congestion data from inferior grids is expected and no + * data was received yet. + */ + def awaitingInferiorData: Boolean = + inferiorCongestions.values.exists(_.isEmpty) + + /** Method for updating the data with the received data. + * + * @param receivedData + * data that was received + * @return + * a updated copy of this data + */ + def handleReceivingData( + receivedData: Map[ActorRef[GridAgent.Request], Option[Congestions]] + ): CongestionManagementData = { + copy(inferiorCongestions = inferiorCongestions ++ receivedData) + } + + def inferiorRefs: Set[ActorRef[GridAgent.Request]] = + gridAgentBaseData.inferiorGridGates + .map(gridAgentBaseData.gridEnv.subgridGateToActorRef(_)) + .distinct + .toSet + } + + object CongestionManagementData { + def apply( + gridAgentBaseData: GridAgentBaseData, + currentTick: Long, + powerFlowResults: PowerFlowResultEvent, + ): CongestionManagementData = { + val gridModel = gridAgentBaseData.gridEnv.gridModel + val components = gridModel.gridComponents + val nodes = components.nodes.map(_.uuid) + + // checking for voltage congestions + val voltageLimits = gridModel.voltageLimits + val voltageCongestion = powerFlowResults.nodeResults.exists { res => + !voltageLimits.isInLimits(res.getvMag().getValue.doubleValue()) + } + + // checking for line congestions + val linesLimits = components.lines.map { line => line.uuid -> line }.toMap + val lineCongestion = powerFlowResults.lineResults.exists { res => + val iA = res.getiAMag().getValue.doubleValue() + val iB = res.getiBMag().getValue.doubleValue() + val iNom = linesLimits(res.getInputModel).iNom.value + + iA > iNom || iB > iNom + } + + // checking for transformer congestions + val transformer2w = components.transformers.map { transformer => + transformer.uuid -> transformer + }.toMap + val transformer2wCongestion = + powerFlowResults.transformer2wResults.exists { res => + val transformer = transformer2w(res.getInputModel) + + if (nodes.contains(transformer.hvNodeUuid)) { + res.getiAMag().getValue.doubleValue() > transformer.iNomHv.value + } else { + res.getiBMag().getValue.doubleValue() > transformer.iNomLv.value + } + } + + // TODO: Add Transformer3w congestion check + val transformer3wCongestion = false + + CongestionManagementData( + gridAgentBaseData, + currentTick, + powerFlowResults, + Congestions( + voltageCongestion, + lineCongestion, + transformer2wCongestion || transformer3wCongestion, + ), + gridAgentBaseData.gridEnv.subgridGateToActorRef.values + .map(_ -> None) + .toMap, + ) + } + + case class Congestions( + voltageCongestions: Boolean, + lineCongestions: Boolean, + transformerCongestions: Boolean, + ) { + + def combine(options: Iterable[Congestions]): Congestions = + Congestions( + voltageCongestions || options.exists(_.voltageCongestions), + lineCongestions || options.exists(_.lineCongestions), + transformerCongestions || options.exists(_.transformerCongestions), + ) + + } + } + } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala index b0d98be1e9..689e24a5b1 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala @@ -6,7 +6,11 @@ package edu.ie3.simona.agent.grid -import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData +import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions +import edu.ie3.simona.agent.grid.GridAgentData.{ + CongestionManagementData, + GridAgentInitData, +} import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ ExchangePower, ExchangeVoltage, @@ -14,7 +18,7 @@ import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ import edu.ie3.simona.ontology.messages.Activation import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.util.scala.quantities.ReactivePower -import org.apache.pekko.actor.typed.ActorRef +import org.apache.pekko.actor.typed.{ActorRef, Behavior} import squants.Power import squants.electro.ElectricPotential @@ -256,4 +260,30 @@ object GridAgentMessages { f: ElectricPotential, ) } + + // DCM messages + + case object Check extends GridAgent.InternalRequest + + case class CongestionCheckRequest(sender: ActorRef[GridAgent.Request]) + extends GridAgent.InternalRequest + + case class CongestionResponse( + congestions: Congestions, + sender: ActorRef[GridAgent.Request], + ) extends GridAgent.InternalReply + + case class ReceivedCongestions(congestions: Vector[CongestionResponse]) + extends GridAgent.InternalRequest + + case class NextStepRequest( + next: CongestionManagementData => Behavior[GridAgent.Request] + ) extends GridAgent.InternalRequest + + case object StartStep extends GridAgent.InternalRequest + + case object FinishStep extends GridAgent.InternalRequest + + case object GotoIdle extends GridAgent.InternalRequest + } diff --git a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala index 09f2bf62ae..92fae3ddd3 100644 --- a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala +++ b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala @@ -15,6 +15,7 @@ import edu.ie3.simona.config.SimonaConfig.{ ResultKafkaParams, Simona, TransformerControlGroup, + VoltageLimitsConfig, } import edu.ie3.simona.exceptions.InvalidConfigParameterException import edu.ie3.simona.io.result.ResultSinkType @@ -116,6 +117,8 @@ case object ConfigFailFast extends LazyLogging { val refSystems = simonaConfig.simona.gridConfig.refSystems refSystems.foreach(checkRefSystem) + simonaConfig.simona.gridConfig.voltageLimits.foreach(checkVoltageLimits) + /* Check all participant model configurations */ checkParticipantRuntimeConfiguration( simonaConfig.simona.runtime.participant @@ -495,6 +498,58 @@ case object ConfigFailFast extends LazyLogging { } } + /** Sanity checks for a [[SimonaConfig.VoltageLimitsConfig]] + * + * @param voltageLimits + * the [[SimonaConfig.VoltageLimitsConfig]] that should be checked + */ + private def checkVoltageLimits(voltageLimits: VoltageLimitsConfig): Unit = { + val voltLvls = + voltageLimits.voltLvls.getOrElse(List.empty[SimonaConfig.VoltLvlConfig]) + val gridIds = voltageLimits.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/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index c64c62ca3f..55d231a9f0 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -943,6 +943,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, @@ -1004,6 +1071,7 @@ object SimonaConfig { } final case class Simona( + congestionManagement: SimonaConfig.Simona.CongestionManagement, control: scala.Option[SimonaConfig.Simona.Control], event: SimonaConfig.Simona.Event, gridConfig: SimonaConfig.Simona.GridConfig, @@ -1015,6 +1083,33 @@ object SimonaConfig { time: SimonaConfig.Simona.Time, ) object Simona { + final case class CongestionManagement( + enable: scala.Boolean, + enableTopologyChanges: scala.Boolean, + enableTransformerTapping: scala.Boolean, + useFlexOptions: scala.Boolean, + ) + object CongestionManagement { + def apply( + c: com.typesafe.config.Config, + parentPath: java.lang.String, + $tsCfgValidator: $TsCfgValidator, + ): SimonaConfig.Simona.CongestionManagement = { + SimonaConfig.Simona.CongestionManagement( + enable = c.hasPathOrNull("enable") && c.getBoolean("enable"), + enableTopologyChanges = + c.hasPathOrNull("enableTopologyChanges") && c.getBoolean( + "enableTopologyChanges" + ), + enableTransformerTapping = !c.hasPathOrNull( + "enableTransformerTapping" + ) || c.getBoolean("enableTransformerTapping"), + useFlexOptions = + c.hasPathOrNull("useFlexOptions") && c.getBoolean("useFlexOptions"), + ) + } + } + final case class Control( transformer: scala.List[SimonaConfig.TransformerControlGroup] ) @@ -1136,7 +1231,8 @@ object SimonaConfig { } final case class GridConfig( - refSystems: scala.List[SimonaConfig.RefSystemConfig] + refSystems: scala.List[SimonaConfig.RefSystemConfig], + voltageLimits: scala.List[SimonaConfig.VoltageLimitsConfig], ) object GridConfig { def apply( @@ -1149,7 +1245,12 @@ object SimonaConfig { c.getList("refSystems"), parentPath, $tsCfgValidator, - ) + ), + voltageLimits = $_LSimonaConfig_VoltageLimitsConfig( + c.getList("voltageLimits"), + parentPath, + $tsCfgValidator, + ), ) } private def $_LSimonaConfig_RefSystemConfig( @@ -1168,6 +1269,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( @@ -2781,6 +2898,15 @@ object SimonaConfig { $tsCfgValidator: $TsCfgValidator, ): SimonaConfig.Simona = { SimonaConfig.Simona( + congestionManagement = SimonaConfig.Simona.CongestionManagement( + if (c.hasPathOrNull("congestionManagement")) + c.getConfig("congestionManagement") + else + com.typesafe.config.ConfigFactory + .parseString("congestionManagement{}"), + parentPath + "congestionManagement.", + $tsCfgValidator, + ), control = if (c.hasPathOrNull("control")) scala.Some( 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..f8e1c237d5 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala @@ -0,0 +1,32 @@ +/* + * © 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.VoltageLevel +import edu.ie3.simona.model.grid.VoltageLimits + +object VoltageLimitsParser { + + final case class ConfigVoltageLimits( + private val gridIdVoltageLimits: Map[Int, VoltageLimits], + private val voltLvLVoltageLimits: Map[VoltageLevel, VoltageLimits], + ) { + def find( + gridId: Int, + voltLvl: Option[VoltageLevel] = None, + ): Option[VoltageLimits] = gridIdVoltageLimits + .get(gridId) + .orElse(voltLvl.flatMap(voltLvLVoltageLimits.get)) + } + + def parse( + configVoltageLimits: List[SimonaConfig.VoltageLimitsConfig] + ): ConfigVoltageLimits = { + ??? + } + +} 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 f2eab5ff1d..306dda0034 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/GridModel.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/GridModel.scala @@ -15,7 +15,6 @@ import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.exceptions.GridInconsistencyException import edu.ie3.simona.exceptions.agent.GridAgentInitializationException import edu.ie3.simona.model.SystemComponent -import edu.ie3.simona.model.control.{GridControls, TransformerControlGroupModel} import edu.ie3.simona.model.grid.GridModel.GridComponents import edu.ie3.simona.model.grid.Transformer3wPowerFlowCase.{ PowerFlowCaseA, @@ -40,7 +39,7 @@ final case class GridModel( subnetNo: Int, mainRefSystem: RefSystem, gridComponents: GridComponents, - gridControls: GridControls, + voltageLimits: VoltageLimits, ) { // init nodeUuidToIndexMap @@ -67,12 +66,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, @@ -464,6 +465,7 @@ object GridModel { * @param maybeControlConfig * Config of ControlGroup */ + @deprecated private def validateControlGroups( subGridContainer: SubGridContainer, maybeControlConfig: Option[SimonaConfig.Simona.Control], @@ -514,6 +516,7 @@ object GridModel { private def buildAndValidate( subGridContainer: SubGridContainer, refSystem: RefSystem, + voltageLimits: VoltageLimits, startDate: ZonedDateTime, endDate: ZonedDateTime, simonaConfig: SimonaConfig, @@ -600,21 +603,11 @@ object GridModel { switches, ) - /* Build transformer control groups */ - val transformerControlGroups = simonaConfig.simona.control - .map { controlConfig => - TransformerControlGroupModel.buildControlGroups( - subGridContainer.getRawGrid.getMeasurementUnits.asScala.toSet, - controlConfig.transformer, - ) - } - .getOrElse(Set.empty) - val gridModel = GridModel( subGridContainer.getSubnet, refSystem, gridComponents, - GridControls(transformerControlGroups), + voltageLimits, ) /** Check and validates the grid. Especially the consistency of the grid @@ -626,10 +619,6 @@ object GridModel { // validate validateConsistency(gridModel) validateConnectivity(gridModel) - validateControlGroups( - subGridContainer, - simonaConfig.simona.control, - ) // return gridModel 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..5dd92e0acf --- /dev/null +++ b/src/main/scala/edu/ie3/simona/model/grid/VoltageLimits.scala @@ -0,0 +1,15 @@ +/* + * © 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 + +case class VoltageLimits( + vMin: Double, + vMax: Double, +) { + def isInLimits(voltage: Double): Boolean = + vMin <= voltage && voltage <= vMax +} diff --git a/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala b/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala index 535b140798..1d30da36c7 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SetupHelper.scala @@ -17,10 +17,11 @@ 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.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} @@ -61,6 +62,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( @@ -73,6 +75,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) @@ -83,6 +87,7 @@ trait SetupHelper extends LazyLogging { thermalGrids, subGridGateToActorRef, refSystem, + voltageLimits, ) } @@ -199,6 +204,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 1f0d894316..e510be5c6f 100644 --- a/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala +++ b/src/main/scala/edu/ie3/simona/sim/setup/SimonaStandaloneSetup.scala @@ -19,7 +19,12 @@ import edu.ie3.simona.api.ExtSimAdapter import edu.ie3.simona.api.data.ExtData import edu.ie3.simona.api.data.ev.{ExtEvData, ExtEvSimulation} 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 @@ -89,6 +94,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, @@ -134,6 +142,7 @@ class SimonaStandaloneSetup( subGridToActorRefMap, subGridGates, configRefSystems, + configVoltageLimits, thermalGrids, ) From db6a5697b480fbd8ce026a0365f8b05b297e784d Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Sun, 14 Apr 2024 16:58:27 +0200 Subject: [PATCH 03/55] Improving and enhancing some implementations. --- .../resources/config/config-template.conf | 1 - .../grid/CongestionManagementParams.scala | 7 +- .../ie3/simona/agent/grid/DBFSAlgorithm.scala | 2 +- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 194 ++++++++++-------- .../edu/ie3/simona/agent/grid/GridAgent.scala | 1 - .../ie3/simona/agent/grid/GridAgentData.scala | 69 +++++-- .../simona/agent/grid/GridAgentMessages.scala | 8 +- .../agent/grid/GridResultsSupport.scala | 2 +- .../edu/ie3/simona/config/SimonaConfig.scala | 2 - .../simona/config/VoltageLimitsParser.scala | 85 +++++++- .../model/grid/Transformer3wModel.scala | 28 ++- 11 files changed, 279 insertions(+), 120 deletions(-) diff --git a/src/main/resources/config/config-template.conf b/src/main/resources/config/config-template.conf index fcbe0b631f..85f9bd9fcd 100644 --- a/src/main/resources/config/config-template.conf +++ b/src/main/resources/config/config-template.conf @@ -373,7 +373,6 @@ simona.event.listener = [ # Congestion Management Configuration ################################################################## -simona.congestionManagement.enable = boolean | false simona.congestionManagement.enableTransformerTapping = boolean | true simona.congestionManagement.enableTopologyChanges = boolean | false simona.congestionManagement.useFlexOptions = boolean | false diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala index d269ca88b3..81dd88ed2c 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala @@ -11,8 +11,6 @@ package edu.ie3.simona.agent.grid * is set to false, no congestion management is run and all the other * parameters are ignored * - * @param runCongestionManagement - * defines if the congestion managements should run at all * @param runTransformerTapping * defines if the transformer tapping should be used for tappable * transformers @@ -23,13 +21,16 @@ package edu.ie3.simona.agent.grid * resolve congestions */ final case class CongestionManagementParams( - runCongestionManagement: Boolean, runTransformerTapping: Boolean, runTopologyChanges: Boolean, useFlexOptions: Boolean, hasRunTransformerTapping: Boolean = false, hasRunTopologyChanges: Boolean = false, ) { + + def runCongestionManagement: Boolean = + runTransformerTapping || runTopologyChanges || useFlexOptions + def clean: CongestionManagementParams = { copy(hasRunTransformerTapping = false, hasRunTopologyChanges = false) } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala index f43883939f..1e7c905bf4 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala @@ -490,7 +490,7 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { ), ) - ctx.self ! Check + ctx.self ! StartStep GridAgent.checkForCongestion(congestionManagementData) } else { diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 01dfd74231..6c3f747205 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -7,7 +7,6 @@ package edu.ie3.simona.agent.grid import edu.ie3.simona.agent.grid.GridAgent.{idle, pipeToSelf} -import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions import edu.ie3.simona.agent.grid.GridAgentData.{ CongestionManagementData, GridAgentBaseData, @@ -17,12 +16,8 @@ import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.ontology.messages.Activation import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion import org.apache.pekko.actor.typed.scaladsl.AskPattern.Askable -import org.apache.pekko.actor.typed.scaladsl.{ - ActorContext, - Behaviors, - StashBuffer, -} -import org.apache.pekko.actor.typed.{ActorRef, Behavior, Scheduler} +import org.apache.pekko.actor.typed.scaladsl.{Behaviors, StashBuffer} +import org.apache.pekko.actor.typed.{Behavior, Scheduler} import org.apache.pekko.util.Timeout import scala.concurrent.duration.SECONDS @@ -52,13 +47,27 @@ trait DCMAlgorithm { buffer: StashBuffer[GridAgent.Request], ): Behavior[GridAgent.Request] = Behaviors.receivePartial { - case (ctx, Check) => - // ask all inferior grids for a congestion check - askInferiorGridsForCongestionCheck( - stateData.inferiorRefs - )(ctx) + case (ctx, StartStep) => + // request congestion check if we have inferior grids + if (stateData.inferiorRefs.nonEmpty) { + implicit val askTimeout: Timeout = Timeout(10, SECONDS) + implicit val ec: ExecutionContext = ctx.executionContext + implicit val scheduler: Scheduler = ctx.system.scheduler + + val future = Future + .sequence( + stateData.inferiorRefs.map { inferiorGridAgentRef => + inferiorGridAgentRef + .ask(ref => CongestionCheckRequest(ref)) + .map { case response: CongestionResponse => response } + }.toVector + ) + .map(res => ReceivedCongestions(res)) + pipeToSelf(future, ctx) + } Behaviors.same + case (ctx, congestionRequest @ CongestionCheckRequest(sender)) => // check if waiting for inferior data is needed if (stateData.awaitingInferiorData) { @@ -80,11 +89,10 @@ trait DCMAlgorithm { } Behaviors.same + case (ctx, ReceivedCongestions(congestions)) => // updating the state data with received data from inferior grids - val updatedStateData = stateData.handleReceivingData( - congestions.map { msg => msg.sender -> Some(msg.congestions) }.toMap - ) + val updatedStateData = stateData.handleReceivingData(congestions) if (updatedStateData.gridAgentBaseData.isSuperior) { // if we are the superior grid, we find the next behavior @@ -93,7 +101,40 @@ trait DCMAlgorithm { updatedStateData.inferiorCongestions.values.flatten ) - findNextStep(congestions, updatedStateData, ctx) + // checking for any congestion in the complete grid + if (!congestions.any) { + ctx.log.debug( + s"No congestions found. Finishing the congestion management." + ) + + ctx.self ! GotoIdle + checkForCongestion(updatedStateData) + } else { + val steps = + updatedStateData.gridAgentBaseData.congestionManagementParams + + val msg = + if ( + congestions.voltageCongestions && steps.runTransformerTapping && !steps.hasRunTransformerTapping + ) { + NextStepRequest(updateTransformerTapping) + } else if ( + congestions.lineCongestions && steps.runTopologyChanges && !steps.hasRunTopologyChanges + ) { + NextStepRequest(useTopologyChanges) + } else if (congestions.any && !steps.useFlexOptions) { + NextStepRequest(usePlexOptions) + } else { + ctx.log.info( + s"There were some congestions that could not be resolved for timestamp: ${updatedStateData.currentTick}" + ) + GotoIdle + } + + ctx.self ! msg + checkForCongestion(updatedStateData) + } + } else { // un-stash all messages buffer.unstashAll(checkForCongestion(updatedStateData)) @@ -153,7 +194,10 @@ trait DCMAlgorithm { buffer: StashBuffer[GridAgent.Request], ): Behavior[GridAgent.Request] = Behaviors.receivePartial { case (_, StartStep) => + // TODO: Implement a proper behavior + Behaviors.same + case (ctx, FinishStep) => // inform my inferior grids about the end of this step stateData.inferiorRefs.foreach(_ ! FinishStep) @@ -161,92 +205,74 @@ trait DCMAlgorithm { // switching to simulating grid val currentTick = stateData.currentTick - ctx.self ! WrappedActivation(Activation(currentTick)) - val updatedData = stateData.gridAgentBaseData.copy( congestionManagementParams = stateData.gridAgentBaseData.congestionManagementParams .copy(hasRunTransformerTapping = true) ) + // simulate grid after changing the transformer tapping + ctx.self ! WrappedActivation(Activation(currentTick)) GridAgent.simulateGrid(updatedData, currentTick) } - /** Triggers an execution of the pekko `ask` pattern for all congestions in - * inferior grids (if any) of this [[GridAgent]]. - * - * @param subGridActorRefs - * a set of [[ActorRef]]s to all inferior grids - * @param ctx - * actor context - * @param askTimeout - * a timeout for the request - */ - private def askInferiorGridsForCongestionCheck( - subGridActorRefs: Set[ActorRef[GridAgent.Request]] - )(implicit - ctx: ActorContext[GridAgent.Request], - askTimeout: Timeout = Timeout(10, SECONDS), - ): Unit = { - - // request congestion check if we have inferior grids - if (subGridActorRefs.nonEmpty) { - implicit val ec: ExecutionContext = ctx.executionContext - implicit val scheduler: Scheduler = ctx.system.scheduler - - val future = Future - .sequence( - subGridActorRefs.map { inferiorGridAgentRef => - inferiorGridAgentRef - .ask(ref => CongestionCheckRequest(ref)) - .map { case response: CongestionResponse => response } - }.toVector - ) - .map(res => ReceivedCongestions(res)) - pipeToSelf(future, ctx) - } + // TODO: Implement a proper behavior + private def useTopologyChanges(stateData: CongestionManagementData)(implicit + constantData: GridAgentConstantData, + buffer: StashBuffer[GridAgent.Request], + ): Behavior[GridAgent.Request] = Behaviors.receivePartial { + case (ctx, StartStep) => + // for now this step is skipped + ctx.log.debug( + s"Using topology changes to resolve a congestion is not implemented yet. Skipping this step!" + ) + + ctx.self ! FinishStep + Behaviors.same + + case (ctx, FinishStep) => + // inform my inferior grids about the end of this step + stateData.inferiorRefs.foreach(_ ! FinishStep) + + val updatedGridAgentData = stateData.gridAgentBaseData.copy( + congestionManagementParams = + stateData.gridAgentBaseData.congestionManagementParams + .copy(hasRunTopologyChanges = true) + ) + + ctx.self ! StartStep + checkForCongestion( + stateData.copy(gridAgentBaseData = updatedGridAgentData) + ) } - /** Method to determine the next congestion management step. - * @param congestions - * information if there is any congestion in the grid - * @param stateData - * current state data - * @param ctx - * actor context - * @param constantData - * constant data of the [[GridAgent]] - * @param buffer - * for stashed messages - * @return - * a [[Behavior]] - */ - private def findNextStep( - congestions: Congestions, - stateData: CongestionManagementData, - ctx: ActorContext[GridAgent.Request], - )(implicit + // TODO: Implement a proper behavior + private def usePlexOptions(stateData: CongestionManagementData)(implicit constantData: GridAgentConstantData, buffer: StashBuffer[GridAgent.Request], - ): Behavior[GridAgent.Request] = { - - // checking for any congestion in the complete grid - if ( - !congestions.voltageCongestions && !congestions.lineCongestions && !congestions.transformerCongestions - ) { + ): Behavior[GridAgent.Request] = Behaviors.receivePartial { + case (ctx, StartStep) => + // for now this step is skipped ctx.log.debug( - s"No congestions found. Finishing the congestion management." + s"Using flex options to resolve a congestion is not implemented yet. Skipping this step!" ) - ctx.self ! GotoIdle - checkForCongestion(stateData) - } else { - + ctx.self ! FinishStep + Behaviors.same + case (ctx, FinishStep) => + // inform my inferior grids about the end of this step + stateData.inferiorRefs.foreach(_ ! FinishStep) + val updatedGridAgentData = stateData.gridAgentBaseData.copy( + congestionManagementParams = + stateData.gridAgentBaseData.congestionManagementParams + .copy(hasRunTopologyChanges = true) + ) - ??? - } + ctx.self ! StartStep + checkForCongestion( + stateData.copy(gridAgentBaseData = updatedGridAgentData) + ) } - } 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 81f035f227..7d160db803 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -190,7 +190,6 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { cfg.powerflow.stopOnFailure, ), CongestionManagementParams( - cfg.congestionManagement.enable, cfg.congestionManagement.enableTransformerTapping, cfg.congestionManagement.enableTopologyChanges, cfg.congestionManagement.useFlexOptions, 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 54ef383b0b..b3e0409f61 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -18,6 +18,7 @@ import edu.ie3.simona.agent.participant.ParticipantAgent.ParticipantMessage import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.event.ResultEvent import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent +import edu.ie3.simona.model.grid.GridModel.GridComponents import edu.ie3.simona.model.grid.{GridModel, RefSystem, VoltageLimits} import edu.ie3.simona.ontology.messages.Activation import org.apache.pekko.actor.typed.ActorRef @@ -494,9 +495,11 @@ object GridAgentData { * a updated copy of this data */ def handleReceivingData( - receivedData: Map[ActorRef[GridAgent.Request], Option[Congestions]] + receivedData: Vector[CongestionResponse] ): CongestionManagementData = { - copy(inferiorCongestions = inferiorCongestions ++ receivedData) + val mappedData = + receivedData.map(res => res.sender -> Some(res.congestions)).toMap + copy(inferiorCongestions = inferiorCongestions ++ mappedData) } def inferiorRefs: Set[ActorRef[GridAgent.Request]] = @@ -513,17 +516,40 @@ object GridAgentData { powerFlowResults: PowerFlowResultEvent, ): CongestionManagementData = { val gridModel = gridAgentBaseData.gridEnv.gridModel - val components = gridModel.gridComponents - val nodes = components.nodes.map(_.uuid) + + val congestions = getCongestions( + powerFlowResults, + gridModel.gridComponents, + gridModel.voltageLimits, + ) + + CongestionManagementData( + gridAgentBaseData, + currentTick, + powerFlowResults, + congestions, + gridAgentBaseData.gridEnv.subgridGateToActorRef.values + .map(_ -> None) + .toMap, + ) + } + + private def getCongestions( + powerFlowResults: PowerFlowResultEvent, + gridComponents: GridComponents, + voltageLimits: VoltageLimits, + ): Congestions = { + val nodes = gridComponents.nodes.map(_.uuid) // checking for voltage congestions - val voltageLimits = gridModel.voltageLimits val voltageCongestion = powerFlowResults.nodeResults.exists { res => !voltageLimits.isInLimits(res.getvMag().getValue.doubleValue()) } // checking for line congestions - val linesLimits = components.lines.map { line => line.uuid -> line }.toMap + val linesLimits = gridComponents.lines.map { line => + line.uuid -> line + }.toMap val lineCongestion = powerFlowResults.lineResults.exists { res => val iA = res.getiAMag().getValue.doubleValue() val iB = res.getiBMag().getValue.doubleValue() @@ -533,7 +559,7 @@ object GridAgentData { } // checking for transformer congestions - val transformer2w = components.transformers.map { transformer => + val transformer2w = gridComponents.transformers.map { transformer => transformer.uuid -> transformer }.toMap val transformer2wCongestion = @@ -547,21 +573,18 @@ object GridAgentData { } } - // TODO: Add Transformer3w congestion check - val transformer3wCongestion = false + val transformer3w = gridComponents.transformers3w.map { transformer => + transformer.uuid -> transformer + }.toMap + val transformer3wCongestion = + powerFlowResults.transformer3wResults.exists { res => + res.currentMagnitude > transformer3w(res.input).iNom + } - CongestionManagementData( - gridAgentBaseData, - currentTick, - powerFlowResults, - Congestions( - voltageCongestion, - lineCongestion, - transformer2wCongestion || transformer3wCongestion, - ), - gridAgentBaseData.gridEnv.subgridGateToActorRef.values - .map(_ -> None) - .toMap, + Congestions( + voltageCongestion, + lineCongestion, + transformer2wCongestion || transformer3wCongestion, ) } @@ -571,6 +594,9 @@ object GridAgentData { transformerCongestions: Boolean, ) { + def any: Boolean = + voltageCongestions || lineCongestions || transformerCongestions + def combine(options: Iterable[Congestions]): Congestions = Congestions( voltageCongestions || options.exists(_.voltageCongestions), @@ -580,5 +606,4 @@ object GridAgentData { } } - } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala index 689e24a5b1..d650753c63 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala @@ -263,8 +263,6 @@ object GridAgentMessages { // DCM messages - case object Check extends GridAgent.InternalRequest - case class CongestionCheckRequest(sender: ActorRef[GridAgent.Request]) extends GridAgent.InternalRequest @@ -280,10 +278,16 @@ object GridAgentMessages { next: CongestionManagementData => Behavior[GridAgent.Request] ) extends GridAgent.InternalRequest + /** Message that indicates all actors that the current step is started. + */ case object StartStep extends GridAgent.InternalRequest + /** Message that indicates all actors that the current step is finished. + */ case object FinishStep extends GridAgent.InternalRequest + /** Message that indicates all actors that the next state is the idle state. + */ case object GotoIdle extends GridAgent.InternalRequest } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala index c872cc8e54..1557597c85 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala @@ -607,7 +607,7 @@ object GridResultsSupport { sealed trait PartialTransformer3wResult { val time: ZonedDateTime val input: UUID - protected val currentMagnitude: ElectricCurrent + val currentMagnitude: ElectricCurrent protected val currentAngle: Angle } diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index 55d231a9f0..9cdc46f41e 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -1084,7 +1084,6 @@ object SimonaConfig { ) object Simona { final case class CongestionManagement( - enable: scala.Boolean, enableTopologyChanges: scala.Boolean, enableTransformerTapping: scala.Boolean, useFlexOptions: scala.Boolean, @@ -1096,7 +1095,6 @@ object SimonaConfig { $tsCfgValidator: $TsCfgValidator, ): SimonaConfig.Simona.CongestionManagement = { SimonaConfig.Simona.CongestionManagement( - enable = c.hasPathOrNull("enable") && c.getBoolean("enable"), enableTopologyChanges = c.hasPathOrNull("enableTopologyChanges") && c.getBoolean( "enableTopologyChanges" diff --git a/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala b/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala index f8e1c237d5..3e6a8637f3 100644 --- a/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala +++ b/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala @@ -6,8 +6,13 @@ package edu.ie3.simona.config -import edu.ie3.datamodel.models.voltagelevels.VoltageLevel +import edu.ie3.datamodel.models.voltagelevels.{ + GermanVoltageLevelUtils, + VoltageLevel, +} +import edu.ie3.simona.exceptions.InvalidConfigParameterException import edu.ie3.simona.model.grid.VoltageLimits +import edu.ie3.simona.util.CollectionUtils object VoltageLimitsParser { @@ -26,7 +31,83 @@ object VoltageLimitsParser { def parse( configVoltageLimits: List[SimonaConfig.VoltageLimitsConfig] ): ConfigVoltageLimits = { - ??? + + val voltageLimits = configVoltageLimits.map { configVoltageLimit => + ( + configVoltageLimit, + VoltageLimits(configVoltageLimit.vMin, configVoltageLimit.vMax), + ) + } + + val gridIdVoltageLimits = voltageLimits.flatMap { case (config, limits) => + config.gridIds + .map { + _.flatMap { gridId => + { + val allGridIds = gridId match { + case ConfigConventions.gridIdDotRange(from, to) => + from.toInt to to.toInt + case ConfigConventions.gridIdMinusRange(from, to) => + from.toInt to to.toInt + case ConfigConventions.singleGridId(singleGridId) => + Seq(singleGridId.toInt) + case unknownGridIdFormat => + throw new InvalidConfigParameterException( + s"Unknown gridId format $unknownGridIdFormat provided for voltage limits $config" + ) + } + + allGridIds.map(gridId => (gridId, limits)) + } + } + } + .getOrElse(Seq.empty[(Int, VoltageLimits)]) + } + + val voltLvlVoltageLimits = voltageLimits.flatMap { + case (configRefSystem, parsedRefSystem) => + val voltageLimit = VoltageLimits(0.9, 1.1) + + configRefSystem.voltLvls + .map { + _.map { voltLvlDef => + (VoltLvlParser.from(voltLvlDef), parsedRefSystem) + } + } + .getOrElse( + Seq( + (GermanVoltageLevelUtils.LV, voltageLimit), + (GermanVoltageLevelUtils.MV_10KV, voltageLimit), + (GermanVoltageLevelUtils.MV_20KV, voltageLimit), + (GermanVoltageLevelUtils.MV_30KV, voltageLimit), + (GermanVoltageLevelUtils.HV, voltageLimit), + (GermanVoltageLevelUtils.EHV_220KV, VoltageLimits(0.9, 1.118)), + (GermanVoltageLevelUtils.EHV_380KV, VoltageLimits(0.9, 1.05)), + ) + ) + } + + // check for duplicates of gridIds and voltLevels which will be the key for the following map conversion + if ( + CollectionUtils.listHasDuplicates( + gridIdVoltageLimits.map { case (gridId, _) => gridId } + ) + ) + throw new InvalidConfigParameterException( + s"The provided gridIds in simona.gridConfig.voltageLimits contains duplicates. " + + s"Please check if there are either duplicate entries or overlapping ranges!" + ) + if ( + CollectionUtils.listHasDuplicates( + voltLvlVoltageLimits.map { case (voltLvl, _) => voltLvl } + ) + ) + throw new InvalidConfigParameterException( + s"The provided voltLvls in simona.gridConfig.voltageLimits contains duplicates. " + + s"Please check your configuration for duplicates in voltLvl entries!" + ) + + ConfigVoltageLimits(gridIdVoltageLimits.toMap, voltLvlVoltageLimits.toMap) } } diff --git a/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala b/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala index 8f61c2a897..d7f42c933e 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala @@ -28,7 +28,7 @@ import edu.ie3.simona.util.SimonaConstants import edu.ie3.util.quantities.PowerSystemUnits._ import edu.ie3.util.scala.OperationInterval import squants.electro.{Kilovolts, Ohms, Siemens} -import squants.energy.Megawatts +import squants.energy.{Megawatts, Watts} import tech.units.indriya.AbstractUnit import tech.units.indriya.quantity.Quantities import tech.units.indriya.unit.Units.{OHM, SIEMENS} @@ -66,6 +66,9 @@ import scala.math.BigDecimal.RoundingMode * number of parallel transformers * @param powerFlowCase * the [[Transformer3wPowerFlowCase]] + * @param iNom + * the nominal current at the port that is defined by the + * [[Transformer3wPowerFlowCase]] * @param r * resistance r, real part of the transformer impedance z (referenced to the * nominal impedance of the grid) in p.u. @@ -91,6 +94,7 @@ final case class Transformer3wModel( override protected val transformerTappingModel: TransformerTappingModel, amount: Int, powerFlowCase: Transformer3wPowerFlowCase, + iNom: squants.electro.ElectricCurrent, protected val r: squants.Dimensionless, protected val x: squants.Dimensionless, protected val g: squants.Dimensionless, @@ -258,6 +262,27 @@ case object Transformer3wModel extends LazyLogging { .setScale(5, RoundingMode.HALF_UP) } + val iNom = powerFlowCase match { + case PowerFlowCaseA => + Watts( + trafo3wType.getsRatedA().to(VOLTAMPERE).getValue.doubleValue() + ) / Math.sqrt(3) / Kilovolts( + trafo3wType.getvRatedA().to(KILOVOLT).getValue.doubleValue() + ) + case PowerFlowCaseB => + Watts( + trafo3wType.getsRatedB().to(VOLTAMPERE).getValue.doubleValue() + ) / Math.sqrt(3) / Kilovolts( + trafo3wType.getvRatedB().to(KILOVOLT).getValue.doubleValue() + ) + case PowerFlowCaseC => + Watts( + trafo3wType.getsRatedC().to(VOLTAMPERE).getValue.doubleValue() + ) / Math.sqrt(3) / Kilovolts( + trafo3wType.getvRatedC().to(KILOVOLT).getValue.doubleValue() + ) + } + val operationInterval = SystemComponent.determineOperationInterval( startDate, @@ -277,6 +302,7 @@ case object Transformer3wModel extends LazyLogging { transformerTappingModel, transformer3wInput.getParallelDevices, powerFlowCase, + iNom, r, x, g, From 72d7a3caa55e58168d1d94d29ef1ddb0fdba22b5 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 16 Apr 2024 13:53:26 +0200 Subject: [PATCH 04/55] Adding `DCMAlgorithmSupGridSpec`. Small improvement. --- input/samples/vn_simona/vn_simona.conf | 23 ++ .../resources/config/config-template.conf | 3 +- .../grid/CongestionManagementParams.scala | 3 + .../ie3/simona/agent/grid/DBFSAlgorithm.scala | 43 +--- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 49 ++-- .../edu/ie3/simona/agent/grid/GridAgent.scala | 49 +++- .../ie3/simona/agent/grid/GridAgentData.scala | 15 ++ .../edu/ie3/simona/config/SimonaConfig.scala | 8 +- .../simona/config/VoltageLimitsParser.scala | 137 +++++----- .../agent/grid/DBFSAlgorithmCenGridSpec.scala | 3 +- .../DBFSAlgorithmFailedPowerFlowSpec.scala | 3 +- .../grid/DBFSAlgorithmParticipantSpec.scala | 3 +- .../agent/grid/DBFSAlgorithmSupGridSpec.scala | 3 +- .../agent/grid/DCMAlgorithmSupGridSpec.scala | 234 ++++++++++++++++++ .../agent/grid/GridResultsSupportSpec.scala | 3 +- .../edu/ie3/simona/model/grid/GridSpec.scala | 11 +- .../model/grid/Transformer3wModelSpec.scala | 10 +- .../model/grid/TransformerModelSpec.scala | 1 + .../simona/test/common/ConfigTestData.scala | 5 + .../simona/test/common/DefaultTestData.scala | 9 +- .../test/common/input/GridInputTestData.scala | 5 +- .../model/grid/BasicGridWithSwitches.scala | 2 +- .../model/grid/TransformerTestData.scala | 4 +- 23 files changed, 479 insertions(+), 147 deletions(-) create mode 100644 src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala diff --git a/input/samples/vn_simona/vn_simona.conf b/input/samples/vn_simona/vn_simona.conf index 38626c593d..4dc8e50118 100644 --- a/input/samples/vn_simona/vn_simona.conf +++ b/input/samples/vn_simona/vn_simona.conf @@ -186,6 +186,21 @@ simona.gridConfig.refSystems = [ {sNom = "1000 MVA", vNom = "380 kV", voltLvls = [{id = "HoeS", vNom = "380 kV"}]} ] +simona.gridConfig.voltageLimits = [ + { + vMin = 0.9, + vMax = 1.1, + voltLvls = [ + {id = "lv", vNom = "0.4 kV"}, + {id = "mv", vNom = "10 kV"}, + {id = "mv", vNom = "20 kV"}, + {id = "mv", vNom = "30 kV"}, + {id = "hv", vNom = "110 kV"}, + ]}, + {vMin = 0.9, vMax = 1.118, voltLvls = [{id = "EHV", vNom = "220 kV"}]}, + {vMin = 0.9, vMax = 1.05, voltLvls = [{id = "EHV", vNom = "380 kV"}]}, +] + ################################################################## # Power Flow Configuration ################################################################## @@ -208,3 +223,11 @@ simona.control.transformer = [ vMax = 1.02 } ] + +################################################################## +# Congestion Management Configuration +################################################################## + +simona.congestionManagement.enableTransformerTapping = false +simona.congestionManagement.enableTopologyChanges = false +simona.congestionManagement.useFlexOptions = false diff --git a/src/main/resources/config/config-template.conf b/src/main/resources/config/config-template.conf index 85f9bd9fcd..3ce1e9f254 100644 --- a/src/main/resources/config/config-template.conf +++ b/src/main/resources/config/config-template.conf @@ -373,9 +373,10 @@ simona.event.listener = [ # Congestion Management Configuration ################################################################## -simona.congestionManagement.enableTransformerTapping = boolean | true +simona.congestionManagement.enableTransformerTapping = boolean | false simona.congestionManagement.enableTopologyChanges = boolean | false simona.congestionManagement.useFlexOptions = boolean | false +simona.congestionManagement.timeout = "duration:seconds | 30 seconds" // maximum timeout ################################################################## # Configuration of Control Schemes diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala index 81dd88ed2c..c50c251577 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala @@ -6,6 +6,8 @@ package edu.ie3.simona.agent.grid +import java.time.Duration + /** Holds all congestion management configuration parameters used in * [[edu.ie3.simona.agent.grid]]. If the parameter [[runCongestionManagement]] * is set to false, no congestion management is run and all the other @@ -24,6 +26,7 @@ final case class CongestionManagementParams( runTransformerTapping: Boolean, runTopologyChanges: Boolean, useFlexOptions: Boolean, + timeout: Duration, hasRunTransformerTapping: Boolean = false, hasRunTopologyChanges: Boolean = false, ) { diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala index 1e7c905bf4..0eff967f30 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala @@ -15,7 +15,7 @@ import edu.ie3.powerflow.model.PowerFlowResult import edu.ie3.powerflow.model.PowerFlowResult.FailedPowerFlowResult.FailedNewtonRaphsonPFResult import edu.ie3.powerflow.model.PowerFlowResult.SuccessFullPowerFlowResult.ValidNewtonRaphsonPFResult import edu.ie3.powerflow.model.enums.NodeType -import edu.ie3.simona.agent.grid.GridAgent.{idle, pipeToSelf} +import edu.ie3.simona.agent.grid.GridAgent.pipeToSelf import edu.ie3.simona.agent.grid.GridAgentData.{ CongestionManagementData, GridAgentBaseData, @@ -475,20 +475,19 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { )(currentTick.toDateTime(constantData.simStartTime), ctx.log) } + // check if congestion management is enabled if ( - gridAgentBaseData.congestionManagementParams.runCongestionManagement && results.isDefined + gridAgentBaseData.congestionManagementParams.runCongestionManagement ) { - // go to congestion management, if enabled - val congestionManagementData = CongestionManagementData( - gridAgentBaseData, - currentTick, - results.getOrElse( - throw new DBFSAlgorithmException( - s"No results received for tick $currentTick!" - ) - ), - ) + // get result or build empty data + val congestionManagementData = results + .map(res => + CongestionManagementData(gridAgentBaseData, currentTick, res) + ) + .getOrElse( + CongestionManagementData.empty(gridAgentBaseData, currentTick) + ) ctx.self ! StartStep GridAgent.checkForCongestion(congestionManagementData) @@ -497,24 +496,8 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { // notify listener about the results results.foreach(constantData.notifyListeners) - // do my cleanup stuff - ctx.log.debug("Doing my cleanup stuff") - - // / clean copy of the gridAgentBaseData - val cleanedGridAgentBaseData = GridAgentBaseData.clean( - gridAgentBaseData, - gridAgentBaseData.superiorGridNodeUuids, - gridAgentBaseData.inferiorGridGates, - ) - - // / inform scheduler that we are done with the whole simulation and request new trigger for next time step - constantData.environmentRefs.scheduler ! Completion( - constantData.activationAdapter, - Some(currentTick + constantData.resolution), - ) - - // return to Idle - idle(cleanedGridAgentBaseData) + // clean up agent and go back to idle + GridAgent.gotoIdle(gridAgentBaseData, currentTick, ctx) } case _ => diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 6c3f747205..78094c072b 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -6,21 +6,18 @@ package edu.ie3.simona.agent.grid -import edu.ie3.simona.agent.grid.GridAgent.{idle, pipeToSelf} +import edu.ie3.simona.agent.grid.GridAgent.pipeToSelf import edu.ie3.simona.agent.grid.GridAgentData.{ CongestionManagementData, - GridAgentBaseData, GridAgentConstantData, } import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.ontology.messages.Activation -import edu.ie3.simona.ontology.messages.SchedulerMessage.Completion import org.apache.pekko.actor.typed.scaladsl.AskPattern.Askable import org.apache.pekko.actor.typed.scaladsl.{Behaviors, StashBuffer} import org.apache.pekko.actor.typed.{Behavior, Scheduler} import org.apache.pekko.util.Timeout -import scala.concurrent.duration.SECONDS import scala.concurrent.{ExecutionContext, Future} /** Trait that is normally mixed into every [[GridAgent]] to enable distributed @@ -50,7 +47,9 @@ trait DCMAlgorithm { case (ctx, StartStep) => // request congestion check if we have inferior grids if (stateData.inferiorRefs.nonEmpty) { - implicit val askTimeout: Timeout = Timeout(10, SECONDS) + implicit val askTimeout: Timeout = Timeout.create( + stateData.gridAgentBaseData.congestionManagementParams.timeout + ) implicit val ec: ExecutionContext = ctx.executionContext implicit val scheduler: Scheduler = ctx.system.scheduler @@ -122,11 +121,14 @@ trait DCMAlgorithm { congestions.lineCongestions && steps.runTopologyChanges && !steps.hasRunTopologyChanges ) { NextStepRequest(useTopologyChanges) - } else if (congestions.any && !steps.useFlexOptions) { + } else if (congestions.any && steps.useFlexOptions) { NextStepRequest(usePlexOptions) } else { + val timestamp = + constantData.simStartTime.plusSeconds(stateData.currentTick) + ctx.log.info( - s"There were some congestions that could not be resolved for timestamp: ${updatedStateData.currentTick}" + s"There were some congestions that could not be resolved for timestamp: $timestamp." ) GotoIdle } @@ -140,13 +142,14 @@ trait DCMAlgorithm { buffer.unstashAll(checkForCongestion(updatedStateData)) } - case (_, NextStepRequest(next)) => + case (ctx, NextStepRequest(next)) => // inform my inferior grids about the next behavior stateData.inferiorRefs.foreach( _ ! NextStepRequest(next) ) // switching to the next behavior + ctx.self ! StartStep next(stateData) case (ctx, GotoIdle) => @@ -155,24 +158,17 @@ trait DCMAlgorithm { _ ! GotoIdle ) - // do my cleanup stuff - ctx.log.debug("Doing my cleanup stuff") - - // / clean copy of the gridAgentBaseData - val cleanedGridAgentBaseData = GridAgentBaseData.clean( + // clean up agent and go back to idle + GridAgent.gotoIdle( stateData.gridAgentBaseData, - stateData.gridAgentBaseData.superiorGridNodeUuids, - stateData.gridAgentBaseData.inferiorGridGates, + stateData.currentTick, + ctx, ) - // / inform scheduler that we are done with the whole simulation and request new trigger for next time step - constantData.environmentRefs.scheduler ! Completion( - constantData.activationAdapter, - Some(stateData.currentTick + constantData.resolution), - ) + case (ctx, msg) => + ctx.log.error(s"$msg") - // return to Idle - idle(cleanedGridAgentBaseData) + Behaviors.same } /** Method that defines the [[Behavior]] for changing the tapping for @@ -187,15 +183,20 @@ trait DCMAlgorithm { * @return * a [[Behavior]] */ + // TODO: Implement a proper behavior private def updateTransformerTapping( stateData: CongestionManagementData )(implicit constantData: GridAgentConstantData, buffer: StashBuffer[GridAgent.Request], ): Behavior[GridAgent.Request] = Behaviors.receivePartial { - case (_, StartStep) => - // TODO: Implement a proper behavior + case (ctx, StartStep) => + // for now this step is skipped + ctx.log.debug( + s"Using transformer taping to resolve a congestion is not implemented yet. Skipping this step!" + ) + ctx.self ! FinishStep Behaviors.same case (ctx, FinishStep) => 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 7d160db803..f71f40dcdc 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -193,6 +193,7 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { cfg.congestionManagement.enableTransformerTapping, cfg.congestionManagement.enableTopologyChanges, cfg.congestionManagement.useFlexOptions, + cfg.congestionManagement.timeout, ), SimonaActorNaming.actorName(ctx.self), ) @@ -238,17 +239,32 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { Behaviors.same } - private def failFast( - gridAgentInitData: GridAgentInitData, - actorName: String, - ): Unit = { - if ( - gridAgentInitData.superiorGridGates.isEmpty && gridAgentInitData.inferiorGridGates.isEmpty + private[grid] def gotoIdle( + gridAgentBaseData: GridAgentBaseData, + currentTick: Long, + ctx: ActorContext[Request], + )(implicit + constantData: GridAgentConstantData, + buffer: StashBuffer[GridAgent.Request], + ): Behavior[Request] = { + // do my cleanup stuff + ctx.log.debug("Doing my cleanup stuff") + + // / clean copy of the gridAgentBaseData + val cleanedGridAgentBaseData = GridAgentBaseData.clean( + gridAgentBaseData, + gridAgentBaseData.superiorGridNodeUuids, + gridAgentBaseData.inferiorGridGates, ) - throw new GridAgentInitializationException( - s"$actorName has neither superior nor inferior grids! This can either " + - s"be cause by wrong subnetGate information or invalid parametrization of the simulation!" - ) + + // / inform scheduler that we are done with the whole simulation and request new trigger for next time step + constantData.environmentRefs.scheduler ! Completion( + constantData.activationAdapter, + Some(currentTick + constantData.resolution), + ) + + // return to Idle + idle(cleanedGridAgentBaseData) } /** This method uses [[ActorContext.pipeToSelf()]] to send a future message to @@ -269,4 +285,17 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { case Failure(exception) => WrappedFailure(exception) } } + + private def failFast( + gridAgentInitData: GridAgentInitData, + actorName: String, + ): Unit = { + if ( + gridAgentInitData.superiorGridGates.isEmpty && gridAgentInitData.inferiorGridGates.isEmpty + ) + throw new GridAgentInitializationException( + s"$actorName has neither superior nor inferior grids! This can either " + + s"be cause by wrong subnetGate information or invalid parametrization of the simulation!" + ) + } } 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 b3e0409f61..d4e8dbc1e0 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -534,6 +534,21 @@ object GridAgentData { ) } + def empty( + gridAgentBaseData: GridAgentBaseData, + currentTick: Long, + ): CongestionManagementData = apply( + gridAgentBaseData, + currentTick, + PowerFlowResultEvent( + Seq.empty, + Seq.empty, + Seq.empty, + Seq.empty, + Seq.empty, + ), + ) + private def getCongestions( powerFlowResults: PowerFlowResultEvent, gridComponents: GridComponents, diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index 9cdc46f41e..a75bcf7f05 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -1086,6 +1086,7 @@ object SimonaConfig { final case class CongestionManagement( enableTopologyChanges: scala.Boolean, enableTransformerTapping: scala.Boolean, + timeout: java.time.Duration, useFlexOptions: scala.Boolean, ) object CongestionManagement { @@ -1099,9 +1100,12 @@ object SimonaConfig { c.hasPathOrNull("enableTopologyChanges") && c.getBoolean( "enableTopologyChanges" ), - enableTransformerTapping = !c.hasPathOrNull( + enableTransformerTapping = c.hasPathOrNull( "enableTransformerTapping" - ) || c.getBoolean("enableTransformerTapping"), + ) && c.getBoolean("enableTransformerTapping"), + timeout = + if (c.hasPathOrNull("timeout")) c.getDuration("timeout") + else java.time.Duration.parse("PT30S"), useFlexOptions = c.hasPathOrNull("useFlexOptions") && c.getBoolean("useFlexOptions"), ) diff --git a/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala b/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala index 3e6a8637f3..871d1a398e 100644 --- a/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala +++ b/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala @@ -32,82 +32,89 @@ object VoltageLimitsParser { configVoltageLimits: List[SimonaConfig.VoltageLimitsConfig] ): ConfigVoltageLimits = { - val voltageLimits = configVoltageLimits.map { configVoltageLimit => - ( - configVoltageLimit, - VoltageLimits(configVoltageLimit.vMin, configVoltageLimit.vMax), - ) - } + if (configVoltageLimits.isEmpty) { + val voltageLimit = VoltageLimits(0.9, 1.1) - val gridIdVoltageLimits = voltageLimits.flatMap { case (config, limits) => - config.gridIds - .map { - _.flatMap { gridId => - { - val allGridIds = gridId match { - case ConfigConventions.gridIdDotRange(from, to) => - from.toInt to to.toInt - case ConfigConventions.gridIdMinusRange(from, to) => - from.toInt to to.toInt - case ConfigConventions.singleGridId(singleGridId) => - Seq(singleGridId.toInt) - case unknownGridIdFormat => - throw new InvalidConfigParameterException( - s"Unknown gridId format $unknownGridIdFormat provided for voltage limits $config" - ) - } + ConfigVoltageLimits( + Map.empty, + Map( + GermanVoltageLevelUtils.LV -> voltageLimit, + GermanVoltageLevelUtils.MV_10KV -> voltageLimit, + GermanVoltageLevelUtils.MV_20KV -> voltageLimit, + GermanVoltageLevelUtils.MV_30KV -> voltageLimit, + GermanVoltageLevelUtils.HV -> voltageLimit, + GermanVoltageLevelUtils.EHV_220KV -> VoltageLimits(0.9, 1.118), + GermanVoltageLevelUtils.EHV_380KV -> VoltageLimits(0.9, 1.05), + ), + ) - allGridIds.map(gridId => (gridId, limits)) - } - } - } - .getOrElse(Seq.empty[(Int, VoltageLimits)]) - } + } else { - val voltLvlVoltageLimits = voltageLimits.flatMap { - case (configRefSystem, parsedRefSystem) => - val voltageLimit = VoltageLimits(0.9, 1.1) + val voltageLimits = configVoltageLimits.map { configVoltageLimit => + ( + configVoltageLimit, + VoltageLimits(configVoltageLimit.vMin, configVoltageLimit.vMax), + ) + } - configRefSystem.voltLvls + val gridIdVoltageLimits = voltageLimits.flatMap { case (config, limits) => + config.gridIds .map { - _.map { voltLvlDef => - (VoltLvlParser.from(voltLvlDef), parsedRefSystem) + _.flatMap { gridId => + { + val allGridIds = gridId match { + case ConfigConventions.gridIdDotRange(from, to) => + from.toInt to to.toInt + case ConfigConventions.gridIdMinusRange(from, to) => + from.toInt to to.toInt + case ConfigConventions.singleGridId(singleGridId) => + Seq(singleGridId.toInt) + case unknownGridIdFormat => + throw new InvalidConfigParameterException( + s"Unknown gridId format $unknownGridIdFormat provided for voltage limits $config" + ) + } + + allGridIds.map(gridId => (gridId, limits)) + } } } - .getOrElse( - Seq( - (GermanVoltageLevelUtils.LV, voltageLimit), - (GermanVoltageLevelUtils.MV_10KV, voltageLimit), - (GermanVoltageLevelUtils.MV_20KV, voltageLimit), - (GermanVoltageLevelUtils.MV_30KV, voltageLimit), - (GermanVoltageLevelUtils.HV, voltageLimit), - (GermanVoltageLevelUtils.EHV_220KV, VoltageLimits(0.9, 1.118)), - (GermanVoltageLevelUtils.EHV_380KV, VoltageLimits(0.9, 1.05)), - ) - ) - } + .getOrElse(Seq.empty[(Int, VoltageLimits)]) + } - // check for duplicates of gridIds and voltLevels which will be the key for the following map conversion - if ( - CollectionUtils.listHasDuplicates( - gridIdVoltageLimits.map { case (gridId, _) => gridId } - ) - ) - throw new InvalidConfigParameterException( - s"The provided gridIds in simona.gridConfig.voltageLimits contains duplicates. " + - s"Please check if there are either duplicate entries or overlapping ranges!" - ) - if ( - CollectionUtils.listHasDuplicates( - voltLvlVoltageLimits.map { case (voltLvl, _) => voltLvl } + val voltLvlVoltageLimits = voltageLimits.flatMap { + case (configRefSystem, parsedRefSystem) => + configRefSystem.voltLvls + .map { + _.map { voltLvlDef => + (VoltLvlParser.from(voltLvlDef), parsedRefSystem) + } + } + .getOrElse(Seq.empty) + } + + // check for duplicates of gridIds and voltLevels which will be the key for the following map conversion + if ( + CollectionUtils.listHasDuplicates( + gridIdVoltageLimits.map { case (gridId, _) => gridId } + ) ) - ) - throw new InvalidConfigParameterException( - s"The provided voltLvls in simona.gridConfig.voltageLimits contains duplicates. " + - s"Please check your configuration for duplicates in voltLvl entries!" + throw new InvalidConfigParameterException( + s"The provided gridIds in simona.gridConfig.voltageLimits contains duplicates. " + + s"Please check if there are either duplicate entries or overlapping ranges!" + ) + if ( + CollectionUtils.listHasDuplicates( + voltLvlVoltageLimits.map { case (voltLvl, _) => voltLvl } + ) ) + throw new InvalidConfigParameterException( + s"The provided voltLvls in simona.gridConfig.voltageLimits contains duplicates. " + + s"Please check your configuration for duplicates in voltLvl entries!" + ) - ConfigVoltageLimits(gridIdVoltageLimits.toMap, voltLvlVoltageLimits.toMap) + ConfigVoltageLimits(gridIdVoltageLimits.toMap, voltLvlVoltageLimits.toMap) + } } } 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 4da922de62..50082d6ac6 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 cb9c4678d0..737c52269a 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, @@ -97,6 +97,7 @@ class DBFSAlgorithmFailedPowerFlowSpec Seq.empty[ThermalGrid], subGridGateToActorRef, RefSystem("2000 MVA", "110 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 9c716def88..b33489d215 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/DCMAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala new file mode 100644 index 0000000000..9b1285231c --- /dev/null +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala @@ -0,0 +1,234 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.agent.grid + +import com.typesafe.config.ConfigFactory +import edu.ie3.datamodel.graph.SubGridGate +import edu.ie3.datamodel.models.input.container.ThermalGrid +import edu.ie3.simona.agent.EnvironmentRefs +import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions +import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData +import edu.ie3.simona.agent.grid.GridAgentMessages._ +import edu.ie3.simona.config.SimonaConfig +import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} +import edu.ie3.simona.model.grid.{RefSystem, VoltageLimits} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} +import edu.ie3.simona.ontology.messages.services.ServiceMessage +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.scheduler.ScheduleLock +import edu.ie3.simona.test.common.model.grid.DbfsTestGrid +import edu.ie3.simona.test.common.{ConfigTestData, TestSpawnerTyped, UnitSpec} +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} +import org.apache.pekko.actor.typed.ActorRef +import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps + +import scala.concurrent.duration.DurationInt + +/** Test to ensure the functions that a [[GridAgent]] in superior position + * should be able to do if the DCMSAlgorithm is used. The scheduler, the + * weather service as well as the [[GridAgent]] inferior to the superior + * [[GridAgent]] are simulated by the TestKit. + */ +class DCMAlgorithmSupGridSpec + extends ScalaTestWithActorTestKit + with UnitSpec + with ConfigTestData + with DbfsTestGrid + with TestSpawnerTyped { + + private val cmConfig = ConfigFactory.parseString(""" + |simona.congestionManagement.enableTransformerTapping = true + |""".stripMargin) + + private val config = SimonaConfig(cmConfig.withFallback(typesafeConfig)) + + private val scheduler: TestProbe[SchedulerMessage] = TestProbe("scheduler") + private val runtimeEvents: TestProbe[RuntimeEvent] = + TestProbe("runtimeEvents") + private val primaryService: TestProbe[ServiceMessage] = + TestProbe("primaryService") + private val weatherService = TestProbe("weatherService") + private val hvGrid: TestProbe[GridAgent.Request] = TestProbe("hvGrid") + + private val environmentRefs = EnvironmentRefs( + scheduler = scheduler.ref, + runtimeEventListener = runtimeEvents.ref, + primaryServiceProxy = primaryService.ref.toClassic, + weather = weatherService.ref.toClassic, + evDataService = None, + ) + + val resultListener: TestProbe[ResultEvent] = TestProbe("resultListener") + + "A GridAgent actor in superior position" should { + val superiorGridAgent: ActorRef[GridAgent.Request] = testKit.spawn( + GridAgent( + environmentRefs, + config, + listener = Iterable(resultListener.ref), + ) + ) + + s"initialize itself when it receives an init activation" in { + val subnetGatesToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]] = + ehvSubGridGates.map(gate => gate -> hvGrid.ref).toMap + + val gridAgentInitData = + GridAgentInitData( + ehvGridContainer, + Seq.empty[ThermalGrid], + subnetGatesToActorRef, + RefSystem("5000 MVA", "380 kV"), + VoltageLimits(0.9, 1.1), + ) + + val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) + // lock activation scheduled + scheduler.expectMessageType[ScheduleActivation] + + superiorGridAgent ! CreateGridAgent(gridAgentInitData, key) + + val scheduleActivationMsg = + scheduler.expectMessageType[ScheduleActivation] + scheduleActivationMsg.tick shouldBe INIT_SIM_TICK + scheduleActivationMsg.unlockKey shouldBe Some(key) + val gridAgentActivation = scheduleActivationMsg.actor + + superiorGridAgent ! WrappedActivation(Activation(INIT_SIM_TICK)) + scheduler.expectMessage(Completion(gridAgentActivation, Some(3600))) + } + + s"skip simulate grid and check for congestions correctly if no congestions occurred" in { + gotoSimulateGrid() + + skipSimulationAndGetNextStep( + Congestions( + voltageCongestions = false, + lineCongestions = false, + transformerCongestions = false, + ) + ) + + // inferior should receive a next state message to go to the idle state + hvGrid.expectMessageType[GotoIdle.type] + + // expect a completion message from the superior grid + scheduler.expectMessageType[Completion] match { + case Completion(_, Some(7200)) => + case x => + fail( + s"Invalid message received when expecting a completion message for simulate grid! Message was $x" + ) + } + } + + s"handle unresolvable congestions correctly" in { + gotoSimulateGrid() + + // transformer congestion cannot be resolved, because using flex options is not + // enable by the provided config + skipSimulationAndGetNextStep( + Congestions( + voltageCongestions = false, + lineCongestions = false, + transformerCongestions = true, + ) + ) + + // inferior should receive a next state message to go to the idle state + hvGrid.expectMessageType[GotoIdle.type] + + // expect a completion message from the superior grid + scheduler.expectMessageType[Completion] match { + case Completion(_, Some(7200)) => + case x => + fail( + s"Invalid message received when expecting a completion message for simulate grid! Message was $x" + ) + } + } + + s"update transformer tapping correctly" in { + gotoSimulateGrid() + + skipSimulationAndGetNextStep( + Congestions( + voltageCongestions = true, + lineCongestions = false, + transformerCongestions = false, + ) + ) + + // inferior should receive a next state message to go to a congestion management step + hvGrid.expectMessageType[NextStepRequest] + + // skipping the step for now + // TODO: Update test after implementing transformer tapping + superiorGridAgent ! FinishStep + hvGrid.expectMessageType[FinishStep.type] + + // skipping the simulation + hvGrid.expectMessageType[RequestGridPower] + + skipSimulationAndGetNextStep( + Congestions( + voltageCongestions = false, + lineCongestions = false, + transformerCongestions = false, + ) + ) + + // inferior should receive a next state message to go to the idle state + hvGrid.expectMessageType[GotoIdle.type] + + // expect a completion message from the superior grid + // after all steps are finished + scheduler.expectMessageType[Completion](10.seconds) match { + case Completion(_, Some(7200)) => + case x => + fail( + s"Invalid message received when expecting a completion message for simulate grid! Message was $x" + ) + } + } + + def skipSimulationAndGetNextStep(congestions: Congestions): Unit = { + // skip simulation and go to congestion check + superiorGridAgent ! FinishGridSimulationTrigger(3600) + + // inferior grid receives a FinishGridSimulationTrigger and goes into the congestion check state + hvGrid.expectMessage(FinishGridSimulationTrigger(3600)) + + // we expect a request for grid congestion values here + val lastSender = + hvGrid.expectMessageType[CongestionCheckRequest](10.seconds) match { + case CongestionCheckRequest(sender) => sender + case x => + fail( + s"Invalid message received when expecting a request for grid congestion values! Message was $x" + ) + } + + // send congestions + lastSender ! CongestionResponse(congestions, hvGrid.ref) + } + + def gotoSimulateGrid(): Unit = { + superiorGridAgent ! WrappedActivation(Activation(3600)) + + // we expect a completion message + scheduler.expectMessageType[Completion].newTick shouldBe Some(3600) + } + } +} diff --git a/src/test/scala/edu/ie3/simona/agent/grid/GridResultsSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/GridResultsSupportSpec.scala index a0fc556f49..ad69aabff0 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/GridResultsSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/GridResultsSupportSpec.scala @@ -44,7 +44,7 @@ import edu.ie3.util.scala.OperationInterval import edu.ie3.util.scala.quantities.{QuantityUtil => ScalaQuantityUtil} import org.scalatest.prop.TableDrivenPropertyChecks import squants.Each -import squants.electro.{Amperes, Volts} +import squants.electro.{Amperes, ElectricCurrent, Volts} import squants.energy.Kilowatts import squants.space.Degrees import tech.units.indriya.quantity.Quantities @@ -444,6 +444,7 @@ class GridResultsSupportSpec ), 1, PowerFlowCaseA, + Amperes(100), Each(0.1d), Each(0.2d), Each(0.3d), 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 53165fc018..f7c647e421 100644 --- a/src/test/scala/edu/ie3/simona/model/grid/GridSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/grid/GridSpec.scala @@ -213,7 +213,7 @@ class GridSpec Set.empty[Transformer3wModel], switches, ), - GridControls.empty, + defaultVoltageLimits, ) // get the private method for validation val validateConnectivity: PrivateMethod[Unit] = @@ -242,7 +242,7 @@ class GridSpec Set.empty[Transformer3wModel], Set.empty[SwitchModel], ), - GridControls.empty, + defaultVoltageLimits, ) // get the private method for validation @@ -288,7 +288,7 @@ class GridSpec Set.empty[Transformer3wModel], switches, ), - GridControls.empty, + defaultVoltageLimits, ) // get the private method for validation @@ -393,7 +393,7 @@ class GridSpec Set.empty[Transformer3wModel], switches, ), - GridControls.empty, + defaultVoltageLimits, ) // update the uuidToIndexMap @@ -445,7 +445,7 @@ class GridSpec Set.empty[Transformer3wModel], Set.empty[SwitchModel], ), - GridControls.empty, + defaultVoltageLimits, ) // update the uuidToIndexMap @@ -556,6 +556,7 @@ class GridSpec GridModel( validTestGridInputModel, gridInputModelTestDataRefSystem, + defaultVoltageLimits, defaultSimulationStart, defaultSimulationEnd, simonaConfig, diff --git a/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala b/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala index 4f865f58af..d666f2fd6a 100644 --- a/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala @@ -17,7 +17,7 @@ import edu.ie3.simona.test.common.UnitSpec import edu.ie3.simona.test.common.input.Transformer3wTestData import edu.ie3.util.quantities.PowerSystemUnits._ import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor4} -import squants.Each +import squants.{Amperes, Each} import tech.units.indriya.quantity.Quantities import scala.math.BigDecimal.RoundingMode @@ -27,6 +27,8 @@ class Transformer3wModelSpec with TableDrivenPropertyChecks with Transformer3wTestData { val testingTolerance = 1e-5 + implicit val electricCurrentTolerance: squants.electro.ElectricCurrent = + Amperes(1e-8) implicit val dimensionlessTolerance: squants.Dimensionless = Each(1e-8) "A three winding transformer input model" should { @@ -67,6 +69,7 @@ class Transformer3wModelSpec transformerTappingModel, amount, powerFlowCase, + iNom, r, x, g, @@ -85,6 +88,7 @@ class Transformer3wModelSpec transformerTappingModel shouldBe expectedTappingModel amount shouldBe transformer3wInput.getParallelDevices powerFlowCase shouldBe PowerFlowCaseA + iNom shouldBe Amperes(182.3211376388292) r should approximate(Each(1.03878e-3)) x should approximate(Each(166.34349e-3)) g should approximate(Each(1.874312e-6)) @@ -142,6 +146,7 @@ class Transformer3wModelSpec transformerTappingModel, amount, powerFlowCase, + iNom, r, x, g, @@ -160,6 +165,7 @@ class Transformer3wModelSpec transformerTappingModel shouldBe expectedTappingModel amount shouldBe transformer3wInput.getParallelDevices powerFlowCase shouldBe PowerFlowCaseB + iNom shouldBe Amperes(314.9183286488868) r should approximate(Each(240.9972299e-6)) x should approximate(Each(24.99307479224e-3)) g should approximate(Each(0d)) @@ -217,6 +223,7 @@ class Transformer3wModelSpec transformerTappingModel, amount, powerFlowCase, + iNom, r, x, g, @@ -235,6 +242,7 @@ class Transformer3wModelSpec transformerTappingModel shouldBe expectedTappingModel amount shouldBe transformer3wInput.getParallelDevices powerFlowCase shouldBe PowerFlowCaseC + iNom shouldBe Amperes(1154.7005383792516) r should approximate(Each(3.185595567e-6)) x should approximate(Each(556.0941828e-6)) g should approximate(Each(0d)) 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..2d00ddf804 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, + voltageLimits, defaultSimulationStart, defaultSimulationEnd, simonaConfig, diff --git a/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala b/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala index 26cb038257..f2b033a7c9 100644 --- a/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala @@ -136,6 +136,11 @@ trait ConfigTestData { |simona.powerflow.newtonraphson.iterations = 50 | |simona.gridConfig.refSystems = [] + |simona.gridConfig.voltageLimits = [] + | + |simona.congestionManagement.enableTransformerTapping = false + |simona.congestionManagement.enableTopologyChanges = false + |simona.congestionManagement.useFlexOptions = false |""".stripMargin ) protected val simonaConfig: SimonaConfig = SimonaConfig(typesafeConfig) 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 c1d93d7476..5647d30ce1 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 * @@ -219,6 +221,11 @@ trait DefaultTestData { |simona.powerflow.resolution = "3600s" | |simona.gridConfig.refSystems = [] + |simona.gridConfig.voltageLimits = [] + | + |simona.congestionManagement.enableTransformerTapping = false + |simona.congestionManagement.enableTopologyChanges = false + |simona.congestionManagement.useFlexOptions = false |""".stripMargin ) SimonaConfig(typesafeConfig) diff --git a/src/test/scala/edu/ie3/simona/test/common/input/GridInputTestData.scala b/src/test/scala/edu/ie3/simona/test/common/input/GridInputTestData.scala index d9cdbb2681..7a3ded4c6a 100644 --- a/src/test/scala/edu/ie3/simona/test/common/input/GridInputTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/input/GridInputTestData.scala @@ -21,7 +21,7 @@ import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils.{ HV, MV_10KV, } -import edu.ie3.simona.model.grid.RefSystem +import edu.ie3.simona.model.grid.{RefSystem, VoltageLimits} import edu.ie3.simona.test.common.DefaultTestData import edu.ie3.simona.util.TestGridFactory import testutils.TestObjectFactory @@ -155,6 +155,9 @@ trait GridInputTestData protected def gridInputModelTestDataRefSystem: RefSystem = default400Kva10KvRefSystem + protected def gridInputModelTestDataVoltageLimits: VoltageLimits = + defaultVoltageLimits + // create the grid protected val validTestGridInputModel: SubGridContainer = { val rawGridElements = new RawGridElements( 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 bd709daa01..d20e19b26a 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,7 +231,7 @@ trait BasicGridWithSwitches extends BasicGrid { Set.empty[Transformer3wModel], gridSwitches, ), - GridControls.empty, + defaultVoltageLimits, ) } 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..cc53f961b6 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,7 +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.simona.model.grid.{RefSystem, VoltageLimits} import edu.ie3.util.quantities.PowerSystemUnits._ import org.scalatest.prop.TableDrivenPropertyChecks.Table import org.scalatest.prop.{TableFor5, TableFor9} @@ -34,6 +34,8 @@ trait TransformerTestData extends TransformerTestGrid { Kilovolts(0.4d), ) + val voltageLimits: VoltageLimits = VoltageLimits(0.9, 1.1) + val nodeUuidToIndexMap: Map[UUID, Int] = gridTapHv.getRawGrid.getNodes.asScala .map(node => node.getUuid -> node.getSubnet) .toMap From 81b2308f106be8f64214480bab342cc4382e0270 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 16 Apr 2024 15:50:52 +0200 Subject: [PATCH 05/55] Adding `DCMAlgorithmCenGridSpec`. Small improvement. --- .../grid/CongestionManagementParams.scala | 27 +- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 31 +- .../ie3/simona/agent/grid/GridAgentData.scala | 35 +- .../simona/agent/grid/GridAgentMessages.scala | 3 +- .../agent/grid/DCMAlgorithmCenGridSpec.scala | 369 ++++++++++++++++++ .../agent/grid/DCMAlgorithmSupGridSpec.scala | 2 +- 6 files changed, 434 insertions(+), 33 deletions(-) create mode 100644 src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala index c50c251577..b522779f2a 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala @@ -13,28 +13,41 @@ import java.time.Duration * is set to false, no congestion management is run and all the other * parameters are ignored * - * @param runTransformerTapping + * @param transformerTapping * defines if the transformer tapping should be used for tappable * transformers - * @param runTopologyChanges + * @param topologyChanges * defines if switches should be used to change the topology of the grid - * @param useFlexOptions + * @param flexOptions * defines if available [[edu.ie3.simona.agent.em.EmAgent]] should be used to * resolve congestions */ final case class CongestionManagementParams( - runTransformerTapping: Boolean, - runTopologyChanges: Boolean, - useFlexOptions: Boolean, + transformerTapping: Boolean, + topologyChanges: Boolean, + flexOptions: Boolean, timeout: Duration, hasRunTransformerTapping: Boolean = false, hasRunTopologyChanges: Boolean = false, ) { def runCongestionManagement: Boolean = - runTransformerTapping || runTopologyChanges || useFlexOptions + transformerTapping || topologyChanges || flexOptions + + def runTransformerTapping: Boolean = + transformerTapping && !hasRunTransformerTapping + + def runTopologyChanges: Boolean = topologyChanges && !hasRunTopologyChanges + + def useFlexOptions: Boolean = flexOptions def clean: CongestionManagementParams = { copy(hasRunTransformerTapping = false, hasRunTopologyChanges = false) } } + +object CongestionManagementParams { + object CongestionManagementSteps extends Enumeration { + val TransformerTapping, TopologyChanges, UsingFlexibilities = Value + } +} diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 78094c072b..13effef6c2 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.agent.grid +import edu.ie3.simona.agent.grid.CongestionManagementParams.CongestionManagementSteps._ import edu.ie3.simona.agent.grid.GridAgent.pipeToSelf import edu.ie3.simona.agent.grid.GridAgentData.{ CongestionManagementData, @@ -81,7 +82,7 @@ trait DCMAlgorithm { // sends the results to the superior grid sender ! CongestionResponse( stateData.congestions.combine( - stateData.inferiorCongestions.values.flatten + stateData.inferiorCongestionMap.values.flatten ), ctx.self, ) @@ -97,7 +98,7 @@ trait DCMAlgorithm { // if we are the superior grid, we find the next behavior val congestions = updatedStateData.congestions.combine( - updatedStateData.inferiorCongestions.values.flatten + updatedStateData.inferiorCongestionMap.values.flatten ) // checking for any congestion in the complete grid @@ -113,16 +114,14 @@ trait DCMAlgorithm { updatedStateData.gridAgentBaseData.congestionManagementParams val msg = - if ( - congestions.voltageCongestions && steps.runTransformerTapping && !steps.hasRunTransformerTapping - ) { - NextStepRequest(updateTransformerTapping) + if (congestions.voltageCongestions && steps.runTransformerTapping) { + NextStepRequest(TransformerTapping) } else if ( - congestions.lineCongestions && steps.runTopologyChanges && !steps.hasRunTopologyChanges + congestions.lineCongestions && steps.runTopologyChanges ) { - NextStepRequest(useTopologyChanges) + NextStepRequest(TopologyChanges) } else if (congestions.any && steps.useFlexOptions) { - NextStepRequest(usePlexOptions) + NextStepRequest(UsingFlexibilities) } else { val timestamp = constantData.simStartTime.plusSeconds(stateData.currentTick) @@ -150,7 +149,12 @@ trait DCMAlgorithm { // switching to the next behavior ctx.self ! StartStep - next(stateData) + + next match { + case TransformerTapping => updateTransformerTapping(stateData) + case TopologyChanges => useTopologyChanges(stateData) + case UsingFlexibilities => useFlexOptions(stateData) + } case (ctx, GotoIdle) => // inform my inferior grids about the end of the congestion management @@ -184,7 +188,7 @@ trait DCMAlgorithm { * a [[Behavior]] */ // TODO: Implement a proper behavior - private def updateTransformerTapping( + private[grid] def updateTransformerTapping( stateData: CongestionManagementData )(implicit constantData: GridAgentConstantData, @@ -218,7 +222,8 @@ trait DCMAlgorithm { } // TODO: Implement a proper behavior - private def useTopologyChanges(stateData: CongestionManagementData)(implicit + private[grid] def useTopologyChanges(stateData: CongestionManagementData)( + implicit constantData: GridAgentConstantData, buffer: StashBuffer[GridAgent.Request], ): Behavior[GridAgent.Request] = Behaviors.receivePartial { @@ -248,7 +253,7 @@ trait DCMAlgorithm { } // TODO: Implement a proper behavior - private def usePlexOptions(stateData: CongestionManagementData)(implicit + private[grid] def useFlexOptions(stateData: CongestionManagementData)(implicit constantData: GridAgentConstantData, buffer: StashBuffer[GridAgent.Request], ): Behavior[GridAgent.Request] = Behaviors.receivePartial { 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 d4e8dbc1e0..871f8289e2 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -478,14 +478,16 @@ object GridAgentData { currentTick: Long, powerFlowResults: PowerFlowResultEvent, congestions: Congestions, - inferiorCongestions: Map[ActorRef[GridAgent.Request], Option[Congestions]], + inferiorCongestionMap: Map[ActorRef[GridAgent.Request], Option[ + Congestions + ]], ) extends GridAgentData { /** Returns true if congestion data from inferior grids is expected and no * data was received yet. */ def awaitingInferiorData: Boolean = - inferiorCongestions.values.exists(_.isEmpty) + inferiorCongestionMap.values.exists(_.isEmpty) /** Method for updating the data with the received data. * @@ -499,14 +501,11 @@ object GridAgentData { ): CongestionManagementData = { val mappedData = receivedData.map(res => res.sender -> Some(res.congestions)).toMap - copy(inferiorCongestions = inferiorCongestions ++ mappedData) + copy(inferiorCongestionMap = inferiorCongestionMap ++ mappedData) } def inferiorRefs: Set[ActorRef[GridAgent.Request]] = - gridAgentBaseData.inferiorGridGates - .map(gridAgentBaseData.gridEnv.subgridGateToActorRef(_)) - .distinct - .toSet + inferiorCongestionMap.keySet } object CongestionManagementData { @@ -523,14 +522,30 @@ object GridAgentData { gridModel.voltageLimits, ) + // extracting one inferior ref for all inferior grids + val inferiorCongestionMap = gridAgentBaseData.inferiorGridGates + .map { inferiorGridGate => + gridAgentBaseData.gridEnv.subgridGateToActorRef( + inferiorGridGate + ) -> inferiorGridGate.superiorNode.getUuid + } + .groupMap { + // Group the gates by target actor, so that only one request is sent per grid agent + case (inferiorGridAgentRef, _) => + inferiorGridAgentRef + } { case (_, inferiorGridGates) => + inferiorGridGates + } + .map { case (inferiorGridAgentRef, _) => + inferiorGridAgentRef -> None + } + CongestionManagementData( gridAgentBaseData, currentTick, powerFlowResults, congestions, - gridAgentBaseData.gridEnv.subgridGateToActorRef.values - .map(_ -> None) - .toMap, + inferiorCongestionMap, ) } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala index d650753c63..d29f623942 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala @@ -262,7 +262,6 @@ object GridAgentMessages { } // DCM messages - case class CongestionCheckRequest(sender: ActorRef[GridAgent.Request]) extends GridAgent.InternalRequest @@ -275,7 +274,7 @@ object GridAgentMessages { extends GridAgent.InternalRequest case class NextStepRequest( - next: CongestionManagementData => Behavior[GridAgent.Request] + nextStep: CongestionManagementParams.CongestionManagementSteps.Value ) extends GridAgent.InternalRequest /** Message that indicates all actors that the current step is started. diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala new file mode 100644 index 0000000000..c93ac1739d --- /dev/null +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -0,0 +1,369 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.agent.grid + +import com.typesafe.config.ConfigFactory +import edu.ie3.datamodel.models.input.container.ThermalGrid +import edu.ie3.simona.agent.EnvironmentRefs +import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions +import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData +import edu.ie3.simona.agent.grid.GridAgentMessages.{ + CongestionCheckRequest, + CongestionResponse, + CreateGridAgent, + FinishGridSimulationTrigger, + FinishStep, + GotoIdle, + NextStepRequest, + RequestGridPower, + SlackVoltageRequest, + WrappedActivation, +} +import edu.ie3.simona.config.SimonaConfig +import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} +import edu.ie3.simona.model.grid.{RefSystem, VoltageLimits} +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} +import edu.ie3.simona.ontology.messages.SchedulerMessage.{ + Completion, + ScheduleActivation, +} +import edu.ie3.simona.scheduler.ScheduleLock +import edu.ie3.simona.test.common.{ConfigTestData, TestSpawnerTyped} +import edu.ie3.simona.test.common.model.grid.DbfsTestGrid +import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} +import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps + +import scala.concurrent.duration.DurationInt + +class DCMAlgorithmCenGridSpec + extends ScalaTestWithActorTestKit + with DBFSMockGridAgents + with ConfigTestData + with DbfsTestGrid + with TestSpawnerTyped { + + private val cmConfig = ConfigFactory.parseString(""" + |simona.congestionManagement.enableTransformerTapping = true + |""".stripMargin) + + private val config = SimonaConfig(cmConfig.withFallback(typesafeConfig)) + + private val scheduler: TestProbe[SchedulerMessage] = TestProbe("scheduler") + private val runtimeEvents: TestProbe[RuntimeEvent] = + TestProbe("runtimeEvents") + private val primaryService = TestProbe("primaryService") + private val weatherService = TestProbe("weatherService") + + private val superiorGridAgent: TestProbe[GridAgent.Request] = TestProbe( + "superiorGridAgent_1000" + ) + + private val inferiorGrid11: TestProbe[GridAgent.Request] = TestProbe( + "inferiorGridAgent_11" + ) + + private val inferiorGrid12: TestProbe[GridAgent.Request] = TestProbe( + "inferiorGridAgent_12" + ) + + private val inferiorGrid13: TestProbe[GridAgent.Request] = TestProbe( + "inferiorGridAgent_13" + ) + + private val environmentRefs = EnvironmentRefs( + scheduler = scheduler.ref, + runtimeEventListener = runtimeEvents.ref, + primaryServiceProxy = primaryService.ref.toClassic, + weather = weatherService.ref.toClassic, + evDataService = None, + ) + + val resultListener: TestProbe[ResultEvent] = TestProbe("resultListener") + + "A GridAgent actor in center position with async test" should { + + val centerGridAgent = + testKit.spawn( + GridAgent( + environmentRefs, + config, + listener = Iterable(resultListener.ref), + ) + ) + s"initialize itself when it receives an init activation" in { + + // this subnet has 1 superior grid (ehv) and 3 inferior grids (mv). Map the gates to test probes accordingly + val subGridGateToActorRef = hvSubGridGates.map { + case gate if gate.getInferiorSubGrid == hvGridContainer.getSubnet => + gate -> superiorGridAgent.ref + case gate => + val actor = gate.getInferiorSubGrid match { + case 11 => inferiorGrid11 + case 12 => inferiorGrid12 + case 13 => inferiorGrid13 + } + gate -> actor.ref + }.toMap + + val gridAgentInitData = + GridAgentInitData( + hvGridContainer, + Seq.empty[ThermalGrid], + subGridGateToActorRef, + RefSystem("2000 MVA", "110 kV"), + VoltageLimits(0.9, 1.1), + ) + + val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) + // lock activation scheduled + scheduler.expectMessageType[ScheduleActivation] + + centerGridAgent ! CreateGridAgent( + gridAgentInitData, + key, + ) + + val scheduleActivationMsg = + scheduler.expectMessageType[ScheduleActivation] + scheduleActivationMsg.tick shouldBe INIT_SIM_TICK + scheduleActivationMsg.unlockKey shouldBe Some(key) + val gridAgentActivation = scheduleActivationMsg.actor + + centerGridAgent ! WrappedActivation(Activation(INIT_SIM_TICK)) + scheduler.expectMessage(Completion(gridAgentActivation, Some(3600))) + } + + s"skip simulate grid and check for congestions correctly if no congestions occurred" in { + skipSimulation() + + val congestions = Congestions( + voltageCongestions = false, + lineCongestions = false, + transformerCongestions = false, + ) + + centerGridAgent ! CongestionCheckRequest(superiorGridAgent.ref) + + // we expect a request for grid congestion values here + val congestionCheckRequestSender11 = + inferiorGrid11.expectMessageType[CongestionCheckRequest] + val congestionCheckRequestSender12 = + inferiorGrid12.expectMessageType[CongestionCheckRequest] + val congestionCheckRequestSender13 = + inferiorGrid13.expectMessageType[CongestionCheckRequest] + + // send congestions + congestionCheckRequestSender11.sender ! CongestionResponse( + congestions, + inferiorGrid11.ref, + ) + congestionCheckRequestSender12.sender ! CongestionResponse( + congestions, + inferiorGrid12.ref, + ) + congestionCheckRequestSender13.sender ! CongestionResponse( + congestions, + inferiorGrid13.ref, + ) + + // we expect no congestions in the whole grid + val allCongestions = superiorGridAgent + .expectMessageType[CongestionResponse](30.seconds) + .congestions + allCongestions shouldBe congestions + + // telling all inferior grids to go back to idle + centerGridAgent ! GotoIdle + + inferiorGrid11.expectMessageType[GotoIdle.type] + inferiorGrid12.expectMessageType[GotoIdle.type] + inferiorGrid13.expectMessageType[GotoIdle.type] + + // expect a completion message from the superior grid + scheduler.expectMessageType[Completion] match { + case Completion(_, Some(7200)) => + case x => + fail( + s"Invalid message received when expecting a completion message for simulate grid! Message was $x" + ) + } + } + + s"handle unresolvable congestions correctly" in { + skipSimulation() + + val congestions = Congestions( + voltageCongestions = false, + lineCongestions = false, + transformerCongestions = false, + ) + + // transformer congestion cannot be resolved, because using flex options is not + // enable by the provided config + val unresolvableCongestion = Congestions( + voltageCongestions = false, + lineCongestions = false, + transformerCongestions = true, + ) + + centerGridAgent ! CongestionCheckRequest(superiorGridAgent.ref) + + // we expect a request for grid congestion values here + val congestionCheckRequestSender11 = + inferiorGrid11.expectMessageType[CongestionCheckRequest] + val congestionCheckRequestSender12 = + inferiorGrid12.expectMessageType[CongestionCheckRequest] + val congestionCheckRequestSender13 = + inferiorGrid13.expectMessageType[CongestionCheckRequest] + + // send congestions + congestionCheckRequestSender11.sender ! CongestionResponse( + congestions, + inferiorGrid11.ref, + ) + congestionCheckRequestSender12.sender ! CongestionResponse( + congestions, + inferiorGrid12.ref, + ) + congestionCheckRequestSender13.sender ! CongestionResponse( + unresolvableCongestion, + inferiorGrid13.ref, + ) + + // we expect no congestions in the whole grid + val allCongestions = superiorGridAgent + .expectMessageType[CongestionResponse](30.seconds) + .congestions + allCongestions shouldBe unresolvableCongestion + + // telling all inferior grids to go back to idle + centerGridAgent ! GotoIdle + + inferiorGrid11.expectMessageType[GotoIdle.type] + inferiorGrid12.expectMessageType[GotoIdle.type] + inferiorGrid13.expectMessageType[GotoIdle.type] + + // expect a completion message from the superior grid + scheduler.expectMessageType[Completion] match { + case Completion(_, Some(7200)) => + case x => + fail( + s"Invalid message received when expecting a completion message for simulate grid! Message was $x" + ) + } + } + + s"update transformer tapping correctly" in { + skipSimulation() + + val congestions = Congestions( + voltageCongestions = true, + lineCongestions = false, + transformerCongestions = false, + ) + + centerGridAgent ! CongestionCheckRequest(superiorGridAgent.ref) + + // we expect a request for grid congestion values here + val congestionCheckRequestSender11 = + inferiorGrid11.expectMessageType[CongestionCheckRequest] + val congestionCheckRequestSender12 = + inferiorGrid12.expectMessageType[CongestionCheckRequest] + val congestionCheckRequestSender13 = + inferiorGrid13.expectMessageType[CongestionCheckRequest] + + // send congestions + congestionCheckRequestSender11.sender ! CongestionResponse( + congestions, + inferiorGrid11.ref, + ) + congestionCheckRequestSender12.sender ! CongestionResponse( + congestions, + inferiorGrid12.ref, + ) + congestionCheckRequestSender13.sender ! CongestionResponse( + congestions, + inferiorGrid13.ref, + ) + + // we expect transformer congestions in the whole grid + val allCongestions = superiorGridAgent + .expectMessageType[CongestionResponse](30.seconds) + .congestions + allCongestions shouldBe congestions + + centerGridAgent ! NextStepRequest( + CongestionManagementParams.CongestionManagementSteps.TransformerTapping + ) + + // inferior grids should receive a next state message to go to a congestion management step + inferiorGrid11.expectMessageType[NextStepRequest] + inferiorGrid12.expectMessageType[NextStepRequest] + inferiorGrid13.expectMessageType[NextStepRequest] + + // skipping the step for now + // TODO: Update test after implementing transformer tapping + centerGridAgent ! FinishStep + inferiorGrid11.expectMessageType[FinishStep.type] + inferiorGrid12.expectMessageType[FinishStep.type] + inferiorGrid13.expectMessageType[FinishStep.type] + + // skipping the simulation + inferiorGrid11.expectMessageType[RequestGridPower] + inferiorGrid12.expectMessageType[RequestGridPower] + inferiorGrid13.expectMessageType[RequestGridPower] + superiorGridAgent.expectMessageType[SlackVoltageRequest] + + centerGridAgent ! FinishGridSimulationTrigger(3600) + + // inferior grid receives a FinishGridSimulationTrigger and goes into the congestion check state + inferiorGrid11.expectMessage(FinishGridSimulationTrigger(3600)) + inferiorGrid12.expectMessage(FinishGridSimulationTrigger(3600)) + inferiorGrid13.expectMessage(FinishGridSimulationTrigger(3600)) + + // we expect a request for grid congestion values here + inferiorGrid11.expectMessageType[CongestionCheckRequest] + inferiorGrid12.expectMessageType[CongestionCheckRequest] + inferiorGrid13.expectMessageType[CongestionCheckRequest] + + centerGridAgent ! GotoIdle + + // inferior should receive a next state message to go to the idle state + inferiorGrid11.expectMessageType[GotoIdle.type] + inferiorGrid12.expectMessageType[GotoIdle.type] + inferiorGrid13.expectMessageType[GotoIdle.type] + + // expect a completion message from the superior grid + scheduler.expectMessageType[Completion] match { + case Completion(_, Some(7200)) => + case x => + fail( + s"Invalid message received when expecting a completion message for simulate grid! Message was $x" + ) + } + } + + def skipSimulation(): Unit = { + centerGridAgent ! WrappedActivation(Activation(3600)) + + // we expect a completion message + scheduler.expectMessageType[Completion].newTick shouldBe Some(3600) + + // skip simulation and go to congestion check + centerGridAgent ! FinishGridSimulationTrigger(3600) + + // inferior grid receives a FinishGridSimulationTrigger and goes into the congestion check state + inferiorGrid11.expectMessage(FinishGridSimulationTrigger(3600)) + inferiorGrid12.expectMessage(FinishGridSimulationTrigger(3600)) + inferiorGrid13.expectMessage(FinishGridSimulationTrigger(3600)) + } + } +} diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala index 9b1285231c..b46b316307 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala @@ -71,7 +71,7 @@ class DCMAlgorithmSupGridSpec val resultListener: TestProbe[ResultEvent] = TestProbe("resultListener") - "A GridAgent actor in superior position" should { + "A GridAgent actor in superior position with async test" should { val superiorGridAgent: ActorRef[GridAgent.Request] = testKit.spawn( GridAgent( environmentRefs, From fbfa92401e12ef81eb6c718668f450b161383fe4 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 16 Apr 2024 18:54:22 +0200 Subject: [PATCH 06/55] Small improvements. --- .../ie3/simona/agent/grid/DBFSAlgorithm.scala | 6 +- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 148 ++++++++---- .../edu/ie3/simona/agent/grid/GridAgent.scala | 8 +- .../ie3/simona/agent/grid/GridAgentData.scala | 2 - .../agent/grid/DCMAlgorithmCenGridSpec.scala | 217 +++++------------- 5 files changed, 172 insertions(+), 209 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala index 0eff967f30..e02747e45d 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala @@ -492,12 +492,8 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { ctx.self ! StartStep GridAgent.checkForCongestion(congestionManagementData) } else { - - // notify listener about the results - results.foreach(constantData.notifyListeners) - // clean up agent and go back to idle - GridAgent.gotoIdle(gridAgentBaseData, currentTick, ctx) + GridAgent.gotoIdle(gridAgentBaseData, currentTick, results, ctx) } case _ => diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 13effef6c2..1e9e5b1dce 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -10,12 +10,17 @@ import edu.ie3.simona.agent.grid.CongestionManagementParams.CongestionManagement import edu.ie3.simona.agent.grid.GridAgent.pipeToSelf import edu.ie3.simona.agent.grid.GridAgentData.{ CongestionManagementData, + GridAgentBaseData, GridAgentConstantData, } import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.ontology.messages.Activation import org.apache.pekko.actor.typed.scaladsl.AskPattern.Askable -import org.apache.pekko.actor.typed.scaladsl.{Behaviors, StashBuffer} +import org.apache.pekko.actor.typed.scaladsl.{ + ActorContext, + Behaviors, + StashBuffer, +} import org.apache.pekko.actor.typed.{Behavior, Scheduler} import org.apache.pekko.util.Timeout @@ -79,9 +84,17 @@ trait DCMAlgorithm { buffer.stash(congestionRequest) } else { // check if there are any congestions in the grid + val congestions = stateData.congestions + + if (congestions.any) { + ctx.log.warn( + s"In the grid ${stateData.gridAgentBaseData.gridEnv.gridModel.subnetNo}, the following congestions were found: $congestions" + ) + } + // sends the results to the superior grid sender ! CongestionResponse( - stateData.congestions.combine( + congestions.combine( stateData.inferiorCongestionMap.values.flatten ), ctx.self, @@ -103,13 +116,15 @@ trait DCMAlgorithm { // checking for any congestion in the complete grid if (!congestions.any) { - ctx.log.debug( + ctx.log.warn( s"No congestions found. Finishing the congestion management." ) ctx.self ! GotoIdle checkForCongestion(updatedStateData) } else { + ctx.log.warn(s"Congestions: $congestions") + val steps = updatedStateData.gridAgentBaseData.congestionManagementParams @@ -151,9 +166,10 @@ trait DCMAlgorithm { ctx.self ! StartStep next match { - case TransformerTapping => updateTransformerTapping(stateData) - case TopologyChanges => useTopologyChanges(stateData) - case UsingFlexibilities => useFlexOptions(stateData) + case TransformerTapping => + buffer.unstashAll(updateTransformerTapping(stateData)) + case TopologyChanges => buffer.unstashAll(useTopologyChanges(stateData)) + case UsingFlexibilities => buffer.unstashAll(useFlexOptions(stateData)) } case (ctx, GotoIdle) => @@ -166,12 +182,13 @@ trait DCMAlgorithm { GridAgent.gotoIdle( stateData.gridAgentBaseData, stateData.currentTick, + Some(stateData.powerFlowResults), ctx, ) - case (ctx, msg) => - ctx.log.error(s"$msg") - + case (ctx, msg: GridAgent.Request) => + ctx.log.error(s"Received unsupported msg: $msg. Stash away!") + buffer.stash(msg) Behaviors.same } @@ -195,12 +212,15 @@ trait DCMAlgorithm { buffer: StashBuffer[GridAgent.Request], ): Behavior[GridAgent.Request] = Behaviors.receivePartial { case (ctx, StartStep) => - // for now this step is skipped - ctx.log.debug( - s"Using transformer taping to resolve a congestion is not implemented yet. Skipping this step!" - ) + if (stateData.gridAgentBaseData.isSuperior) { + // for now this step is skipped + ctx.log.warn( + s"Using transformer taping to resolve a congestion is not implemented yet. Skipping this step!" + ) + + ctx.self ! FinishStep + } - ctx.self ! FinishStep Behaviors.same case (ctx, FinishStep) => @@ -210,15 +230,19 @@ trait DCMAlgorithm { // switching to simulating grid val currentTick = stateData.currentTick - val updatedData = stateData.gridAgentBaseData.copy( - congestionManagementParams = - stateData.gridAgentBaseData.congestionManagementParams - .copy(hasRunTransformerTapping = true) + val gridAgentBaseData = stateData.gridAgentBaseData + val updatedData = gridAgentBaseData.copy(congestionManagementParams = + gridAgentBaseData.congestionManagementParams + .copy(hasRunTransformerTapping = true) ) // simulate grid after changing the transformer tapping - ctx.self ! WrappedActivation(Activation(currentTick)) - GridAgent.simulateGrid(updatedData, currentTick) + clearAndGotoSimulateGrid(updatedData, currentTick, ctx) + + case (ctx, msg: GridAgent.Request) => + ctx.log.error(s"Received unsupported msg: $msg. Stash away!") + buffer.stash(msg) + Behaviors.same } // TODO: Implement a proper behavior @@ -228,28 +252,34 @@ trait DCMAlgorithm { buffer: StashBuffer[GridAgent.Request], ): Behavior[GridAgent.Request] = Behaviors.receivePartial { case (ctx, StartStep) => - // for now this step is skipped - ctx.log.debug( - s"Using topology changes to resolve a congestion is not implemented yet. Skipping this step!" - ) + if (stateData.gridAgentBaseData.isSuperior) { + // for now this step is skipped + ctx.log.warn( + s"Using transformer taping to resolve a congestion is not implemented yet. Skipping this step!" + ) + + ctx.self ! FinishStep + } - ctx.self ! FinishStep Behaviors.same case (ctx, FinishStep) => // inform my inferior grids about the end of this step stateData.inferiorRefs.foreach(_ ! FinishStep) - val updatedGridAgentData = stateData.gridAgentBaseData.copy( - congestionManagementParams = - stateData.gridAgentBaseData.congestionManagementParams - .copy(hasRunTopologyChanges = true) + val gridAgentBaseData = stateData.gridAgentBaseData + val updatedData = gridAgentBaseData.copy(congestionManagementParams = + gridAgentBaseData.congestionManagementParams + .copy(hasRunTopologyChanges = true) ) ctx.self ! StartStep - checkForCongestion( - stateData.copy(gridAgentBaseData = updatedGridAgentData) - ) + checkForCongestion(stateData.copy(gridAgentBaseData = updatedData)) + + case (ctx, msg: GridAgent.Request) => + ctx.log.error(s"Received unsupported msg: $msg. Stash away!") + buffer.stash(msg) + Behaviors.same } // TODO: Implement a proper behavior @@ -258,27 +288,55 @@ trait DCMAlgorithm { buffer: StashBuffer[GridAgent.Request], ): Behavior[GridAgent.Request] = Behaviors.receivePartial { case (ctx, StartStep) => - // for now this step is skipped - ctx.log.debug( - s"Using flex options to resolve a congestion is not implemented yet. Skipping this step!" - ) + if (stateData.gridAgentBaseData.isSuperior) { + // for now this step is skipped + ctx.log.warn( + s"Using transformer taping to resolve a congestion is not implemented yet. Skipping this step!" + ) + + ctx.self ! FinishStep + } - ctx.self ! FinishStep Behaviors.same case (ctx, FinishStep) => // inform my inferior grids about the end of this step stateData.inferiorRefs.foreach(_ ! FinishStep) - val updatedGridAgentData = stateData.gridAgentBaseData.copy( - congestionManagementParams = - stateData.gridAgentBaseData.congestionManagementParams - .copy(hasRunTopologyChanges = true) + GridAgent.gotoIdle( + stateData.gridAgentBaseData, + stateData.currentTick, + Some(stateData.powerFlowResults), + ctx, ) - ctx.self ! StartStep - checkForCongestion( - stateData.copy(gridAgentBaseData = updatedGridAgentData) - ) + case (ctx, msg: GridAgent.Request) => + ctx.log.error(s"Received unsupported msg: $msg. Stash away!") + buffer.stash(msg) + Behaviors.same + } + + private def clearAndGotoSimulateGrid( + gridAgentBaseData: GridAgentBaseData, + currentTick: Long, + ctx: ActorContext[GridAgent.Request], + )(implicit + constantData: GridAgentConstantData, + buffer: StashBuffer[GridAgent.Request], + ): Behavior[GridAgent.Request] = { + + val cleanedData = GridAgentBaseData.clean( + gridAgentBaseData, + gridAgentBaseData.superiorGridNodeUuids, + gridAgentBaseData.inferiorGridGates, + ) + + ctx.self ! WrappedActivation(Activation(currentTick)) + GridAgent.simulateGrid( + cleanedData.copy(congestionManagementParams = + gridAgentBaseData.congestionManagementParams + ), + currentTick, + ) } } 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 f71f40dcdc..a327a72a9f 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -17,6 +17,7 @@ import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.agent.participant.ParticipantAgent.ParticipantMessage import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.event.ResultEvent +import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.exceptions.agent.GridAgentInitializationException import edu.ie3.simona.model.grid.GridModel import edu.ie3.simona.ontology.messages.Activation @@ -242,11 +243,16 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { private[grid] def gotoIdle( gridAgentBaseData: GridAgentBaseData, currentTick: Long, + results: Option[PowerFlowResultEvent], ctx: ActorContext[Request], )(implicit constantData: GridAgentConstantData, buffer: StashBuffer[GridAgent.Request], ): Behavior[Request] = { + + // notify listener about the results + results.foreach(constantData.notifyListeners) + // do my cleanup stuff ctx.log.debug("Doing my cleanup stuff") @@ -264,7 +270,7 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { ) // return to Idle - idle(cleanedGridAgentBaseData) + buffer.unstashAll(idle(cleanedGridAgentBaseData)) } /** This method uses [[ActorContext.pipeToSelf()]] to send a future message to 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 871f8289e2..a26db88d31 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -191,9 +191,7 @@ object GridAgentData { congestionManagementParams = gridAgentBaseData.congestionManagementParams.clean, ) - } - } /** The base aka default data of a [[GridAgent]]. Contains information on the diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index c93ac1739d..78a2e998c0 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -144,132 +144,81 @@ class DCMAlgorithmCenGridSpec s"skip simulate grid and check for congestions correctly if no congestions occurred" in { skipSimulation() - val congestions = Congestions( - voltageCongestions = false, - lineCongestions = false, - transformerCongestions = false, - ) - - centerGridAgent ! CongestionCheckRequest(superiorGridAgent.ref) - - // we expect a request for grid congestion values here - val congestionCheckRequestSender11 = - inferiorGrid11.expectMessageType[CongestionCheckRequest] - val congestionCheckRequestSender12 = - inferiorGrid12.expectMessageType[CongestionCheckRequest] - val congestionCheckRequestSender13 = - inferiorGrid13.expectMessageType[CongestionCheckRequest] - - // send congestions - congestionCheckRequestSender11.sender ! CongestionResponse( - congestions, - inferiorGrid11.ref, - ) - congestionCheckRequestSender12.sender ! CongestionResponse( - congestions, - inferiorGrid12.ref, - ) - congestionCheckRequestSender13.sender ! CongestionResponse( - congestions, - inferiorGrid13.ref, - ) - - // we expect no congestions in the whole grid - val allCongestions = superiorGridAgent - .expectMessageType[CongestionResponse](30.seconds) - .congestions - allCongestions shouldBe congestions - - // telling all inferior grids to go back to idle - centerGridAgent ! GotoIdle - - inferiorGrid11.expectMessageType[GotoIdle.type] - inferiorGrid12.expectMessageType[GotoIdle.type] - inferiorGrid13.expectMessageType[GotoIdle.type] - - // expect a completion message from the superior grid - scheduler.expectMessageType[Completion] match { - case Completion(_, Some(7200)) => - case x => - fail( - s"Invalid message received when expecting a completion message for simulate grid! Message was $x" - ) - } + cmStart() + cmFinish() } - s"handle unresolvable congestions correctly" in { + s"update transformer tapping correctly" in { skipSimulation() val congestions = Congestions( - voltageCongestions = false, + voltageCongestions = true, lineCongestions = false, transformerCongestions = false, ) - // transformer congestion cannot be resolved, because using flex options is not - // enable by the provided config - val unresolvableCongestion = Congestions( - voltageCongestions = false, - lineCongestions = false, - transformerCongestions = true, - ) - - centerGridAgent ! CongestionCheckRequest(superiorGridAgent.ref) - - // we expect a request for grid congestion values here - val congestionCheckRequestSender11 = - inferiorGrid11.expectMessageType[CongestionCheckRequest] - val congestionCheckRequestSender12 = - inferiorGrid12.expectMessageType[CongestionCheckRequest] - val congestionCheckRequestSender13 = - inferiorGrid13.expectMessageType[CongestionCheckRequest] + cmStart(congestions) - // send congestions - congestionCheckRequestSender11.sender ! CongestionResponse( - congestions, - inferiorGrid11.ref, - ) - congestionCheckRequestSender12.sender ! CongestionResponse( - congestions, - inferiorGrid12.ref, - ) - congestionCheckRequestSender13.sender ! CongestionResponse( - unresolvableCongestion, - inferiorGrid13.ref, + // initiate transformer tapping + centerGridAgent ! NextStepRequest( + CongestionManagementParams.CongestionManagementSteps.TransformerTapping ) - // we expect no congestions in the whole grid - val allCongestions = superiorGridAgent - .expectMessageType[CongestionResponse](30.seconds) - .congestions - allCongestions shouldBe unresolvableCongestion + // inferior grids should receive a next state message to go to a congestion management step + inferiorGrid11.expectMessageType[NextStepRequest] + inferiorGrid12.expectMessageType[NextStepRequest] + inferiorGrid13.expectMessageType[NextStepRequest] - // telling all inferior grids to go back to idle - centerGridAgent ! GotoIdle + // skipping the step for now + // TODO: Update test after implementing transformer tapping + centerGridAgent ! FinishStep + inferiorGrid11.expectMessageType[FinishStep.type] + inferiorGrid12.expectMessageType[FinishStep.type] + inferiorGrid13.expectMessageType[FinishStep.type] - inferiorGrid11.expectMessageType[GotoIdle.type] - inferiorGrid12.expectMessageType[GotoIdle.type] - inferiorGrid13.expectMessageType[GotoIdle.type] + // skipping the simulation + skipSimulation(true) + cmStart() + cmFinish() + } - // expect a completion message from the superior grid - scheduler.expectMessageType[Completion] match { - case Completion(_, Some(7200)) => - case x => - fail( - s"Invalid message received when expecting a completion message for simulate grid! Message was $x" - ) + /** Method to reduce duplicate code + * @param midTest + * to check if this is in the middle of a test or at the beginning + */ + def skipSimulation(midTest: Boolean = false): Unit = { + if (!midTest) { + centerGridAgent ! WrappedActivation(Activation(3600)) + + // we expect a completion message + scheduler.expectMessageType[Completion].newTick shouldBe Some(3600) + } else { + inferiorGrid11.expectMessageType[RequestGridPower] + inferiorGrid12.expectMessageType[RequestGridPower] + inferiorGrid13.expectMessageType[RequestGridPower] + superiorGridAgent.expectMessageType[SlackVoltageRequest] } - } - s"update transformer tapping correctly" in { - skipSimulation() + // skip simulation and go to congestion check + centerGridAgent ! FinishGridSimulationTrigger(3600) - val congestions = Congestions( - voltageCongestions = true, - lineCongestions = false, - transformerCongestions = false, - ) + // inferior grid receives a FinishGridSimulationTrigger and goes into the congestion check state + inferiorGrid11.expectMessage(FinishGridSimulationTrigger(3600)) + inferiorGrid12.expectMessage(FinishGridSimulationTrigger(3600)) + inferiorGrid13.expectMessage(FinishGridSimulationTrigger(3600)) + } + /** Method to reduce duplicate code + * @param congestions + * to be send to the [[centerGridAgent]] (default: no congestions) + */ + def cmStart( + congestions: Congestions = Congestions( + voltageCongestions = false, + lineCongestions = false, + transformerCongestions = false, + ) + ): Unit = { centerGridAgent ! CongestionCheckRequest(superiorGridAgent.ref) // we expect a request for grid congestion values here @@ -299,41 +248,12 @@ class DCMAlgorithmCenGridSpec .expectMessageType[CongestionResponse](30.seconds) .congestions allCongestions shouldBe congestions + } - centerGridAgent ! NextStepRequest( - CongestionManagementParams.CongestionManagementSteps.TransformerTapping - ) - - // inferior grids should receive a next state message to go to a congestion management step - inferiorGrid11.expectMessageType[NextStepRequest] - inferiorGrid12.expectMessageType[NextStepRequest] - inferiorGrid13.expectMessageType[NextStepRequest] - - // skipping the step for now - // TODO: Update test after implementing transformer tapping - centerGridAgent ! FinishStep - inferiorGrid11.expectMessageType[FinishStep.type] - inferiorGrid12.expectMessageType[FinishStep.type] - inferiorGrid13.expectMessageType[FinishStep.type] - - // skipping the simulation - inferiorGrid11.expectMessageType[RequestGridPower] - inferiorGrid12.expectMessageType[RequestGridPower] - inferiorGrid13.expectMessageType[RequestGridPower] - superiorGridAgent.expectMessageType[SlackVoltageRequest] - - centerGridAgent ! FinishGridSimulationTrigger(3600) - - // inferior grid receives a FinishGridSimulationTrigger and goes into the congestion check state - inferiorGrid11.expectMessage(FinishGridSimulationTrigger(3600)) - inferiorGrid12.expectMessage(FinishGridSimulationTrigger(3600)) - inferiorGrid13.expectMessage(FinishGridSimulationTrigger(3600)) - - // we expect a request for grid congestion values here - inferiorGrid11.expectMessageType[CongestionCheckRequest] - inferiorGrid12.expectMessageType[CongestionCheckRequest] - inferiorGrid13.expectMessageType[CongestionCheckRequest] - + /** Method to reduce duplicate code + */ + def cmFinish(): Unit = { + // telling all inferior grids to go back to idle centerGridAgent ! GotoIdle // inferior should receive a next state message to go to the idle state @@ -350,20 +270,5 @@ class DCMAlgorithmCenGridSpec ) } } - - def skipSimulation(): Unit = { - centerGridAgent ! WrappedActivation(Activation(3600)) - - // we expect a completion message - scheduler.expectMessageType[Completion].newTick shouldBe Some(3600) - - // skip simulation and go to congestion check - centerGridAgent ! FinishGridSimulationTrigger(3600) - - // inferior grid receives a FinishGridSimulationTrigger and goes into the congestion check state - inferiorGrid11.expectMessage(FinishGridSimulationTrigger(3600)) - inferiorGrid12.expectMessage(FinishGridSimulationTrigger(3600)) - inferiorGrid13.expectMessage(FinishGridSimulationTrigger(3600)) - } } } From c47655834de818fdd34d6f2abad5c70398e4515a Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 17 Apr 2024 08:36:23 +0200 Subject: [PATCH 07/55] Testing and fixing finding of congestions. --- .../ie3/simona/agent/grid/GridAgentData.scala | 13 +- .../simona/agent/grid/GridAgentDataSpec.scala | 211 ++++++++++++++++++ 2 files changed, 218 insertions(+), 6 deletions(-) create mode 100644 src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala 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 a26db88d31..b76ae8d7c7 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -22,6 +22,7 @@ import edu.ie3.simona.model.grid.GridModel.GridComponents import edu.ie3.simona.model.grid.{GridModel, RefSystem, VoltageLimits} import edu.ie3.simona.ontology.messages.Activation import org.apache.pekko.actor.typed.ActorRef +import squants.electro.Kilovolts import java.time.ZonedDateTime import java.util.UUID @@ -514,7 +515,7 @@ object GridAgentData { ): CongestionManagementData = { val gridModel = gridAgentBaseData.gridEnv.gridModel - val congestions = getCongestions( + val congestions = findCongestions( powerFlowResults, gridModel.gridComponents, gridModel.voltageLimits, @@ -562,12 +563,11 @@ object GridAgentData { ), ) - private def getCongestions( + private def findCongestions( powerFlowResults: PowerFlowResultEvent, gridComponents: GridComponents, voltageLimits: VoltageLimits, ): Congestions = { - val nodes = gridComponents.nodes.map(_.uuid) // checking for voltage congestions val voltageCongestion = powerFlowResults.nodeResults.exists { res => @@ -594,11 +594,12 @@ object GridAgentData { powerFlowResults.transformer2wResults.exists { res => val transformer = transformer2w(res.getInputModel) - if (nodes.contains(transformer.hvNodeUuid)) { + val iA = res.getiAMag().getValue.doubleValue() > transformer.iNomHv.value - } else { + val iB = res.getiBMag().getValue.doubleValue() > transformer.iNomLv.value - } + + iA || iB } val transformer3w = gridComponents.transformers3w.map { transformer => diff --git a/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala new file mode 100644 index 0000000000..bd65466efb --- /dev/null +++ b/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala @@ -0,0 +1,211 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.agent.grid + +import edu.ie3.datamodel.models.StandardUnits +import edu.ie3.datamodel.models.result.NodeResult +import edu.ie3.datamodel.models.result.connector.{ + LineResult, + Transformer2WResult, +} +import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData +import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions +import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent +import edu.ie3.simona.model.grid.{GridModel, RefSystem, VoltageLimits} +import edu.ie3.simona.test.common.model.grid.DbfsTestGrid +import edu.ie3.simona.test.common.{ConfigTestData, UnitSpec} +import edu.ie3.util.quantities.PowerSystemUnits.PU +import squants.electro.Kilovolts +import squants.energy.Kilowatts +import tech.units.indriya.quantity.Quantities + +import java.time.ZonedDateTime + +class GridAgentDataSpec extends UnitSpec with DbfsTestGrid with ConfigTestData { + + "The CongestionManagementData" should { + val startTime = ZonedDateTime.now() + + val gridModel = GridModel( + hvGridContainer, + RefSystem(Kilowatts(600), Kilovolts(110)), + VoltageLimits(0.9, 1.1), + startTime, + startTime.plusHours(2), + simonaConfig, + ) + + val findCongestions = PrivateMethod[Congestions](Symbol("findCongestions")) + + "find congestions correctly for empty results" in { + val emptyResults = PowerFlowResultEvent( + Seq.empty, + Seq.empty, + Seq.empty, + Seq.empty, + Seq.empty, + ) + + CongestionManagementData invokePrivate findCongestions( + emptyResults, + gridModel.gridComponents, + gridModel.voltageLimits, + ) shouldBe Congestions( + voltageCongestions = false, + lineCongestions = false, + transformerCongestions = false, + ) + } + + "find voltage congestions correctly" in { + val nodeResult1 = new NodeResult( + startTime, + node1.getUuid, + Quantities.getQuantity(1d, PU), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + ) + + val nodeResult2 = new NodeResult( + startTime, + node2.getUuid, + Quantities.getQuantity(0.9d, PU), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + ) + + val nodeResult3 = new NodeResult( + startTime, + node3.getUuid, + Quantities.getQuantity(1.1d, PU), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + ) + + val nodeResult4 = new NodeResult( + startTime, + node4.getUuid, + Quantities.getQuantity(0.89d, PU), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + ) + + val results = PowerFlowResultEvent( + Seq(nodeResult1, nodeResult2, nodeResult3, nodeResult4), + Seq.empty, + Seq.empty, + Seq.empty, + Seq.empty, + ) + + CongestionManagementData invokePrivate findCongestions( + results, + gridModel.gridComponents, + gridModel.voltageLimits, + ) shouldBe Congestions( + voltageCongestions = true, + lineCongestions = false, + transformerCongestions = false, + ) + } + + "find line congestions correctly" in { + val lineResult1to2 = new LineResult( + startTime, + line1To2.getUuid, + Quantities.getQuantity(1360d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + Quantities.getQuantity(1360d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + ) + + val lineResult1to3 = new LineResult( + startTime, + line1To3.getUuid, + Quantities.getQuantity(500d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + Quantities.getQuantity(500d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + ) + + val lineResult1to4 = new LineResult( + startTime, + line1To4.getUuid, + Quantities.getQuantity(801d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + Quantities.getQuantity(799d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + ) + + val lineResult2to3 = new LineResult( + startTime, + line2To3.getUuid, + Quantities.getQuantity(801d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + Quantities.getQuantity(799d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + ) + + val results = PowerFlowResultEvent( + Seq.empty, + Seq.empty, + Seq(lineResult1to2, lineResult1to3, lineResult1to4, lineResult2to3), + Seq.empty, + Seq.empty, + ) + + CongestionManagementData invokePrivate findCongestions( + results, + gridModel.gridComponents, + gridModel.voltageLimits, + ) shouldBe Congestions( + voltageCongestions = false, + lineCongestions = true, + transformerCongestions = false, + ) + } + + "find transformer2w congestions correctly" in { + val transformerResult1 = new Transformer2WResult( + startTime, + transformer1.getUuid, + Quantities.getQuantity(300d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + Quantities + .getQuantity(1036.3, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + 0, + ) + + val transformerResult2 = new Transformer2WResult( + startTime, + transformer2.getUuid, + Quantities.getQuantity(310d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + Quantities.getQuantity(1070d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), + 0, + ) + + val results = PowerFlowResultEvent( + Seq.empty, + Seq.empty, + Seq.empty, + Seq(transformerResult1, transformerResult2), + Seq.empty, + ) + + CongestionManagementData invokePrivate findCongestions( + results, + gridModel.gridComponents, + gridModel.voltageLimits, + ) shouldBe Congestions( + voltageCongestions = false, + lineCongestions = false, + transformerCongestions = true, + ) + } + + } + +} From d917c044f0f545a78d537130d8e0d117bc7284db Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 17 Apr 2024 12:56:37 +0200 Subject: [PATCH 08/55] Adding iterations to `Optimization` step of the `CongestionManagement`. --- .../resources/config/config-template.conf | 1 + .../grid/CongestionManagementParams.scala | 22 ++++---- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 56 ++++++------------- .../edu/ie3/simona/agent/grid/GridAgent.scala | 1 + .../ie3/simona/agent/grid/GridAgentData.scala | 34 ++++++++++- .../simona/agent/grid/GridAgentMessages.scala | 12 ++-- .../edu/ie3/simona/config/SimonaConfig.scala | 5 ++ .../agent/grid/DCMAlgorithmCenGridSpec.scala | 3 +- 8 files changed, 77 insertions(+), 57 deletions(-) diff --git a/src/main/resources/config/config-template.conf b/src/main/resources/config/config-template.conf index 3ce1e9f254..15a5043618 100644 --- a/src/main/resources/config/config-template.conf +++ b/src/main/resources/config/config-template.conf @@ -375,6 +375,7 @@ simona.event.listener = [ simona.congestionManagement.enableTransformerTapping = boolean | false simona.congestionManagement.enableTopologyChanges = boolean | false +simona.congestionManagement.maxOptimizationIterations = int | 1 simona.congestionManagement.useFlexOptions = boolean | false simona.congestionManagement.timeout = "duration:seconds | 30 seconds" // maximum timeout diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala index b522779f2a..8de2d31144 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala @@ -26,28 +26,30 @@ final case class CongestionManagementParams( transformerTapping: Boolean, topologyChanges: Boolean, flexOptions: Boolean, + maxOptimizationIterations: Int, timeout: Duration, + iteration: Int = 0, hasRunTransformerTapping: Boolean = false, - hasRunTopologyChanges: Boolean = false, + hasUsedFlexOptions: Boolean = false, ) { def runCongestionManagement: Boolean = transformerTapping || topologyChanges || flexOptions def runTransformerTapping: Boolean = - transformerTapping && !hasRunTransformerTapping + transformerTapping && !hasRunTransformerTapping && runOptimization - def runTopologyChanges: Boolean = topologyChanges && !hasRunTopologyChanges + def runTopologyChanges: Boolean = topologyChanges && runOptimization - def useFlexOptions: Boolean = flexOptions + def useFlexOptions: Boolean = flexOptions && !hasUsedFlexOptions def clean: CongestionManagementParams = { - copy(hasRunTransformerTapping = false, hasRunTopologyChanges = false) + copy( + hasRunTransformerTapping = false, + hasUsedFlexOptions = false, + iteration = 0, + ) } -} -object CongestionManagementParams { - object CongestionManagementSteps extends Enumeration { - val TransformerTapping, TopologyChanges, UsingFlexibilities = Value - } + private def runOptimization: Boolean = iteration < maxOptimizationIterations } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 1e9e5b1dce..fc18c3d14c 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -6,7 +6,7 @@ package edu.ie3.simona.agent.grid -import edu.ie3.simona.agent.grid.CongestionManagementParams.CongestionManagementSteps._ +import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.CongestionManagementSteps._ import edu.ie3.simona.agent.grid.GridAgent.pipeToSelf import edu.ie3.simona.agent.grid.GridAgentData.{ CongestionManagementData, @@ -132,7 +132,7 @@ trait DCMAlgorithm { if (congestions.voltageCongestions && steps.runTransformerTapping) { NextStepRequest(TransformerTapping) } else if ( - congestions.lineCongestions && steps.runTopologyChanges + congestions.assetCongestion && steps.runTopologyChanges ) { NextStepRequest(TopologyChanges) } else if (congestions.any && steps.useFlexOptions) { @@ -227,22 +227,12 @@ trait DCMAlgorithm { // inform my inferior grids about the end of this step stateData.inferiorRefs.foreach(_ ! FinishStep) - // switching to simulating grid - val currentTick = stateData.currentTick - - val gridAgentBaseData = stateData.gridAgentBaseData - val updatedData = gridAgentBaseData.copy(congestionManagementParams = - gridAgentBaseData.congestionManagementParams - .copy(hasRunTransformerTapping = true) - ) - // simulate grid after changing the transformer tapping - clearAndGotoSimulateGrid(updatedData, currentTick, ctx) - - case (ctx, msg: GridAgent.Request) => - ctx.log.error(s"Received unsupported msg: $msg. Stash away!") - buffer.stash(msg) - Behaviors.same + clearAndGotoSimulateGrid( + stateData.cleanAfterTransformerTapping, + stateData.currentTick, + ctx, + ) } // TODO: Implement a proper behavior @@ -255,7 +245,7 @@ trait DCMAlgorithm { if (stateData.gridAgentBaseData.isSuperior) { // for now this step is skipped ctx.log.warn( - s"Using transformer taping to resolve a congestion is not implemented yet. Skipping this step!" + s"Using topology changes to resolve a congestion is not implemented yet. Skipping this step!" ) ctx.self ! FinishStep @@ -267,19 +257,12 @@ trait DCMAlgorithm { // inform my inferior grids about the end of this step stateData.inferiorRefs.foreach(_ ! FinishStep) - val gridAgentBaseData = stateData.gridAgentBaseData - val updatedData = gridAgentBaseData.copy(congestionManagementParams = - gridAgentBaseData.congestionManagementParams - .copy(hasRunTopologyChanges = true) + // simulate grid after using topology changes + clearAndGotoSimulateGrid( + stateData.cleanAfterTopologyChange, + stateData.currentTick, + ctx, ) - - ctx.self ! StartStep - checkForCongestion(stateData.copy(gridAgentBaseData = updatedData)) - - case (ctx, msg: GridAgent.Request) => - ctx.log.error(s"Received unsupported msg: $msg. Stash away!") - buffer.stash(msg) - Behaviors.same } // TODO: Implement a proper behavior @@ -291,7 +274,7 @@ trait DCMAlgorithm { if (stateData.gridAgentBaseData.isSuperior) { // for now this step is skipped ctx.log.warn( - s"Using transformer taping to resolve a congestion is not implemented yet. Skipping this step!" + s"Using flex options to resolve a congestion is not implemented yet. Skipping this step!" ) ctx.self ! FinishStep @@ -303,17 +286,12 @@ trait DCMAlgorithm { // inform my inferior grids about the end of this step stateData.inferiorRefs.foreach(_ ! FinishStep) - GridAgent.gotoIdle( - stateData.gridAgentBaseData, + // simulate grid after finishing the congestion management + clearAndGotoSimulateGrid( + stateData.cleanAfterFlexOptions, stateData.currentTick, - Some(stateData.powerFlowResults), ctx, ) - - case (ctx, msg: GridAgent.Request) => - ctx.log.error(s"Received unsupported msg: $msg. Stash away!") - buffer.stash(msg) - Behaviors.same } private def clearAndGotoSimulateGrid( 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 a327a72a9f..e6909234e0 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -194,6 +194,7 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { cfg.congestionManagement.enableTransformerTapping, cfg.congestionManagement.enableTopologyChanges, cfg.congestionManagement.useFlexOptions, + cfg.congestionManagement.maxOptimizationIterations, cfg.congestionManagement.timeout, ), SimonaActorNaming.actorName(ctx.self), 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 b76ae8d7c7..d88ce43aaa 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -22,7 +22,6 @@ import edu.ie3.simona.model.grid.GridModel.GridComponents import edu.ie3.simona.model.grid.{GridModel, RefSystem, VoltageLimits} import edu.ie3.simona.ontology.messages.Activation import org.apache.pekko.actor.typed.ActorRef -import squants.electro.Kilovolts import java.time.ZonedDateTime import java.util.UUID @@ -505,6 +504,34 @@ object GridAgentData { def inferiorRefs: Set[ActorRef[GridAgent.Request]] = inferiorCongestionMap.keySet + + def cleanAfterTransformerTapping: GridAgentBaseData = { + val params = gridAgentBaseData.congestionManagementParams + val updatedParams = params.copy(hasRunTransformerTapping = true) + + gridAgentBaseData.copy(congestionManagementParams = updatedParams) + } + + def cleanAfterTopologyChange: GridAgentBaseData = { + val params = gridAgentBaseData.congestionManagementParams + + // updating the params to the next iteration + val updatedParams = params.copy( + hasRunTransformerTapping = false, + iteration = params.iteration + 1, + ) + + gridAgentBaseData.copy(congestionManagementParams = updatedParams) + } + + def cleanAfterFlexOptions: GridAgentBaseData = { + val params = gridAgentBaseData.congestionManagementParams + val updatedData = params.copy( + hasUsedFlexOptions = true + ) + + gridAgentBaseData.copy(congestionManagementParams = updatedData) + } } object CongestionManagementData { @@ -626,13 +653,18 @@ object GridAgentData { def any: Boolean = voltageCongestions || lineCongestions || transformerCongestions + def assetCongestion: Boolean = lineCongestions || transformerCongestions + def combine(options: Iterable[Congestions]): Congestions = Congestions( voltageCongestions || options.exists(_.voltageCongestions), lineCongestions || options.exists(_.lineCongestions), transformerCongestions || options.exists(_.transformerCongestions), ) + } + object CongestionManagementSteps extends Enumeration { + val TransformerTapping, TopologyChanges, UsingFlexibilities = Value } } } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala index d29f623942..98c9743c26 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala @@ -6,11 +6,11 @@ package edu.ie3.simona.agent.grid -import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions -import edu.ie3.simona.agent.grid.GridAgentData.{ - CongestionManagementData, - GridAgentInitData, +import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.{ + CongestionManagementSteps, + Congestions, } +import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ ExchangePower, ExchangeVoltage, @@ -18,7 +18,7 @@ import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ import edu.ie3.simona.ontology.messages.Activation import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.util.scala.quantities.ReactivePower -import org.apache.pekko.actor.typed.{ActorRef, Behavior} +import org.apache.pekko.actor.typed.ActorRef import squants.Power import squants.electro.ElectricPotential @@ -274,7 +274,7 @@ object GridAgentMessages { extends GridAgent.InternalRequest case class NextStepRequest( - nextStep: CongestionManagementParams.CongestionManagementSteps.Value + nextStep: CongestionManagementSteps.Value ) extends GridAgent.InternalRequest /** Message that indicates all actors that the current step is started. diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index a75bcf7f05..192b512a05 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -1086,6 +1086,7 @@ object SimonaConfig { final case class CongestionManagement( enableTopologyChanges: scala.Boolean, enableTransformerTapping: scala.Boolean, + maxOptimizationIterations: scala.Int, timeout: java.time.Duration, useFlexOptions: scala.Boolean, ) @@ -1103,6 +1104,10 @@ object SimonaConfig { enableTransformerTapping = c.hasPathOrNull( "enableTransformerTapping" ) && c.getBoolean("enableTransformerTapping"), + maxOptimizationIterations = + if (c.hasPathOrNull("maxOptimizationIterations")) + c.getInt("maxOptimizationIterations") + else 1, timeout = if (c.hasPathOrNull("timeout")) c.getDuration("timeout") else java.time.Duration.parse("PT30S"), diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index 78a2e998c0..647f40dcb8 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -9,6 +9,7 @@ package edu.ie3.simona.agent.grid import com.typesafe.config.ConfigFactory import edu.ie3.datamodel.models.input.container.ThermalGrid import edu.ie3.simona.agent.EnvironmentRefs +import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.CongestionManagementSteps.TransformerTapping import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData import edu.ie3.simona.agent.grid.GridAgentMessages.{ @@ -161,7 +162,7 @@ class DCMAlgorithmCenGridSpec // initiate transformer tapping centerGridAgent ! NextStepRequest( - CongestionManagementParams.CongestionManagementSteps.TransformerTapping + TransformerTapping ) // inferior grids should receive a next state message to go to a congestion management step From 39eb8ca2f49dace7cc18efb7b736744e5e4f0886 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 22 Apr 2024 21:16:04 +0200 Subject: [PATCH 09/55] Adding `AwaitingData` to `GridAgentData`. --- .../grid/CongestionManagementSupport.scala | 12 ++++ .../ie3/simona/agent/grid/DBFSAlgorithm.scala | 6 +- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 69 +++++++++++------- .../ie3/simona/agent/grid/GridAgentData.scala | 70 +++++++++++-------- .../simona/agent/grid/GridAgentMessages.scala | 5 +- 5 files changed, 105 insertions(+), 57 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala new file mode 100644 index 0000000000..f644e841b8 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -0,0 +1,12 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.agent.grid + +/** Support and helper methods for calculations done during the congestion + * management. + */ +trait CongestionManagementSupport {} diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala index e02747e45d..5cdaa457b2 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala @@ -17,6 +17,7 @@ import edu.ie3.powerflow.model.PowerFlowResult.SuccessFullPowerFlowResult.ValidN import edu.ie3.powerflow.model.enums.NodeType import edu.ie3.simona.agent.grid.GridAgent.pipeToSelf import edu.ie3.simona.agent.grid.GridAgentData.{ + AwaitingData, CongestionManagementData, GridAgentBaseData, GridAgentConstantData, @@ -490,7 +491,10 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { ) ctx.self ! StartStep - GridAgent.checkForCongestion(congestionManagementData) + GridAgent.checkForCongestion( + congestionManagementData, + AwaitingData(congestionManagementData.inferiorGrids), + ) } else { // clean up agent and go back to idle GridAgent.gotoIdle(gridAgentBaseData, currentTick, results, ctx) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index fc18c3d14c..58a6db23e4 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -6,9 +6,11 @@ package edu.ie3.simona.agent.grid -import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.CongestionManagementSteps._ import edu.ie3.simona.agent.grid.GridAgent.pipeToSelf +import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.CongestionManagementSteps._ +import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions import edu.ie3.simona.agent.grid.GridAgentData.{ + AwaitingData, CongestionManagementData, GridAgentBaseData, GridAgentConstantData, @@ -44,7 +46,8 @@ trait DCMAlgorithm { * a [[Behavior]] */ private[grid] def checkForCongestion( - stateData: CongestionManagementData + stateData: CongestionManagementData, + awaitingData: AwaitingData[Congestions], )(implicit constantData: GridAgentConstantData, buffer: StashBuffer[GridAgent.Request], @@ -64,7 +67,9 @@ trait DCMAlgorithm { stateData.inferiorRefs.map { inferiorGridAgentRef => inferiorGridAgentRef .ask(ref => CongestionCheckRequest(ref)) - .map { case response: CongestionResponse => response } + .map { case response: CongestionResponse => + (response.sender, response.congestions) + } }.toVector ) .map(res => ReceivedCongestions(res)) @@ -75,7 +80,7 @@ trait DCMAlgorithm { case (ctx, congestionRequest @ CongestionCheckRequest(sender)) => // check if waiting for inferior data is needed - if (stateData.awaitingInferiorData) { + if (awaitingData.isDone) { ctx.log.debug( s"Received request for congestions before all data from inferior grids were received. Stashing away." ) @@ -94,9 +99,7 @@ trait DCMAlgorithm { // sends the results to the superior grid sender ! CongestionResponse( - congestions.combine( - stateData.inferiorCongestionMap.values.flatten - ), + congestions.combine(awaitingData.values), ctx.self, ) } @@ -105,14 +108,12 @@ trait DCMAlgorithm { case (ctx, ReceivedCongestions(congestions)) => // updating the state data with received data from inferior grids - val updatedStateData = stateData.handleReceivingData(congestions) + val updatedData = awaitingData.handleReceivingData(congestions) - if (updatedStateData.gridAgentBaseData.isSuperior) { + if (stateData.gridAgentBaseData.isSuperior) { // if we are the superior grid, we find the next behavior - val congestions = updatedStateData.congestions.combine( - updatedStateData.inferiorCongestionMap.values.flatten - ) + val congestions = stateData.congestions.combine(updatedData.values) // checking for any congestion in the complete grid if (!congestions.any) { @@ -121,15 +122,16 @@ trait DCMAlgorithm { ) ctx.self ! GotoIdle - checkForCongestion(updatedStateData) + checkForCongestion(stateData, updatedData) } else { ctx.log.warn(s"Congestions: $congestions") - val steps = - updatedStateData.gridAgentBaseData.congestionManagementParams + val steps = stateData.gridAgentBaseData.congestionManagementParams val msg = - if (congestions.voltageCongestions && steps.runTransformerTapping) { + if ( + (congestions.voltageCongestions || congestions.lineCongestions) && steps.runTransformerTapping + ) { NextStepRequest(TransformerTapping) } else if ( congestions.assetCongestion && steps.runTopologyChanges @@ -148,12 +150,12 @@ trait DCMAlgorithm { } ctx.self ! msg - checkForCongestion(updatedStateData) + checkForCongestion(stateData, updatedData) } } else { // un-stash all messages - buffer.unstashAll(checkForCongestion(updatedStateData)) + buffer.unstashAll(checkForCongestion(stateData, updatedData)) } case (ctx, NextStepRequest(next)) => @@ -167,9 +169,20 @@ trait DCMAlgorithm { next match { case TransformerTapping => - buffer.unstashAll(updateTransformerTapping(stateData)) - case TopologyChanges => buffer.unstashAll(useTopologyChanges(stateData)) - case UsingFlexibilities => buffer.unstashAll(useFlexOptions(stateData)) + buffer.unstashAll( + updateTransformerTapping( + stateData, + AwaitingData(stateData.inferiorGrids), + ) + ) + case TopologyChanges => + buffer.unstashAll( + useTopologyChanges(stateData, AwaitingData(stateData.inferiorGrids)) + ) + case UsingFlexibilities => + buffer.unstashAll( + useFlexOptions(stateData, AwaitingData(stateData.inferiorGrids)) + ) } case (ctx, GotoIdle) => @@ -206,7 +219,8 @@ trait DCMAlgorithm { */ // TODO: Implement a proper behavior private[grid] def updateTransformerTapping( - stateData: CongestionManagementData + stateData: CongestionManagementData, + awaitingData: AwaitingData[_], )(implicit constantData: GridAgentConstantData, buffer: StashBuffer[GridAgent.Request], @@ -236,8 +250,10 @@ trait DCMAlgorithm { } // TODO: Implement a proper behavior - private[grid] def useTopologyChanges(stateData: CongestionManagementData)( - implicit + private[grid] def useTopologyChanges( + stateData: CongestionManagementData, + awaitingData: AwaitingData[_], + )(implicit constantData: GridAgentConstantData, buffer: StashBuffer[GridAgent.Request], ): Behavior[GridAgent.Request] = Behaviors.receivePartial { @@ -266,7 +282,10 @@ trait DCMAlgorithm { } // TODO: Implement a proper behavior - private[grid] def useFlexOptions(stateData: CongestionManagementData)(implicit + private[grid] def useFlexOptions( + stateData: CongestionManagementData, + awaitingData: AwaitingData[_], + )(implicit constantData: GridAgentConstantData, buffer: StashBuffer[GridAgent.Request], ): Behavior[GridAgent.Request] = Behaviors.receivePartial { 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 d88ce43aaa..adde6c440c 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -59,6 +59,41 @@ object GridAgentData { } } + final case class AwaitingData[T] private ( + inferiorGridMap: Map[ActorRef[GridAgent.Request], Option[T]] + ) { + + /** Returns true if congestion data from inferior grids is expected and no + * data was received yet. + */ + def isDone: Boolean = + inferiorGridMap.values.exists(_.isEmpty) + + def values: Iterable[T] = inferiorGridMap.values.flatten.toSeq + + /** Method for updating the data with the received data. + * + * @param receivedData + * data that was received + * @return + * a updated copy of this data + */ + def handleReceivingData( + receivedData: Vector[(ActorRef[GridAgent.Request], T)] + ): AwaitingData[T] = { + val mappedData = receivedData.map(res => res._1 -> Some(res._2)).toMap + copy(inferiorGridMap = inferiorGridMap ++ mappedData) + } + } + + object AwaitingData { + def apply[T]( + inferiorGrids: Seq[ActorRef[GridAgent.Request]] + ): AwaitingData[T] = { + AwaitingData(inferiorGrids.map(ref => ref -> None).toMap) + } + } + /** Data that is send to the [[GridAgent]] directly after startup. It contains * the main information for initialization. This data should include all * [[GridAgent]] individual data, for data that is the same for all @@ -476,34 +511,10 @@ object GridAgentData { currentTick: Long, powerFlowResults: PowerFlowResultEvent, congestions: Congestions, - inferiorCongestionMap: Map[ActorRef[GridAgent.Request], Option[ - Congestions - ]], + inferiorGrids: Seq[ActorRef[GridAgent.Request]], ) extends GridAgentData { - /** Returns true if congestion data from inferior grids is expected and no - * data was received yet. - */ - def awaitingInferiorData: Boolean = - inferiorCongestionMap.values.exists(_.isEmpty) - - /** Method for updating the data with the received data. - * - * @param receivedData - * data that was received - * @return - * a updated copy of this data - */ - def handleReceivingData( - receivedData: Vector[CongestionResponse] - ): CongestionManagementData = { - val mappedData = - receivedData.map(res => res.sender -> Some(res.congestions)).toMap - copy(inferiorCongestionMap = inferiorCongestionMap ++ mappedData) - } - - def inferiorRefs: Set[ActorRef[GridAgent.Request]] = - inferiorCongestionMap.keySet + def inferiorRefs: Set[ActorRef[GridAgent.Request]] = inferiorGrids.toSet def cleanAfterTransformerTapping: GridAgentBaseData = { val params = gridAgentBaseData.congestionManagementParams @@ -549,7 +560,7 @@ object GridAgentData { ) // extracting one inferior ref for all inferior grids - val inferiorCongestionMap = gridAgentBaseData.inferiorGridGates + val inferiorGrids = gridAgentBaseData.inferiorGridGates .map { inferiorGridGate => gridAgentBaseData.gridEnv.subgridGateToActorRef( inferiorGridGate @@ -563,15 +574,16 @@ object GridAgentData { inferiorGridGates } .map { case (inferiorGridAgentRef, _) => - inferiorGridAgentRef -> None + inferiorGridAgentRef } + .toSeq CongestionManagementData( gridAgentBaseData, currentTick, powerFlowResults, congestions, - inferiorCongestionMap, + inferiorGrids, ) } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala index 98c9743c26..9a51fbd260 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala @@ -270,8 +270,9 @@ object GridAgentMessages { sender: ActorRef[GridAgent.Request], ) extends GridAgent.InternalReply - case class ReceivedCongestions(congestions: Vector[CongestionResponse]) - extends GridAgent.InternalRequest + case class ReceivedCongestions( + congestions: Vector[(ActorRef[GridAgent.Request], Congestions)] + ) extends GridAgent.InternalRequest case class NextStepRequest( nextStep: CongestionManagementSteps.Value From 65174a6a4c3cbeffac3657aebcd689fbcb035073 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 23 Apr 2024 16:56:11 +0200 Subject: [PATCH 10/55] Adding `CongestionManagementSupport`. --- .../grid/CongestionManagementSupport.scala | 354 +++++++++++++++++- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 6 +- .../ie3/simona/agent/grid/GridAgentData.scala | 27 +- .../simona/agent/grid/GridAgentMessages.scala | 2 +- .../simona/exceptions/ResultException.scala | 9 + .../model/grid/Transformer3wModel.scala | 4 + .../simona/model/grid/TransformerModel.scala | 4 + .../ie3/simona/model/grid/VoltageLimits.scala | 20 +- .../CongestionManagementSupportSpec.scala | 304 +++++++++++++++ .../agent/grid/DCMAlgorithmCenGridSpec.scala | 4 +- .../agent/grid/DCMAlgorithmSupGridSpec.scala | 2 +- .../simona/agent/grid/GridAgentDataSpec.scala | 2 +- .../model/grid/GridComponentsMokka.scala | 63 ++++ .../common/model/grid/SubGridGateMokka.scala | 41 ++ .../test/common/result/ResultMokka.scala | 42 +++ 15 files changed, 846 insertions(+), 38 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/exceptions/ResultException.scala create mode 100644 src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala create mode 100644 src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala create mode 100644 src/test/scala/edu/ie3/simona/test/common/result/ResultMokka.scala diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index f644e841b8..898a720db0 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -6,7 +6,359 @@ package edu.ie3.simona.agent.grid +import edu.ie3.datamodel.graph.SubGridGate +import edu.ie3.datamodel.models.result.connector.LineResult +import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange +import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent +import edu.ie3.simona.exceptions.ResultException +import edu.ie3.simona.model.SystemComponent +import edu.ie3.simona.model.grid.GridModel.GridComponents +import edu.ie3.simona.model.grid.{ + Transformer3wModel, + TransformerModel, + TransformerTappingModel, + VoltageLimits, +} +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import org.apache.pekko.actor.typed.ActorRef +import squants.electro.Amperes +import tech.units.indriya.ComparableQuantity + +import java.util.UUID +import javax.measure.quantity.Dimensionless + /** Support and helper methods for calculations done during the congestion * management. */ -trait CongestionManagementSupport {} +trait CongestionManagementSupport { + + /** Method for mapping the [[TransformerModel]]s to the given inferior grid + * refs. + * @param inferiorGrids + * set of [[ActorRef]]s + * @param subGridGateToActorRef + * map: [[SubGridGate]] to [[ActorRef]] + * @param gridComponents + * all components of the grid + * @return + * a map: [[ActorRef]] to transformer model + */ + def getTransformer( + inferiorGrids: Seq[ActorRef[GridAgent.Request]], + subGridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], + gridComponents: GridComponents, + ): Map[ActorRef[GridAgent.Request], SystemComponent] = { + val transformer2wMap = + gridComponents.transformers + .map(transformer => transformer.uuid -> transformer) + .toMap + + val transformer3wMap = gridComponents.transformers3w + .map(transformer => transformer.uuid -> transformer) + .toMap + + subGridGateToActorRef + .flatMap { entry => + val ref = entry._2 + Option.when(inferiorGrids.contains(ref))(ref -> entry._1) + } + .map { case (ref, gate) => + val uuid = gate.link().getUuid + ref -> transformer2wMap.getOrElse(uuid, transformer3wMap(uuid)) + } + } + + /** Method to calculate the range of possible voltage changes. + * @param powerFlowResultEvent + * results from simulating the grid + * @param voltageLimits + * voltage limits + * @param gridComponents + * all components of the grid + * @param transformers + * map: inferior grid to connecting transformer + * @param inferiorRange + * map: inferior grid to voltage range + * @return + */ + def calculateVoltageOptions( + powerFlowResultEvent: PowerFlowResultEvent, + voltageLimits: VoltageLimits, + gridComponents: GridComponents, + transformers: Map[ActorRef[GridAgent.Request], SystemComponent], + inferiorRange: Map[ActorRef[GridAgent.Request], VoltageRange], + ): VoltageRange = { + // calculate voltage range + val nodeResMap = powerFlowResultEvent.nodeResults + .map(res => res.getInputModel -> res.getvMag()) + .toMap + val minVoltage = nodeResMap + .minByOption(_._2) + .getOrElse(throw new ResultException(s"No node result found!")) + val maxVoltage = nodeResMap + .maxByOption(_._2) + .getOrElse(throw new ResultException(s"No node result found!")) + + val range = VoltageRange( + voltageLimits.vMax.subtract(maxVoltage._2), + voltageLimits.vMin.subtract(minVoltage._2), + ) + + // updating the voltage range prevent or cure line congestions + val deltaV = calculatePossibleVoltageDeltaForLines( + nodeResMap, + powerFlowResultEvent.lineResults, + gridComponents, + ) + val updatedRange = range.updateWithLineDelta(deltaV) + + if (inferiorRange.isEmpty) { + // if there are no inferior grids, return the voltage range + updatedRange + } else { + // if there are inferior grids, update the voltage range + + val tappingModels = transformers.map { case (ref, component) => + component match { + case transformerModel: TransformerModel => + ref -> transformerModel.tappingModelCopy + case transformer3wModel: Transformer3wModel => + ref -> transformer3wModel.tappingModelCopy + case unsupported => + throw new IllegalArgumentException( + s"Unsupported value: $unsupported!" + ) + } + } + + updatedRange.updateWithInferiorRanges( + tappingModels, + inferiorRange, + ) + } + } + + /** Method to calculate a voltage delta for the given line currents.

- If + * there is a line congestion, increasing the voltage by the returned delta + * should mitigate them.

- If there is no line congestion, the returned + * voltage shows the possible voltage decrease.

- Formula: V * I = (V + + * deltaV) * (I + deltaI) + * + * @param nodeResults + * node voltages + * @param lineResults + * line currents + * @param gridComponents + * information of components + * @return + * a voltage delta + */ + def calculatePossibleVoltageDeltaForLines( + nodeResults: Map[UUID, ComparableQuantity[Dimensionless]], + lineResults: Iterable[LineResult], + gridComponents: GridComponents, + ): ComparableQuantity[Dimensionless] = { + val lineResMap = lineResults.map(res => res.getInputModel -> res).toMap + + val lineMap = gridComponents.lines.map(line => line.uuid -> line).toMap + + // calculates the + val lineUtilisation = lineResMap.map { case (uuid, res) => + val iNom = lineMap(uuid).iNom + val diffA = Amperes(res.getiAMag().getValue.doubleValue()) / iNom + val diffB = Amperes(res.getiBMag().getValue.doubleValue()) / iNom + + uuid -> Math.max(diffA, diffB) + } + + val maxUtilisation = lineUtilisation + .maxByOption(_._2) + .getOrElse(throw new ResultException(s"No line result found!")) + ._1 + + val line = lineMap(maxUtilisation) + val res = lineResMap(maxUtilisation) + val resA = res.getiAMag() + val resB = res.getiBMag() + + val deltaV = if (resA.isGreaterThan(resB)) { + val nodeRes = nodeResults(line.nodeAUuid).getValue.doubleValue() + val current = resA.getValue.doubleValue() + val deltaI = line.iNom.value - current + (nodeRes * deltaI) / (current + deltaI) * -1 + } else { + val nodeRes = nodeResults(line.nodeBUuid).getValue.doubleValue() + val current = resB.getValue.doubleValue() + val deltaI = line.iNom.value - current + (nodeRes * current) / (current + deltaI) * -1 + } + + // deltaV < 0 => tapping down possible + // deltaV > 0 => tapping up is necessary + deltaV.asPu + } + +} + +object CongestionManagementSupport { + + case class VoltageRange( + deltaPlus: ComparableQuantity[Dimensionless], + deltaMinus: ComparableQuantity[Dimensionless], + suggestion: ComparableQuantity[Dimensionless], + ) { + + /** Method to update this voltage range with line voltage delta. + * @param deltaV + * to consider + * @return + * a new [[VoltageRange]] + */ + def updateWithLineDelta( + deltaV: ComparableQuantity[Dimensionless] + ): VoltageRange = { + + val (plus, minus) = ( + deltaV.isGreaterThan(deltaPlus), + deltaV.isGreaterThan(deltaMinus), + ) match { + case (true, true) => + (deltaPlus, deltaPlus) + case (false, true) => + (deltaPlus, deltaV) + case (true, false) => + (deltaPlus, deltaPlus) + case (false, false) => + (deltaPlus, deltaMinus) + } + + VoltageRange(plus, minus) + + } + + /** Method to update this voltage range with inferior voltage ranges + * @param tappingModels + * map: inferior grid to [[TransformerTappingModel]] + * @param inferiorRange + * map: inferior grid to [[VoltageRange]] + * @return + * a new [[VoltageRange]] + */ + def updateWithInferiorRanges( + tappingModels: Map[ActorRef[ + GridAgent.Request + ], TransformerTappingModel], + inferiorRange: Map[ActorRef[GridAgent.Request], VoltageRange], + ): VoltageRange = { + + inferiorRange.foldLeft(this) { case (range, (ref, infRange)) => + // getting the tapping model + val tappingModel: TransformerTappingModel = tappingModels(ref) + + if (tappingModel.autoTap) { + val currentPos = tappingModel.currentTapPos + val deltaV = tappingModel.deltaV + val possiblePlus = deltaV.multiply(tappingModel.tapMax - currentPos) + val possibleMinus = deltaV.multiply(tappingModel.tapMin - currentPos) + + ( + range.deltaPlus + .add(possibleMinus) + .isGreaterThanOrEqualTo(infRange.deltaPlus), + range.deltaMinus + .subtract(possiblePlus) + .isLessThanOrEqualTo(infRange.deltaMinus), + ) match { + case (true, true) => + range + case (true, false) => + range.copy(deltaMinus = infRange.deltaMinus.add(possiblePlus)) + case (false, true) => + range.copy(deltaPlus = infRange.deltaPlus.subtract(possibleMinus)) + case (false, false) => + infRange + } + } else { + // no tapping possible, just update the range + + ( + range.deltaPlus.isGreaterThanOrEqualTo(infRange.deltaPlus), + range.deltaMinus.isLessThanOrEqualTo(infRange.deltaMinus), + ) match { + case (true, true) => + range + case (true, false) => + range.copy(deltaMinus = infRange.deltaMinus) + case (false, true) => + range.copy(deltaPlus = infRange.deltaPlus) + case (false, false) => + infRange + } + } + } + } + } + + object VoltageRange { + def apply( + deltaPlus: ComparableQuantity[Dimensionless], + deltaMinus: ComparableQuantity[Dimensionless], + ): VoltageRange = { + + val suggestion = ( + deltaPlus.isGreaterThanOrEqualTo(0.asPu), + deltaMinus.isLessThanOrEqualTo(0.asPu), + ) match { + case (true, true) => + // calculate ann equal distance to 1 pu + deltaPlus.add(deltaMinus) + case (false, true) => + // violation of the upper voltage limit + if (deltaPlus.isGreaterThan(deltaMinus)) { + // if deltaPlus > deltaMinus, we can decrease the voltage by deltaPlus + deltaPlus + } else deltaMinus + case (true, false) => + // violation of the upper voltage limit + + if (deltaMinus.isLessThan(deltaPlus)) { + // if deltaMinus < deltaPlus, we can increase the voltage by deltaMinus + deltaMinus + } else deltaPlus + case (false, false) => + // violation of both limit + deltaPlus + } + + VoltageRange( + deltaPlus, + deltaMinus, + suggestion, + ) + } + } + + case class Congestions( + voltageCongestions: Boolean, + lineCongestions: Boolean, + transformerCongestions: Boolean, + ) { + + def any: Boolean = + voltageCongestions || lineCongestions || transformerCongestions + + def assetCongestion: Boolean = lineCongestions || transformerCongestions + + def combine(options: Iterable[Congestions]): Congestions = + Congestions( + voltageCongestions || options.exists(_.voltageCongestions), + lineCongestions || options.exists(_.lineCongestions), + transformerCongestions || options.exists(_.transformerCongestions), + ) + } + + object CongestionManagementSteps extends Enumeration { + val TransformerTapping, TopologyChanges, UsingFlexibilities = Value + } + +} diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 58a6db23e4..7713a9c0da 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -7,8 +7,8 @@ package edu.ie3.simona.agent.grid import edu.ie3.simona.agent.grid.GridAgent.pipeToSelf -import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.CongestionManagementSteps._ -import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions +import edu.ie3.simona.agent.grid.CongestionManagementSupport.CongestionManagementSteps._ +import edu.ie3.simona.agent.grid.CongestionManagementSupport.Congestions import edu.ie3.simona.agent.grid.GridAgentData.{ AwaitingData, CongestionManagementData, @@ -32,7 +32,7 @@ import scala.concurrent.{ExecutionContext, Future} * congestion management (DCM) algorithm execution. It is considered to be the * standard behaviour of a [[GridAgent]]. */ -trait DCMAlgorithm { +trait DCMAlgorithm extends CongestionManagementSupport { /** Method that defines the [[Behavior]] for checking if there are any * congestion in the grid. 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 adde6c440c..d169a2d511 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -11,7 +11,7 @@ import edu.ie3.datamodel.models.input.container.{SubGridContainer, ThermalGrid} import edu.ie3.powerflow.model.PowerFlowResult import edu.ie3.powerflow.model.PowerFlowResult.SuccessFullPowerFlowResult.ValidNewtonRaphsonPFResult import edu.ie3.simona.agent.EnvironmentRefs -import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions +import edu.ie3.simona.agent.grid.CongestionManagementSupport.Congestions import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.agent.grid.ReceivedValuesStore.NodeToReceivedPower import edu.ie3.simona.agent.participant.ParticipantAgent.ParticipantMessage @@ -610,7 +610,7 @@ object GridAgentData { // checking for voltage congestions val voltageCongestion = powerFlowResults.nodeResults.exists { res => - !voltageLimits.isInLimits(res.getvMag().getValue.doubleValue()) + !voltageLimits.isInLimits(res.getvMag()) } // checking for line congestions @@ -655,28 +655,5 @@ object GridAgentData { transformer2wCongestion || transformer3wCongestion, ) } - - case class Congestions( - voltageCongestions: Boolean, - lineCongestions: Boolean, - transformerCongestions: Boolean, - ) { - - def any: Boolean = - voltageCongestions || lineCongestions || transformerCongestions - - def assetCongestion: Boolean = lineCongestions || transformerCongestions - - def combine(options: Iterable[Congestions]): Congestions = - Congestions( - voltageCongestions || options.exists(_.voltageCongestions), - lineCongestions || options.exists(_.lineCongestions), - transformerCongestions || options.exists(_.transformerCongestions), - ) - } - - object CongestionManagementSteps extends Enumeration { - val TransformerTapping, TopologyChanges, UsingFlexibilities = Value - } } } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala index 9a51fbd260..e85b8147a1 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala @@ -6,7 +6,7 @@ package edu.ie3.simona.agent.grid -import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.{ +import edu.ie3.simona.agent.grid.CongestionManagementSupport.{ CongestionManagementSteps, Congestions, } diff --git a/src/main/scala/edu/ie3/simona/exceptions/ResultException.scala b/src/main/scala/edu/ie3/simona/exceptions/ResultException.scala new file mode 100644 index 0000000000..472025bee1 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/exceptions/ResultException.scala @@ -0,0 +1,9 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.exceptions + +class ResultException(message: String) extends Exception(message) {} diff --git a/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala b/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala index d7f42c933e..1e383abbd7 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala @@ -166,6 +166,10 @@ final case class Transformer3wModel( ) } } + + /** Returns a copy of the [[TransformerTappingModel]] + */ + def tappingModelCopy: TransformerTappingModel = transformerTappingModel.copy() } case object Transformer3wModel extends LazyLogging { diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerModel.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerModel.scala index 7a63cbf489..36da5949c8 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerModel.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerModel.scala @@ -87,6 +87,10 @@ final case class TransformerModel( with TransformerTapping { private val tapSide = transformerTappingModel.tapSide + + /** Returns a copy of the [[TransformerTappingModel]] + */ + def tappingModelCopy: TransformerTappingModel = transformerTappingModel.copy() } case object TransformerModel { diff --git a/src/main/scala/edu/ie3/simona/model/grid/VoltageLimits.scala b/src/main/scala/edu/ie3/simona/model/grid/VoltageLimits.scala index 5dd92e0acf..dc90679f46 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/VoltageLimits.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/VoltageLimits.scala @@ -6,10 +6,22 @@ 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 VoltageLimits( - vMin: Double, - vMax: Double, + vMin: ComparableQuantity[Dimensionless], + vMax: ComparableQuantity[Dimensionless], ) { - def isInLimits(voltage: Double): Boolean = - vMin <= voltage && voltage <= vMax + 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/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala new file mode 100644 index 0000000000..6797d6d6f4 --- /dev/null +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -0,0 +1,304 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.agent.grid + +import edu.ie3.datamodel.graph.SubGridGate +import edu.ie3.datamodel.models.input.connector.ConnectorPort +import edu.ie3.datamodel.models.result.NodeResult +import edu.ie3.datamodel.models.result.connector.LineResult +import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange +import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent +import edu.ie3.simona.model.grid.GridModel.GridComponents +import edu.ie3.simona.model.grid.VoltageLimits +import edu.ie3.simona.test.common.UnitSpec +import edu.ie3.simona.test.common.model.grid.{ + GridComponentsMokka, + SubGridGateMokka, +} +import edu.ie3.simona.test.common.result.ResultMokka +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import org.apache.pekko.actor.testkit.typed.scaladsl.{ + ScalaTestWithActorTestKit, + TestProbe, +} +import org.apache.pekko.actor.typed.ActorRef + +import java.util.UUID + +class CongestionManagementSupportSpec + extends ScalaTestWithActorTestKit + with UnitSpec + with GridComponentsMokka + with ResultMokka + with SubGridGateMokka + with CongestionManagementSupport { + + val voltageTolerance = 1e-3 + + val inferior1: TestProbe[GridAgent.Request] = + TestProbe[GridAgent.Request]("inferior1") + val inferior2: TestProbe[GridAgent.Request] = + TestProbe[GridAgent.Request]("inferior2") + + "CongestionManagementSupport" should { + + val inferiorGrids: Seq[ActorRef[GridAgent.Request]] = + Seq(inferior1.ref, inferior2.ref) + + "map all transformers to inferior grids" in { + val nodeHv = mockNode(UUID.randomUUID(), 100) + val node30kV = mockNode(UUID.randomUUID(), 30) + val node20kV = mockNode(UUID.randomUUID(), 20) + val node10kV = mockNode(UUID.randomUUID(), 10) + + val transformer2w = mockTransformer2w(UUID.randomUUID(), nodeHv, node30kV) + val transformer3w = + mockTransformer3w(UUID.randomUUID(), nodeHv, 100, node20kV, node10kV) + + val transformer1 = mockTransformerModel(transformer2w.getUuid) + val transformer2 = mockTransformer3wModel(transformer3w.getUuid) + + val gridGates = Map( + SubGridGate.fromTransformer2W(transformer2w) -> inferior1.ref, + SubGridGate.fromTransformer3W( + transformer3w, + ConnectorPort.B, + ) -> inferior2.ref, + ) + + val gridComponents = GridComponents( + Seq.empty, + Set.empty, + Set(transformer1, mockTransformerModel(UUID.randomUUID())), + Set(transformer2), + Set.empty, + ) + + val map = getTransformer( + inferiorGrids, + gridGates, + gridComponents, + ) + + map shouldBe Map( + inferior1.ref -> transformer1, + inferior2.ref -> transformer2, + ) + } + + "calculates the possible voltage delta for lines correctly" in { + val node1 = nodeModel() + val node2 = nodeModel() + val node3 = nodeModel() + + val line12 = lineModel(node1.uuid, node2.uuid) + val line13 = lineModel(node1.uuid, node3.uuid) + + val gridComponents = GridComponents( + Seq(node1, node2, node3), + Set(line12, line13), + Set.empty, + Set.empty, + Set.empty, + ) + + val cases = Table( + ("results", "deltaV"), + ( + buildPowerFlowResultEvent( + Set( + mockNodeResult(node1.uuid, 0.93.asPu), + mockNodeResult(node2.uuid, 0.95.asPu), + mockNodeResult(node3.uuid, 0.95.asPu), + ), + Set( + mockLineResult(line12.uuid, 5.asAmpere, 5.asAmpere), + mockLineResult(line13.uuid, 11.asAmpere, 10.9.asAmpere), + ), + ), + 0.093.asPu, + ), + ( + buildPowerFlowResultEvent( + Set( + mockNodeResult(node1.uuid, 0.93.asPu), + mockNodeResult(node2.uuid, 0.95.asPu), + mockNodeResult(node3.uuid, 0.95.asPu), + ), + Set( + mockLineResult(line12.uuid, 9.3.asAmpere, 9.2.asAmpere), + mockLineResult(line13.uuid, 8.asAmpere, 8.asAmpere), + ), + ), + (-0.0651).asPu, + ), + ) + + forAll(cases) { (results, deltaV) => + val nodeResults = results.nodeResults + .map(res => res.getInputModel -> res.getvMag()) + .toMap + + calculatePossibleVoltageDeltaForLines( + nodeResults, + results.lineResults, + gridComponents, + ) should equalWithTolerance(deltaV, 1e-3) + } + } + + "calculate the voltage range for a lowest grid correctly" in { + val node1 = nodeModel() + val node2 = nodeModel() + val node3 = nodeModel() + val node4 = nodeModel() + + val line12 = lineModel(node1.uuid, node2.uuid) + val line13 = lineModel(node1.uuid, node3.uuid) + val line34 = lineModel(node3.uuid, node4.uuid) + + val gridComponents = GridComponents( + Seq(node1, node2, node3, node4), + Set(line12, line13, line34), + Set.empty, + Set.empty, + Set.empty, + ) + + val powerFlowResult = buildPowerFlowResultEvent( + Set( + mockNodeResult(node1.uuid, 0.93.asPu), + mockNodeResult(node2.uuid, 0.95.asPu), + mockNodeResult(node3.uuid, 1.05.asPu), + mockNodeResult(node4.uuid, 0.97.asPu), + ), + Set( + mockLineResult(line12.uuid, 5.asAmpere, 5.asAmpere), + mockLineResult(line13.uuid, 8.asAmpere, 8.asAmpere), + mockLineResult(line34.uuid, 7.asAmpere, 7.asAmpere), + ), + ) + + val range = calculateVoltageOptions( + powerFlowResult, + VoltageLimits(0.9, 1.1), + gridComponents, + Map.empty, + Map.empty, + ) + + range.deltaPlus should equalWithTolerance(0.05.asPu, voltageTolerance) + range.deltaMinus should equalWithTolerance((-0.03).asPu, voltageTolerance) + range.suggestion should equalWithTolerance(0.02.asPu, voltageTolerance) + } + + "calculates the voltage range for a middle grid correctly" in {} + + def buildPowerFlowResultEvent( + nodeResults: Set[NodeResult], + lineResults: Set[LineResult], + ): PowerFlowResultEvent = { + PowerFlowResultEvent( + nodeResults, + Set.empty, + lineResults, + Set.empty, + Set.empty, + ) + } + + } + + "A VoltageRange" should { + + "calculate the suggestion correctly" in { + val cases = Table( + ("deltaPlus", "deltaMinus", "expected"), + (0.05.asPu, (-0.05).asPu, 0.asPu), // no voltage limit violation + ( + (-0.01).asPu, + (-0.02).asPu, + (-0.01).asPu, + ), // upper voltage limit violation, decreasing voltage + ( + 0.02.asPu, + 0.01.asPu, + 0.01.asPu, + ), // lower voltage limit violation, increasing voltage + ( + (-0.01).asPu, + 0.01.asPu, + (-0.01).asPu, + ), // violation of both voltage limits, decreasing voltage + ) + + forAll(cases) { (deltaPlus, deltaMinus, expected) => + VoltageRange( + deltaPlus, + deltaMinus, + ).suggestion should equalWithTolerance(expected) + } + + } + + "be updated with a line voltage delta correctly" in { + val range1 = VoltageRange(0.05.asPu, (-0.05).asPu) + val cases1 = Table( + ("deltaV", "plus", "minus"), + (0.01.asPu, 0.05.asPu, 0.01.asPu), + (0.06.asPu, 0.05.asPu, 0.05.asPu), + ((-0.01).asPu, 0.05.asPu, (-0.01).asPu), + ((-0.04).asPu, 0.05.asPu, (-0.04).asPu), + ((-0.06).asPu, 0.05.asPu, (-0.05).asPu), + ) + + forAll(cases1) { (deltaV, plus, minus) => + val updated = range1.updateWithLineDelta(deltaV) + updated.deltaPlus should equalWithTolerance(plus) + updated.deltaMinus should equalWithTolerance(minus) + } + + val range2 = VoltageRange((-0.01).asPu, (-0.05).asPu) + val cases2 = Table( + ("deltaV", "plus", "minus"), + (0.01.asPu, (-0.01).asPu, (-0.01).asPu), + (0.06.asPu, (-0.01).asPu, (-0.01).asPu), + ((-0.01).asPu, (-0.01).asPu, (-0.01).asPu), + ((-0.04).asPu, (-0.01).asPu, (-0.04).asPu), + ((-0.06).asPu, (-0.01).asPu, (-0.05).asPu), + ) + + forAll(cases2) { (deltaV, plus, minus) => + val updated = range2.updateWithLineDelta(deltaV) + updated.deltaPlus should equalWithTolerance(plus) + updated.deltaMinus should equalWithTolerance(minus) + } + + val range3 = VoltageRange(0.05.asPu, 0.01.asPu) + val cases3 = Table( + ("deltaV", "plus", "minus"), + (0.01.asPu, 0.05.asPu, 0.01.asPu), + (0.06.asPu, 0.05.asPu, 0.05.asPu), + ((-0.01).asPu, 0.05.asPu, 0.01.asPu), + ((-0.04).asPu, 0.05.asPu, 0.01.asPu), + ((-0.06).asPu, 0.05.asPu, 0.01.asPu), + ) + + forAll(cases3) { (deltaV, plus, minus) => + val updated = range3.updateWithLineDelta(deltaV) + updated.deltaPlus should equalWithTolerance(plus) + updated.deltaMinus should equalWithTolerance(minus) + } + + } + + "with inferior voltage ranges" in { + val range = VoltageRange(0.05.asPu, (-0.05).asPu) + + } + } +} diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index 647f40dcb8..86b63e79cb 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -9,8 +9,8 @@ package edu.ie3.simona.agent.grid import com.typesafe.config.ConfigFactory import edu.ie3.datamodel.models.input.container.ThermalGrid import edu.ie3.simona.agent.EnvironmentRefs -import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.CongestionManagementSteps.TransformerTapping -import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions +import edu.ie3.simona.agent.grid.CongestionManagementSupport.CongestionManagementSteps.TransformerTapping +import edu.ie3.simona.agent.grid.CongestionManagementSupport.Congestions import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData import edu.ie3.simona.agent.grid.GridAgentMessages.{ CongestionCheckRequest, diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala index b46b316307..88518402d8 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala @@ -10,7 +10,7 @@ import com.typesafe.config.ConfigFactory import edu.ie3.datamodel.graph.SubGridGate import edu.ie3.datamodel.models.input.container.ThermalGrid import edu.ie3.simona.agent.EnvironmentRefs -import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions +import edu.ie3.simona.agent.grid.CongestionManagementSupport.Congestions import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.config.SimonaConfig diff --git a/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala index bd65466efb..36e8655a24 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala @@ -13,7 +13,7 @@ import edu.ie3.datamodel.models.result.connector.{ Transformer2WResult, } import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData -import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData.Congestions +import edu.ie3.simona.agent.grid.CongestionManagementSupport.Congestions import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.model.grid.{GridModel, RefSystem, VoltageLimits} import edu.ie3.simona.test.common.model.grid.DbfsTestGrid diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala new file mode 100644 index 0000000000..6e2f8e0905 --- /dev/null +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.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.test.common.model.grid + +import edu.ie3.simona.model.grid.{ + LineModel, + NodeModel, + Transformer3wModel, + TransformerModel, +} +import org.mockito.Mockito.when +import org.scalatestplus.mockito.MockitoSugar +import squants.Amperes + +import java.util.UUID + +/** Hold my cup of coffee and let me mock you some models. + */ +trait GridComponentsMokka extends MockitoSugar { + + protected def nodeModel( + uuid: UUID = UUID.randomUUID() + ): NodeModel = { + val node = mock[NodeModel] + when(node.uuid).thenReturn(uuid) + node + } + + protected def lineModel( + nodeA: UUID, + nodeB: UUID, + iNom: Double = 10.0, + uuid: UUID = UUID.randomUUID(), + ): LineModel = { + val line = mock[LineModel] + when(line.uuid).thenReturn(uuid) + when(line.nodeAUuid).thenReturn(nodeA) + when(line.nodeBUuid).thenReturn(nodeB) + when(line.iNom).thenReturn(Amperes(iNom)) + + line + } + + protected def mockTransformerModel(uuid: UUID): TransformerModel = { + val transformer = mock[TransformerModel] + when(transformer.uuid).thenReturn(uuid) + transformer + } + + protected def mockTransformer3wModel( + uuid: UUID + ): Transformer3wModel = { + val transformer = mock[Transformer3wModel] + when(transformer.uuid).thenReturn(uuid) + + transformer + } + +} diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/SubGridGateMokka.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/SubGridGateMokka.scala index 6cf1b682d4..16337af6bd 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/SubGridGateMokka.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/SubGridGateMokka.scala @@ -9,13 +9,22 @@ package edu.ie3.simona.test.common.model.grid import java.util.UUID import edu.ie3.datamodel.graph.SubGridGate import edu.ie3.datamodel.models.input.NodeInput +import edu.ie3.datamodel.models.input.connector.`type`.{ + Transformer2WTypeInput, + Transformer3WTypeInput, +} import edu.ie3.datamodel.models.input.connector.{ ConnectorPort, Transformer2WInput, Transformer3WInput, } +import edu.ie3.simona.model.grid.{Transformer3wModel, TransformerModel} +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import org.mockito.Mockito._ import org.scalatestplus.mockito.MockitoSugar +import tech.units.indriya.ComparableQuantity + +import javax.measure.quantity.{ElectricPotential, Power} /** Hold my cup of coffee and let me mock you some models. */ @@ -57,6 +66,18 @@ trait SubGridGateMokka extends MockitoSugar { transformer } + protected def mockTransformer2w( + uuid: UUID, + nodeA: NodeInput, + nodeB: NodeInput, + ): Transformer2WInput = { + val transformer = mock[Transformer2WInput] + when(transformer.getNodeA).thenReturn(nodeA) + when(transformer.getNodeB).thenReturn(nodeB) + when(transformer.getUuid).thenReturn(uuid) + transformer + } + /** Mocks a transformer, that only holds information on what nodes are * connected * @@ -89,6 +110,26 @@ trait SubGridGateMokka extends MockitoSugar { transformer } + protected def mockTransformer3w( + uuid: UUID, + nodeA: NodeInput, + nodeASubnet: Int, + nodeB: NodeInput, + nodeC: NodeInput, + ): Transformer3WInput = { + val internalNode = mock[NodeInput] + when(internalNode.getUuid).thenReturn(UUID.randomUUID()) + when(internalNode.getSubnet).thenReturn(nodeASubnet) + + val transformer = mock[Transformer3WInput] + when(transformer.getNodeA).thenReturn(nodeA) + when(transformer.getNodeB).thenReturn(nodeB) + when(transformer.getNodeC).thenReturn(nodeC) + when(transformer.getNodeInternal).thenReturn(internalNode) + when(transformer.getUuid).thenReturn(uuid) + transformer + } + /** Builds a sub grid gate by mocking the underlying nodes and transformer * * @param nodeAUuid diff --git a/src/test/scala/edu/ie3/simona/test/common/result/ResultMokka.scala b/src/test/scala/edu/ie3/simona/test/common/result/ResultMokka.scala new file mode 100644 index 0000000000..6ae635f080 --- /dev/null +++ b/src/test/scala/edu/ie3/simona/test/common/result/ResultMokka.scala @@ -0,0 +1,42 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.test.common.result + +import edu.ie3.datamodel.models.result.NodeResult +import edu.ie3.datamodel.models.result.connector.LineResult +import org.mockito.Mockito.when +import org.scalatestplus.mockito.MockitoSugar +import tech.units.indriya.ComparableQuantity + +import java.util.UUID +import javax.measure.quantity.{Dimensionless, ElectricCurrent} + +trait ResultMokka extends MockitoSugar { + + protected def mockNodeResult( + uuid: UUID, + vMag: ComparableQuantity[Dimensionless], + ): NodeResult = { + val result = mock[NodeResult] + when(result.getInputModel).thenReturn(uuid) + when(result.getvMag()).thenReturn(vMag) + + result + } + + protected def mockLineResult( + uuid: UUID, + iAMag: ComparableQuantity[ElectricCurrent], + iBMag: ComparableQuantity[ElectricCurrent], + ): LineResult = { + val result = mock[LineResult] + when(result.getInputModel).thenReturn(uuid) + when(result.getiAMag()).thenReturn(iAMag) + when(result.getiBMag()).thenReturn(iBMag) + result + } +} From f24d81bee4bbf80d7f8169d1a45e3facd618eb6d Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 23 Apr 2024 18:58:43 +0200 Subject: [PATCH 11/55] Improving `CongestionManagementSupport`. --- .../grid/CongestionManagementSupport.scala | 43 +++--- .../CongestionManagementSupportSpec.scala | 144 +++++++++++++++++- 2 files changed, 155 insertions(+), 32 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 898a720db0..193bc41803 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -14,7 +14,6 @@ import edu.ie3.simona.exceptions.ResultException import edu.ie3.simona.model.SystemComponent import edu.ie3.simona.model.grid.GridModel.GridComponents import edu.ie3.simona.model.grid.{ - Transformer3wModel, TransformerModel, TransformerTappingModel, VoltageLimits, @@ -75,8 +74,9 @@ trait CongestionManagementSupport { * voltage limits * @param gridComponents * all components of the grid - * @param transformers - * map: inferior grid to connecting transformer + * @param transformerTapping + * map: inferior grid to [[TransformerTappingModel]] as the used + * transformer * @param inferiorRange * map: inferior grid to voltage range * @return @@ -85,7 +85,9 @@ trait CongestionManagementSupport { powerFlowResultEvent: PowerFlowResultEvent, voltageLimits: VoltageLimits, gridComponents: GridComponents, - transformers: Map[ActorRef[GridAgent.Request], SystemComponent], + transformerTapping: Map[ActorRef[ + GridAgent.Request + ], TransformerTappingModel], inferiorRange: Map[ActorRef[GridAgent.Request], VoltageRange], ): VoltageRange = { // calculate voltage range @@ -118,21 +120,8 @@ trait CongestionManagementSupport { } else { // if there are inferior grids, update the voltage range - val tappingModels = transformers.map { case (ref, component) => - component match { - case transformerModel: TransformerModel => - ref -> transformerModel.tappingModelCopy - case transformer3wModel: Transformer3wModel => - ref -> transformer3wModel.tappingModelCopy - case unsupported => - throw new IllegalArgumentException( - s"Unsupported value: $unsupported!" - ) - } - } - updatedRange.updateWithInferiorRanges( - tappingModels, + transformerTapping, inferiorRange, ) } @@ -264,15 +253,17 @@ object CongestionManagementSupport { ( range.deltaPlus .add(possibleMinus) - .isGreaterThanOrEqualTo(infRange.deltaPlus), + .isLessThanOrEqualTo(infRange.deltaPlus), range.deltaMinus - .subtract(possiblePlus) - .isLessThanOrEqualTo(infRange.deltaMinus), + .add(possiblePlus) + .isGreaterThanOrEqualTo(infRange.deltaMinus), ) match { case (true, true) => range case (true, false) => - range.copy(deltaMinus = infRange.deltaMinus.add(possiblePlus)) + range.copy(deltaMinus = + infRange.deltaMinus.subtract(possiblePlus) + ) case (false, true) => range.copy(deltaPlus = infRange.deltaPlus.subtract(possibleMinus)) case (false, false) => @@ -286,13 +277,13 @@ object CongestionManagementSupport { range.deltaMinus.isLessThanOrEqualTo(infRange.deltaMinus), ) match { case (true, true) => - range + infRange case (true, false) => - range.copy(deltaMinus = infRange.deltaMinus) - case (false, true) => range.copy(deltaPlus = infRange.deltaPlus) + case (false, true) => + range.copy(deltaMinus = infRange.deltaMinus) case (false, false) => - infRange + range } } } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index 6797d6d6f4..0c810e75bf 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -13,7 +13,7 @@ import edu.ie3.datamodel.models.result.connector.LineResult import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.model.grid.GridModel.GridComponents -import edu.ie3.simona.model.grid.VoltageLimits +import edu.ie3.simona.model.grid.{TransformerTappingModel, VoltageLimits} import edu.ie3.simona.test.common.UnitSpec import edu.ie3.simona.test.common.model.grid.{ GridComponentsMokka, @@ -191,12 +191,63 @@ class CongestionManagementSupportSpec Map.empty, ) - range.deltaPlus should equalWithTolerance(0.05.asPu, voltageTolerance) - range.deltaMinus should equalWithTolerance((-0.03).asPu, voltageTolerance) - range.suggestion should equalWithTolerance(0.02.asPu, voltageTolerance) + range.deltaPlus should equalWithTolerance(0.05.asPu) + range.deltaMinus should equalWithTolerance((-0.03).asPu) + range.suggestion should equalWithTolerance(0.02.asPu) } - "calculates the voltage range for a middle grid correctly" in {} + "calculates the voltage range for a middle grid correctly" in { + val node1 = nodeModel() + val node2 = nodeModel() + val node3 = nodeModel() + val node4 = nodeModel() + + val line12 = lineModel(node1.uuid, node2.uuid) + val line13 = lineModel(node1.uuid, node3.uuid) + val line34 = lineModel(node3.uuid, node4.uuid) + + val gridComponents = GridComponents( + Seq(node1, node2, node3, node4), + Set(line12, line13, line34), + Set.empty, + Set.empty, + Set.empty, + ) + + val tappingModel = + TransformerTappingModel(0.01.asPu, 0, 3, -3, 0, autoTap = true) + val tappingMap = + Map(inferior1.ref -> tappingModel, inferior2.ref -> tappingModel) + + val powerFlowResult = buildPowerFlowResultEvent( + Set( + mockNodeResult(node1.uuid, 0.93.asPu), + mockNodeResult(node2.uuid, 0.95.asPu), + mockNodeResult(node3.uuid, 1.05.asPu), + mockNodeResult(node4.uuid, 0.97.asPu), + ), + Set( + mockLineResult(line12.uuid, 5.asAmpere, 5.asAmpere), + mockLineResult(line13.uuid, 8.asAmpere, 8.asAmpere), + mockLineResult(line34.uuid, 7.asAmpere, 7.asAmpere), + ), + ) + + val range = calculateVoltageOptions( + powerFlowResult, + VoltageLimits(0.9, 1.1), + gridComponents, + tappingMap, + Map( + inferior1.ref -> VoltageRange(0.1.asPu, 0.01.asPu), + inferior2.ref -> VoltageRange(0.01.asPu, (-0.04).asPu), + ), + ) + + range.deltaPlus should equalWithTolerance(0.04.asPu) + range.deltaMinus should equalWithTolerance((-0.02).asPu) + range.suggestion should equalWithTolerance(0.02.asPu) + } def buildPowerFlowResultEvent( nodeResults: Set[NodeResult], @@ -296,9 +347,90 @@ class CongestionManagementSupportSpec } - "with inferior voltage ranges" in { + "be updated with inferior voltage ranges and without tapping correctly" in { + val range = VoltageRange(0.05.asPu, (-0.05).asPu) + + val tappingModel = + TransformerTappingModel(0.15.asPu, 0, 10, -10, 0, autoTap = false) + val tappingMap = + Map(inferior1.ref -> tappingModel, inferior2.ref -> tappingModel) + + val cases = Table( + ("range1", "range2", "expected"), + ( + VoltageRange(0.02.asPu, (-0.06).asPu), + VoltageRange(0.06.asPu, (-0.03).asPu), + VoltageRange(0.02.asPu, (-0.03).asPu), + ), + ( + VoltageRange(0.06.asPu, (-0.06).asPu), + VoltageRange(0.06.asPu, (-0.06).asPu), + VoltageRange(0.05.asPu, (-0.05).asPu), + ), + ( + VoltageRange(0.asPu, (-0.01).asPu), + VoltageRange(0.02.asPu, (-0.03).asPu), + VoltageRange(0.asPu, (-0.01).asPu), + ), + ( + VoltageRange(0.02.asPu, 0.01.asPu), + VoltageRange(0.04.asPu, (-0.01).asPu), + VoltageRange(0.02.asPu, 0.01.asPu), + ), + ) + + forAll(cases) { (range1, range2, expected) => + val updatedRange = range.updateWithInferiorRanges( + tappingMap, + Map(inferior1.ref -> range1, inferior2.ref -> range2), + ) + + updatedRange.deltaPlus should equalWithTolerance(expected.deltaPlus) + updatedRange.deltaMinus should equalWithTolerance(expected.deltaMinus) + } + } + + "be updated with inferior voltage ranges and with tapping correctly" in { val range = VoltageRange(0.05.asPu, (-0.05).asPu) + val tappingModel = + TransformerTappingModel(0.01.asPu, 7, 10, -10, 0, autoTap = true) + val tappingMap = + Map(inferior1.ref -> tappingModel, inferior2.ref -> tappingModel) + + val cases = Table( + ("range1", "range2", "expected"), + ( + VoltageRange(0.02.asPu, (-0.06).asPu), + VoltageRange(0.06.asPu, (-0.03).asPu), + VoltageRange(0.05.asPu, (-0.05).asPu), + ), + ( + VoltageRange(0.06.asPu, (-0.06).asPu), + VoltageRange(0.06.asPu, (-0.06).asPu), + VoltageRange(0.05.asPu, (-0.05).asPu), + ), + ( + VoltageRange(0.asPu, (-0.01).asPu), + VoltageRange(0.02.asPu, (-0.03).asPu), + VoltageRange(0.05.asPu, (-0.04).asPu), + ), + ( + VoltageRange(0.02.asPu, 0.01.asPu), + VoltageRange(0.04.asPu, (-0.01).asPu), + VoltageRange(0.05.asPu, (-0.02).asPu), + ), + ) + + forAll(cases) { (range1, range2, expected) => + val updatedRange = range.updateWithInferiorRanges( + tappingMap, + Map(inferior1.ref -> range1, inferior2.ref -> range2), + ) + + updatedRange.deltaPlus should equalWithTolerance(expected.deltaPlus) + updatedRange.deltaMinus should equalWithTolerance(expected.deltaMinus) + } } } } From aac25ff0f3c1d23400bb0b372e98b9e7ff11bdc8 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 25 Apr 2024 14:36:00 +0200 Subject: [PATCH 12/55] Adding some function necessary for transformer tapping. --- .../grid/CongestionManagementSupport.scala | 83 ++++++- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 216 +++++++++++++++--- .../ie3/simona/agent/grid/GridAgentData.scala | 5 + .../simona/agent/grid/GridAgentMessages.scala | 51 ++++- .../model/grid/TransformerTapping.scala | 4 + .../CongestionManagementSupportSpec.scala | 8 +- .../agent/grid/DCMAlgorithmCenGridSpec.scala | 2 +- 7 files changed, 317 insertions(+), 52 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 193bc41803..c82d468de0 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -11,9 +11,9 @@ import edu.ie3.datamodel.models.result.connector.LineResult import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.exceptions.ResultException -import edu.ie3.simona.model.SystemComponent import edu.ie3.simona.model.grid.GridModel.GridComponents import edu.ie3.simona.model.grid.{ + Transformer3wModel, TransformerModel, TransformerTappingModel, VoltageLimits, @@ -31,6 +31,44 @@ import javax.measure.quantity.Dimensionless */ trait CongestionManagementSupport { + /** Method for retrieving all needed information for transformers. + * @param inferiorGrids + * sequence of inferior grids + * @param subGridGateToActorRef + * mapping of [[SubGridGate]]s to inferior grids + * @param gridComponents + * the [[GridComponents]] to consider + * @return + * transformer information + */ + def getTransformerInfos( + inferiorGrids: Seq[ActorRef[GridAgent.Request]], + subGridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], + gridComponents: GridComponents, + ): ( + Map[ActorRef[GridAgent.Request], TransformerModel], + Map[ActorRef[GridAgent.Request], Transformer3wModel], + Map[ActorRef[GridAgent.Request], TransformerTappingModel], + ) = { + val transformerMap = getTransformer( + inferiorGrids, + subGridGateToActorRef, + gridComponents, + ) + + val transformer3wMap = getTransformer3w( + inferiorGrids, + subGridGateToActorRef, + gridComponents, + ) + + val tappingModels = transformerMap.map(e => + e._1 -> e._2.tappingModelCopy + ) ++ transformer3wMap.map(e => e._1 -> e._2.tappingModelCopy) + + (transformerMap, transformer3wMap, tappingModels) + } + /** Method for mapping the [[TransformerModel]]s to the given inferior grid * refs. * @param inferiorGrids @@ -46,24 +84,53 @@ trait CongestionManagementSupport { inferiorGrids: Seq[ActorRef[GridAgent.Request]], subGridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], gridComponents: GridComponents, - ): Map[ActorRef[GridAgent.Request], SystemComponent] = { - val transformer2wMap = + ): Map[ActorRef[GridAgent.Request], TransformerModel] = { + val transformerMap = gridComponents.transformers .map(transformer => transformer.uuid -> transformer) .toMap - val transformer3wMap = gridComponents.transformers3w - .map(transformer => transformer.uuid -> transformer) - .toMap + subGridGateToActorRef + .flatMap { entry => + val ref = entry._2 + Option.when(inferiorGrids.contains(ref))(ref -> entry._1) + } + .flatMap { case (ref, gate) => + val uuid = gate.link().getUuid + transformerMap.get(uuid).map(value => ref -> value) + } + } + + /** Method for mapping the [[Transformer3wModel]]s to the given inferior grid + * refs. + * + * @param inferiorGrids + * set of [[ActorRef]]s + * @param subGridGateToActorRef + * map: [[SubGridGate]] to [[ActorRef]] + * @param gridComponents + * all components of the grid + * @return + * a map: [[ActorRef]] to transformer model + */ + def getTransformer3w( + inferiorGrids: Seq[ActorRef[GridAgent.Request]], + subGridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], + gridComponents: GridComponents, + ): Map[ActorRef[GridAgent.Request], Transformer3wModel] = { + val transformerMap = + gridComponents.transformers3w + .map(transformer => transformer.uuid -> transformer) + .toMap subGridGateToActorRef .flatMap { entry => val ref = entry._2 Option.when(inferiorGrids.contains(ref))(ref -> entry._1) } - .map { case (ref, gate) => + .flatMap { case (ref, gate) => val uuid = gate.link().getUuid - ref -> transformer2wMap.getOrElse(uuid, transformer3wMap(uuid)) + transformerMap.get(uuid).map(value => ref -> value) } } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 7713a9c0da..dd44bd946d 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -6,9 +6,12 @@ package edu.ie3.simona.agent.grid -import edu.ie3.simona.agent.grid.GridAgent.pipeToSelf import edu.ie3.simona.agent.grid.CongestionManagementSupport.CongestionManagementSteps._ -import edu.ie3.simona.agent.grid.CongestionManagementSupport.Congestions +import edu.ie3.simona.agent.grid.CongestionManagementSupport.{ + Congestions, + VoltageRange, +} +import edu.ie3.simona.agent.grid.GridAgent.pipeToSelf import edu.ie3.simona.agent.grid.GridAgentData.{ AwaitingData, CongestionManagementData, @@ -17,13 +20,14 @@ import edu.ie3.simona.agent.grid.GridAgentData.{ } import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.ontology.messages.Activation +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import org.apache.pekko.actor.typed.scaladsl.AskPattern.Askable import org.apache.pekko.actor.typed.scaladsl.{ ActorContext, Behaviors, StashBuffer, } -import org.apache.pekko.actor.typed.{Behavior, Scheduler} +import org.apache.pekko.actor.typed.{ActorRef, Behavior, Scheduler} import org.apache.pekko.util.Timeout import scala.concurrent.{ExecutionContext, Future} @@ -55,32 +59,18 @@ trait DCMAlgorithm extends CongestionManagementSupport { case (ctx, StartStep) => // request congestion check if we have inferior grids - if (stateData.inferiorRefs.nonEmpty) { - implicit val askTimeout: Timeout = Timeout.create( - stateData.gridAgentBaseData.congestionManagementParams.timeout - ) - implicit val ec: ExecutionContext = ctx.executionContext - implicit val scheduler: Scheduler = ctx.system.scheduler - - val future = Future - .sequence( - stateData.inferiorRefs.map { inferiorGridAgentRef => - inferiorGridAgentRef - .ask(ref => CongestionCheckRequest(ref)) - .map { case response: CongestionResponse => - (response.sender, response.congestions) - } - }.toVector - ) - .map(res => ReceivedCongestions(res)) - pipeToSelf(future, ctx) - } + askInferior( + stateData, + CongestionCheckRequest, + ReceivedCongestions, + ctx, + ) Behaviors.same case (ctx, congestionRequest @ CongestionCheckRequest(sender)) => // check if waiting for inferior data is needed - if (awaitingData.isDone) { + if (!awaitingData.isDone) { ctx.log.debug( s"Received request for congestions before all data from inferior grids were received. Stashing away." ) @@ -217,26 +207,132 @@ trait DCMAlgorithm extends CongestionManagementSupport { * @return * a [[Behavior]] */ - // TODO: Implement a proper behavior private[grid] def updateTransformerTapping( stateData: CongestionManagementData, - awaitingData: AwaitingData[_], + awaitingData: AwaitingData[VoltageRange], )(implicit constantData: GridAgentConstantData, buffer: StashBuffer[GridAgent.Request], ): Behavior[GridAgent.Request] = Behaviors.receivePartial { case (ctx, StartStep) => - if (stateData.gridAgentBaseData.isSuperior) { - // for now this step is skipped - ctx.log.warn( - s"Using transformer taping to resolve a congestion is not implemented yet. Skipping this step!" + // request congestion check if we have inferior grids + askInferior( + stateData, + RequestVoltageOptions, + ReceivedVoltageRange, + ctx, + ) + + Behaviors.same + + case (ctx, voltageRangeRequest @ RequestVoltageOptions(sender)) => + // check if waiting for inferior data is needed + if (!awaitingData.isDone) { + ctx.log.debug( + s"Received request for congestions before all data from inferior grids were received. Stashing away." ) - ctx.self ! FinishStep + // stash away the message, because we need to wait for data from inferior grids + buffer.stash(voltageRangeRequest) + } else { + // calculate the voltage range for this grid + val gridEnv = stateData.gridAgentBaseData.gridEnv + val gridModel = gridEnv.gridModel + val gridComponents = gridModel.gridComponents + + val (_, _, tappingModels) = getTransformerInfos( + stateData.inferiorGrids, + gridEnv.subgridGateToActorRef, + gridComponents, + ) + + val range = calculateVoltageOptions( + stateData.powerFlowResults, + gridModel.voltageLimits, + gridModel.gridComponents, + tappingModels, + awaitingData.mappedValues, + ) + + sender ! VoltageRangeResponse( + range, + ctx.self, + ) } Behaviors.same + case (ctx, ReceivedVoltageRange(voltageRange)) => + // updating the state data with received data from inferior grids + val updatedData = awaitingData.handleReceivingData(voltageRange) + + if (stateData.gridAgentBaseData.isSuperior) { + // there should be no voltage change in the superior grid, + // because the slack grid should always have 1 pu + + ctx.self ! VoltageDeltaResponse(0.asPu) + Behaviors.same + } else { + // un-stash all messages + buffer.unstashAll(updateTransformerTapping(stateData, updatedData)) + } + + case (ctx, VoltageDeltaResponse(delta)) => + // if we are the superior grid to another grid, we check for transformer tapping option + // and send the new delta to the inferior grid + + val inferiorRefs = stateData.inferiorRefs + + if (inferiorRefs.nonEmpty) { + // we calculate a voltage delta for all inferior grids + + val gridEnv = stateData.gridAgentBaseData.gridEnv + val gridModel = gridEnv.gridModel + val gridComponents = gridModel.gridComponents + + val (transformer2ws, transformer3ws, _) = + getTransformerInfos( + stateData.inferiorGrids, + gridEnv.subgridGateToActorRef, + gridComponents, + ) + + // update inferior grid connected by a two winding transformer + transformer2ws.foreach { case (ref, model) => + if (model.hasAutoTap) { + // the given transformer can be tapped, calculate the new tap pos + + // TODO: Add code + + } else { + // no tapping possible, just send the delta to the inferior grid + ref ! VoltageDeltaResponse(delta) + } + } + + // update inferior grid connected by a three winding transformer + val transformer3wMap = + gridComponents.transformers3w.map(t => t.uuid -> t).toMap + + transformer3ws.groupBy(_._2.uuid).foreach { case (uuid, refMap) => + val transformer = transformer3wMap(uuid) + val refs = refMap.keySet + + if (transformer.hasAutoTap) { + // the given transformer can be tapped, calculate the new tap pos + + // TODO: Add code + } else { + // no tapping possible, just send the delta to the inferior grid + refs.foreach(_ ! VoltageDeltaResponse(delta)) + } + } + } + + // all work is done in this grid, finish this step + ctx.self ! FinishStep + Behaviors.same + case (ctx, FinishStep) => // inform my inferior grids about the end of this step stateData.inferiorRefs.foreach(_ ! FinishStep) @@ -313,6 +409,64 @@ trait DCMAlgorithm extends CongestionManagementSupport { ) } + /** Method to ask all inferior grids a [[CMRequest]]. + * + * @param stateData + * current state data + * @param askMsgBuilder + * function to build the asked message + * @param resMsgBuilder + * function to build the returned message + * @param ctx + * actor context to use + * @tparam T + * type of data + */ + private def askInferior[T]( + stateData: CongestionManagementData, + askMsgBuilder: ActorRef[GridAgent.Request] => CMRequest, + resMsgBuilder: Vector[(ActorRef[GridAgent.Request], T)] => CMResponse[T], + ctx: ActorContext[GridAgent.Request], + ): Unit = { + + if (stateData.inferiorRefs.nonEmpty) { + implicit val askTimeout: Timeout = Timeout.create( + stateData.gridAgentBaseData.congestionManagementParams.timeout + ) + implicit val ec: ExecutionContext = ctx.executionContext + implicit val scheduler: Scheduler = ctx.system.scheduler + + val future = Future + .sequence( + stateData.inferiorRefs.map { inferiorGridAgentRef => + inferiorGridAgentRef + .ask(askMsgBuilder) + .map { case response: CMReceiveResponse[T] => + (response.sender, response.value) + } + }.toVector + ) + .map(resMsgBuilder) + pipeToSelf(future, ctx) + } + + } + + /** Method to clear all data and go to the [[DBFSAlgorithm.simulateGrid]]. + * + * @param gridAgentBaseData + * to clear + * @param currentTick + * to use + * @param ctx + * actor context + * @param constantData + * constant grid agent data + * @param buffer + * for buffered messages + * @return + * a new [[Behavior]] + */ private def clearAndGotoSimulateGrid( gridAgentBaseData: GridAgentBaseData, currentTick: Long, 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 d169a2d511..b4fd958870 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -71,6 +71,11 @@ object GridAgentData { def values: Iterable[T] = inferiorGridMap.values.flatten.toSeq + def mappedValues: Map[ActorRef[GridAgent.Request], T] = + inferiorGridMap.flatMap { case (ref, option) => + option.map(value => ref -> value) + } + /** Method for updating the data with the received data. * * @param receivedData diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala index e85b8147a1..1f113628bc 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala @@ -9,6 +9,7 @@ package edu.ie3.simona.agent.grid import edu.ie3.simona.agent.grid.CongestionManagementSupport.{ CongestionManagementSteps, Congestions, + VoltageRange, } import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ @@ -21,8 +22,10 @@ import edu.ie3.util.scala.quantities.ReactivePower import org.apache.pekko.actor.typed.ActorRef import squants.Power import squants.electro.ElectricPotential +import tech.units.indriya.ComparableQuantity import java.util.UUID +import javax.measure.quantity.Dimensionless /** Defines all messages that can be received by a [[GridAgent]] without the * need for an adapter. @@ -262,17 +265,51 @@ object GridAgentMessages { } // DCM messages - case class CongestionCheckRequest(sender: ActorRef[GridAgent.Request]) - extends GridAgent.InternalRequest + + sealed trait CMRequest extends GridAgent.InternalRequest { + def sender: ActorRef[GridAgent.Request] + } + + sealed trait CMReceiveResponse[T] extends GridAgent.InternalReply { + def value: T + def sender: ActorRef[GridAgent.Request] + } + + sealed trait CMResponse[T] extends GridAgent.InternalReply { + def values: Vector[(ActorRef[GridAgent.Request], T)] + } + + // general congestion messages + case class CongestionCheckRequest( + override val sender: ActorRef[GridAgent.Request] + ) extends CMRequest case class CongestionResponse( - congestions: Congestions, - sender: ActorRef[GridAgent.Request], - ) extends GridAgent.InternalReply + override val value: Congestions, + override val sender: ActorRef[GridAgent.Request], + ) extends CMReceiveResponse[Congestions] case class ReceivedCongestions( - congestions: Vector[(ActorRef[GridAgent.Request], Congestions)] - ) extends GridAgent.InternalRequest + override val values: Vector[(ActorRef[GridAgent.Request], Congestions)] + ) extends CMResponse[Congestions] + + // transformer tapping messages + case class RequestVoltageOptions( + override val sender: ActorRef[GridAgent.Request] + ) extends CMRequest + + case class VoltageRangeResponse( + override val value: VoltageRange, + override val sender: ActorRef[GridAgent.Request], + ) extends CMReceiveResponse[VoltageRange] + + case class ReceivedVoltageRange( + override val values: Vector[(ActorRef[GridAgent.Request], VoltageRange)] + ) extends CMResponse[VoltageRange] + + case class VoltageDeltaResponse( + delta: ComparableQuantity[Dimensionless] + ) extends GridAgent.InternalReply case class NextStepRequest( nextStep: CongestionManagementSteps.Value diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala index 809e7c5f8f..a2cbbea16c 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala @@ -26,6 +26,10 @@ trait TransformerTapping { protected var tapRatio: Double = _ + /** Returns [[TransformerTappingModel.autoTap]]. + */ + def hasAutoTap: Boolean = transformerTappingModel.autoTap + def currentTapPos: Int = transformerTappingModel.currentTapPos /** Initialize the tapping model. Should be called after creating the diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index 0c810e75bf..f1cbd9b654 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -78,16 +78,14 @@ class CongestionManagementSupportSpec Set.empty, ) - val map = getTransformer( + val (transformer2ws, transformer3ws, _) = getTransformerInfos( inferiorGrids, gridGates, gridComponents, ) - map shouldBe Map( - inferior1.ref -> transformer1, - inferior2.ref -> transformer2, - ) + transformer2ws shouldBe Map(inferior1.ref -> transformer1) + transformer3ws shouldBe Map(inferior2.ref -> transformer2) } "calculates the possible voltage delta for lines correctly" in { diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index 86b63e79cb..d93b7133fa 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -247,7 +247,7 @@ class DCMAlgorithmCenGridSpec // we expect transformer congestions in the whole grid val allCongestions = superiorGridAgent .expectMessageType[CongestionResponse](30.seconds) - .congestions + .value allCongestions shouldBe congestions } From 92622cc2f9530271a51929be8aefd5ef906abea2 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 25 Apr 2024 19:01:31 +0200 Subject: [PATCH 13/55] Implementing the remaining code for transformer tapping. --- .../grid/CongestionManagementSupport.scala | 52 ++++++++++--- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 73 +++++++++++-------- .../simona/agent/grid/GridAgentMessages.scala | 6 +- .../model/grid/TransformerTapping.scala | 10 +++ .../model/grid/TransformerTappingModel.scala | 6 ++ .../agent/grid/DCMAlgorithmCenGridSpec.scala | 6 +- .../agent/grid/DCMAlgorithmSupGridSpec.scala | 28 +++++-- .../test/common/model/grid/DbfsTestGrid.scala | 13 ++-- 8 files changed, 137 insertions(+), 57 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index c82d468de0..462ba598a8 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -34,7 +34,7 @@ trait CongestionManagementSupport { /** Method for retrieving all needed information for transformers. * @param inferiorGrids * sequence of inferior grids - * @param subGridGateToActorRef + * @param subgridGateToActorRef * mapping of [[SubGridGate]]s to inferior grids * @param gridComponents * the [[GridComponents]] to consider @@ -43,7 +43,7 @@ trait CongestionManagementSupport { */ def getTransformerInfos( inferiorGrids: Seq[ActorRef[GridAgent.Request]], - subGridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], + subgridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], gridComponents: GridComponents, ): ( Map[ActorRef[GridAgent.Request], TransformerModel], @@ -52,13 +52,13 @@ trait CongestionManagementSupport { ) = { val transformerMap = getTransformer( inferiorGrids, - subGridGateToActorRef, + subgridGateToActorRef, gridComponents, ) val transformer3wMap = getTransformer3w( inferiorGrids, - subGridGateToActorRef, + subgridGateToActorRef, gridComponents, ) @@ -73,7 +73,7 @@ trait CongestionManagementSupport { * refs. * @param inferiorGrids * set of [[ActorRef]]s - * @param subGridGateToActorRef + * @param subgridGateToActorRef * map: [[SubGridGate]] to [[ActorRef]] * @param gridComponents * all components of the grid @@ -82,7 +82,7 @@ trait CongestionManagementSupport { */ def getTransformer( inferiorGrids: Seq[ActorRef[GridAgent.Request]], - subGridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], + subgridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], gridComponents: GridComponents, ): Map[ActorRef[GridAgent.Request], TransformerModel] = { val transformerMap = @@ -90,7 +90,7 @@ trait CongestionManagementSupport { .map(transformer => transformer.uuid -> transformer) .toMap - subGridGateToActorRef + subgridGateToActorRef .flatMap { entry => val ref = entry._2 Option.when(inferiorGrids.contains(ref))(ref -> entry._1) @@ -106,7 +106,7 @@ trait CongestionManagementSupport { * * @param inferiorGrids * set of [[ActorRef]]s - * @param subGridGateToActorRef + * @param subgridGateToActorRef * map: [[SubGridGate]] to [[ActorRef]] * @param gridComponents * all components of the grid @@ -115,7 +115,7 @@ trait CongestionManagementSupport { */ def getTransformer3w( inferiorGrids: Seq[ActorRef[GridAgent.Request]], - subGridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], + subgridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], gridComponents: GridComponents, ): Map[ActorRef[GridAgent.Request], Transformer3wModel] = { val transformerMap = @@ -123,7 +123,7 @@ trait CongestionManagementSupport { .map(transformer => transformer.uuid -> transformer) .toMap - subGridGateToActorRef + subgridGateToActorRef .flatMap { entry => val ref = entry._2 Option.when(inferiorGrids.contains(ref))(ref -> entry._1) @@ -394,6 +394,38 @@ object CongestionManagementSupport { suggestion, ) } + + def combineSuggestions( + ranges: Set[VoltageRange] + ): ComparableQuantity[Dimensionless] = { + ranges.headOption match { + case Some(value) => + if (ranges.size == 1) { + value.suggestion + } else { + ranges + .foldLeft(value) { case (combined, current) => + ( + combined.deltaPlus.isGreaterThanOrEqualTo(current.deltaPlus), + combined.deltaMinus.isLessThanOrEqualTo(current.deltaMinus), + ) match { + case (true, true) => + current + case (true, false) => + combined.copy(deltaPlus = current.deltaPlus) + case (false, true) => + combined.copy(deltaMinus = current.deltaMinus) + case (false, false) => + combined + } + } + .suggestion + } + case None => + // no suggestion found => no tapping suggestion + 0.asPu + } + } } case class Congestions( diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index dd44bd946d..f27f48a470 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -89,8 +89,8 @@ trait DCMAlgorithm extends CongestionManagementSupport { // sends the results to the superior grid sender ! CongestionResponse( - congestions.combine(awaitingData.values), ctx.self, + congestions.combine(awaitingData.values), ) } @@ -255,8 +255,8 @@ trait DCMAlgorithm extends CongestionManagementSupport { ) sender ! VoltageRangeResponse( - range, ctx.self, + range, ) } @@ -271,7 +271,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { // because the slack grid should always have 1 pu ctx.self ! VoltageDeltaResponse(0.asPu) - Behaviors.same + updateTransformerTapping(stateData, updatedData) } else { // un-stash all messages buffer.unstashAll(updateTransformerTapping(stateData, updatedData)) @@ -281,47 +281,62 @@ trait DCMAlgorithm extends CongestionManagementSupport { // if we are the superior grid to another grid, we check for transformer tapping option // and send the new delta to the inferior grid - val inferiorRefs = stateData.inferiorRefs - - if (inferiorRefs.nonEmpty) { + if (stateData.inferiorRefs.nonEmpty) { // we calculate a voltage delta for all inferior grids + val inferiorData = awaitingData.mappedValues val gridEnv = stateData.gridAgentBaseData.gridEnv val gridModel = gridEnv.gridModel val gridComponents = gridModel.gridComponents - val (transformer2ws, transformer3ws, _) = - getTransformerInfos( - stateData.inferiorGrids, - gridEnv.subgridGateToActorRef, - gridComponents, - ) + val (transformer2ws, transformer3ws, _) = getTransformerInfos( + stateData.inferiorGrids, + gridEnv.subgridGateToActorRef, + gridComponents, + ) + + val transformer3wMap = + gridComponents.transformers3w.map(t => t.uuid -> t).toMap + + val modelMap = transformer2ws + .groupBy(_._2) ++ transformer3ws.groupBy(_._2.uuid).map { + case (uuid, refMap) => + transformer3wMap(uuid) -> refMap + } + + modelMap.foreach { case (model, refMap) => + val refs = refMap.keySet - // update inferior grid connected by a two winding transformer - transformer2ws.foreach { case (ref, model) => if (model.hasAutoTap) { // the given transformer can be tapped, calculate the new tap pos - // TODO: Add code + val suggestion = + VoltageRange.combineSuggestions(refs.map(inferiorData)) - } else { - // no tapping possible, just send the delta to the inferior grid - ref ! VoltageDeltaResponse(delta) - } - } + val tapOption = model.computeDeltaTap(suggestion) + val deltaV = if (tapOption == 0) { + // we can not change the voltage as we would like to + if (suggestion.isLessThan(0.asPu)) { + // if suggestion < 0, we decrease the voltage as much as we can - // update inferior grid connected by a three winding transformer - val transformer3wMap = - gridComponents.transformers3w.map(t => t.uuid -> t).toMap + val tapChange = model.maxTapDecrease + model.decrTapPos(tapChange) - transformer3ws.groupBy(_._2.uuid).foreach { case (uuid, refMap) => - val transformer = transformer3wMap(uuid) - val refs = refMap.keySet + model.deltaV.multiply(tapChange) + } else { + // we increase the voltage as much as we can + val tapChange = model.maxTapIncrease + model.decrTapPos(tapChange) - if (transformer.hasAutoTap) { - // the given transformer can be tapped, calculate the new tap pos + model.deltaV.multiply(tapChange) + } + } else { + // we can change the voltage without a problem + model.updateTapPos(tapOption) + model.deltaV.multiply(tapOption) + } - // TODO: Add code + refs.foreach(_ ! VoltageDeltaResponse(deltaV.divide(100))) } else { // no tapping possible, just send the delta to the inferior grid refs.foreach(_ ! VoltageDeltaResponse(delta)) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala index 1f113628bc..f1b322094f 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala @@ -271,8 +271,8 @@ object GridAgentMessages { } sealed trait CMReceiveResponse[T] extends GridAgent.InternalReply { - def value: T def sender: ActorRef[GridAgent.Request] + def value: T } sealed trait CMResponse[T] extends GridAgent.InternalReply { @@ -285,8 +285,8 @@ object GridAgentMessages { ) extends CMRequest case class CongestionResponse( - override val value: Congestions, override val sender: ActorRef[GridAgent.Request], + override val value: Congestions, ) extends CMReceiveResponse[Congestions] case class ReceivedCongestions( @@ -299,8 +299,8 @@ object GridAgentMessages { ) extends CMRequest case class VoltageRangeResponse( - override val value: VoltageRange, override val sender: ActorRef[GridAgent.Request], + override val value: VoltageRange, ) extends CMReceiveResponse[VoltageRange] case class ReceivedVoltageRange( diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala index a2cbbea16c..ef7e431a53 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala @@ -7,6 +7,9 @@ package edu.ie3.simona.model.grid import edu.ie3.util.quantities.PowerSystemUnits._ +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import tech.units.indriya.ComparableQuantity + import javax.measure.Quantity import javax.measure.quantity.Dimensionless import tech.units.indriya.quantity.Quantities @@ -30,6 +33,13 @@ trait TransformerTapping { */ def hasAutoTap: Boolean = transformerTappingModel.autoTap + def deltaV: ComparableQuantity[Dimensionless] = + transformerTappingModel.deltaV.getValue.doubleValue().asPu + + def maxTapIncrease: Int = transformerTappingModel.tapMax - currentTapPos + + def maxTapDecrease: Int = currentTapPos - transformerTappingModel.tapMin + def currentTapPos: Int = transformerTappingModel.currentTapPos /** Initialize the tapping model. Should be called after creating the diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerTappingModel.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerTappingModel.scala index 602259cec2..ef8b4d3ce4 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerTappingModel.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerTappingModel.scala @@ -50,6 +50,12 @@ final case class TransformerTappingModel( def currentTapPos: Int = _currentTapPos + def maxIncrease: Quantity[Dimensionless] = + deltaV.multiply(tapMax - currentTapPos) + + def maxDecrease: Quantity[Dimensionless] = + deltaV.multiply(tapMin - currentTapPos) + /** Increase tap position by the provided number of delta taps * * @param deltaTap diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index d93b7133fa..17ab138542 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -232,16 +232,16 @@ class DCMAlgorithmCenGridSpec // send congestions congestionCheckRequestSender11.sender ! CongestionResponse( - congestions, inferiorGrid11.ref, + congestions, ) congestionCheckRequestSender12.sender ! CongestionResponse( - congestions, inferiorGrid12.ref, + congestions, ) congestionCheckRequestSender13.sender ! CongestionResponse( - congestions, inferiorGrid13.ref, + congestions, ) // we expect transformer congestions in the whole grid diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala index 88518402d8..6b06ffb105 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala @@ -10,7 +10,10 @@ import com.typesafe.config.ConfigFactory import edu.ie3.datamodel.graph.SubGridGate import edu.ie3.datamodel.models.input.container.ThermalGrid import edu.ie3.simona.agent.EnvironmentRefs -import edu.ie3.simona.agent.grid.CongestionManagementSupport.Congestions +import edu.ie3.simona.agent.grid.CongestionManagementSupport.{ + Congestions, + VoltageRange, +} import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.config.SimonaConfig @@ -26,6 +29,7 @@ import edu.ie3.simona.scheduler.ScheduleLock import edu.ie3.simona.test.common.model.grid.DbfsTestGrid import edu.ie3.simona.test.common.{ConfigTestData, TestSpawnerTyped, UnitSpec} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import org.apache.pekko.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe, @@ -89,8 +93,8 @@ class DCMAlgorithmSupGridSpec ehvGridContainer, Seq.empty[ThermalGrid], subnetGatesToActorRef, - RefSystem("5000 MVA", "380 kV"), - VoltageLimits(0.9, 1.1), + RefSystem("5000 MVA", "110 kV"), + VoltageLimits(0.9, 1.05), ) val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) @@ -173,9 +177,19 @@ class DCMAlgorithmSupGridSpec // inferior should receive a next state message to go to a congestion management step hvGrid.expectMessageType[NextStepRequest] - // skipping the step for now - // TODO: Update test after implementing transformer tapping - superiorGridAgent ! FinishStep + hvGrid.expectMessageType[RequestVoltageOptions] match { + case RequestVoltageOptions(sender) => + sender ! VoltageRangeResponse( + hvGrid.ref, + VoltageRange(0.04.asPu, 0.asPu), + ) + } + + hvGrid.expectMessageType[VoltageDeltaResponse](120.seconds) match { + case VoltageDeltaResponse(delta) => + delta should equalWithTolerance(0.03.asPu) + } + hvGrid.expectMessageType[FinishStep.type] // skipping the simulation @@ -221,7 +235,7 @@ class DCMAlgorithmSupGridSpec } // send congestions - lastSender ! CongestionResponse(congestions, hvGrid.ref) + lastSender ! CongestionResponse(hvGrid.ref, congestions) } def gotoSimulateGrid(): Unit = { diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala index 07a2ef7f8f..8e052fc08c 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala @@ -13,7 +13,10 @@ import edu.ie3.datamodel.models.input.connector.`type`.{ LineTypeInput, Transformer2WTypeInput, } -import edu.ie3.datamodel.models.input.container.RawGridElements +import edu.ie3.datamodel.models.input.container.{ + JointGridContainer, + RawGridElements, +} import edu.ie3.datamodel.models.input.system.characteristic.OlmCharacteristicInput import edu.ie3.datamodel.models.input.{ MeasurementUnitInput, @@ -268,7 +271,7 @@ trait DbfsTestGrid extends SubGridGateMokka { 1, trafoType, 0, - false, + true, ) protected val transformer2 = new Transformer2WInput( UUID.fromString("ceccd8cb-29dc-45d6-8a13-4b0033c5f1ef"), @@ -280,7 +283,7 @@ trait DbfsTestGrid extends SubGridGateMokka { 1, trafoType, 0, - false, + true, ) protected val (hvGridContainer, hvSubGridGates) = { @@ -389,11 +392,11 @@ trait DbfsTestGrid extends SubGridGateMokka { } protected val (ehvGridContainer, ehvSubGridGates) = { - val nodes = Set(supNodeA) + val nodes = Set(supNodeA, node1) val rawGridElements = new RawGridElements( nodes.asJava, Set.empty[LineInput].asJava, - Set.empty[Transformer2WInput].asJava, + Set(transformer1).asJava, Set.empty[Transformer3WInput].asJava, Set.empty[SwitchInput].asJava, Set.empty[MeasurementUnitInput].asJava, From c30ed4fde978a10d823597752cbeecf3a3a5c8c4 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 26 Apr 2024 14:29:03 +0200 Subject: [PATCH 14/55] Adding some tests. Fixing some issues. --- .../grid/CongestionManagementSupport.scala | 153 +----- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 140 +++--- .../ie3/simona/agent/grid/GridAgentData.scala | 5 +- .../simona/agent/grid/GridAgentMessages.scala | 11 +- .../model/grid/TransformerTapping.scala | 8 +- .../CongestionManagementSupportSpec.scala | 88 +--- .../agent/grid/DBFSMockGridAgents.scala | 21 + .../agent/grid/DCMAlgorithmCenGridSpec.scala | 472 +++++++++++++++--- .../agent/grid/DCMAlgorithmSupGridSpec.scala | 13 +- .../test/common/model/grid/DbfsTestGrid.scala | 146 ++++-- .../model/grid/GridComponentsMokka.scala | 19 +- .../common/model/grid/SubGridGateMokka.scala | 34 +- 12 files changed, 722 insertions(+), 388 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 462ba598a8..07c62b3fe0 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -6,15 +6,13 @@ package edu.ie3.simona.agent.grid -import edu.ie3.datamodel.graph.SubGridGate import edu.ie3.datamodel.models.result.connector.LineResult import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.exceptions.ResultException import edu.ie3.simona.model.grid.GridModel.GridComponents import edu.ie3.simona.model.grid.{ - Transformer3wModel, - TransformerModel, + TransformerTapping, TransformerTappingModel, VoltageLimits, } @@ -31,131 +29,25 @@ import javax.measure.quantity.Dimensionless */ trait CongestionManagementSupport { - /** Method for retrieving all needed information for transformers. - * @param inferiorGrids - * sequence of inferior grids - * @param subgridGateToActorRef - * mapping of [[SubGridGate]]s to inferior grids - * @param gridComponents - * the [[GridComponents]] to consider - * @return - * transformer information - */ - def getTransformerInfos( - inferiorGrids: Seq[ActorRef[GridAgent.Request]], - subgridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], - gridComponents: GridComponents, - ): ( - Map[ActorRef[GridAgent.Request], TransformerModel], - Map[ActorRef[GridAgent.Request], Transformer3wModel], - Map[ActorRef[GridAgent.Request], TransformerTappingModel], - ) = { - val transformerMap = getTransformer( - inferiorGrids, - subgridGateToActorRef, - gridComponents, - ) - - val transformer3wMap = getTransformer3w( - inferiorGrids, - subgridGateToActorRef, - gridComponents, - ) - - val tappingModels = transformerMap.map(e => - e._1 -> e._2.tappingModelCopy - ) ++ transformer3wMap.map(e => e._1 -> e._2.tappingModelCopy) - - (transformerMap, transformer3wMap, tappingModels) - } - - /** Method for mapping the [[TransformerModel]]s to the given inferior grid - * refs. - * @param inferiorGrids - * set of [[ActorRef]]s - * @param subgridGateToActorRef - * map: [[SubGridGate]] to [[ActorRef]] - * @param gridComponents - * all components of the grid - * @return - * a map: [[ActorRef]] to transformer model - */ - def getTransformer( - inferiorGrids: Seq[ActorRef[GridAgent.Request]], - subgridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], - gridComponents: GridComponents, - ): Map[ActorRef[GridAgent.Request], TransformerModel] = { - val transformerMap = - gridComponents.transformers - .map(transformer => transformer.uuid -> transformer) - .toMap - - subgridGateToActorRef - .flatMap { entry => - val ref = entry._2 - Option.when(inferiorGrids.contains(ref))(ref -> entry._1) - } - .flatMap { case (ref, gate) => - val uuid = gate.link().getUuid - transformerMap.get(uuid).map(value => ref -> value) - } - } - - /** Method for mapping the [[Transformer3wModel]]s to the given inferior grid - * refs. - * - * @param inferiorGrids - * set of [[ActorRef]]s - * @param subgridGateToActorRef - * map: [[SubGridGate]] to [[ActorRef]] - * @param gridComponents - * all components of the grid - * @return - * a map: [[ActorRef]] to transformer model - */ - def getTransformer3w( - inferiorGrids: Seq[ActorRef[GridAgent.Request]], - subgridGateToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]], - gridComponents: GridComponents, - ): Map[ActorRef[GridAgent.Request], Transformer3wModel] = { - val transformerMap = - gridComponents.transformers3w - .map(transformer => transformer.uuid -> transformer) - .toMap - - subgridGateToActorRef - .flatMap { entry => - val ref = entry._2 - Option.when(inferiorGrids.contains(ref))(ref -> entry._1) - } - .flatMap { case (ref, gate) => - val uuid = gate.link().getUuid - transformerMap.get(uuid).map(value => ref -> value) - } - } - /** Method to calculate the range of possible voltage changes. + * * @param powerFlowResultEvent * results from simulating the grid * @param voltageLimits * voltage limits * @param gridComponents * all components of the grid - * @param transformerTapping - * map: inferior grid to [[TransformerTappingModel]] as the used - * transformer - * @param inferiorRange - * map: inferior grid to voltage range + * @param inferiorData + * map: inferior grid to [[VoltageRange]] and [[TransformerTappingModel]] * @return */ def calculateVoltageOptions( powerFlowResultEvent: PowerFlowResultEvent, voltageLimits: VoltageLimits, gridComponents: GridComponents, - transformerTapping: Map[ActorRef[ + inferiorData: Map[ActorRef[ GridAgent.Request - ], TransformerTappingModel], - inferiorRange: Map[ActorRef[GridAgent.Request], VoltageRange], + ], (VoltageRange, TransformerTapping)], ): VoltageRange = { // calculate voltage range val nodeResMap = powerFlowResultEvent.nodeResults @@ -181,16 +73,13 @@ trait CongestionManagementSupport { ) val updatedRange = range.updateWithLineDelta(deltaV) - if (inferiorRange.isEmpty) { + if (inferiorData.isEmpty) { // if there are no inferior grids, return the voltage range updatedRange } else { // if there are inferior grids, update the voltage range - updatedRange.updateWithInferiorRanges( - transformerTapping, - inferiorRange, - ) + updatedRange.updateWithInferiorRanges(inferiorData) } } @@ -293,29 +182,23 @@ object CongestionManagementSupport { } /** Method to update this voltage range with inferior voltage ranges - * @param tappingModels - * map: inferior grid to [[TransformerTappingModel]] - * @param inferiorRange - * map: inferior grid to [[VoltageRange]] + * @param inferiorData + * map: inferior grid to [[VoltageRange]] and [[TransformerTappingModel]] * @return * a new [[VoltageRange]] */ def updateWithInferiorRanges( - tappingModels: Map[ActorRef[ + inferiorData: Map[ActorRef[ GridAgent.Request - ], TransformerTappingModel], - inferiorRange: Map[ActorRef[GridAgent.Request], VoltageRange], + ], (VoltageRange, TransformerTapping)] ): VoltageRange = { - inferiorRange.foldLeft(this) { case (range, (ref, infRange)) => - // getting the tapping model - val tappingModel: TransformerTappingModel = tappingModels(ref) - - if (tappingModel.autoTap) { - val currentPos = tappingModel.currentTapPos - val deltaV = tappingModel.deltaV - val possiblePlus = deltaV.multiply(tappingModel.tapMax - currentPos) - val possibleMinus = deltaV.multiply(tappingModel.tapMin - currentPos) + inferiorData.foldLeft(this) { case (range, (ref, (infRange, tapping))) => + if (tapping.hasAutoTap) { + val currentPos = tapping.currentTapPos + val deltaV = tapping.deltaV + val possiblePlus = deltaV.multiply(tapping.tapMax - currentPos) + val possibleMinus = deltaV.multiply(tapping.tapMin - currentPos) ( range.deltaPlus diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index f27f48a470..2d4e0a3132 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -19,6 +19,7 @@ import edu.ie3.simona.agent.grid.GridAgentData.{ GridAgentConstantData, } import edu.ie3.simona.agent.grid.GridAgentMessages._ +import edu.ie3.simona.model.grid.TransformerTapping import edu.ie3.simona.ontology.messages.Activation import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import org.apache.pekko.actor.typed.scaladsl.AskPattern.Askable @@ -70,7 +71,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { case (ctx, congestionRequest @ CongestionCheckRequest(sender)) => // check if waiting for inferior data is needed - if (!awaitingData.isDone) { + if (awaitingData.notDone) { ctx.log.debug( s"Received request for congestions before all data from inferior grids were received. Stashing away." ) @@ -209,7 +210,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { */ private[grid] def updateTransformerTapping( stateData: CongestionManagementData, - awaitingData: AwaitingData[VoltageRange], + awaitingData: AwaitingData[(VoltageRange, TransformerTapping)], )(implicit constantData: GridAgentConstantData, buffer: StashBuffer[GridAgent.Request], @@ -227,7 +228,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { case (ctx, voltageRangeRequest @ RequestVoltageOptions(sender)) => // check if waiting for inferior data is needed - if (!awaitingData.isDone) { + if (awaitingData.notDone) { ctx.log.debug( s"Received request for congestions before all data from inferior grids were received. Stashing away." ) @@ -240,23 +241,28 @@ trait DCMAlgorithm extends CongestionManagementSupport { val gridModel = gridEnv.gridModel val gridComponents = gridModel.gridComponents - val (_, _, tappingModels) = getTransformerInfos( - stateData.inferiorGrids, - gridEnv.subgridGateToActorRef, - gridComponents, - ) + val transformers = + (gridComponents.transformers ++ gridComponents.transformers3w) + .map(t => t.uuid -> t) + .toMap + val transformersToSup = gridEnv.subgridGateToActorRef + .find(_._2 == sender) + .map(_._1.link().getUuid) + .getOrElse( + throw new IllegalArgumentException(s"No superior actor ref found!") + ) + val transformer = transformers(transformersToSup) val range = calculateVoltageOptions( stateData.powerFlowResults, gridModel.voltageLimits, gridModel.gridComponents, - tappingModels, awaitingData.mappedValues, ) sender ! VoltageRangeResponse( ctx.self, - range, + (range, transformer), ) } @@ -283,81 +289,68 @@ trait DCMAlgorithm extends CongestionManagementSupport { if (stateData.inferiorRefs.nonEmpty) { // we calculate a voltage delta for all inferior grids - val inferiorData = awaitingData.mappedValues - - val gridEnv = stateData.gridAgentBaseData.gridEnv - val gridModel = gridEnv.gridModel - val gridComponents = gridModel.gridComponents - - val (transformer2ws, transformer3ws, _) = getTransformerInfos( - stateData.inferiorGrids, - gridEnv.subgridGateToActorRef, - gridComponents, - ) - val transformer3wMap = - gridComponents.transformers3w.map(t => t.uuid -> t).toMap + awaitingData.mappedValues.groupBy(_._2._2).foreach { + case (tappingModel, refMap) => + val inferiorRanges: Map[ActorRef[GridAgent.Request], VoltageRange] = + refMap.map { case (value, (range, _)) => + value -> range + } - val modelMap = transformer2ws - .groupBy(_._2) ++ transformer3ws.groupBy(_._2.uuid).map { - case (uuid, refMap) => - transformer3wMap(uuid) -> refMap - } + val refs: Set[ActorRef[GridAgent.Request]] = inferiorRanges.keySet - modelMap.foreach { case (model, refMap) => - val refs = refMap.keySet + if (tappingModel.hasAutoTap) { + // the given transformer can be tapped, calculate the new tap pos - if (model.hasAutoTap) { - // the given transformer can be tapped, calculate the new tap pos + val suggestion = + VoltageRange + .combineSuggestions(inferiorRanges.values.toSet) + .subtract(delta) - val suggestion = - VoltageRange.combineSuggestions(refs.map(inferiorData)) + val tapOption = tappingModel.computeDeltaTap(suggestion) + val deltaV = if (tapOption == 0) { + // we can not change the voltage as we would like to + if (suggestion.isLessThan(0.asPu)) { + // if suggestion < 0, we decrease the voltage as much as we can - val tapOption = model.computeDeltaTap(suggestion) - val deltaV = if (tapOption == 0) { - // we can not change the voltage as we would like to - if (suggestion.isLessThan(0.asPu)) { - // if suggestion < 0, we decrease the voltage as much as we can + val tapChange = tappingModel.maxTapDecrease + tappingModel.decrTapPos(tapChange) - val tapChange = model.maxTapDecrease - model.decrTapPos(tapChange) + tappingModel.deltaV.multiply(tapChange) + } else { + // we increase the voltage as much as we can + val tapChange = tappingModel.maxTapIncrease + tappingModel.decrTapPos(tapChange) - model.deltaV.multiply(tapChange) + tappingModel.deltaV.multiply(tapChange) + } } else { - // we increase the voltage as much as we can - val tapChange = model.maxTapIncrease - model.decrTapPos(tapChange) - - model.deltaV.multiply(tapChange) + // we can change the voltage without a problem + tappingModel.updateTapPos(tapOption) + tappingModel.deltaV.multiply(tapOption) } + + refs.foreach(_ ! VoltageDeltaResponse(deltaV.divide(100))) } else { - // we can change the voltage without a problem - model.updateTapPos(tapOption) - model.deltaV.multiply(tapOption) + // no tapping possible, just send the delta to the inferior grid + refs.foreach(_ ! VoltageDeltaResponse(delta)) } - - refs.foreach(_ ! VoltageDeltaResponse(deltaV.divide(100))) - } else { - // no tapping possible, just send the delta to the inferior grid - refs.foreach(_ ! VoltageDeltaResponse(delta)) - } } } // all work is done in this grid, finish this step - ctx.self ! FinishStep - Behaviors.same - - case (ctx, FinishStep) => - // inform my inferior grids about the end of this step - stateData.inferiorRefs.foreach(_ ! FinishStep) - // simulate grid after changing the transformer tapping - clearAndGotoSimulateGrid( - stateData.cleanAfterTransformerTapping, - stateData.currentTick, - ctx, + buffer.unstashAll( + clearAndGotoSimulateGrid( + stateData.cleanAfterTransformerTapping, + stateData.currentTick, + ctx, + ) ) + + case (_, msg) => + buffer.stash(msg) + Behaviors.same } // TODO: Implement a proper behavior @@ -498,11 +491,14 @@ trait DCMAlgorithm extends CongestionManagementSupport { ) ctx.self ! WrappedActivation(Activation(currentTick)) - GridAgent.simulateGrid( - cleanedData.copy(congestionManagementParams = - gridAgentBaseData.congestionManagementParams - ), - currentTick, + + buffer.unstashAll( + GridAgent.simulateGrid( + cleanedData.copy(congestionManagementParams = + gridAgentBaseData.congestionManagementParams + ), + currentTick, + ) ) } } 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 b4fd958870..cd624a9295 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -66,7 +66,7 @@ object GridAgentData { /** Returns true if congestion data from inferior grids is expected and no * data was received yet. */ - def isDone: Boolean = + def notDone: Boolean = inferiorGridMap.values.exists(_.isEmpty) def values: Iterable[T] = inferiorGridMap.values.flatten.toSeq @@ -76,6 +76,9 @@ object GridAgentData { option.map(value => ref -> value) } + def update(sender: ActorRef[GridAgent.Request], data: T): AwaitingData[T] = + handleReceivingData(Vector((sender, data))) + /** Method for updating the data with the received data. * * @param receivedData diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala index f1b322094f..051304909f 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala @@ -16,6 +16,7 @@ import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ ExchangePower, ExchangeVoltage, } +import edu.ie3.simona.model.grid.TransformerTapping import edu.ie3.simona.ontology.messages.Activation import edu.ie3.simona.scheduler.ScheduleLock.ScheduleKey import edu.ie3.util.scala.quantities.ReactivePower @@ -300,12 +301,14 @@ object GridAgentMessages { case class VoltageRangeResponse( override val sender: ActorRef[GridAgent.Request], - override val value: VoltageRange, - ) extends CMReceiveResponse[VoltageRange] + override val value: (VoltageRange, TransformerTapping), + ) extends CMReceiveResponse[(VoltageRange, TransformerTapping)] case class ReceivedVoltageRange( - override val values: Vector[(ActorRef[GridAgent.Request], VoltageRange)] - ) extends CMResponse[VoltageRange] + override val values: Vector[ + (ActorRef[GridAgent.Request], (VoltageRange, TransformerTapping)) + ] + ) extends CMResponse[(VoltageRange, TransformerTapping)] case class VoltageDeltaResponse( delta: ComparableQuantity[Dimensionless] diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala index ef7e431a53..f1964cb56e 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala @@ -33,12 +33,16 @@ trait TransformerTapping { */ def hasAutoTap: Boolean = transformerTappingModel.autoTap + def tapMax: Int = transformerTappingModel.tapMax + + def tapMin: Int = transformerTappingModel.tapMin + def deltaV: ComparableQuantity[Dimensionless] = transformerTappingModel.deltaV.getValue.doubleValue().asPu - def maxTapIncrease: Int = transformerTappingModel.tapMax - currentTapPos + def maxTapIncrease: Int = tapMax - currentTapPos - def maxTapDecrease: Int = currentTapPos - transformerTappingModel.tapMin + def maxTapDecrease: Int = currentTapPos - tapMin def currentTapPos: Int = transformerTappingModel.currentTapPos diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index f1cbd9b654..4534acbde9 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -6,14 +6,12 @@ package edu.ie3.simona.agent.grid -import edu.ie3.datamodel.graph.SubGridGate -import edu.ie3.datamodel.models.input.connector.ConnectorPort import edu.ie3.datamodel.models.result.NodeResult import edu.ie3.datamodel.models.result.connector.LineResult import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.model.grid.GridModel.GridComponents -import edu.ie3.simona.model.grid.{TransformerTappingModel, VoltageLimits} +import edu.ie3.simona.model.grid.VoltageLimits import edu.ie3.simona.test.common.UnitSpec import edu.ie3.simona.test.common.model.grid.{ GridComponentsMokka, @@ -27,8 +25,6 @@ import org.apache.pekko.actor.testkit.typed.scaladsl.{ } import org.apache.pekko.actor.typed.ActorRef -import java.util.UUID - class CongestionManagementSupportSpec extends ScalaTestWithActorTestKit with UnitSpec @@ -49,45 +45,6 @@ class CongestionManagementSupportSpec val inferiorGrids: Seq[ActorRef[GridAgent.Request]] = Seq(inferior1.ref, inferior2.ref) - "map all transformers to inferior grids" in { - val nodeHv = mockNode(UUID.randomUUID(), 100) - val node30kV = mockNode(UUID.randomUUID(), 30) - val node20kV = mockNode(UUID.randomUUID(), 20) - val node10kV = mockNode(UUID.randomUUID(), 10) - - val transformer2w = mockTransformer2w(UUID.randomUUID(), nodeHv, node30kV) - val transformer3w = - mockTransformer3w(UUID.randomUUID(), nodeHv, 100, node20kV, node10kV) - - val transformer1 = mockTransformerModel(transformer2w.getUuid) - val transformer2 = mockTransformer3wModel(transformer3w.getUuid) - - val gridGates = Map( - SubGridGate.fromTransformer2W(transformer2w) -> inferior1.ref, - SubGridGate.fromTransformer3W( - transformer3w, - ConnectorPort.B, - ) -> inferior2.ref, - ) - - val gridComponents = GridComponents( - Seq.empty, - Set.empty, - Set(transformer1, mockTransformerModel(UUID.randomUUID())), - Set(transformer2), - Set.empty, - ) - - val (transformer2ws, transformer3ws, _) = getTransformerInfos( - inferiorGrids, - gridGates, - gridComponents, - ) - - transformer2ws shouldBe Map(inferior1.ref -> transformer1) - transformer3ws shouldBe Map(inferior2.ref -> transformer2) - } - "calculates the possible voltage delta for lines correctly" in { val node1 = nodeModel() val node2 = nodeModel() @@ -186,7 +143,6 @@ class CongestionManagementSupportSpec VoltageLimits(0.9, 1.1), gridComponents, Map.empty, - Map.empty, ) range.deltaPlus should equalWithTolerance(0.05.asPu) @@ -212,10 +168,12 @@ class CongestionManagementSupportSpec Set.empty, ) - val tappingModel = - TransformerTappingModel(0.01.asPu, 0, 3, -3, 0, autoTap = true) - val tappingMap = - Map(inferior1.ref -> tappingModel, inferior2.ref -> tappingModel) + val tappingModel = mockTransformerModel( + autoTap = true, + tapMax = 3, + tapMin = -3, + deltaV = 0.01.asPu, + ) val powerFlowResult = buildPowerFlowResultEvent( Set( @@ -235,10 +193,9 @@ class CongestionManagementSupportSpec powerFlowResult, VoltageLimits(0.9, 1.1), gridComponents, - tappingMap, Map( - inferior1.ref -> VoltageRange(0.1.asPu, 0.01.asPu), - inferior2.ref -> VoltageRange(0.01.asPu, (-0.04).asPu), + inferior1.ref -> (VoltageRange(0.1.asPu, 0.01.asPu), tappingModel), + inferior2.ref -> (VoltageRange(0.01.asPu, (-0.04).asPu), tappingModel), ), ) @@ -349,9 +306,7 @@ class CongestionManagementSupportSpec val range = VoltageRange(0.05.asPu, (-0.05).asPu) val tappingModel = - TransformerTappingModel(0.15.asPu, 0, 10, -10, 0, autoTap = false) - val tappingMap = - Map(inferior1.ref -> tappingModel, inferior2.ref -> tappingModel) + mockTransformerModel(tapMax = 10, tapMin = -10, deltaV = 0.01.asPu) val cases = Table( ("range1", "range2", "expected"), @@ -379,8 +334,10 @@ class CongestionManagementSupportSpec forAll(cases) { (range1, range2, expected) => val updatedRange = range.updateWithInferiorRanges( - tappingMap, - Map(inferior1.ref -> range1, inferior2.ref -> range2), + Map( + inferior1.ref -> (range1, tappingModel), + inferior2.ref -> (range2, tappingModel), + ) ) updatedRange.deltaPlus should equalWithTolerance(expected.deltaPlus) @@ -391,10 +348,13 @@ class CongestionManagementSupportSpec "be updated with inferior voltage ranges and with tapping correctly" in { val range = VoltageRange(0.05.asPu, (-0.05).asPu) - val tappingModel = - TransformerTappingModel(0.01.asPu, 7, 10, -10, 0, autoTap = true) - val tappingMap = - Map(inferior1.ref -> tappingModel, inferior2.ref -> tappingModel) + val tappingModel = mockTransformerModel( + autoTap = true, + currentTapPos = 7, + tapMax = 10, + tapMin = -10, + deltaV = 0.01.asPu, + ) val cases = Table( ("range1", "range2", "expected"), @@ -422,8 +382,10 @@ class CongestionManagementSupportSpec forAll(cases) { (range1, range2, expected) => val updatedRange = range.updateWithInferiorRanges( - tappingMap, - Map(inferior1.ref -> range1, inferior2.ref -> range2), + Map( + inferior1.ref -> (range1, tappingModel), + inferior2.ref -> (range2, tappingModel), + ) ) updatedRange.deltaPlus should equalWithTolerance(expected.deltaPlus) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala index 6f492335e5..86c5200ca2 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.agent.grid +import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ ExchangePower, ExchangeVoltage, @@ -18,8 +19,10 @@ import org.apache.pekko.actor.typed.ActorRef import squants.Power import squants.electro.Volts import squants.energy.Megawatts +import tech.units.indriya.ComparableQuantity import java.util.UUID +import javax.measure.quantity.Dimensionless import scala.concurrent.duration.{DurationInt, FiniteDuration} import scala.language.postfixOps @@ -86,6 +89,10 @@ trait DBFSMockGridAgents extends UnitSpec { sweepNo: Int, ): Unit = receiver ! SlackVoltageRequest(sweepNo, nodeUuids, gaProbe.ref) + + def expectVoltageRangeRequest(): ActorRef[GridAgent.Request] = { + gaProbe.expectMessageType[RequestVoltageOptions].sender + } } final case class SuperiorGA( @@ -137,5 +144,19 @@ trait DBFSMockGridAgents extends UnitSpec { ): Unit = { receiver ! RequestGridPower(sweepNo, nodeUuids, gaProbe.ref) } + + def expectVoltageRangeResponse( + voltageRange: VoltageRange, + maxDuration: FiniteDuration = 30 seconds, + ): ActorRef[GridAgent.Request] = { + gaProbe.expectMessageType[VoltageRangeResponse](maxDuration) match { + case VoltageRangeResponse(sender, (range, _)) => + range.deltaPlus shouldBe voltageRange.deltaPlus + range.deltaMinus shouldBe voltageRange.deltaMinus + range.suggestion shouldBe voltageRange.suggestion + + sender + } + } } } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index 17ab138542..882b48ddbe 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -10,37 +10,37 @@ import com.typesafe.config.ConfigFactory import edu.ie3.datamodel.models.input.container.ThermalGrid import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.CongestionManagementSupport.CongestionManagementSteps.TransformerTapping -import edu.ie3.simona.agent.grid.CongestionManagementSupport.Congestions +import edu.ie3.simona.agent.grid.CongestionManagementSupport.{ + Congestions, + VoltageRange, +} import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData -import edu.ie3.simona.agent.grid.GridAgentMessages.{ - CongestionCheckRequest, - CongestionResponse, - CreateGridAgent, - FinishGridSimulationTrigger, - FinishStep, - GotoIdle, - NextStepRequest, - RequestGridPower, - SlackVoltageRequest, - WrappedActivation, +import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ + ExchangePower, + ExchangeVoltage, } +import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} import edu.ie3.simona.model.grid.{RefSystem, VoltageLimits} -import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, } +import edu.ie3.simona.ontology.messages.{Activation, SchedulerMessage} import edu.ie3.simona.scheduler.ScheduleLock -import edu.ie3.simona.test.common.{ConfigTestData, TestSpawnerTyped} import edu.ie3.simona.test.common.model.grid.DbfsTestGrid +import edu.ie3.simona.test.common.{ConfigTestData, TestSpawnerTyped} import edu.ie3.simona.util.SimonaConstants.INIT_SIM_TICK +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import edu.ie3.util.scala.quantities.Megavars import org.apache.pekko.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe, } import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps +import squants.electro.Kilovolts +import squants.energy.Megawatts import scala.concurrent.duration.DurationInt @@ -63,20 +63,20 @@ class DCMAlgorithmCenGridSpec private val primaryService = TestProbe("primaryService") private val weatherService = TestProbe("weatherService") - private val superiorGridAgent: TestProbe[GridAgent.Request] = TestProbe( - "superiorGridAgent_1000" + private val superiorGridAgent = SuperiorGA( + TestProbe("superiorGridAgent_1000"), + Seq(supNodeA.getUuid, supNodeB.getUuid), ) - private val inferiorGrid11: TestProbe[GridAgent.Request] = TestProbe( - "inferiorGridAgent_11" - ) + private val inferiorGrid11 = + InferiorGA(TestProbe("inferiorGridAgent_11"), Seq(node1.getUuid)) - private val inferiorGrid12: TestProbe[GridAgent.Request] = TestProbe( - "inferiorGridAgent_12" - ) + private val inferiorGrid12 = + InferiorGA(TestProbe("inferiorGridAgent_12"), Seq(node2.getUuid)) - private val inferiorGrid13: TestProbe[GridAgent.Request] = TestProbe( - "inferiorGridAgent_13" + private val inferiorGrid13 = InferiorGA( + TestProbe("inferiorGridAgent_13"), + Seq(node3.getUuid, node4.getUuid), ) private val environmentRefs = EnvironmentRefs( @@ -143,14 +143,15 @@ class DCMAlgorithmCenGridSpec } s"skip simulate grid and check for congestions correctly if no congestions occurred" in { - skipSimulation() - + goToSimulateGrid() + simulateGrid() cmStart() cmFinish() } s"update transformer tapping correctly" in { - skipSimulation() + goToSimulateGrid() + simulateGrid() val congestions = Congestions( voltageCongestions = true, @@ -166,47 +167,374 @@ class DCMAlgorithmCenGridSpec ) // inferior grids should receive a next state message to go to a congestion management step - inferiorGrid11.expectMessageType[NextStepRequest] - inferiorGrid12.expectMessageType[NextStepRequest] - inferiorGrid13.expectMessageType[NextStepRequest] - - // skipping the step for now - // TODO: Update test after implementing transformer tapping - centerGridAgent ! FinishStep - inferiorGrid11.expectMessageType[FinishStep.type] - inferiorGrid12.expectMessageType[FinishStep.type] - inferiorGrid13.expectMessageType[FinishStep.type] - - // skipping the simulation - skipSimulation(true) + inferiorGrid11.gaProbe.expectMessageType[NextStepRequest] + inferiorGrid12.gaProbe.expectMessageType[NextStepRequest] + inferiorGrid13.gaProbe.expectMessageType[NextStepRequest] + + // ask the center grid for the voltage options + centerGridAgent ! RequestVoltageOptions(superiorGridAgent.ref) + + // the inferior grids should receive a VoltageRangeRequest + val voltageRangeRequester11 = inferiorGrid11.expectVoltageRangeRequest() + val voltageRangeRequester12 = inferiorGrid12.expectVoltageRangeRequest() + val voltageRangeRequester13 = inferiorGrid13.expectVoltageRangeRequest() + + // each inferior grid will send its voltage range to the center grid + voltageRangeRequester11 ! VoltageRangeResponse( + inferiorGrid11.ref, + ( + VoltageRange(0.05.asPu, (-0.05).asPu), + mvTransformers(transformer11.getUuid), + ), + ) + voltageRangeRequester12 ! VoltageRangeResponse( + inferiorGrid12.ref, + (VoltageRange(0.03.asPu, 0.asPu), mvTransformers(transformer12.getUuid)), + ) + voltageRangeRequester13 ! VoltageRangeResponse( + inferiorGrid13.ref, + ( + VoltageRange(0.06.asPu, (-0.04).asPu), + mvTransformers(transformer13_1.getUuid), + ), + ) + + // the superior grid should receive a voltage range from the center grid + val voltageDeltaRequest = superiorGridAgent.expectVoltageRangeResponse( + VoltageRange(0.03.asPu, 0.asPu) + ) + + // send a new delta to the center grid + voltageDeltaRequest ! VoltageDeltaResponse(0.01.asPu) + + inferiorGrid11.gaProbe + .expectMessageType[VoltageDeltaResponse] + .delta should equalWithTolerance(0.01.asPu) + inferiorGrid12.gaProbe + .expectMessageType[VoltageDeltaResponse] + .delta should equalWithTolerance(0.01.asPu) + inferiorGrid13.gaProbe + .expectMessageType[VoltageDeltaResponse] + .delta should equalWithTolerance(0.01.asPu) + + skipSimulation() cmStart() cmFinish() } - /** Method to reduce duplicate code - * @param midTest - * to check if this is in the middle of a test or at the beginning - */ - def skipSimulation(midTest: Boolean = false): Unit = { - if (!midTest) { - centerGridAgent ! WrappedActivation(Activation(3600)) - - // we expect a completion message - scheduler.expectMessageType[Completion].newTick shouldBe Some(3600) - } else { - inferiorGrid11.expectMessageType[RequestGridPower] - inferiorGrid12.expectMessageType[RequestGridPower] - inferiorGrid13.expectMessageType[RequestGridPower] - superiorGridAgent.expectMessageType[SlackVoltageRequest] - } + def goToSimulateGrid(): Unit = { + centerGridAgent ! WrappedActivation(Activation(3600)) + + // we expect a completion message + scheduler.expectMessageType[Completion].newTick shouldBe Some(3600) + } + + def skipSimulation(): Unit = { + inferiorGrid11.gaProbe.expectMessageType[RequestGridPower] + inferiorGrid12.gaProbe.expectMessageType[RequestGridPower] + inferiorGrid13.gaProbe.expectMessageType[RequestGridPower] + superiorGridAgent.gaProbe.expectMessageType[SlackVoltageRequest] // skip simulation and go to congestion check centerGridAgent ! FinishGridSimulationTrigger(3600) // inferior grid receives a FinishGridSimulationTrigger and goes into the congestion check state - inferiorGrid11.expectMessage(FinishGridSimulationTrigger(3600)) - inferiorGrid12.expectMessage(FinishGridSimulationTrigger(3600)) - inferiorGrid13.expectMessage(FinishGridSimulationTrigger(3600)) + inferiorGrid11.gaProbe.expectMessage(FinishGridSimulationTrigger(3600)) + inferiorGrid12.gaProbe.expectMessage(FinishGridSimulationTrigger(3600)) + inferiorGrid13.gaProbe.expectMessage(FinishGridSimulationTrigger(3600)) + } + + /** Method to reduce duplicate code. This runs a simple simulation based on + * the [[DBFSAlgorithmCenGridSpec]]. + */ + def simulateGrid(): Unit = { + // start the simulation + val firstSweepNo = 0 + + // send the start grid simulation trigger + centerGridAgent ! WrappedActivation(Activation(3600)) + + /* We expect one grid power request message per inferior grid */ + + val firstPowerRequestSender11 = inferiorGrid11.expectGridPowerRequest() + + val firstPowerRequestSender12 = inferiorGrid12.expectGridPowerRequest() + + val firstPowerRequestSender13 = inferiorGrid13.expectGridPowerRequest() + + // we expect a request for voltage values of two nodes + // (voltages are requested by our agent under test from the superior grid) + val firstSlackVoltageRequestSender = + superiorGridAgent.expectSlackVoltageRequest(firstSweepNo) + + // normally the inferior grid agents ask for the slack voltage as well to do their power flow calculations + // we simulate this behaviour now by doing the same for our three inferior grid agents + inferiorGrid11.requestSlackVoltage(centerGridAgent, firstSweepNo) + + inferiorGrid12.requestSlackVoltage(centerGridAgent, firstSweepNo) + + inferiorGrid13.requestSlackVoltage(centerGridAgent, firstSweepNo) + + // as we are in the first sweep, all provided slack voltages should be equal + // to 1 p.u. (in physical values, here: 110kV) from the superior grid agent perspective + // (here: centerGridAgent perspective) + inferiorGrid11.expectSlackVoltageProvision( + firstSweepNo, + Seq( + ExchangeVoltage( + node1.getUuid, + Kilovolts(110d), + Kilovolts(0d), + ) + ), + ) + + inferiorGrid12.expectSlackVoltageProvision( + firstSweepNo, + Seq( + ExchangeVoltage( + node2.getUuid, + Kilovolts(110d), + Kilovolts(0d), + ) + ), + ) + + inferiorGrid13.expectSlackVoltageProvision( + firstSweepNo, + Seq( + ExchangeVoltage( + node3.getUuid, + Kilovolts(110d), + Kilovolts(0d), + ), + ExchangeVoltage( + node4.getUuid, + Kilovolts(110d), + Kilovolts(0d), + ), + ), + ) + + // we now answer the request of our centerGridAgent + // with three fake grid power messages and one fake slack voltage message + + firstPowerRequestSender11 ! GridPowerResponse( + inferiorGrid11.nodeUuids.map(nodeUuid => + ExchangePower( + nodeUuid, + Megawatts(0.0), + Megavars(0.0), + ) + ) + ) + + firstPowerRequestSender12 ! GridPowerResponse( + inferiorGrid12.nodeUuids.map(nodeUuid => + ExchangePower( + nodeUuid, + Megawatts(0.0), + Megavars(0.0), + ) + ) + ) + + firstPowerRequestSender13 ! GridPowerResponse( + inferiorGrid13.nodeUuids.map(nodeUuid => + ExchangePower( + nodeUuid, + Megawatts(0.0), + Megavars(0.0), + ) + ) + ) + + firstSlackVoltageRequestSender ! SlackVoltageResponse( + firstSweepNo, + Seq( + ExchangeVoltage( + supNodeA.getUuid, + Kilovolts(380d), + Kilovolts(0d), + ), + ExchangeVoltage( + supNodeB.getUuid, + Kilovolts(380d), + Kilovolts(0d), + ), + ), + ) + + // power flow calculation should run now. After it's done, + // our test agent should now be ready to provide the grid power values, + // hence we ask for them and expect a corresponding response + superiorGridAgent.requestGridPower(centerGridAgent, firstSweepNo) + + superiorGridAgent.expectGridPowerProvision( + Seq( + ExchangePower( + supNodeA.getUuid, + Megawatts(0.0), + Megavars(0.0), + ), + ExchangePower( + supNodeB.getUuid, + Megawatts(0.160905770717798), + Megavars(-1.4535602349123878), + ), + ) + ) + + // we start a second sweep by asking for next sweep values which should trigger the whole procedure again + val secondSweepNo = 1 + + superiorGridAgent.requestGridPower(centerGridAgent, secondSweepNo) + + // the agent now should ask for updated slack voltages from the superior grid + val secondSlackAskSender = + superiorGridAgent.expectSlackVoltageRequest(secondSweepNo) + + // the superior grid would answer with updated slack voltage values + secondSlackAskSender ! SlackVoltageResponse( + secondSweepNo, + Seq( + ExchangeVoltage( + supNodeB.getUuid, + Kilovolts(374.22694614463d), // 380 kV @ 10° + Kilovolts(65.9863075134335d), // 380 kV @ 10° + ), + ExchangeVoltage( // this one should currently be ignored anyways + supNodeA.getUuid, + Kilovolts(380d), + Kilovolts(0d), + ), + ), + ) + + // After the intermediate power flow calculation, we expect one grid power + // request message per inferior subgrid + + val secondPowerRequestSender11 = + inferiorGrid11.expectGridPowerRequest() + + val secondPowerRequestSender12 = + inferiorGrid12.expectGridPowerRequest() + + val secondPowerRequestSender13 = + inferiorGrid13.expectGridPowerRequest() + + // normally the inferior grid agents ask for the slack voltage as well to do their power flow calculations + // we simulate this behaviour now by doing the same for our three inferior grid agents + + inferiorGrid11.requestSlackVoltage(centerGridAgent, secondSweepNo) + + inferiorGrid12.requestSlackVoltage(centerGridAgent, secondSweepNo) + + inferiorGrid13.requestSlackVoltage(centerGridAgent, secondSweepNo) + + // as we are in the second sweep, all provided slack voltages should be unequal + // to 1 p.u. (in physical values, here: 110kV) from the superior grid agent perspective + // (here: centerGridAgent perspective) + + inferiorGrid11.expectSlackVoltageProvision( + secondSweepNo, + Seq( + ExchangeVoltage( + node1.getUuid, + Kilovolts(108.487669651919932d), + Kilovolts(19.101878551141232d), + ) + ), + ) + + inferiorGrid12.expectSlackVoltageProvision( + secondSweepNo, + Seq( + ExchangeVoltage( + node2.getUuid, + Kilovolts(108.449088870497683d), + Kilovolts(19.10630456834157630d), + ) + ), + ) + + inferiorGrid13.expectSlackVoltageProvision( + secondSweepNo, + Seq( + ExchangeVoltage( + node3.getUuid, + Kilovolts(108.470028019077087d), + Kilovolts(19.104403047662570d), + ), + ExchangeVoltage( + node4.getUuid, + Kilovolts(108.482524607256866d), + Kilovolts(19.1025584700935336d), + ), + ), + ) + + // we now answer the requests of our centerGridAgent + // with three fake grid power message + + secondPowerRequestSender11 ! GridPowerResponse( + inferiorGrid11.nodeUuids.map(nodeUuid => + ExchangePower( + nodeUuid, + Megawatts(0.0), + Megavars(0.0), + ) + ) + ) + + secondPowerRequestSender12 ! GridPowerResponse( + inferiorGrid12.nodeUuids.map(nodeUuid => + ExchangePower( + nodeUuid, + Megawatts(0.0), + Megavars(0.0), + ) + ) + ) + + secondPowerRequestSender13 ! GridPowerResponse( + inferiorGrid13.nodeUuids.map(nodeUuid => + ExchangePower( + nodeUuid, + Megawatts(0.0), + Megavars(0.0), + ) + ) + ) + + // we expect that the GridAgent unstashes the messages and return a value for our power request + superiorGridAgent.expectGridPowerProvision( + Seq( + ExchangePower( + supNodeA.getUuid, + Megawatts(0.0), + Megavars(0.0), + ), + ExchangePower( + supNodeB.getUuid, + Megawatts(0.16090577067051856), + Megavars(-1.4535602358772026), + ), + ) + ) + + // normally the slack node would send a FinishGridSimulationTrigger to all + // connected inferior grids, because the slack node is just a mock, we imitate this behavior + centerGridAgent ! FinishGridSimulationTrigger(3600) + + // after a FinishGridSimulationTrigger is send the inferior grids, they themselves will send the + // Trigger forward the trigger to their connected inferior grids. Therefore the inferior grid + // agent should receive a FinishGridSimulationTrigger + inferiorGrid11.gaProbe.expectMessage(FinishGridSimulationTrigger(3600)) + + inferiorGrid12.gaProbe.expectMessage(FinishGridSimulationTrigger(3600)) + + inferiorGrid13.gaProbe.expectMessage(FinishGridSimulationTrigger(3600)) } /** Method to reduce duplicate code @@ -224,28 +552,34 @@ class DCMAlgorithmCenGridSpec // we expect a request for grid congestion values here val congestionCheckRequestSender11 = - inferiorGrid11.expectMessageType[CongestionCheckRequest] + inferiorGrid11.gaProbe + .expectMessageType[CongestionCheckRequest](10.seconds) + .sender val congestionCheckRequestSender12 = - inferiorGrid12.expectMessageType[CongestionCheckRequest] + inferiorGrid12.gaProbe + .expectMessageType[CongestionCheckRequest](10.seconds) + .sender val congestionCheckRequestSender13 = - inferiorGrid13.expectMessageType[CongestionCheckRequest] + inferiorGrid13.gaProbe + .expectMessageType[CongestionCheckRequest](10.seconds) + .sender // send congestions - congestionCheckRequestSender11.sender ! CongestionResponse( + congestionCheckRequestSender11 ! CongestionResponse( inferiorGrid11.ref, congestions, ) - congestionCheckRequestSender12.sender ! CongestionResponse( + congestionCheckRequestSender12 ! CongestionResponse( inferiorGrid12.ref, congestions, ) - congestionCheckRequestSender13.sender ! CongestionResponse( + congestionCheckRequestSender13 ! CongestionResponse( inferiorGrid13.ref, congestions, ) // we expect transformer congestions in the whole grid - val allCongestions = superiorGridAgent + val allCongestions = superiorGridAgent.gaProbe .expectMessageType[CongestionResponse](30.seconds) .value allCongestions shouldBe congestions @@ -258,9 +592,9 @@ class DCMAlgorithmCenGridSpec centerGridAgent ! GotoIdle // inferior should receive a next state message to go to the idle state - inferiorGrid11.expectMessageType[GotoIdle.type] - inferiorGrid12.expectMessageType[GotoIdle.type] - inferiorGrid13.expectMessageType[GotoIdle.type] + inferiorGrid11.gaProbe.expectMessageType[GotoIdle.type] + inferiorGrid12.gaProbe.expectMessageType[GotoIdle.type] + inferiorGrid13.gaProbe.expectMessageType[GotoIdle.type] // expect a completion message from the superior grid scheduler.expectMessageType[Completion] match { diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala index 6b06ffb105..caf691a16f 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala @@ -18,7 +18,7 @@ import edu.ie3.simona.agent.grid.GridAgentData.GridAgentInitData import edu.ie3.simona.agent.grid.GridAgentMessages._ import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.event.{ResultEvent, RuntimeEvent} -import edu.ie3.simona.model.grid.{RefSystem, VoltageLimits} +import edu.ie3.simona.model.grid.{RefSystem, TransformerModel, VoltageLimits} import edu.ie3.simona.ontology.messages.SchedulerMessage.{ Completion, ScheduleActivation, @@ -36,6 +36,8 @@ import org.apache.pekko.actor.testkit.typed.scaladsl.{ } import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps +import squants.electro.Kilovolts +import squants.energy.Kilowatts import scala.concurrent.duration.DurationInt @@ -177,11 +179,18 @@ class DCMAlgorithmSupGridSpec // inferior should receive a next state message to go to a congestion management step hvGrid.expectMessageType[NextStepRequest] + val tappingModel = TransformerModel( + transformer1, + RefSystem(Kilowatts(600), Kilovolts(110)), + start, + end, + ) + hvGrid.expectMessageType[RequestVoltageOptions] match { case RequestVoltageOptions(sender) => sender ! VoltageRangeResponse( hvGrid.ref, - VoltageRange(0.04.asPu, 0.asPu), + (VoltageRange(0.04.asPu, (-0.01).asPu), tappingModel), ) } diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala index 8e052fc08c..bc1a5ab9fd 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala @@ -13,23 +13,27 @@ import edu.ie3.datamodel.models.input.connector.`type`.{ LineTypeInput, Transformer2WTypeInput, } -import edu.ie3.datamodel.models.input.container.{ - JointGridContainer, - RawGridElements, -} +import edu.ie3.datamodel.models.input.container.RawGridElements import edu.ie3.datamodel.models.input.system.characteristic.OlmCharacteristicInput import edu.ie3.datamodel.models.input.{ MeasurementUnitInput, NodeInput, OperatorInput, } -import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils +import edu.ie3.datamodel.models.voltagelevels.{ + CommonVoltageLevel, + GermanVoltageLevelUtils, +} import edu.ie3.datamodel.utils.GridAndGeoUtils +import edu.ie3.simona.model.grid.{RefSystem, TransformerModel} import edu.ie3.simona.util.TestGridFactory import edu.ie3.util.quantities.PowerSystemUnits._ +import squants.electro.Kilovolts +import squants.energy.Kilowatts import tech.units.indriya.quantity.Quantities import tech.units.indriya.unit.Units._ +import java.time.ZonedDateTime import java.util.UUID import scala.collection.mutable import scala.jdk.CollectionConverters._ @@ -50,7 +54,7 @@ import scala.jdk.CollectionConverters._ * (3)----(4) * }}} */ -trait DbfsTestGrid extends SubGridGateMokka { +trait DbfsTestGrid extends SubGridGateMokka with GridComponentsMokka { // 4 HV nodes, 1 slack EHV node protected val node1 = new NodeInput( UUID.fromString("78c5d473-e01b-44c4-afd2-e4ff3c4a5d7c"), @@ -126,6 +130,26 @@ trait DbfsTestGrid extends SubGridGateMokka { * MS1_01 @ 11 -> 1676e48c-5353-4f06-b671-c579cf6a7072 @ 11 * MS3_01 @ 13 -> 9237e237-01e9-446f-899f-c3b5cf69d288 @ 13 */ + protected val node13_1: NodeInput = mockNode( + UUID.fromString("1129b00d-3d89-4a4a-8ae1-2a56041b95aa"), + 13, + GermanVoltageLevelUtils.MV_10KV, + ) + protected val node12: NodeInput = mockNode( + UUID.fromString("139c435d-e550-48d8-b590-ee897621f42a"), + 12, + GermanVoltageLevelUtils.MV_10KV, + ) + protected val node11: NodeInput = mockNode( + UUID.fromString("1676e48c-5353-4f06-b671-c579cf6a7072"), + 11, + GermanVoltageLevelUtils.MV_10KV, + ) + protected val node13_2: NodeInput = mockNode( + UUID.fromString("9237e237-01e9-446f-899f-c3b5cf69d288"), + 13, + GermanVoltageLevelUtils.MV_10KV, + ) // 5 lines between the nodes protected val lineType1 = new LineTypeInput( @@ -260,6 +284,23 @@ trait DbfsTestGrid extends SubGridGateMokka { -5, 5, ) + private val trafoType10kV = new Transformer2WTypeInput( + UUID.randomUUID(), + "HV-10kV", + Quantities.getQuantity(5.415, OHM), + Quantities.getQuantity(108.165, OHM), + Quantities.getQuantity(200000.0, KILOVOLTAMPERE), + Quantities.getQuantity(110.0, KILOVOLT), + Quantities.getQuantity(10.0, KILOVOLT), + Quantities.getQuantity(555.5, NANOSIEMENS), + Quantities.getQuantity(-1.27, NANOSIEMENS), + Quantities.getQuantity(1.5, PERCENT), + Quantities.getQuantity(0, RADIAN), + false, + 0, + -5, + 5, + ) protected val transformer1 = new Transformer2WInput( UUID.fromString("6e9d912b-b652-471b-84d2-6ed571e53a7b"), @@ -285,6 +326,71 @@ trait DbfsTestGrid extends SubGridGateMokka { 0, true, ) + protected val transformer11 = new Transformer2WInput( + UUID.randomUUID(), + "HV-MV-Trafo_11", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + node1, + node11, + 1, + trafoType10kV, + 0, + false, + ) + protected val transformer12 = new Transformer2WInput( + UUID.randomUUID(), + "HV-MV-Trafo_12", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + node2, + node12, + 1, + trafoType10kV, + 0, + false, + ) + protected val transformer13_1 = new Transformer2WInput( + UUID.randomUUID(), + "HV-MV-Trafo_13_1", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + node4, + node13_1, + 1, + trafoType10kV, + 0, + false, + ) + protected val transformer13_2 = new Transformer2WInput( + UUID.randomUUID(), + "HV-MV-Trafo_13_2", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + node4, + node13_2, + 1, + trafoType10kV, + 0, + false, + ) + + protected val start: ZonedDateTime = ZonedDateTime.now() + protected val end: ZonedDateTime = start.plusHours(3) + + protected val mvTransformers: Map[UUID, TransformerModel] = Seq( + transformer11, + transformer12, + transformer13_1, + transformer13_2, + ).map { model => + model.getUuid -> TransformerModel( + model, + RefSystem(Kilowatts(30), Kilovolts(10)), + start, + end, + ) + }.toMap protected val (hvGridContainer, hvSubGridGates) = { // LinkedHashSet in order to preserve the given order. @@ -312,30 +418,10 @@ trait DbfsTestGrid extends SubGridGateMokka { SubGridGate.fromTransformer3W(transformer, ConnectorPort.C), ) ) ++ Seq( - build2wSubGridGate( - node4.getUuid, - 1, - UUID.fromString("1129b00d-3d89-4a4a-8ae1-2a56041b95aa"), - 13, - ), - build2wSubGridGate( - node2.getUuid, - 1, - UUID.fromString("139c435d-e550-48d8-b590-ee897621f42a"), - 12, - ), - build2wSubGridGate( - node1.getUuid, - 1, - UUID.fromString("1676e48c-5353-4f06-b671-c579cf6a7072"), - 11, - ), - build2wSubGridGate( - node3.getUuid, - 1, - UUID.fromString("9237e237-01e9-446f-899f-c3b5cf69d288"), - 13, - ), + new SubGridGate(transformer13_1, node4, node13_1), + new SubGridGate(transformer12, node2, node12), + new SubGridGate(transformer11, node1, node11), + new SubGridGate(transformer13_2, node3, node13_2), ) ( diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala index 6e2f8e0905..b6f6beac44 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala @@ -12,11 +12,14 @@ import edu.ie3.simona.model.grid.{ Transformer3wModel, TransformerModel, } +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import org.mockito.Mockito.when import org.scalatestplus.mockito.MockitoSugar import squants.Amperes +import tech.units.indriya.ComparableQuantity import java.util.UUID +import javax.measure.quantity.Dimensionless /** Hold my cup of coffee and let me mock you some models. */ @@ -45,9 +48,23 @@ trait GridComponentsMokka extends MockitoSugar { line } - protected def mockTransformerModel(uuid: UUID): TransformerModel = { + protected def mockTransformerModel( + uuid: UUID = UUID.randomUUID(), + autoTap: Boolean = false, + tapMax: Int = 5, + tapMin: Int = -5, + currentTapPos: Int = 0, + deltaV: ComparableQuantity[Dimensionless] = 0.015.asPu, + ): TransformerModel = { val transformer = mock[TransformerModel] when(transformer.uuid).thenReturn(uuid) + + when(transformer.hasAutoTap).thenReturn(autoTap) + when(transformer.tapMax).thenReturn(tapMax) + when(transformer.tapMin).thenReturn(tapMin) + when(transformer.currentTapPos).thenReturn(currentTapPos) + when(transformer.deltaV).thenReturn(deltaV) + transformer } diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/SubGridGateMokka.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/SubGridGateMokka.scala index 16337af6bd..001cb12e4f 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/SubGridGateMokka.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/SubGridGateMokka.scala @@ -6,25 +6,18 @@ package edu.ie3.simona.test.common.model.grid -import java.util.UUID import edu.ie3.datamodel.graph.SubGridGate import edu.ie3.datamodel.models.input.NodeInput -import edu.ie3.datamodel.models.input.connector.`type`.{ - Transformer2WTypeInput, - Transformer3WTypeInput, -} import edu.ie3.datamodel.models.input.connector.{ ConnectorPort, Transformer2WInput, Transformer3WInput, } -import edu.ie3.simona.model.grid.{Transformer3wModel, TransformerModel} -import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import edu.ie3.datamodel.models.voltagelevels.VoltageLevel import org.mockito.Mockito._ import org.scalatestplus.mockito.MockitoSugar -import tech.units.indriya.ComparableQuantity -import javax.measure.quantity.{ElectricPotential, Power} +import java.util.UUID /** Hold my cup of coffee and let me mock you some models. */ @@ -46,6 +39,29 @@ trait SubGridGateMokka extends MockitoSugar { node } + /** Mocks a node with it's basic needed information + * + * @param uuid + * Unique identifier of the node + * @param subnet + * Sub net number + * @param voltLvl + * [[VoltageLevel]] of the node + * @return + * [[NodeInput]] with these information + */ + protected def mockNode( + uuid: UUID, + subnet: Int, + voltLvl: VoltageLevel, + ): NodeInput = { + val node = mock[NodeInput] + when(node.getUuid).thenReturn(uuid) + when(node.getSubnet).thenReturn(subnet) + when(node.getVoltLvl).thenReturn(voltLvl) + node + } + /** Mocks a transformer, that only holds information on what nodes are * connected * From 8589f13eb881c5b4318977e5ca0c5cbca0615925 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 26 Apr 2024 16:14:32 +0200 Subject: [PATCH 15/55] Fixing some issues. --- .../grid/CongestionManagementSupport.scala | 2 +- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 107 ++++++++++++------ 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 07c62b3fe0..ffd30dd17a 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -251,7 +251,7 @@ object CongestionManagementSupport { deltaMinus.isLessThanOrEqualTo(0.asPu), ) match { case (true, true) => - // calculate ann equal distance to 1 pu + // calculate an equal distance to 1 pu deltaPlus.add(deltaMinus) case (false, true) => // violation of the upper voltage limit diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 2d4e0a3132..2d8c17e94e 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -245,13 +245,22 @@ trait DCMAlgorithm extends CongestionManagementSupport { (gridComponents.transformers ++ gridComponents.transformers3w) .map(t => t.uuid -> t) .toMap - val transformersToSup = gridEnv.subgridGateToActorRef - .find(_._2 == sender) + + val voltage = gridModel.mainRefSystem.nominalVoltage.value.asKiloVolt + + val transformerToSup = gridEnv.subgridGateToActorRef + .find( + _._1 + .superiorNode() + .getVoltLvl + .getNominalVoltage + .isGreaterThan(voltage) + ) .map(_._1.link().getUuid) .getOrElse( throw new IllegalArgumentException(s"No superior actor ref found!") ) - val transformer = transformers(transformersToSup) + val transformer = transformers(transformerToSup) val range = calculateVoltageOptions( stateData.powerFlowResults, @@ -260,6 +269,10 @@ trait DCMAlgorithm extends CongestionManagementSupport { awaitingData.mappedValues, ) + ctx.log.warn( + s"For Grid ${stateData.gridAgentBaseData.gridEnv.gridModel.subnetNo}, range: $range" + ) + sender ! VoltageRangeResponse( ctx.self, (range, transformer), @@ -287,54 +300,74 @@ trait DCMAlgorithm extends CongestionManagementSupport { // if we are the superior grid to another grid, we check for transformer tapping option // and send the new delta to the inferior grid + ctx.log.warn( + s"For Grid ${stateData.gridAgentBaseData.gridEnv.gridModel.subnetNo}, received delta: $delta" + ) + if (stateData.inferiorRefs.nonEmpty) { // we calculate a voltage delta for all inferior grids - awaitingData.mappedValues.groupBy(_._2._2).foreach { - case (tappingModel, refMap) => - val inferiorRanges: Map[ActorRef[GridAgent.Request], VoltageRange] = - refMap.map { case (value, (range, _)) => - value -> range - } + val receivedData = awaitingData.mappedValues + + val refMap = receivedData.map { case (ref, (range, _)) => + ref -> range + } - val refs: Set[ActorRef[GridAgent.Request]] = inferiorRanges.keySet + val tappingModels = receivedData + .map { case (value, (_, tapping)) => + (value, tapping) + } + .groupBy(_._2) + .map { case (tapping, value) => + tapping -> value.keySet + } - if (tappingModel.hasAutoTap) { - // the given transformer can be tapped, calculate the new tap pos + tappingModels.foreach { case (tappingModel, refs) => + val inferiorRanges = refs.map(refMap) - val suggestion = - VoltageRange - .combineSuggestions(inferiorRanges.values.toSet) - .subtract(delta) + if (tappingModel.hasAutoTap) { + // the given transformer can be tapped, calculate the new tap pos - val tapOption = tappingModel.computeDeltaTap(suggestion) - val deltaV = if (tapOption == 0) { - // we can not change the voltage as we would like to - if (suggestion.isLessThan(0.asPu)) { - // if suggestion < 0, we decrease the voltage as much as we can + val suggestion = + VoltageRange + .combineSuggestions(inferiorRanges) + .subtract(delta) - val tapChange = tappingModel.maxTapDecrease - tappingModel.decrTapPos(tapChange) + val tapOption = tappingModel.computeDeltaTap(suggestion) + val deltaV = if (tapOption == 0) { + // we can not change the voltage as we would like to + if (suggestion.isLessThan(0.asPu)) { + // if suggestion < 0, we decrease the voltage as much as we can - tappingModel.deltaV.multiply(tapChange) - } else { - // we increase the voltage as much as we can - val tapChange = tappingModel.maxTapIncrease - tappingModel.decrTapPos(tapChange) + val tapChange = tappingModel.maxTapDecrease + tappingModel.decrTapPos(tapChange) - tappingModel.deltaV.multiply(tapChange) - } + tappingModel.deltaV.multiply(tapChange) } else { - // we can change the voltage without a problem - tappingModel.updateTapPos(tapOption) - tappingModel.deltaV.multiply(tapOption) - } + // we increase the voltage as much as we can + val tapChange = tappingModel.maxTapIncrease + tappingModel.decrTapPos(tapChange) - refs.foreach(_ ! VoltageDeltaResponse(deltaV.divide(100))) + tappingModel.deltaV.multiply(tapChange) + } } else { - // no tapping possible, just send the delta to the inferior grid - refs.foreach(_ ! VoltageDeltaResponse(delta)) + // we can change the voltage without a problem + tappingModel.updateTapPos(tapOption) + tappingModel.deltaV.multiply(tapOption) } + + ctx.log.warn( + s"For inferior grids $refs, " + + s"suggestion: $suggestion, delta: ${deltaV.divide(100)}, " + + s"maxIncrease: ${tappingModel.deltaV.multiply(tappingModel.maxTapIncrease).divide(100)}," + + s"maxDecrease: ${tappingModel.deltaV.multiply(tappingModel.maxTapDecrease).divide(100)}" + ) + + refs.foreach(_ ! VoltageDeltaResponse(deltaV.divide(100))) + } else { + // no tapping possible, just send the delta to the inferior grid + refs.foreach(_ ! VoltageDeltaResponse(delta)) + } } } From 41cdb432747151b6344c0219922947350cc2371d Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 26 Apr 2024 17:58:29 +0200 Subject: [PATCH 16/55] Fixing some issues. --- .../grid/CongestionManagementSupport.scala | 25 +++++++++++- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 39 +++++++------------ .../CongestionManagementSupportSpec.scala | 24 ++++++++++++ 3 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index ffd30dd17a..e972947452 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -29,6 +29,29 @@ import javax.measure.quantity.Dimensionless */ trait CongestionManagementSupport { + def calculateTapAndVoltage( + suggestion: ComparableQuantity[Dimensionless], + delta: ComparableQuantity[Dimensionless], + currentTap: Int, + tapMax: Int, + tapMin: Int, + ): (Int, ComparableQuantity[Dimensionless]) = { + val tapNeeded = suggestion.divide(delta).multiply(-1).getValue.doubleValue() + + val increase = tapMax - currentTap + val decrease = tapMin - currentTap + + val taps = if (tapNeeded >= increase) { + increase + } else if (tapNeeded <= decrease) { + decrease + } else { + tapNeeded.round.toInt + } + + (taps, delta.multiply(taps * -1)) + } + /** Method to calculate the range of possible voltage changes. * * @param powerFlowResultEvent @@ -193,7 +216,7 @@ object CongestionManagementSupport { ], (VoltageRange, TransformerTapping)] ): VoltageRange = { - inferiorData.foldLeft(this) { case (range, (ref, (infRange, tapping))) => + inferiorData.foldLeft(this) { case (range, (_, (infRange, tapping))) => if (tapping.hasAutoTap) { val currentPos = tapping.currentTapPos val deltaV = tapping.deltaV diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 2d8c17e94e..fbaa89baba 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -57,7 +57,6 @@ trait DCMAlgorithm extends CongestionManagementSupport { constantData: GridAgentConstantData, buffer: StashBuffer[GridAgent.Request], ): Behavior[GridAgent.Request] = Behaviors.receivePartial { - case (ctx, StartStep) => // request congestion check if we have inferior grids askInferior( @@ -301,7 +300,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { // and send the new delta to the inferior grid ctx.log.warn( - s"For Grid ${stateData.gridAgentBaseData.gridEnv.gridModel.subnetNo}, received delta: $delta" + s"Grid ${stateData.gridAgentBaseData.gridEnv.gridModel.subnetNo}, received delta: $delta" ) if (stateData.inferiorRefs.nonEmpty) { @@ -333,37 +332,25 @@ trait DCMAlgorithm extends CongestionManagementSupport { .combineSuggestions(inferiorRanges) .subtract(delta) - val tapOption = tappingModel.computeDeltaTap(suggestion) - val deltaV = if (tapOption == 0) { - // we can not change the voltage as we would like to - if (suggestion.isLessThan(0.asPu)) { - // if suggestion < 0, we decrease the voltage as much as we can - - val tapChange = tappingModel.maxTapDecrease - tappingModel.decrTapPos(tapChange) - - tappingModel.deltaV.multiply(tapChange) - } else { - // we increase the voltage as much as we can - val tapChange = tappingModel.maxTapIncrease - tappingModel.decrTapPos(tapChange) + val (tapChange, deltaV) = calculateTapAndVoltage( + suggestion, + tappingModel.deltaV.divide(100), + tappingModel.currentTapPos, + tappingModel.tapMax, + tappingModel.tapMin, + ) - tappingModel.deltaV.multiply(tapChange) - } + if (tapChange > 0) { + tappingModel.incrTapPos(tapChange) } else { - // we can change the voltage without a problem - tappingModel.updateTapPos(tapOption) - tappingModel.deltaV.multiply(tapOption) + tappingModel.decrTapPos(tapChange * -1) } ctx.log.warn( - s"For inferior grids $refs, " + - s"suggestion: $suggestion, delta: ${deltaV.divide(100)}, " + - s"maxIncrease: ${tappingModel.deltaV.multiply(tappingModel.maxTapIncrease).divide(100)}," + - s"maxDecrease: ${tappingModel.deltaV.multiply(tappingModel.maxTapDecrease).divide(100)}" + s"For inferior grids $refs, suggestion: $suggestion, delta: $deltaV" ) - refs.foreach(_ ! VoltageDeltaResponse(deltaV.divide(100))) + refs.foreach(_ ! VoltageDeltaResponse(deltaV)) } else { // no tapping possible, just send the delta to the inferior grid refs.foreach(_ ! VoltageDeltaResponse(delta)) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index 4534acbde9..f8fe65fc93 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -45,6 +45,30 @@ class CongestionManagementSupportSpec val inferiorGrids: Seq[ActorRef[GridAgent.Request]] = Seq(inferior1.ref, inferior2.ref) + "calculate the tap and voltage change" in { + val currentTap = 1 + val tapMax = 5 + val tapMin = -5 + val delta = 0.015.asPu // 15 % per tap + + val cases = Table( + ("suggestion", "expectedTap", "expectedDelta"), + (0.02.asPu, -1, 0.015.asPu), + ((-0.02).asPu, 1, (-0.015).asPu), + (0.031.asPu, -2, 0.03.asPu), + (0.05.asPu, -3, 0.045.asPu), + ((-0.06).asPu, 4, (-0.06).asPu), + ) + + forAll(cases) { (suggestion, expectedTap, expectedDelta) => + val (actualTap, actualDelta) = + calculateTapAndVoltage(suggestion, delta, currentTap, tapMax, tapMin) + + actualTap shouldBe expectedTap + actualDelta should equalWithTolerance(expectedDelta) + } + } + "calculates the possible voltage delta for lines correctly" in { val node1 = nodeModel() val node2 = nodeModel() From 838d151f5d784200d47dba7367bf80ea1911f8c8 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 26 Apr 2024 21:53:39 +0200 Subject: [PATCH 17/55] Fixing some issues. --- .../grid/CongestionManagementSupport.scala | 69 +++++++--------- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 15 ++-- .../model/grid/TransformerTapping.scala | 2 +- .../CongestionManagementSupportSpec.scala | 82 +++++++++++++------ .../agent/grid/DCMAlgorithmCenGridSpec.scala | 19 +++-- .../test/common/model/grid/DbfsTestGrid.scala | 9 +- .../model/grid/GridComponentsMokka.scala | 11 +-- 7 files changed, 117 insertions(+), 90 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index e972947452..55241f15f4 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -31,25 +31,11 @@ trait CongestionManagementSupport { def calculateTapAndVoltage( suggestion: ComparableQuantity[Dimensionless], - delta: ComparableQuantity[Dimensionless], - currentTap: Int, - tapMax: Int, - tapMin: Int, + tapping: TransformerTapping, ): (Int, ComparableQuantity[Dimensionless]) = { - val tapNeeded = suggestion.divide(delta).multiply(-1).getValue.doubleValue() - - val increase = tapMax - currentTap - val decrease = tapMin - currentTap - - val taps = if (tapNeeded >= increase) { - increase - } else if (tapNeeded <= decrease) { - decrease - } else { - tapNeeded.round.toInt - } - - (taps, delta.multiply(taps * -1)) + val taps = tapping.computeDeltaTap(suggestion) + val delta = tapping.deltaV.getValue.doubleValue() * taps / 100 + (taps, delta.asPu) } /** Method to calculate the range of possible voltage changes. @@ -89,20 +75,22 @@ trait CongestionManagementSupport { ) // updating the voltage range prevent or cure line congestions + /* val deltaV = calculatePossibleVoltageDeltaForLines( nodeResMap, powerFlowResultEvent.lineResults, gridComponents, ) val updatedRange = range.updateWithLineDelta(deltaV) + */ if (inferiorData.isEmpty) { // if there are no inferior grids, return the voltage range - updatedRange + range } else { // if there are inferior grids, update the voltage range - updatedRange.updateWithInferiorRanges(inferiorData) + range.updateWithInferiorRanges(inferiorData) } } @@ -269,35 +257,36 @@ object CongestionManagementSupport { deltaMinus: ComparableQuantity[Dimensionless], ): VoltageRange = { - val suggestion = ( - deltaPlus.isGreaterThanOrEqualTo(0.asPu), - deltaMinus.isLessThanOrEqualTo(0.asPu), - ) match { + val plus = deltaPlus.getValue.doubleValue() + val minus = deltaMinus.getValue.doubleValue() + + val value = (plus > 0, minus < 0) match { case (true, true) => - // calculate an equal distance to 1 pu - deltaPlus.add(deltaMinus) + (plus - minus) / 2 + case (false, true) if plus > minus => + (plus.abs + minus.abs) / -2 case (false, true) => - // violation of the upper voltage limit - if (deltaPlus.isGreaterThan(deltaMinus)) { - // if deltaPlus > deltaMinus, we can decrease the voltage by deltaPlus - deltaPlus - } else deltaMinus + plus + case (true, false) if plus > minus => + (plus + minus) / 2 case (true, false) => - // violation of the upper voltage limit - - if (deltaMinus.isLessThan(deltaPlus)) { - // if deltaMinus < deltaPlus, we can increase the voltage by deltaMinus - deltaMinus - } else deltaPlus + minus case (false, false) => - // violation of both limit - deltaPlus + plus + } + + val factor = 1e3 + + val suggestion = if (value < 0) { + (value * factor).floor / factor + } else { + (value * factor).ceil / factor } VoltageRange( deltaPlus, deltaMinus, - suggestion, + suggestion.asPu, ) } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index fbaa89baba..7ca9b89840 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -247,6 +247,8 @@ trait DCMAlgorithm extends CongestionManagementSupport { val voltage = gridModel.mainRefSystem.nominalVoltage.value.asKiloVolt + // TODO: Consider two transformers connected to different nodes in subgrid + val transformerToSup = gridEnv.subgridGateToActorRef .find( _._1 @@ -321,6 +323,8 @@ trait DCMAlgorithm extends CongestionManagementSupport { tapping -> value.keySet } + // TODO: Consider two transformers connected to different nodes in subgrid + tappingModels.foreach { case (tappingModel, refs) => val inferiorRanges = refs.map(refMap) @@ -334,23 +338,20 @@ trait DCMAlgorithm extends CongestionManagementSupport { val (tapChange, deltaV) = calculateTapAndVoltage( suggestion, - tappingModel.deltaV.divide(100), - tappingModel.currentTapPos, - tappingModel.tapMax, - tappingModel.tapMin, + tappingModel, ) if (tapChange > 0) { - tappingModel.incrTapPos(tapChange) + tappingModel.decrTapPos(tapChange) } else { - tappingModel.decrTapPos(tapChange * -1) + tappingModel.incrTapPos(tapChange) } ctx.log.warn( s"For inferior grids $refs, suggestion: $suggestion, delta: $deltaV" ) - refs.foreach(_ ! VoltageDeltaResponse(deltaV)) + refs.foreach(_ ! VoltageDeltaResponse(deltaV.add(delta))) } else { // no tapping possible, just send the delta to the inferior grid refs.foreach(_ ! VoltageDeltaResponse(delta)) diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala index f1964cb56e..6eb18098a4 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala @@ -42,7 +42,7 @@ trait TransformerTapping { def maxTapIncrease: Int = tapMax - currentTapPos - def maxTapDecrease: Int = currentTapPos - tapMin + def maxTapDecrease: Int = tapMin - currentTapPos def currentTapPos: Int = transformerTappingModel.currentTapPos diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index f8fe65fc93..82e538d189 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -11,7 +11,11 @@ import edu.ie3.datamodel.models.result.connector.LineResult import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.model.grid.GridModel.GridComponents -import edu.ie3.simona.model.grid.VoltageLimits +import edu.ie3.simona.model.grid.{ + TransformerModel, + TransformerTappingModel, + VoltageLimits, +} import edu.ie3.simona.test.common.UnitSpec import edu.ie3.simona.test.common.model.grid.{ GridComponentsMokka, @@ -19,11 +23,14 @@ import edu.ie3.simona.test.common.model.grid.{ } import edu.ie3.simona.test.common.result.ResultMokka import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import edu.ie3.util.scala.OperationInterval import org.apache.pekko.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe, } -import org.apache.pekko.actor.typed.ActorRef +import squants.{Amperes, Each} + +import java.util.UUID class CongestionManagementSupportSpec extends ScalaTestWithActorTestKit @@ -42,27 +49,47 @@ class CongestionManagementSupportSpec "CongestionManagementSupport" should { - val inferiorGrids: Seq[ActorRef[GridAgent.Request]] = - Seq(inferior1.ref, inferior2.ref) - "calculate the tap and voltage change" in { - val currentTap = 1 - val tapMax = 5 - val tapMin = -5 - val delta = 0.015.asPu // 15 % per tap + val tappingModel = TransformerTappingModel( + deltaV = 1.5.asPercent, + currentTapPos = 1, + tapMax = 5, + tapMin = -5, + tapNeutr = 0, + autoTap = true, + ) + + val tapping = TransformerModel( + UUID.randomUUID(), + id = "dummy", + operationInterval = OperationInterval(0L, 1L), + hvNodeUuid = UUID.randomUUID(), + lvNodeUuid = UUID.randomUUID(), + tappingModel, + amount = 1, + voltRatioNominal = BigDecimal(110), + iNomHv = Amperes(1), + iNomLv = Amperes(10), + r = Each(1), + x = Each(1), + g = Each(1), + b = Each(1), + ) val cases = Table( ("suggestion", "expectedTap", "expectedDelta"), - (0.02.asPu, -1, 0.015.asPu), - ((-0.02).asPu, 1, (-0.015).asPu), - (0.031.asPu, -2, 0.03.asPu), - (0.05.asPu, -3, 0.045.asPu), - ((-0.06).asPu, 4, (-0.06).asPu), + (0.02.asPu, 1, 0.015.asPu), + ((-0.02).asPu, -1, (-0.015).asPu), + (0.031.asPu, 2, 0.03.asPu), + (0.05.asPu, 3, 0.045.asPu), + ((-0.06).asPu, -4, (-0.06).asPu), + ((-0.1).asPu, -6, (-0.09).asPu), // max decrease + (0.1.asPu, 4, 0.06.asPu), // max increase ) forAll(cases) { (suggestion, expectedTap, expectedDelta) => val (actualTap, actualDelta) = - calculateTapAndVoltage(suggestion, delta, currentTap, tapMax, tapMin) + calculateTapAndVoltage(suggestion, tapping) actualTap shouldBe expectedTap actualDelta should equalWithTolerance(expectedDelta) @@ -171,7 +198,7 @@ class CongestionManagementSupportSpec range.deltaPlus should equalWithTolerance(0.05.asPu) range.deltaMinus should equalWithTolerance((-0.03).asPu) - range.suggestion should equalWithTolerance(0.02.asPu) + range.suggestion should equalWithTolerance(0.041.asPu) } "calculates the voltage range for a middle grid correctly" in { @@ -194,6 +221,7 @@ class CongestionManagementSupportSpec val tappingModel = mockTransformerModel( autoTap = true, + currentTapPos = 0, tapMax = 3, tapMin = -3, deltaV = 0.01.asPu, @@ -225,7 +253,7 @@ class CongestionManagementSupportSpec range.deltaPlus should equalWithTolerance(0.04.asPu) range.deltaMinus should equalWithTolerance((-0.02).asPu) - range.suggestion should equalWithTolerance(0.02.asPu) + range.suggestion should equalWithTolerance(0.041.asPu) } def buildPowerFlowResultEvent( @@ -248,16 +276,16 @@ class CongestionManagementSupportSpec "calculate the suggestion correctly" in { val cases = Table( ("deltaPlus", "deltaMinus", "expected"), - (0.05.asPu, (-0.05).asPu, 0.asPu), // no voltage limit violation + (0.05.asPu, (-0.03).asPu, 0.04.asPu), // no voltage limit violation ( (-0.01).asPu, (-0.02).asPu, - (-0.01).asPu, + (-0.015).asPu, ), // upper voltage limit violation, decreasing voltage ( 0.02.asPu, 0.01.asPu, - 0.01.asPu, + 0.015.asPu, ), // lower voltage limit violation, increasing voltage ( (-0.01).asPu, @@ -267,10 +295,12 @@ class CongestionManagementSupportSpec ) forAll(cases) { (deltaPlus, deltaMinus, expected) => - VoltageRange( + val suggestion = VoltageRange( deltaPlus, deltaMinus, - ).suggestion should equalWithTolerance(expected) + ).suggestion + + suggestion should equalWithTolerance(expected) } } @@ -330,7 +360,13 @@ class CongestionManagementSupportSpec val range = VoltageRange(0.05.asPu, (-0.05).asPu) val tappingModel = - mockTransformerModel(tapMax = 10, tapMin = -10, deltaV = 0.01.asPu) + mockTransformerModel( + autoTap = false, + currentTapPos = 0, + tapMax = 10, + tapMin = -10, + deltaV = 0.01.asPu, + ) val cases = Table( ("range1", "range2", "expected"), diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index 882b48ddbe..41da3d398a 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -183,39 +183,42 @@ class DCMAlgorithmCenGridSpec voltageRangeRequester11 ! VoltageRangeResponse( inferiorGrid11.ref, ( - VoltageRange(0.05.asPu, (-0.05).asPu), + VoltageRange((-0.01).asPu, (-0.02).asPu), mvTransformers(transformer11.getUuid), ), ) voltageRangeRequester12 ! VoltageRangeResponse( inferiorGrid12.ref, - (VoltageRange(0.03.asPu, 0.asPu), mvTransformers(transformer12.getUuid)), + ( + VoltageRange(0.07.asPu, 0.01.asPu), + mvTransformers(transformer12.getUuid), + ), ) voltageRangeRequester13 ! VoltageRangeResponse( inferiorGrid13.ref, ( - VoltageRange(0.06.asPu, (-0.04).asPu), + VoltageRange(0.06.asPu, 0.asPu), mvTransformers(transformer13_1.getUuid), ), ) // the superior grid should receive a voltage range from the center grid val voltageDeltaRequest = superiorGridAgent.expectVoltageRangeResponse( - VoltageRange(0.03.asPu, 0.asPu) + VoltageRange(0.06.asPu, 0.01.asPu, 0.03.asPu) ) // send a new delta to the center grid - voltageDeltaRequest ! VoltageDeltaResponse(0.01.asPu) + voltageDeltaRequest ! VoltageDeltaResponse(0.04.asPu) inferiorGrid11.gaProbe .expectMessageType[VoltageDeltaResponse] - .delta should equalWithTolerance(0.01.asPu) + .delta should equalWithTolerance((-0.01).asPu) inferiorGrid12.gaProbe .expectMessageType[VoltageDeltaResponse] - .delta should equalWithTolerance(0.01.asPu) + .delta should equalWithTolerance(0.04.asPu) inferiorGrid13.gaProbe .expectMessageType[VoltageDeltaResponse] - .delta should equalWithTolerance(0.01.asPu) + .delta should equalWithTolerance(0.04.asPu) skipSimulation() cmStart() diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala index bc1a5ab9fd..56e5f43814 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala @@ -20,10 +20,7 @@ import edu.ie3.datamodel.models.input.{ NodeInput, OperatorInput, } -import edu.ie3.datamodel.models.voltagelevels.{ - CommonVoltageLevel, - GermanVoltageLevelUtils, -} +import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils import edu.ie3.datamodel.utils.GridAndGeoUtils import edu.ie3.simona.model.grid.{RefSystem, TransformerModel} import edu.ie3.simona.util.TestGridFactory @@ -294,7 +291,7 @@ trait DbfsTestGrid extends SubGridGateMokka with GridComponentsMokka { Quantities.getQuantity(10.0, KILOVOLT), Quantities.getQuantity(555.5, NANOSIEMENS), Quantities.getQuantity(-1.27, NANOSIEMENS), - Quantities.getQuantity(1.5, PERCENT), + Quantities.getQuantity(1, PERCENT), Quantities.getQuantity(0, RADIAN), false, 0, @@ -336,7 +333,7 @@ trait DbfsTestGrid extends SubGridGateMokka with GridComponentsMokka { 1, trafoType10kV, 0, - false, + true, ) protected val transformer12 = new Transformer2WInput( UUID.randomUUID(), diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala index b6f6beac44..6c3cae1ca1 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala @@ -11,6 +11,7 @@ import edu.ie3.simona.model.grid.{ NodeModel, Transformer3wModel, TransformerModel, + TransformerTappingModel, } import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import org.mockito.Mockito.when @@ -50,11 +51,11 @@ trait GridComponentsMokka extends MockitoSugar { protected def mockTransformerModel( uuid: UUID = UUID.randomUUID(), - autoTap: Boolean = false, - tapMax: Int = 5, - tapMin: Int = -5, - currentTapPos: Int = 0, - deltaV: ComparableQuantity[Dimensionless] = 0.015.asPu, + autoTap: Boolean, + tapMax: Int, + tapMin: Int, + currentTapPos: Int, + deltaV: ComparableQuantity[Dimensionless], ): TransformerModel = { val transformer = mock[TransformerModel] when(transformer.uuid).thenReturn(uuid) From 7ebd70c76d2aabca79006a1f5255e4c45093f1b7 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 26 Apr 2024 23:30:13 +0200 Subject: [PATCH 18/55] Enhancing transformer tapping. --- .../grid/CongestionManagementSupport.scala | 48 ++++++++++++++----- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 23 ++++----- .../simona/agent/grid/GridAgentMessages.scala | 8 ++-- .../CongestionManagementSupportSpec.scala | 18 ++++--- .../agent/grid/DBFSMockGridAgents.scala | 1 - .../agent/grid/DCMAlgorithmCenGridSpec.scala | 9 ++-- .../agent/grid/DCMAlgorithmSupGridSpec.scala | 18 +++++-- .../test/common/model/grid/DbfsTestGrid.scala | 4 +- 8 files changed, 81 insertions(+), 48 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 55241f15f4..f8ba0f0ac7 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -31,11 +31,19 @@ trait CongestionManagementSupport { def calculateTapAndVoltage( suggestion: ComparableQuantity[Dimensionless], - tapping: TransformerTapping, + tappings: Seq[TransformerTapping], ): (Int, ComparableQuantity[Dimensionless]) = { - val taps = tapping.computeDeltaTap(suggestion) - val delta = tapping.deltaV.getValue.doubleValue() * taps / 100 - (taps, delta.asPu) + + if (tappings.size == 1) { + val tapping = tappings(0) + + val taps = tapping.computeDeltaTap(suggestion) + val delta = tapping.deltaV.getValue.doubleValue() * taps / 100 + (taps, delta.asPu) + } else { + // TODO: Consider two transformers connected to different nodes in subgrid + (0, 0.asPu) + } } /** Method to calculate the range of possible voltage changes. @@ -56,7 +64,7 @@ trait CongestionManagementSupport { gridComponents: GridComponents, inferiorData: Map[ActorRef[ GridAgent.Request - ], (VoltageRange, TransformerTapping)], + ], (VoltageRange, Seq[TransformerTapping])], ): VoltageRange = { // calculate voltage range val nodeResMap = powerFlowResultEvent.nodeResults @@ -201,15 +209,31 @@ object CongestionManagementSupport { def updateWithInferiorRanges( inferiorData: Map[ActorRef[ GridAgent.Request - ], (VoltageRange, TransformerTapping)] + ], (VoltageRange, Seq[TransformerTapping])] ): VoltageRange = { - inferiorData.foldLeft(this) { case (range, (_, (infRange, tapping))) => - if (tapping.hasAutoTap) { - val currentPos = tapping.currentTapPos - val deltaV = tapping.deltaV - val possiblePlus = deltaV.multiply(tapping.tapMax - currentPos) - val possibleMinus = deltaV.multiply(tapping.tapMin - currentPos) + inferiorData.foldLeft(this) { case (range, (_, (infRange, tappings))) => + // allow tapping only if all transformers support tapping + if (tappings.forall(_.hasAutoTap)) { + + val tappingRanges = tappings.map { tapping => + val currentPos = tapping.currentTapPos + val deltaV = tapping.deltaV + val increase = deltaV.multiply(tapping.tapMax - currentPos) + val decrease = deltaV.multiply(tapping.tapMin - currentPos) + + (increase, decrease) + } + + val (possiblePlus, possibleMinus) = if (tappings.size == 1) { + tappingRanges(0) + } else { + // check for possible increase and decrease that can be applied to all transformers + ( + tappingRanges.map(_._1).minOption.getOrElse(0.asPu), + tappingRanges.map(_._2).maxOption.getOrElse(0.asPu), + ) + } ( range.deltaPlus diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 7ca9b89840..a0273cb9e2 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -209,7 +209,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { */ private[grid] def updateTransformerTapping( stateData: CongestionManagementData, - awaitingData: AwaitingData[(VoltageRange, TransformerTapping)], + awaitingData: AwaitingData[(VoltageRange, Seq[TransformerTapping])], )(implicit constantData: GridAgentConstantData, buffer: StashBuffer[GridAgent.Request], @@ -247,10 +247,8 @@ trait DCMAlgorithm extends CongestionManagementSupport { val voltage = gridModel.mainRefSystem.nominalVoltage.value.asKiloVolt - // TODO: Consider two transformers connected to different nodes in subgrid - val transformerToSup = gridEnv.subgridGateToActorRef - .find( + .filter( _._1 .superiorNode() .getVoltLvl @@ -258,10 +256,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { .isGreaterThan(voltage) ) .map(_._1.link().getUuid) - .getOrElse( - throw new IllegalArgumentException(s"No superior actor ref found!") - ) - val transformer = transformers(transformerToSup) + val transformer = transformerToSup.map(transformers).toSeq val range = calculateVoltageOptions( stateData.powerFlowResults, @@ -323,12 +318,10 @@ trait DCMAlgorithm extends CongestionManagementSupport { tapping -> value.keySet } - // TODO: Consider two transformers connected to different nodes in subgrid - - tappingModels.foreach { case (tappingModel, refs) => + tappingModels.foreach { case (tappingModels, refs) => val inferiorRanges = refs.map(refMap) - if (tappingModel.hasAutoTap) { + if (tappingModels.forall(_.hasAutoTap)) { // the given transformer can be tapped, calculate the new tap pos val suggestion = @@ -338,13 +331,13 @@ trait DCMAlgorithm extends CongestionManagementSupport { val (tapChange, deltaV) = calculateTapAndVoltage( suggestion, - tappingModel, + tappingModels, ) if (tapChange > 0) { - tappingModel.decrTapPos(tapChange) + tappingModels.foreach(_.decrTapPos(tapChange)) } else { - tappingModel.incrTapPos(tapChange) + tappingModels.foreach(_.incrTapPos(tapChange)) } ctx.log.warn( diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala index 051304909f..0a77cc91fc 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala @@ -301,14 +301,14 @@ object GridAgentMessages { case class VoltageRangeResponse( override val sender: ActorRef[GridAgent.Request], - override val value: (VoltageRange, TransformerTapping), - ) extends CMReceiveResponse[(VoltageRange, TransformerTapping)] + override val value: (VoltageRange, Seq[TransformerTapping]), + ) extends CMReceiveResponse[(VoltageRange, Seq[TransformerTapping])] case class ReceivedVoltageRange( override val values: Vector[ - (ActorRef[GridAgent.Request], (VoltageRange, TransformerTapping)) + (ActorRef[GridAgent.Request], (VoltageRange, Seq[TransformerTapping])) ] - ) extends CMResponse[(VoltageRange, TransformerTapping)] + ) extends CMResponse[(VoltageRange, Seq[TransformerTapping])] case class VoltageDeltaResponse( delta: ComparableQuantity[Dimensionless] diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index 82e538d189..40c640e046 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -89,7 +89,7 @@ class CongestionManagementSupportSpec forAll(cases) { (suggestion, expectedTap, expectedDelta) => val (actualTap, actualDelta) = - calculateTapAndVoltage(suggestion, tapping) + calculateTapAndVoltage(suggestion, Seq(tapping)) actualTap shouldBe expectedTap actualDelta should equalWithTolerance(expectedDelta) @@ -246,8 +246,12 @@ class CongestionManagementSupportSpec VoltageLimits(0.9, 1.1), gridComponents, Map( - inferior1.ref -> (VoltageRange(0.1.asPu, 0.01.asPu), tappingModel), - inferior2.ref -> (VoltageRange(0.01.asPu, (-0.04).asPu), tappingModel), + inferior1.ref -> (VoltageRange(0.1.asPu, 0.01.asPu), Seq( + tappingModel + )), + inferior2.ref -> (VoltageRange(0.01.asPu, (-0.04).asPu), Seq( + tappingModel + )), ), ) @@ -395,8 +399,8 @@ class CongestionManagementSupportSpec forAll(cases) { (range1, range2, expected) => val updatedRange = range.updateWithInferiorRanges( Map( - inferior1.ref -> (range1, tappingModel), - inferior2.ref -> (range2, tappingModel), + inferior1.ref -> (range1, Seq(tappingModel)), + inferior2.ref -> (range2, Seq(tappingModel)), ) ) @@ -443,8 +447,8 @@ class CongestionManagementSupportSpec forAll(cases) { (range1, range2, expected) => val updatedRange = range.updateWithInferiorRanges( Map( - inferior1.ref -> (range1, tappingModel), - inferior2.ref -> (range2, tappingModel), + inferior1.ref -> (range1, Seq(tappingModel)), + inferior2.ref -> (range2, Seq(tappingModel)), ) ) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala index 86c5200ca2..55534606c2 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala @@ -153,7 +153,6 @@ trait DBFSMockGridAgents extends UnitSpec { case VoltageRangeResponse(sender, (range, _)) => range.deltaPlus shouldBe voltageRange.deltaPlus range.deltaMinus shouldBe voltageRange.deltaMinus - range.suggestion shouldBe voltageRange.suggestion sender } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index 41da3d398a..274c4dfce2 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -184,21 +184,24 @@ class DCMAlgorithmCenGridSpec inferiorGrid11.ref, ( VoltageRange((-0.01).asPu, (-0.02).asPu), - mvTransformers(transformer11.getUuid), + Seq(mvTransformers(transformer11.getUuid)), ), ) voltageRangeRequester12 ! VoltageRangeResponse( inferiorGrid12.ref, ( VoltageRange(0.07.asPu, 0.01.asPu), - mvTransformers(transformer12.getUuid), + Seq(mvTransformers(transformer12.getUuid)), ), ) voltageRangeRequester13 ! VoltageRangeResponse( inferiorGrid13.ref, ( VoltageRange(0.06.asPu, 0.asPu), - mvTransformers(transformer13_1.getUuid), + Seq( + mvTransformers(transformer13_1.getUuid), + mvTransformers(transformer13_2.getUuid), + ), ), ) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala index caf691a16f..7aac49e56e 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala @@ -186,21 +186,31 @@ class DCMAlgorithmSupGridSpec end, ) + val tappingModel2 = TransformerModel( + transformer2, + RefSystem(Kilowatts(600), Kilovolts(110)), + start, + end, + ) + hvGrid.expectMessageType[RequestVoltageOptions] match { case RequestVoltageOptions(sender) => sender ! VoltageRangeResponse( hvGrid.ref, - (VoltageRange(0.04.asPu, (-0.01).asPu), tappingModel), + ( + VoltageRange(0.04.asPu, (-0.01).asPu), + Seq(tappingModel, tappingModel2), + ), ) } hvGrid.expectMessageType[VoltageDeltaResponse](120.seconds) match { case VoltageDeltaResponse(delta) => - delta should equalWithTolerance(0.03.asPu) + delta should equalWithTolerance( + 0.asPu + ) // equalWithTolerance(0.015.asPu) } - hvGrid.expectMessageType[FinishStep.type] - // skipping the simulation hvGrid.expectMessageType[RequestGridPower] diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala index 56e5f43814..47d5e02905 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala @@ -475,11 +475,11 @@ trait DbfsTestGrid extends SubGridGateMokka with GridComponentsMokka { } protected val (ehvGridContainer, ehvSubGridGates) = { - val nodes = Set(supNodeA, node1) + val nodes = Set(supNodeA) val rawGridElements = new RawGridElements( nodes.asJava, Set.empty[LineInput].asJava, - Set(transformer1).asJava, + Set.empty[Transformer2WInput].asJava, Set.empty[Transformer3WInput].asJava, Set.empty[SwitchInput].asJava, Set.empty[MeasurementUnitInput].asJava, From b5f810d7138a6a20840cd7c4db8042eb8ebb8962 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Sat, 27 Apr 2024 08:25:36 +0200 Subject: [PATCH 19/55] Enhancing transformer tapping. --- .../grid/CongestionManagementSupport.scala | 52 ++++++++++++++++++- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 15 +++--- .../CongestionManagementSupportSpec.scala | 51 ++++++++++++++++-- .../model/grid/GridComponentsMokka.scala | 39 +++++++++----- 4 files changed, 130 insertions(+), 27 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index f8ba0f0ac7..e820f3f3f3 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -9,9 +9,11 @@ package edu.ie3.simona.agent.grid import edu.ie3.datamodel.models.result.connector.LineResult import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent -import edu.ie3.simona.exceptions.ResultException +import edu.ie3.simona.exceptions.{GridInconsistencyException, ResultException} import edu.ie3.simona.model.grid.GridModel.GridComponents import edu.ie3.simona.model.grid.{ + Transformer3wModel, + TransformerModel, TransformerTapping, TransformerTappingModel, VoltageLimits, @@ -29,6 +31,51 @@ import javax.measure.quantity.Dimensionless */ trait CongestionManagementSupport { + def groupTappingModels( + receivedData: Map[ActorRef[GridAgent.Request], Seq[TransformerTapping]], + transformer3ws: Set[Transformer3wModel], + ): Map[Set[TransformerTapping], Set[ActorRef[GridAgent.Request]]] = { + val transformer3wMap = transformer3ws.map(t => t.uuid -> t).toMap + + receivedData.foldLeft( + Map.empty[Set[TransformerTapping], Set[ActorRef[GridAgent.Request]]] + ) { case (combined, (ref, tappings)) => + val updated: Set[TransformerTapping] = tappings.map { + case transformerModel: TransformerModel => + transformerModel + case transformer3wModel: Transformer3wModel => + transformer3wMap.getOrElse( + transformer3wModel.uuid, + throw new GridInconsistencyException( + s"No three winding transformer found." + ), + ) + case unsupported => + throw new IllegalArgumentException( + s"The transformer type ${unsupported.getClass} is not supported." + ) + }.toSet + + val keyOption = combined.keySet.find { keys => + updated.exists(key => keys.contains(key)) + } + + keyOption + .map { key => + val refs = combined(key) + val updatedMap = combined.removed(key) + + val newKey = key ++ updated + val newValue = refs ++ Set(ref) + + updatedMap ++ Map(newKey -> newValue) + } + .getOrElse { + combined ++ Map(updated -> Set(ref)) + } + } + } + def calculateTapAndVoltage( suggestion: ComparableQuantity[Dimensionless], tappings: Seq[TransformerTapping], @@ -216,6 +263,7 @@ object CongestionManagementSupport { // allow tapping only if all transformers support tapping if (tappings.forall(_.hasAutoTap)) { + // TODO: Enhance tests, to tests these changes val tappingRanges = tappings.map { tapping => val currentPos = tapping.currentTapPos val deltaV = tapping.deltaV @@ -315,7 +363,7 @@ object CongestionManagementSupport { } def combineSuggestions( - ranges: Set[VoltageRange] + ranges: Iterable[VoltageRange] ): ComparableQuantity[Dimensionless] = { ranges.headOption match { case Some(value) => diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index a0273cb9e2..991be50420 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -309,14 +309,11 @@ trait DCMAlgorithm extends CongestionManagementSupport { ref -> range } - val tappingModels = receivedData - .map { case (value, (_, tapping)) => - (value, tapping) - } - .groupBy(_._2) - .map { case (tapping, value) => - tapping -> value.keySet - } + val tappingModels = + groupTappingModels( + receivedData.map { case (ref, (_, tappings)) => ref -> tappings }, + stateData.gridAgentBaseData.gridEnv.gridModel.gridComponents.transformers3w, + ) tappingModels.foreach { case (tappingModels, refs) => val inferiorRanges = refs.map(refMap) @@ -331,7 +328,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { val (tapChange, deltaV) = calculateTapAndVoltage( suggestion, - tappingModels, + tappingModels.toSeq, ) if (tapChange > 0) { diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index 40c640e046..d1fcfb40d6 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -49,6 +49,51 @@ class CongestionManagementSupportSpec "CongestionManagementSupport" should { + "group transformers correctly" in { + // grid 1 is connected via a transformer2w and one port of a transformer3w + // grid 2 is connected via one port of a transformer3w + // grid 3 is connected via a transformer2w + // grid 4 is connected via two transformer2ws + + val (transformer3wA, transformer3wB, transformer3wC) = + mockTransformer3wModel() + val transformer1 = mockTransformerModel() + val ref1 = TestProbe[GridAgent.Request]("ref1").ref + val ref2 = TestProbe[GridAgent.Request]("ref2").ref + + val ref3 = TestProbe[GridAgent.Request]("ref3").ref + val transformer3 = mockTransformerModel() + + val ref4 = TestProbe[GridAgent.Request]("ref4").ref + val transformer4_1 = mockTransformerModel() + val transformer4_2 = mockTransformerModel() + + val receivedData = Map( + ref1 -> Seq( + transformer1, + transformer3wB, + ), // connected with both transformer2w and transformer3w + ref2 -> Seq(transformer3wC), // connected with a transformer3w + ref3 -> Seq(transformer3), // connected with just one transformer model + ref4 -> Seq( + transformer4_1, + transformer4_2, + ), // connected with two transformer2w + ) + + val grouped = groupTappingModels( + receivedData, + Set(transformer3wA), + ) + + grouped shouldBe Map( + Set(transformer1, transformer3wA) -> Set(ref1, ref2), + Set(transformer3) -> Set(ref3), + Set(transformer4_1, transformer4_2) -> Set(ref4), + ) + + } + "calculate the tap and voltage change" in { val tappingModel = TransformerTappingModel( deltaV = 1.5.asPercent, @@ -219,7 +264,7 @@ class CongestionManagementSupportSpec Set.empty, ) - val tappingModel = mockTransformerModel( + val tappingModel = mockTransformerTappingModel( autoTap = true, currentTapPos = 0, tapMax = 3, @@ -364,7 +409,7 @@ class CongestionManagementSupportSpec val range = VoltageRange(0.05.asPu, (-0.05).asPu) val tappingModel = - mockTransformerModel( + mockTransformerTappingModel( autoTap = false, currentTapPos = 0, tapMax = 10, @@ -412,7 +457,7 @@ class CongestionManagementSupportSpec "be updated with inferior voltage ranges and with tapping correctly" in { val range = VoltageRange(0.05.asPu, (-0.05).asPu) - val tappingModel = mockTransformerModel( + val tappingModel = mockTransformerTappingModel( autoTap = true, currentTapPos = 7, tapMax = 10, diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala index 6c3cae1ca1..c9fdb9d846 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala @@ -6,14 +6,8 @@ package edu.ie3.simona.test.common.model.grid -import edu.ie3.simona.model.grid.{ - LineModel, - NodeModel, - Transformer3wModel, - TransformerModel, - TransformerTappingModel, -} -import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import edu.ie3.simona.model.grid.Transformer3wPowerFlowCase._ +import edu.ie3.simona.model.grid._ import org.mockito.Mockito.when import org.scalatestplus.mockito.MockitoSugar import squants.Amperes @@ -49,7 +43,7 @@ trait GridComponentsMokka extends MockitoSugar { line } - protected def mockTransformerModel( + protected def mockTransformerTappingModel( uuid: UUID = UUID.randomUUID(), autoTap: Boolean, tapMax: Int, @@ -69,13 +63,32 @@ trait GridComponentsMokka extends MockitoSugar { transformer } - protected def mockTransformer3wModel( - uuid: UUID - ): Transformer3wModel = { - val transformer = mock[Transformer3wModel] + protected def mockTransformerModel( + uuid: UUID = UUID.randomUUID() + ): TransformerModel = { + val transformer = mock[TransformerModel] when(transformer.uuid).thenReturn(uuid) transformer } + protected def mockTransformer3wModel( + uuid: UUID = UUID.randomUUID() + ): (Transformer3wModel, Transformer3wModel, Transformer3wModel) = { + val transformerA = mock[Transformer3wModel] + val transformerB = mock[Transformer3wModel] + val transformerC = mock[Transformer3wModel] + when(transformerA.uuid).thenReturn(uuid) + when(transformerB.uuid).thenReturn(uuid) + when(transformerC.uuid).thenReturn(uuid) + + when(transformerA.powerFlowCase).thenReturn(PowerFlowCaseA) + when(transformerB.powerFlowCase).thenReturn( + Transformer3wPowerFlowCase.PowerFlowCaseB + ) + when(transformerC.powerFlowCase).thenReturn(PowerFlowCaseC) + + (transformerA, transformerB, transformerC) + } + } From 79c71f6399793a8080cdc99fc9cc496a915fb210 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Sun, 28 Apr 2024 16:29:58 +0200 Subject: [PATCH 20/55] Improving transformer tapping. --- .../grid/CongestionManagementSupport.scala | 68 ++++++--- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 20 ++- .../CongestionManagementSupportSpec.scala | 143 ++++++++++++++---- .../agent/grid/DCMAlgorithmSupGridSpec.scala | 4 +- .../model/grid/GridComponentsMokka.scala | 63 +++++++- 5 files changed, 239 insertions(+), 59 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index e820f3f3f3..b12e3f01af 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -79,17 +79,54 @@ trait CongestionManagementSupport { def calculateTapAndVoltage( suggestion: ComparableQuantity[Dimensionless], tappings: Seq[TransformerTapping], - ): (Int, ComparableQuantity[Dimensionless]) = { + ): (Map[TransformerTapping, Int], ComparableQuantity[Dimensionless]) = { if (tappings.size == 1) { val tapping = tappings(0) val taps = tapping.computeDeltaTap(suggestion) val delta = tapping.deltaV.getValue.doubleValue() * taps / 100 - (taps, delta.asPu) + (Map(tapping -> taps), delta.asPu) } else { - // TODO: Consider two transformers connected to different nodes in subgrid - (0, 0.asPu) + + val possibleChange = tappings.map { tapping => + val taps = tapping.computeDeltaTap(suggestion) + val delta = tapping.deltaV.getValue.doubleValue() * taps / 100 + tapping -> (taps, delta) + }.toMap + + // finds the smallest possible delta, because we are limited by that transformer + val option = if (suggestion.isGreaterThan(0.asPu)) { + possibleChange.minByOption(_._2._2) + } else { + possibleChange.maxByOption(_._2._2) + } + + option.map(_._2._2) match { + case Some(maxValue) => + val max = maxValue.asPu + + val changes = tappings.map { tapping => + val taps = tapping.computeDeltaTap(max) + val delta = tapping.deltaV.getValue.doubleValue() * taps / 100 + + tapping -> (taps, delta) + }.toMap + + val check = changes.forall { case (_, (_, delta)) => + // check if all deltas are in a range of plus minus 0.1 % + Math.abs(Math.abs(maxValue) - Math.abs(delta)) < 1e-3 + } + + if (check) { + (changes.map(t => t._1 -> t._2._1), max) + } else { + (tappings.map(t => t -> 0).toMap, 0.asPu) + } + + case None => + (tappings.map(t => t -> 0).toMap, 0.asPu) + } } } @@ -130,22 +167,20 @@ trait CongestionManagementSupport { ) // updating the voltage range prevent or cure line congestions - /* val deltaV = calculatePossibleVoltageDeltaForLines( nodeResMap, powerFlowResultEvent.lineResults, gridComponents, ) val updatedRange = range.updateWithLineDelta(deltaV) - */ if (inferiorData.isEmpty) { // if there are no inferior grids, return the voltage range - range + updatedRange } else { // if there are inferior grids, update the voltage range - range.updateWithInferiorRanges(inferiorData) + updatedRange.updateWithInferiorRanges(inferiorData) } } @@ -332,19 +367,10 @@ object CongestionManagementSupport { val plus = deltaPlus.getValue.doubleValue() val minus = deltaMinus.getValue.doubleValue() - val value = (plus > 0, minus < 0) match { - case (true, true) => - (plus - minus) / 2 - case (false, true) if plus > minus => - (plus.abs + minus.abs) / -2 - case (false, true) => - plus - case (true, false) if plus > minus => - (plus + minus) / 2 - case (true, false) => - minus - case (false, false) => - plus + val value = if (plus > minus) { + (plus + minus) / 2 + } else { + plus } val factor = 1e3 diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 991be50420..c9ec15be50 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -327,21 +327,27 @@ trait DCMAlgorithm extends CongestionManagementSupport { .subtract(delta) val (tapChange, deltaV) = calculateTapAndVoltage( - suggestion, + suggestion.multiply(-1), tappingModels.toSeq, ) - if (tapChange > 0) { - tappingModels.foreach(_.decrTapPos(tapChange)) - } else { - tappingModels.foreach(_.incrTapPos(tapChange)) + tapChange.foreach { case (tapping, tapChange) => + if (tapChange > 0) { + tapping.incrTapPos(tapChange) + } else if (tapChange < 0) { + tapping.decrTapPos(tapChange) + } else { + // no change, do nothing + } } + val actualDelta = deltaV.multiply(-1) + ctx.log.warn( - s"For inferior grids $refs, suggestion: $suggestion, delta: $deltaV" + s"For inferior grids $refs, suggestion: $suggestion, delta: $actualDelta" ) - refs.foreach(_ ! VoltageDeltaResponse(deltaV.add(delta))) + refs.foreach(_ ! VoltageDeltaResponse(actualDelta.add(delta))) } else { // no tapping possible, just send the delta to the inferior grid refs.foreach(_ ! VoltageDeltaResponse(delta)) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index d1fcfb40d6..ba70924266 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -94,32 +94,9 @@ class CongestionManagementSupportSpec } - "calculate the tap and voltage change" in { - val tappingModel = TransformerTappingModel( - deltaV = 1.5.asPercent, - currentTapPos = 1, - tapMax = 5, - tapMin = -5, - tapNeutr = 0, - autoTap = true, - ) - - val tapping = TransformerModel( - UUID.randomUUID(), - id = "dummy", - operationInterval = OperationInterval(0L, 1L), - hvNodeUuid = UUID.randomUUID(), - lvNodeUuid = UUID.randomUUID(), - tappingModel, - amount = 1, - voltRatioNominal = BigDecimal(110), - iNomHv = Amperes(1), - iNomLv = Amperes(10), - r = Each(1), - x = Each(1), - g = Each(1), - b = Each(1), - ) + "calculate the tap and voltage change for one transformer" in { + val tappingModel = dummyTappingModel() + val tapping = dummyTransformerModel(tappingModel) val cases = Table( ("suggestion", "expectedTap", "expectedDelta"), @@ -136,11 +113,123 @@ class CongestionManagementSupportSpec val (actualTap, actualDelta) = calculateTapAndVoltage(suggestion, Seq(tapping)) - actualTap shouldBe expectedTap + actualTap shouldBe Map(tapping -> expectedTap) actualDelta should equalWithTolerance(expectedDelta) } } + "calculate the tap and voltage change for multiple transformers" in { + val tappingModel1 = dummyTappingModel() + val tappingModel2 = dummyTappingModel( + deltaV = 1.2.asPercent, + tapMin = -5, + tapMax = 3, + currentTapPos = 0, + ) + val tappingModel3 = dummyTappingModel(deltaV = 1.49.asPercent) + + val transformer11 = dummyTransformerModel(tappingModel1) + val transformer12 = dummyTransformerModel(tappingModel1) + + val transformer21 = dummyTransformerModel(tappingModel2) + val transformer22 = dummyTransformer3wModel(tappingModel2) + + val transformer31 = dummyTransformerModel(tappingModel1) + val transformer32 = dummyTransformer3wModel(tappingModel2) + + val transformer41 = dummyTransformerModel(tappingModel1) + val transformer42 = dummyTransformer3wModel(tappingModel3) + + val modelCase1 = Seq(transformer11, transformer12) + val modelCase2 = Seq(transformer21, transformer22) + val modelCase3 = Seq(transformer31, transformer32) + val modelCase4 = Seq(transformer41, transformer42) + + val cases = Table( + ("suggestion", "models", "expectedTaps", "expectedDelta"), + ( + 0.02.asPu, + modelCase1, + Map(transformer11 -> 1, transformer12 -> 1), + 0.015.asPu, + ), + ( + 0.038.asPu, + modelCase1, + Map(transformer11 -> 2, transformer12 -> 2), + 0.03.asPu, + ), + ( + (-0.06).asPu, + modelCase1, + Map(transformer11 -> -4, transformer12 -> -4), + (-0.06).asPu, + ), + ( + 0.02.asPu, + modelCase2, + Map(transformer21 -> 1, transformer22 -> 1), + 0.012.asPu, + ), + ( + 0.038.asPu, + modelCase2, + Map(transformer21 -> 3, transformer22 -> 3), + 0.036.asPu, + ), + ( + (-0.06).asPu, + modelCase2, + Map(transformer21 -> -5, transformer22 -> -5), + (-0.06).asPu, + ), + ( + 0.02.asPu, + modelCase3, + Map(transformer31 -> 0, transformer32 -> 0), + 0.asPu, + ), + ( + 0.038.asPu, + modelCase3, + Map(transformer31 -> 0, transformer32 -> 0), + 0.asPu, + ), + ( + (-0.06).asPu, + modelCase3, + Map(transformer31 -> -4, transformer32 -> -5), + (-0.06).asPu, + ), + ( + 0.02.asPu, + modelCase4, + Map(transformer41 -> 1, transformer42 -> 1), + 0.0149.asPu, + ), + ( + 0.038.asPu, + modelCase4, + Map(transformer41 -> 2, transformer42 -> 2), + 0.0298.asPu, + ), + ( + (-0.06).asPu, + modelCase4, + Map(transformer41 -> -4, transformer42 -> -4), + (-0.0596).asPu, + ), + ) + + forAll(cases) { (suggestion, models, expectedTaps, expectedDelta) => + val (tapChanges, delta) = calculateTapAndVoltage(suggestion, models) + + tapChanges shouldBe expectedTaps + delta should equalWithTolerance(expectedDelta) + } + + } + "calculates the possible voltage delta for lines correctly" in { val node1 = nodeModel() val node2 = nodeModel() diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala index 7aac49e56e..dda24e9c6b 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala @@ -206,9 +206,7 @@ class DCMAlgorithmSupGridSpec hvGrid.expectMessageType[VoltageDeltaResponse](120.seconds) match { case VoltageDeltaResponse(delta) => - delta should equalWithTolerance( - 0.asPu - ) // equalWithTolerance(0.015.asPu) + delta should equalWithTolerance(0.015.asPu) } // skipping the simulation diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala index c9fdb9d846..6753d007aa 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala @@ -8,9 +8,11 @@ package edu.ie3.simona.test.common.model.grid import edu.ie3.simona.model.grid.Transformer3wPowerFlowCase._ import edu.ie3.simona.model.grid._ +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble +import edu.ie3.util.scala.OperationInterval import org.mockito.Mockito.when import org.scalatestplus.mockito.MockitoSugar -import squants.Amperes +import squants.{Amperes, Each} import tech.units.indriya.ComparableQuantity import java.util.UUID @@ -43,6 +45,65 @@ trait GridComponentsMokka extends MockitoSugar { line } + protected def dummyTappingModel( + deltaV: ComparableQuantity[Dimensionless] = 1.5.asPercent, + currentTapPos: Int = 1, + tapMax: Int = 5, + tapMin: Int = -5, + tapNeutr: Int = 0, + autoTap: Boolean = true, + ): TransformerTappingModel = + TransformerTappingModel( + deltaV, + currentTapPos, + tapMax, + tapMin, + tapNeutr, + autoTap, + ) + + protected def dummyTransformerModel( + tappingModel: TransformerTappingModel + ): TransformerModel = + TransformerModel( + UUID.randomUUID(), + id = "dummy", + operationInterval = OperationInterval(0L, 1L), + hvNodeUuid = UUID.randomUUID(), + lvNodeUuid = UUID.randomUUID(), + tappingModel, + amount = 1, + voltRatioNominal = BigDecimal(110), + iNomHv = Amperes(1), + iNomLv = Amperes(10), + r = Each(1), + x = Each(1), + g = Each(1), + b = Each(1), + ) + + protected def dummyTransformer3wModel( + tappingModel: TransformerTappingModel + ): Transformer3wModel = + Transformer3wModel( + UUID.randomUUID(), + id = "dummy", + operationInterval = OperationInterval(0L, 1L), + hvNodeUuid = UUID.randomUUID(), + mvNodeUuid = UUID.randomUUID(), + lvNodeUuid = UUID.randomUUID(), + nodeInternalUuid = UUID.randomUUID(), + voltRatioNominal = BigDecimal(110), + tappingModel, + amount = 1, + powerFlowCase = PowerFlowCaseA, + iNom = Amperes(1), + r = Each(1), + x = Each(1), + g = Each(1), + b = Each(1), + ) + protected def mockTransformerTappingModel( uuid: UUID = UUID.randomUUID(), autoTap: Boolean, From 3c468d1d2d70d72fbfa5168f829c21de5f3360f3 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Sun, 28 Apr 2024 20:11:19 +0200 Subject: [PATCH 21/55] Improving transformer tapping. --- .../grid/CongestionManagementSupport.scala | 14 ++--- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 8 ++- .../CongestionManagementSupportSpec.scala | 52 ++++++++----------- 3 files changed, 32 insertions(+), 42 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index b12e3f01af..92727d56b8 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -81,22 +81,24 @@ trait CongestionManagementSupport { tappings: Seq[TransformerTapping], ): (Map[TransformerTapping, Int], ComparableQuantity[Dimensionless]) = { + val inverted = suggestion.multiply(-1) + if (tappings.size == 1) { val tapping = tappings(0) - val taps = tapping.computeDeltaTap(suggestion) - val delta = tapping.deltaV.getValue.doubleValue() * taps / 100 + val taps = tapping.computeDeltaTap(inverted) + val delta = tapping.deltaV.getValue.doubleValue() * taps / -100 (Map(tapping -> taps), delta.asPu) } else { val possibleChange = tappings.map { tapping => - val taps = tapping.computeDeltaTap(suggestion) + val taps = tapping.computeDeltaTap(inverted) val delta = tapping.deltaV.getValue.doubleValue() * taps / 100 tapping -> (taps, delta) }.toMap // finds the smallest possible delta, because we are limited by that transformer - val option = if (suggestion.isGreaterThan(0.asPu)) { + val option = if (inverted.isGreaterThan(0.asPu)) { possibleChange.minByOption(_._2._2) } else { possibleChange.maxByOption(_._2._2) @@ -108,7 +110,7 @@ trait CongestionManagementSupport { val changes = tappings.map { tapping => val taps = tapping.computeDeltaTap(max) - val delta = tapping.deltaV.getValue.doubleValue() * taps / 100 + val delta = tapping.deltaV.getValue.doubleValue() * taps / -100 tapping -> (taps, delta) }.toMap @@ -119,7 +121,7 @@ trait CongestionManagementSupport { } if (check) { - (changes.map(t => t._1 -> t._2._1), max) + (changes.map(t => t._1 -> t._2._1), max.multiply(-1)) } else { (tappings.map(t => t -> 0).toMap, 0.asPu) } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index c9ec15be50..1483650e09 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -327,7 +327,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { .subtract(delta) val (tapChange, deltaV) = calculateTapAndVoltage( - suggestion.multiply(-1), + suggestion, tappingModels.toSeq, ) @@ -341,13 +341,11 @@ trait DCMAlgorithm extends CongestionManagementSupport { } } - val actualDelta = deltaV.multiply(-1) - ctx.log.warn( - s"For inferior grids $refs, suggestion: $suggestion, delta: $actualDelta" + s"For inferior grids $refs, suggestion: $suggestion, delta: $deltaV" ) - refs.foreach(_ ! VoltageDeltaResponse(actualDelta.add(delta))) + refs.foreach(_ ! VoltageDeltaResponse(deltaV.add(delta))) } else { // no tapping possible, just send the delta to the inferior grid refs.foreach(_ ! VoltageDeltaResponse(delta)) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index ba70924266..4b4d72f10b 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -11,11 +11,7 @@ import edu.ie3.datamodel.models.result.connector.LineResult import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.model.grid.GridModel.GridComponents -import edu.ie3.simona.model.grid.{ - TransformerModel, - TransformerTappingModel, - VoltageLimits, -} +import edu.ie3.simona.model.grid.VoltageLimits import edu.ie3.simona.test.common.UnitSpec import edu.ie3.simona.test.common.model.grid.{ GridComponentsMokka, @@ -23,14 +19,10 @@ import edu.ie3.simona.test.common.model.grid.{ } import edu.ie3.simona.test.common.result.ResultMokka import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble -import edu.ie3.util.scala.OperationInterval import org.apache.pekko.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe, } -import squants.{Amperes, Each} - -import java.util.UUID class CongestionManagementSupportSpec extends ScalaTestWithActorTestKit @@ -100,13 +92,12 @@ class CongestionManagementSupportSpec val cases = Table( ("suggestion", "expectedTap", "expectedDelta"), - (0.02.asPu, 1, 0.015.asPu), - ((-0.02).asPu, -1, (-0.015).asPu), - (0.031.asPu, 2, 0.03.asPu), - (0.05.asPu, 3, 0.045.asPu), - ((-0.06).asPu, -4, (-0.06).asPu), - ((-0.1).asPu, -6, (-0.09).asPu), // max decrease - (0.1.asPu, 4, 0.06.asPu), // max increase + (0.02.asPu, -1, 0.015.asPu), + ((-0.02).asPu, 1, (-0.015).asPu), + (0.031.asPu, -2, 0.03.asPu), + (0.05.asPu, -3, 0.045.asPu), + ((-0.1).asPu, 4, (-0.06).asPu), // max tap increase + (0.1.asPu, -6, 0.09.asPu), // max tap decrease ) forAll(cases) { (suggestion, expectedTap, expectedDelta) => @@ -122,8 +113,7 @@ class CongestionManagementSupportSpec val tappingModel1 = dummyTappingModel() val tappingModel2 = dummyTappingModel( deltaV = 1.2.asPercent, - tapMin = -5, - tapMax = 3, + tapMin = -3, currentTapPos = 0, ) val tappingModel3 = dummyTappingModel(deltaV = 1.49.asPercent) @@ -150,37 +140,37 @@ class CongestionManagementSupportSpec ( 0.02.asPu, modelCase1, - Map(transformer11 -> 1, transformer12 -> 1), + Map(transformer11 -> -1, transformer12 -> -1), 0.015.asPu, ), ( 0.038.asPu, modelCase1, - Map(transformer11 -> 2, transformer12 -> 2), + Map(transformer11 -> -2, transformer12 -> -2), 0.03.asPu, ), ( (-0.06).asPu, modelCase1, - Map(transformer11 -> -4, transformer12 -> -4), + Map(transformer11 -> 4, transformer12 -> 4), (-0.06).asPu, ), ( 0.02.asPu, modelCase2, - Map(transformer21 -> 1, transformer22 -> 1), + Map(transformer21 -> -1, transformer22 -> -1), 0.012.asPu, ), ( 0.038.asPu, modelCase2, - Map(transformer21 -> 3, transformer22 -> 3), + Map(transformer21 -> -3, transformer22 -> -3), 0.036.asPu, ), ( (-0.06).asPu, modelCase2, - Map(transformer21 -> -5, transformer22 -> -5), + Map(transformer21 -> 5, transformer22 -> 5), (-0.06).asPu, ), ( @@ -198,25 +188,25 @@ class CongestionManagementSupportSpec ( (-0.06).asPu, modelCase3, - Map(transformer31 -> -4, transformer32 -> -5), + Map(transformer31 -> 4, transformer32 -> 5), (-0.06).asPu, ), ( 0.02.asPu, modelCase4, - Map(transformer41 -> 1, transformer42 -> 1), + Map(transformer41 -> -1, transformer42 -> -1), 0.0149.asPu, ), ( 0.038.asPu, modelCase4, - Map(transformer41 -> 2, transformer42 -> 2), + Map(transformer41 -> -2, transformer42 -> -2), 0.0298.asPu, ), ( (-0.06).asPu, modelCase4, - Map(transformer41 -> -4, transformer42 -> -4), + Map(transformer41 -> 4, transformer42 -> 4), (-0.0596).asPu, ), ) @@ -332,7 +322,7 @@ class CongestionManagementSupportSpec range.deltaPlus should equalWithTolerance(0.05.asPu) range.deltaMinus should equalWithTolerance((-0.03).asPu) - range.suggestion should equalWithTolerance(0.041.asPu) + range.suggestion should equalWithTolerance(0.011.asPu) } "calculates the voltage range for a middle grid correctly" in { @@ -391,7 +381,7 @@ class CongestionManagementSupportSpec range.deltaPlus should equalWithTolerance(0.04.asPu) range.deltaMinus should equalWithTolerance((-0.02).asPu) - range.suggestion should equalWithTolerance(0.041.asPu) + range.suggestion should equalWithTolerance(0.011.asPu) } def buildPowerFlowResultEvent( @@ -414,7 +404,7 @@ class CongestionManagementSupportSpec "calculate the suggestion correctly" in { val cases = Table( ("deltaPlus", "deltaMinus", "expected"), - (0.05.asPu, (-0.03).asPu, 0.04.asPu), // no voltage limit violation + (0.05.asPu, (-0.03).asPu, 0.011.asPu), // no voltage limit violation ( (-0.01).asPu, (-0.02).asPu, From 9a8bae38c91ad5dd86351081d809005d2dafab91 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 29 Apr 2024 12:45:50 +0200 Subject: [PATCH 22/55] Improving tests. --- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 15 +- .../agent/grid/DBFSMockGridAgents.scala | 25 +- .../agent/grid/DCMAlgorithmCenGridSpec.scala | 327 +++++++++++------- .../agent/grid/DCMAlgorithmSupGridSpec.scala | 167 +++++---- .../simona/agent/grid/GridAgentDataSpec.scala | 2 + 5 files changed, 336 insertions(+), 200 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 1483650e09..91a7b978dc 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -189,7 +189,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { ctx, ) - case (ctx, msg: GridAgent.Request) => + case (ctx, msg) => ctx.log.error(s"Received unsupported msg: $msg. Stash away!") buffer.stash(msg) Behaviors.same @@ -363,7 +363,8 @@ trait DCMAlgorithm extends CongestionManagementSupport { ) ) - case (_, msg) => + case (ctx, msg) => + ctx.log.error(s"Received unsupported msg: $msg. Stash away!") buffer.stash(msg) Behaviors.same } @@ -398,6 +399,11 @@ trait DCMAlgorithm extends CongestionManagementSupport { stateData.currentTick, ctx, ) + + case (ctx, msg) => + ctx.log.error(s"Received unsupported msg: $msg. Stash away!") + buffer.stash(msg) + Behaviors.same } // TODO: Implement a proper behavior @@ -430,6 +436,11 @@ trait DCMAlgorithm extends CongestionManagementSupport { stateData.currentTick, ctx, ) + + case (ctx, msg) => + ctx.log.error(s"Received unsupported msg: $msg. Stash away!") + buffer.stash(msg) + Behaviors.same } /** Method to ask all inferior grids a [[CMRequest]]. diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala index 55534606c2..dcdba05e2c 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala @@ -6,7 +6,10 @@ package edu.ie3.simona.agent.grid -import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange +import edu.ie3.simona.agent.grid.CongestionManagementSupport.{ + Congestions, + VoltageRange, +} import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ ExchangePower, ExchangeVoltage, @@ -90,6 +93,12 @@ trait DBFSMockGridAgents extends UnitSpec { ): Unit = receiver ! SlackVoltageRequest(sweepNo, nodeUuids, gaProbe.ref) + def expectCongestionCheckRequest( + maxDuration: FiniteDuration = 30 seconds + ): ActorRef[GridAgent.Request] = { + gaProbe.expectMessageType[CongestionCheckRequest](maxDuration).sender + } + def expectVoltageRangeRequest(): ActorRef[GridAgent.Request] = { gaProbe.expectMessageType[RequestVoltageOptions].sender } @@ -145,6 +154,20 @@ trait DBFSMockGridAgents extends UnitSpec { receiver ! RequestGridPower(sweepNo, nodeUuids, gaProbe.ref) } + def expectCongestionResponse( + congestions: Congestions, + maxDuration: FiniteDuration = 30 seconds, + ): ActorRef[GridAgent.Request] = { + gaProbe.expectMessageType[CongestionResponse](maxDuration) match { + case CongestionResponse(sender, value) => + value.voltageCongestions shouldBe congestions.voltageCongestions + value.lineCongestions shouldBe congestions.lineCongestions + value.transformerCongestions shouldBe congestions.transformerCongestions + + sender + } + } + def expectVoltageRangeResponse( voltageRange: VoltageRange, maxDuration: FiniteDuration = 30 seconds, diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index 274c4dfce2..6e826f9b66 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -38,12 +38,11 @@ import org.apache.pekko.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe, } +import org.apache.pekko.actor.typed.ActorRef import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps import squants.electro.Kilovolts import squants.energy.Megawatts -import scala.concurrent.duration.DurationInt - class DCMAlgorithmCenGridSpec extends ScalaTestWithActorTestKit with DBFSMockGridAgents @@ -90,77 +89,97 @@ class DCMAlgorithmCenGridSpec val resultListener: TestProbe[ResultEvent] = TestProbe("resultListener") "A GridAgent actor in center position with async test" should { + val noCongestions = Congestions( + voltageCongestions = false, + lineCongestions = false, + transformerCongestions = false, + ) - val centerGridAgent = - testKit.spawn( - GridAgent( - environmentRefs, - config, - listener = Iterable(resultListener.ref), - ) - ) - s"initialize itself when it receives an init activation" in { + val voltageCongestions = noCongestions.copy(voltageCongestions = true) - // this subnet has 1 superior grid (ehv) and 3 inferior grids (mv). Map the gates to test probes accordingly - val subGridGateToActorRef = hvSubGridGates.map { - case gate if gate.getInferiorSubGrid == hvGridContainer.getSubnet => - gate -> superiorGridAgent.ref - case gate => - val actor = gate.getInferiorSubGrid match { - case 11 => inferiorGrid11 - case 12 => inferiorGrid12 - case 13 => inferiorGrid13 - } - gate -> actor.ref - }.toMap + s"simulate grid and check for congestions correctly if no congestions occurred" in { + val centerGridAgent = initAgentAndGotoSimulateGrid() + simulateGrid(centerGridAgent) - val gridAgentInitData = - GridAgentInitData( - hvGridContainer, - Seq.empty[ThermalGrid], - subGridGateToActorRef, - RefSystem("2000 MVA", "110 kV"), - VoltageLimits(0.9, 1.1), - ) + centerGridAgent ! CongestionCheckRequest(superiorGridAgent.ref) - val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) - // lock activation scheduled - scheduler.expectMessageType[ScheduleActivation] + // we expect a request for grid congestion values here + val congestionCheckRequestSender11 = + inferiorGrid11.expectCongestionCheckRequest() + val congestionCheckRequestSender12 = + inferiorGrid12.expectCongestionCheckRequest() + val congestionCheckRequestSender13 = + inferiorGrid13.expectCongestionCheckRequest() - centerGridAgent ! CreateGridAgent( - gridAgentInitData, - key, + // send congestions + congestionCheckRequestSender11 ! CongestionResponse( + inferiorGrid11.ref, + noCongestions, + ) + congestionCheckRequestSender12 ! CongestionResponse( + inferiorGrid12.ref, + noCongestions, + ) + congestionCheckRequestSender13 ! CongestionResponse( + inferiorGrid13.ref, + noCongestions, ) - val scheduleActivationMsg = - scheduler.expectMessageType[ScheduleActivation] - scheduleActivationMsg.tick shouldBe INIT_SIM_TICK - scheduleActivationMsg.unlockKey shouldBe Some(key) - val gridAgentActivation = scheduleActivationMsg.actor + // check the received congestions + superiorGridAgent.expectCongestionResponse(noCongestions) - centerGridAgent ! WrappedActivation(Activation(INIT_SIM_TICK)) - scheduler.expectMessage(Completion(gridAgentActivation, Some(3600))) - } + // there are no congestions + // tell all inferior grids to go back to idle + centerGridAgent ! GotoIdle - s"skip simulate grid and check for congestions correctly if no congestions occurred" in { - goToSimulateGrid() - simulateGrid() - cmStart() - cmFinish() + // inferior should receive a next state message to go to the idle state + inferiorGrid11.gaProbe.expectMessageType[GotoIdle.type] + inferiorGrid12.gaProbe.expectMessageType[GotoIdle.type] + inferiorGrid13.gaProbe.expectMessageType[GotoIdle.type] + + // expect a completion message from the superior grid + scheduler.expectMessageType[Completion] match { + case Completion(_, Some(7200)) => + case x => + fail( + s"Invalid message received when expecting a completion message for simulate grid! Message was $x" + ) + } } - s"update transformer tapping correctly" in { - goToSimulateGrid() - simulateGrid() + s"simulate grid and update transformer tapping correctly" in { + val centerGridAgent = initAgentAndGotoSimulateGrid() + simulateGrid(centerGridAgent) + + // aks for congestion check + centerGridAgent ! CongestionCheckRequest(superiorGridAgent.ref) + + // we expect a request for grid congestion values here + val congestionCheckRequestSender11 = + inferiorGrid11.expectCongestionCheckRequest() + val congestionCheckRequestSender12 = + inferiorGrid12.expectCongestionCheckRequest() + val congestionCheckRequestSender13 = + inferiorGrid13.expectCongestionCheckRequest() - val congestions = Congestions( - voltageCongestions = true, - lineCongestions = false, - transformerCongestions = false, + // send congestions + congestionCheckRequestSender11 ! CongestionResponse( + inferiorGrid11.ref, + voltageCongestions, + ) + congestionCheckRequestSender12 ! CongestionResponse( + inferiorGrid12.ref, + voltageCongestions, + ) + congestionCheckRequestSender13 ! CongestionResponse( + inferiorGrid13.ref, + voltageCongestions, ) - cmStart(congestions) + // check the received congestions + superiorGridAgent.expectCongestionResponse(voltageCongestions) + // found voltage congestions in the grid // initiate transformer tapping centerGridAgent ! NextStepRequest( TransformerTapping @@ -223,19 +242,131 @@ class DCMAlgorithmCenGridSpec .expectMessageType[VoltageDeltaResponse] .delta should equalWithTolerance(0.04.asPu) - skipSimulation() - cmStart() - cmFinish() + skipSimulation(centerGridAgent) + + // aks for congestion check + centerGridAgent ! CongestionCheckRequest(superiorGridAgent.ref) + + // we expect a request for grid congestion values here + val congestionCheckRequestSender21 = + inferiorGrid11.expectCongestionCheckRequest() + val congestionCheckRequestSender22 = + inferiorGrid12.expectCongestionCheckRequest() + val congestionCheckRequestSender23 = + inferiorGrid13.expectCongestionCheckRequest() + + // send congestions + congestionCheckRequestSender21 ! CongestionResponse( + inferiorGrid11.ref, + noCongestions, + ) + congestionCheckRequestSender22 ! CongestionResponse( + inferiorGrid12.ref, + noCongestions, + ) + congestionCheckRequestSender23 ! CongestionResponse( + inferiorGrid13.ref, + noCongestions, + ) + + // check the received congestions + superiorGridAgent.expectCongestionResponse(noCongestions) + + // there are no congestions + // tell all inferior grids to go back to idle + centerGridAgent ! GotoIdle + + // inferior should receive a next state message to go to the idle state + inferiorGrid11.gaProbe.expectMessageType[GotoIdle.type] + inferiorGrid12.gaProbe.expectMessageType[GotoIdle.type] + inferiorGrid13.gaProbe.expectMessageType[GotoIdle.type] + + // expect a completion message from the superior grid + scheduler.expectMessageType[Completion] match { + case Completion(_, Some(7200)) => + case x => + fail( + s"Invalid message received when expecting a completion message for simulate grid! Message was $x" + ) + } } - def goToSimulateGrid(): Unit = { + // helper methods + + /** Method to initialize a superior grid agent with the given config. The + * grid agent is already in the simulateGrid state. + * + * @param simonaConfig + * that enables or disables certain congestion management steps + * @return + * the [[ActorRef]] of the created superior grid agent + */ + def initAgentAndGotoSimulateGrid( + simonaConfig: SimonaConfig = config + ): ActorRef[GridAgent.Request] = { + val centerGridAgent = + testKit.spawn( + GridAgent( + environmentRefs, + simonaConfig, + listener = Iterable(resultListener.ref), + ) + ) + + // this subnet has 1 superior grid (ehv) and 3 inferior grids (mv). Map the gates to test probes accordingly + val subGridGateToActorRef = hvSubGridGates.map { + case gate if gate.getInferiorSubGrid == hvGridContainer.getSubnet => + gate -> superiorGridAgent.ref + case gate => + val actor = gate.getInferiorSubGrid match { + case 11 => inferiorGrid11 + case 12 => inferiorGrid12 + case 13 => inferiorGrid13 + } + gate -> actor.ref + }.toMap + + val gridAgentInitData = + GridAgentInitData( + hvGridContainer, + Seq.empty[ThermalGrid], + subGridGateToActorRef, + RefSystem("2000 MVA", "110 kV"), + VoltageLimits(0.9, 1.1), + ) + + val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) + // lock activation scheduled + scheduler.expectMessageType[ScheduleActivation] + + centerGridAgent ! CreateGridAgent( + gridAgentInitData, + key, + ) + + val scheduleActivationMsg = + scheduler.expectMessageType[ScheduleActivation] + scheduleActivationMsg.tick shouldBe INIT_SIM_TICK + scheduleActivationMsg.unlockKey shouldBe Some(key) + val gridAgentActivation = scheduleActivationMsg.actor + + centerGridAgent ! WrappedActivation(Activation(INIT_SIM_TICK)) + scheduler.expectMessage(Completion(gridAgentActivation, Some(3600))) + + // goto simulate grid centerGridAgent ! WrappedActivation(Activation(3600)) // we expect a completion message scheduler.expectMessageType[Completion].newTick shouldBe Some(3600) + + centerGridAgent } - def skipSimulation(): Unit = { + /** Method to skip a simulation step. + * @param centerGridAgent + * center grid agent + */ + def skipSimulation(centerGridAgent: ActorRef[GridAgent.Request]): Unit = { inferiorGrid11.gaProbe.expectMessageType[RequestGridPower] inferiorGrid12.gaProbe.expectMessageType[RequestGridPower] inferiorGrid13.gaProbe.expectMessageType[RequestGridPower] @@ -253,7 +384,7 @@ class DCMAlgorithmCenGridSpec /** Method to reduce duplicate code. This runs a simple simulation based on * the [[DBFSAlgorithmCenGridSpec]]. */ - def simulateGrid(): Unit = { + def simulateGrid(centerGridAgent: ActorRef[GridAgent.Request]): Unit = { // start the simulation val firstSweepNo = 0 @@ -543,73 +674,5 @@ class DCMAlgorithmCenGridSpec inferiorGrid13.gaProbe.expectMessage(FinishGridSimulationTrigger(3600)) } - /** Method to reduce duplicate code - * @param congestions - * to be send to the [[centerGridAgent]] (default: no congestions) - */ - def cmStart( - congestions: Congestions = Congestions( - voltageCongestions = false, - lineCongestions = false, - transformerCongestions = false, - ) - ): Unit = { - centerGridAgent ! CongestionCheckRequest(superiorGridAgent.ref) - - // we expect a request for grid congestion values here - val congestionCheckRequestSender11 = - inferiorGrid11.gaProbe - .expectMessageType[CongestionCheckRequest](10.seconds) - .sender - val congestionCheckRequestSender12 = - inferiorGrid12.gaProbe - .expectMessageType[CongestionCheckRequest](10.seconds) - .sender - val congestionCheckRequestSender13 = - inferiorGrid13.gaProbe - .expectMessageType[CongestionCheckRequest](10.seconds) - .sender - - // send congestions - congestionCheckRequestSender11 ! CongestionResponse( - inferiorGrid11.ref, - congestions, - ) - congestionCheckRequestSender12 ! CongestionResponse( - inferiorGrid12.ref, - congestions, - ) - congestionCheckRequestSender13 ! CongestionResponse( - inferiorGrid13.ref, - congestions, - ) - - // we expect transformer congestions in the whole grid - val allCongestions = superiorGridAgent.gaProbe - .expectMessageType[CongestionResponse](30.seconds) - .value - allCongestions shouldBe congestions - } - - /** Method to reduce duplicate code - */ - def cmFinish(): Unit = { - // telling all inferior grids to go back to idle - centerGridAgent ! GotoIdle - - // inferior should receive a next state message to go to the idle state - inferiorGrid11.gaProbe.expectMessageType[GotoIdle.type] - inferiorGrid12.gaProbe.expectMessageType[GotoIdle.type] - inferiorGrid13.gaProbe.expectMessageType[GotoIdle.type] - - // expect a completion message from the superior grid - scheduler.expectMessageType[Completion] match { - case Completion(_, Some(7200)) => - case x => - fail( - s"Invalid message received when expecting a completion message for simulate grid! Message was $x" - ) - } - } } } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala index dda24e9c6b..3885d3dd20 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala @@ -78,52 +78,34 @@ class DCMAlgorithmSupGridSpec val resultListener: TestProbe[ResultEvent] = TestProbe("resultListener") "A GridAgent actor in superior position with async test" should { - val superiorGridAgent: ActorRef[GridAgent.Request] = testKit.spawn( - GridAgent( - environmentRefs, - config, - listener = Iterable(resultListener.ref), - ) - ) - - s"initialize itself when it receives an init activation" in { - val subnetGatesToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]] = - ehvSubGridGates.map(gate => gate -> hvGrid.ref).toMap - - val gridAgentInitData = - GridAgentInitData( - ehvGridContainer, - Seq.empty[ThermalGrid], - subnetGatesToActorRef, - RefSystem("5000 MVA", "110 kV"), - VoltageLimits(0.9, 1.05), - ) - - val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) - // lock activation scheduled - scheduler.expectMessageType[ScheduleActivation] - - superiorGridAgent ! CreateGridAgent(gridAgentInitData, key) + val refSystem = RefSystem(Kilowatts(600), Kilovolts(110)) - val scheduleActivationMsg = - scheduler.expectMessageType[ScheduleActivation] - scheduleActivationMsg.tick shouldBe INIT_SIM_TICK - scheduleActivationMsg.unlockKey shouldBe Some(key) - val gridAgentActivation = scheduleActivationMsg.actor + val tappingModel = TransformerModel( + transformer1, + refSystem, + start, + end, + ) - superiorGridAgent ! WrappedActivation(Activation(INIT_SIM_TICK)) - scheduler.expectMessage(Completion(gridAgentActivation, Some(3600))) - } + val tappingModel2 = TransformerModel( + transformer2, + refSystem, + start, + end, + ) s"skip simulate grid and check for congestions correctly if no congestions occurred" in { - gotoSimulateGrid() + val superiorGridAgent = initAgentAndGotoSimulateGrid() + + val lastSender = skipSimulationAndGetNextStep(superiorGridAgent) - skipSimulationAndGetNextStep( + lastSender ! CongestionResponse( + hvGrid.ref, Congestions( voltageCongestions = false, lineCongestions = false, transformerCongestions = false, - ) + ), ) // inferior should receive a next state message to go to the idle state @@ -139,17 +121,20 @@ class DCMAlgorithmSupGridSpec } } - s"handle unresolvable congestions correctly" in { - gotoSimulateGrid() + s"skip simulate grid and handle unresolvable congestions correctly" in { + val superiorGridAgent = initAgentAndGotoSimulateGrid() // transformer congestion cannot be resolved, because using flex options is not // enable by the provided config - skipSimulationAndGetNextStep( + val lastSender = skipSimulationAndGetNextStep(superiorGridAgent) + + lastSender ! CongestionResponse( + hvGrid.ref, Congestions( voltageCongestions = false, lineCongestions = false, transformerCongestions = true, - ) + ), ) // inferior should receive a next state message to go to the idle state @@ -165,34 +150,24 @@ class DCMAlgorithmSupGridSpec } } - s"update transformer tapping correctly" in { - gotoSimulateGrid() + s"skip simulate grid and update transformer tapping correctly" in { + val superiorGridAgent = initAgentAndGotoSimulateGrid() - skipSimulationAndGetNextStep( + val lastSender1 = skipSimulationAndGetNextStep(superiorGridAgent) + + // send congestions + lastSender1 ! CongestionResponse( + hvGrid.ref, Congestions( voltageCongestions = true, lineCongestions = false, transformerCongestions = false, - ) + ), ) // inferior should receive a next state message to go to a congestion management step hvGrid.expectMessageType[NextStepRequest] - val tappingModel = TransformerModel( - transformer1, - RefSystem(Kilowatts(600), Kilovolts(110)), - start, - end, - ) - - val tappingModel2 = TransformerModel( - transformer2, - RefSystem(Kilowatts(600), Kilovolts(110)), - start, - end, - ) - hvGrid.expectMessageType[RequestVoltageOptions] match { case RequestVoltageOptions(sender) => sender ! VoltageRangeResponse( @@ -212,12 +187,16 @@ class DCMAlgorithmSupGridSpec // skipping the simulation hvGrid.expectMessageType[RequestGridPower] - skipSimulationAndGetNextStep( + val lastSender2 = skipSimulationAndGetNextStep(superiorGridAgent) + + // send congestions + lastSender2 ! CongestionResponse( + hvGrid.ref, Congestions( voltageCongestions = false, lineCongestions = false, transformerCongestions = false, - ) + ), ) // inferior should receive a next state message to go to the idle state @@ -234,7 +213,18 @@ class DCMAlgorithmSupGridSpec } } - def skipSimulationAndGetNextStep(congestions: Congestions): Unit = { + // helper methods + + /** There is no need to perform an actual simulation of the grid, therefor + * we can use this method to skip the + * @param superiorGridAgent + * the superior grid agent + * @return + * the [[ActorRef]] of the last sender + */ + def skipSimulationAndGetNextStep( + superiorGridAgent: ActorRef[GridAgent.Request] + ): ActorRef[GridAgent.Request] = { // skip simulation and go to congestion check superiorGridAgent ! FinishGridSimulationTrigger(3600) @@ -251,15 +241,62 @@ class DCMAlgorithmSupGridSpec ) } - // send congestions - lastSender ! CongestionResponse(hvGrid.ref, congestions) + // return the last sender + lastSender } - def gotoSimulateGrid(): Unit = { + /** Method to initialize a superior grid agent with the given config. The + * grid agent is already in the simulateGrid state. + * @param simonaConfig + * that enables or disables certain congestion management steps + * @return + * the [[ActorRef]] of the created superior grid agent + */ + def initAgentAndGotoSimulateGrid( + simonaConfig: SimonaConfig = config + ): ActorRef[GridAgent.Request] = { + val superiorGridAgent: ActorRef[GridAgent.Request] = testKit.spawn( + GridAgent( + environmentRefs, + simonaConfig, + listener = Iterable(resultListener.ref), + ) + ) + + val subnetGatesToActorRef: Map[SubGridGate, ActorRef[GridAgent.Request]] = + ehvSubGridGates.map(gate => gate -> hvGrid.ref).toMap + + val gridAgentInitData = + GridAgentInitData( + ehvGridContainer, + Seq.empty[ThermalGrid], + subnetGatesToActorRef, + RefSystem("5000 MVA", "110 kV"), + VoltageLimits(0.9, 1.05), + ) + + val key = ScheduleLock.singleKey(TSpawner, scheduler.ref, INIT_SIM_TICK) + // lock activation scheduled + scheduler.expectMessageType[ScheduleActivation] + + superiorGridAgent ! CreateGridAgent(gridAgentInitData, key) + + val scheduleActivationMsg = + scheduler.expectMessageType[ScheduleActivation] + scheduleActivationMsg.tick shouldBe INIT_SIM_TICK + scheduleActivationMsg.unlockKey shouldBe Some(key) + val gridAgentActivation = scheduleActivationMsg.actor + + superiorGridAgent ! WrappedActivation(Activation(INIT_SIM_TICK)) + scheduler.expectMessage(Completion(gridAgentActivation, Some(3600))) + + // goto simulate grid superiorGridAgent ! WrappedActivation(Activation(3600)) // we expect a completion message scheduler.expectMessageType[Completion].newTick shouldBe Some(3600) + + superiorGridAgent } } } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala index 36e8655a24..4a6d3caad8 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala @@ -206,6 +206,8 @@ class GridAgentDataSpec extends UnitSpec with DbfsTestGrid with ConfigTestData { ) } + // TODO: Add cleaning method tests + } } From 60ac710d78fd19d64a1bbd51ee17198c1c8fc1a2 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 3 May 2024 11:12:40 +0200 Subject: [PATCH 23/55] Improving tap calculation. --- .../grid/CongestionManagementSupport.scala | 20 ++++++++++++------- .../model/grid/TransformerTapping.scala | 7 +++---- .../model/grid/TransformerTappingModel.scala | 6 ------ 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 92727d56b8..2ad6952e12 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.agent.grid +import edu.ie3.datamodel.models.input.connector.ConnectorPort import edu.ie3.datamodel.models.result.connector.LineResult import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent @@ -80,17 +81,20 @@ trait CongestionManagementSupport { suggestion: ComparableQuantity[Dimensionless], tappings: Seq[TransformerTapping], ): (Map[TransformerTapping, Int], ComparableQuantity[Dimensionless]) = { - val inverted = suggestion.multiply(-1) - if (tappings.size == 1) { + if (tappings.exists(_.tapSide != ConnectorPort.A)) { + // for now only work if all transformers have the tapping at the hv side + return (tappings.map(t => t -> 0).toMap, 0.asPu) + } + + val option = if (tappings.size == 1) { val tapping = tappings(0) val taps = tapping.computeDeltaTap(inverted) val delta = tapping.deltaV.getValue.doubleValue() * taps / -100 - (Map(tapping -> taps), delta.asPu) + Some(Map(tapping -> taps), delta.asPu) } else { - val possibleChange = tappings.map { tapping => val taps = tapping.computeDeltaTap(inverted) val delta = tapping.deltaV.getValue.doubleValue() * taps / 100 @@ -121,15 +125,17 @@ trait CongestionManagementSupport { } if (check) { - (changes.map(t => t._1 -> t._2._1), max.multiply(-1)) + Some(changes.map(t => t._1 -> t._2._1), max.multiply(-1)) } else { - (tappings.map(t => t -> 0).toMap, 0.asPu) + None } case None => - (tappings.map(t => t -> 0).toMap, 0.asPu) + None } } + + option.getOrElse((tappings.map(t => t -> 0).toMap, 0.asPu)) } /** Method to calculate the range of possible voltage changes. diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala index 6eb18098a4..00b7845257 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.model.grid +import edu.ie3.datamodel.models.input.connector.ConnectorPort import edu.ie3.util.quantities.PowerSystemUnits._ import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import tech.units.indriya.ComparableQuantity @@ -40,12 +41,10 @@ trait TransformerTapping { def deltaV: ComparableQuantity[Dimensionless] = transformerTappingModel.deltaV.getValue.doubleValue().asPu - def maxTapIncrease: Int = tapMax - currentTapPos - - def maxTapDecrease: Int = tapMin - currentTapPos - def currentTapPos: Int = transformerTappingModel.currentTapPos + def tapSide: ConnectorPort = transformerTappingModel.tapSide + /** Initialize the tapping model. Should be called after creating the * implementing model */ diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerTappingModel.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerTappingModel.scala index ef8b4d3ce4..602259cec2 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerTappingModel.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerTappingModel.scala @@ -50,12 +50,6 @@ final case class TransformerTappingModel( def currentTapPos: Int = _currentTapPos - def maxIncrease: Quantity[Dimensionless] = - deltaV.multiply(tapMax - currentTapPos) - - def maxDecrease: Quantity[Dimensionless] = - deltaV.multiply(tapMin - currentTapPos) - /** Increase tap position by the provided number of delta taps * * @param deltaTap From 402daf3281cda421efd8496c00f6209839000a88 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 22 May 2024 11:21:40 +0200 Subject: [PATCH 24/55] Some additions. --- input/ma_thesis/ma_thesis.conf | 186 ++++++++++++++++++ .../resources/config/config-template.conf | 2 + .../grid/CongestionManagementSupport.scala | 2 +- .../ie3/simona/config/ConfigFailFast.scala | 77 ++++---- .../edu/ie3/simona/config/SimonaConfig.scala | 144 +++++++++++++- .../simona/config/VoltageLimitsParser.scala | 162 +++++++-------- .../model/grid/TransformerTapping.scala | 2 +- .../edu/ie3/simona/util/CombineGrids.scala | 105 ++++++++++ 8 files changed, 560 insertions(+), 120 deletions(-) create mode 100644 input/ma_thesis/ma_thesis.conf create mode 100644 src/main/scala/edu/ie3/simona/util/CombineGrids.scala diff --git a/input/ma_thesis/ma_thesis.conf b/input/ma_thesis/ma_thesis.conf new file mode 100644 index 0000000000..5da44482da --- /dev/null +++ b/input/ma_thesis/ma_thesis.conf @@ -0,0 +1,186 @@ +include "../samples/common/pekko.conf" + +######### +# ATTENTION: Do not change this config file directly but use it as a base for your personal delta config for the +# vn_simona scenario! Delta configs can be created by including the config you want to change +# parameters from via include (e.g. include "input/samples/vn_simona/vn_simona.conf") at the +# beginning of your config file and then just override the parameters you want to change! +######### + +################################################################## +# Simulation Parameters +################################################################## +simona.simulationName = "ma_thesis" + +################################################################## +# Time Parameters +################################################################## +simona.time.startDateTime = "2016-01-01T00:00:00Z" +simona.time.endDateTime = "2016-01-01T02:00:00Z" +simona.time.schedulerReadyCheckWindow = 900 + +################################################################## +# Input Parameters +################################################################## +simona.input.primary.csvParams = { + directoryPath: "input/ma_thesis/fullGrid/primary" + csvSep: ";" + isHierarchic: false +} +simona.input.grid.datasource.id = "csv" +simona.input.grid.datasource.csvParams = { + directoryPath: "input/ma_thesis/fullGrid" + csvSep: ";" + isHierarchic: false +} + +simona.input.weather.datasource = { + scheme = "icon" + sampleParams.use = true + coordinateSource.sampleParams.use = true + maxCoordinateDistance = 50000 +} + +################################################################## +# Output Parameters +################################################################## +simona.output.base.dir = "output/ma_thesis" +simona.output.base.addTimestampToOutputDir = true + +simona.output.sink.csv { + fileFormat = ".csv" + filePrefix = "" + fileSuffix = "" +} + +simona.output.grid = { + notifier = "grid" + nodes = true + lines = true + switches = true + transformers2w = true + transformers3w = true +} +simona.output.participant.defaultConfig = { + notifier = "default" + powerRequestReply = false + simulationResult = true +} +simona.output.participant.individualConfigs = [] + +simona.output.thermal = { + defaultConfig = { + notifier = "default", + simulationResult = false + } + individualConfigs = [ + { + notifier = "house", + simulationResult = true + } + ] +} + +################################################################## +# Runtime Configuration // todo refactor as this naming is misleading and partly unneeded +################################################################## +simona.runtime.selected_subgrids = [] +simona.runtime.selected_volt_lvls = [] + +simona.runtime.participant.load = { + defaultConfig = { + calculateMissingReactivePowerWithModel = false + uuids = ["default"] + scaling = 1.0 + modelBehaviour = "fix" + reference = "power" + } + individualConfigs = [] +} + +simona.runtime.participant.fixedFeedIn = { + defaultConfig = { + calculateMissingReactivePowerWithModel = false + uuids = ["default"] + scaling = 1.0 + } + individualConfigs = [] +} + +simona.runtime.participant.pv = { + defaultConfig = { + calculateMissingReactivePowerWithModel = false + uuids = ["default"] + scaling = 1.0 + } + individualConfigs = [] +} + +simona.runtime.participant.wec = { + defaultConfig = { + calculateMissingReactivePowerWithModel = false + uuids = ["default"] + scaling = 1.0 + } + individualConfigs = [] +} + +simona.runtime.participant.evcs = { + defaultConfig = { + calculateMissingReactivePowerWithModel = false + uuids = ["default"] + scaling = 1.0 + } + individualConfigs = [] +} + +simona.runtime.participant.hp = { + defaultConfig = { + calculateMissingReactivePowerWithModel = false + uuids = ["default"] + scaling = 1.0 + } + individualConfigs = [] +} + +# # # # # +# ATTENTION: calculateMissingReactivePowerWithModel and scaling is ignored here. +# # # # # +simona.runtime.participant.em = { + defaultConfig = { + calculateMissingReactivePowerWithModel = false + uuids = ["default"] + scaling = 1.0 + } + individualConfigs = [] +} + +################################################################## +# Event Configuration +################################################################## +simona.event.listener = [] + +################################################################## +# Grid Configuration +################################################################## + + + +################################################################## +# Power Flow Configuration +################################################################## +simona.powerflow.maxSweepPowerDeviation = 1E-5 // the maximum allowed deviation in power between two sweeps, before overall convergence is assumed +simona.powerflow.newtonraphson.epsilon = [1E-12] +simona.powerflow.newtonraphson.iterations = 50 +simona.powerflow.resolution = "3600s" +simona.powerflow.stopOnFailure = true + +simona.control.transformer = [] + +################################################################## +# Congestion Management Configuration +################################################################## + +simona.congestionManagement.enableTransformerTapping = false +simona.congestionManagement.enableTopologyChanges = false +simona.congestionManagement.useFlexOptions = false diff --git a/src/main/resources/config/config-template.conf b/src/main/resources/config/config-template.conf index 7df851e1ba..36a0485295 100644 --- a/src/main/resources/config/config-template.conf +++ b/src/main/resources/config/config-template.conf @@ -356,6 +356,8 @@ simona.powerflow.stopOnFailure = boolean | false #@optional simona.gridConfig.refSystems = [RefSystemConfig] + +#@optional simona.gridConfig.voltageLimits = [VoltageLimitsConfig] ################################################################## diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 2ad6952e12..d4f062e3b2 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -83,7 +83,7 @@ trait CongestionManagementSupport { ): (Map[TransformerTapping, Int], ComparableQuantity[Dimensionless]) = { val inverted = suggestion.multiply(-1) - if (tappings.exists(_.tapSide != ConnectorPort.A)) { + if (tappings.exists(_.getTapSide != ConnectorPort.A)) { // for now only work if all transformers have the tapping at the hv side return (tappings.map(t => t -> 0).toMap, 0.asPu) } diff --git a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala index 439379b84b..708902f84f 100644 --- a/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala +++ b/src/main/scala/edu/ie3/simona/config/ConfigFailFast.scala @@ -118,6 +118,7 @@ case 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 */ @@ -508,50 +509,54 @@ case object ConfigFailFast extends LazyLogging { * @param voltageLimits * the [[SimonaConfig.VoltageLimitsConfig]] that should be checked */ - private def checkVoltageLimits(voltageLimits: VoltageLimitsConfig): Unit = { - val voltLvls = - voltageLimits.voltLvls.getOrElse(List.empty[SimonaConfig.VoltLvlConfig]) - val gridIds = voltageLimits.gridIds.getOrElse(List.empty[String]) + 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." - ) + 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)) + 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 electrical potential! Please provide the volt level with its unit, e.g. \"20 kV\"" + 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, ) - case Failure(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 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 provided gridId $gridId is malformed!" ) } - } - 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." - ) + 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." + ) + } } } diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index 2a046a31cf..cba467637b 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -943,6 +943,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, @@ -1004,6 +1071,7 @@ object SimonaConfig { } final case class Simona( + congestionManagement: SimonaConfig.Simona.CongestionManagement, control: scala.Option[SimonaConfig.Simona.Control], event: SimonaConfig.Simona.Event, gridConfig: SimonaConfig.Simona.GridConfig, @@ -1015,6 +1083,40 @@ object SimonaConfig { time: SimonaConfig.Simona.Time, ) object Simona { + final case class CongestionManagement( + enableTopologyChanges: scala.Boolean, + enableTransformerTapping: scala.Boolean, + maxOptimizationIterations: scala.Int, + timeout: java.time.Duration, + useFlexOptions: scala.Boolean, + ) + object CongestionManagement { + def apply( + c: com.typesafe.config.Config, + parentPath: java.lang.String, + $tsCfgValidator: $TsCfgValidator, + ): SimonaConfig.Simona.CongestionManagement = { + SimonaConfig.Simona.CongestionManagement( + enableTopologyChanges = + c.hasPathOrNull("enableTopologyChanges") && c.getBoolean( + "enableTopologyChanges" + ), + enableTransformerTapping = c.hasPathOrNull( + "enableTransformerTapping" + ) && c.getBoolean("enableTransformerTapping"), + maxOptimizationIterations = + if (c.hasPathOrNull("maxOptimizationIterations")) + c.getInt("maxOptimizationIterations") + else 1, + timeout = + if (c.hasPathOrNull("timeout")) c.getDuration("timeout") + else java.time.Duration.parse("PT30S"), + useFlexOptions = + c.hasPathOrNull("useFlexOptions") && c.getBoolean("useFlexOptions"), + ) + } + } + final case class Control( transformer: scala.List[SimonaConfig.TransformerControlGroup] ) @@ -1136,7 +1238,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( @@ -1154,7 +1259,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( @@ -1173,6 +1288,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( @@ -2786,6 +2917,15 @@ object SimonaConfig { $tsCfgValidator: $TsCfgValidator, ): SimonaConfig.Simona = { SimonaConfig.Simona( + congestionManagement = SimonaConfig.Simona.CongestionManagement( + if (c.hasPathOrNull("congestionManagement")) + c.getConfig("congestionManagement") + else + com.typesafe.config.ConfigFactory + .parseString("congestionManagement{}"), + parentPath + "congestionManagement.", + $tsCfgValidator, + ), control = if (c.hasPathOrNull("control")) scala.Some( diff --git a/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala b/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala index 871d1a398e..ed4ccdaeec 100644 --- a/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala +++ b/src/main/scala/edu/ie3/simona/config/VoltageLimitsParser.scala @@ -29,91 +29,93 @@ object VoltageLimitsParser { } def parse( - configVoltageLimits: List[SimonaConfig.VoltageLimitsConfig] + configVoltageLimits: Option[List[SimonaConfig.VoltageLimitsConfig]] ): ConfigVoltageLimits = { + val distributionVoltageLimits = VoltageLimits(0.9, 1.1) - if (configVoltageLimits.isEmpty) { - val voltageLimit = VoltageLimits(0.9, 1.1) - - ConfigVoltageLimits( - Map.empty, - Map( - GermanVoltageLevelUtils.LV -> voltageLimit, - GermanVoltageLevelUtils.MV_10KV -> voltageLimit, - GermanVoltageLevelUtils.MV_20KV -> voltageLimit, - GermanVoltageLevelUtils.MV_30KV -> voltageLimit, - GermanVoltageLevelUtils.HV -> voltageLimit, - GermanVoltageLevelUtils.EHV_220KV -> VoltageLimits(0.9, 1.118), - GermanVoltageLevelUtils.EHV_380KV -> VoltageLimits(0.9, 1.05), - ), - ) - - } else { - - val voltageLimits = configVoltageLimits.map { configVoltageLimit => - ( - configVoltageLimit, - VoltageLimits(configVoltageLimit.vMin, configVoltageLimit.vMax), - ) - } - - val gridIdVoltageLimits = voltageLimits.flatMap { case (config, limits) => - config.gridIds - .map { - _.flatMap { gridId => - { - val allGridIds = gridId match { - case ConfigConventions.gridIdDotRange(from, to) => - from.toInt to to.toInt - case ConfigConventions.gridIdMinusRange(from, to) => - from.toInt to to.toInt - case ConfigConventions.singleGridId(singleGridId) => - Seq(singleGridId.toInt) - case unknownGridIdFormat => - throw new InvalidConfigParameterException( - s"Unknown gridId format $unknownGridIdFormat provided for voltage limits $config" - ) - } - - allGridIds.map(gridId => (gridId, limits)) + 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), + ), + ) + + configVoltageLimits match { + case Some(voltageLimits) if voltageLimits.nonEmpty => + val parsedVoltageLimits = voltageLimits.flatMap { configVoltageLimit => + val voltageLimits = + VoltageLimits(configVoltageLimit.vMin, configVoltageLimit.vMax) + + configVoltageLimit.gridIds.getOrElse(Seq.empty).flatMap { + case ConfigConventions.gridIdDotRange(from, to) => + from.toInt to to.toInt + case ConfigConventions.gridIdMinusRange(from, to) => + from.toInt to to.toInt + case ConfigConventions.singleGridId(singleGridId) => + Seq(singleGridId.toInt) + case unknownGridIdFormat => + throw new InvalidConfigParameterException( + s"Unknown gridId format $unknownGridIdFormat provided for voltage limits $configVoltageLimit" + ) + } ++ configVoltageLimit.voltLvls.getOrElse(Seq.empty).map { + voltLvlDef => + (VoltLvlParser.from(voltLvlDef), voltageLimits) + } + } + + val gridIdVoltageLimitsList: List[(Int, VoltageLimits)] = + parsedVoltageLimits.flatMap { + case (gridId: Int, refSystems) => + refSystems match { + case voltageLimits: VoltageLimits => + Some(gridId -> voltageLimits) + case _ => None } - } + case _ => None } - .getOrElse(Seq.empty[(Int, VoltageLimits)]) - } - - val voltLvlVoltageLimits = voltageLimits.flatMap { - case (configRefSystem, parsedRefSystem) => - configRefSystem.voltLvls - .map { - _.map { voltLvlDef => - (VoltLvlParser.from(voltLvlDef), parsedRefSystem) + + val gridIdVoltageLimits: Map[Int, VoltageLimits] = + gridIdVoltageLimitsList.toMap + + if (CollectionUtils.listHasDuplicates(gridIdVoltageLimitsList)) { + throw new InvalidConfigParameterException( + s"The provided gridIds in simona.gridConfig.voltageLimits contains duplicates. " + + s"Please check if there are either duplicate entries or overlapping ranges!" + ) + } + + val voltLvLVoltageLimitsList: List[(VoltageLevel, VoltageLimits)] = + parsedVoltageLimits.flatMap { + case (voltLvl: VoltageLevel, refSystems) => + refSystems match { + case voltageLimits: VoltageLimits => + Some(voltLvl -> voltageLimits) + case _ => None } - } - .getOrElse(Seq.empty) - } - - // check for duplicates of gridIds and voltLevels which will be the key for the following map conversion - if ( - CollectionUtils.listHasDuplicates( - gridIdVoltageLimits.map { case (gridId, _) => gridId } - ) - ) - throw new InvalidConfigParameterException( - s"The provided gridIds in simona.gridConfig.voltageLimits contains duplicates. " + - s"Please check if there are either duplicate entries or overlapping ranges!" - ) - if ( - CollectionUtils.listHasDuplicates( - voltLvlVoltageLimits.map { case (voltLvl, _) => voltLvl } - ) - ) - throw new InvalidConfigParameterException( - s"The provided voltLvls in simona.gridConfig.voltageLimits contains duplicates. " + - s"Please check your configuration for duplicates in voltLvl entries!" - ) - - ConfigVoltageLimits(gridIdVoltageLimits.toMap, voltLvlVoltageLimits.toMap) + case _ => None + } + + if (CollectionUtils.listHasDuplicates(voltLvLVoltageLimitsList)) + throw new InvalidConfigParameterException( + s"The provided voltLvls in simona.gridConfig.voltageLimits contains duplicates. " + + s"Please check your configuration for duplicates in voltLvl entries!" + ) + + val voltLvLVoltageLimits: Map[VoltageLevel, VoltageLimits] = + parsedVoltageLimits.collect { + case (voltLvl: VoltageLevel, values: VoltageLimits) => + (voltLvl, values) + }.toMap + + ConfigVoltageLimits(gridIdVoltageLimits, voltLvLVoltageLimits) + + case _ => defaultVoltageLimits } } diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala index 00b7845257..a893f85709 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala @@ -43,7 +43,7 @@ trait TransformerTapping { def currentTapPos: Int = transformerTappingModel.currentTapPos - def tapSide: ConnectorPort = transformerTappingModel.tapSide + def getTapSide: ConnectorPort = transformerTappingModel.tapSide /** Initialize the tapping model. Should be called after creating the * implementing model diff --git a/src/main/scala/edu/ie3/simona/util/CombineGrids.scala b/src/main/scala/edu/ie3/simona/util/CombineGrids.scala new file mode 100644 index 0000000000..b241da85c1 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/util/CombineGrids.scala @@ -0,0 +1,105 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona.util + +import edu.ie3.datamodel.io.naming.FileNamingStrategy +import edu.ie3.datamodel.io.sink.CsvFileSink +import edu.ie3.datamodel.io.source.csv.CsvJointGridContainerSource +import edu.ie3.datamodel.models.input.container.{ + GraphicElements, + JointGridContainer, + RawGridElements, + SystemParticipants, +} +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble + +import java.io.File +import java.nio.file.Path +import scala.jdk.CollectionConverters._ + +object CombineGrids { + def main(args: Array[String]): Unit = { + val input = Path.of(".", "input", "grids") + val output = Path.of(".", "output", "grid") + + val files: List[File] = input.toFile.listFiles().toList + + files.foreach(println) + + val grids = files.map(file => + CsvJointGridContainerSource.read(file.getName, ";", file.toPath, false) + ) + + val combinedGrid = combine(grids(0), grids.drop(1)) + + val sink: CsvFileSink = + new CsvFileSink(output, new FileNamingStrategy(), ";") + sink.persistJointGrid(combinedGrid) + } + + def combine( + first: JointGridContainer, + grids: List[JointGridContainer], + ): JointGridContainer = { + + val lvGrids: Seq[(RawGridElements, SystemParticipants)] = grids.map { + grid => + val raw = grid.getRawGrid + + val nodes = raw.getNodes.asScala.filter(node => + node.getVoltLvl.getNominalVoltage.isEquivalentTo(0.4.asKiloVolt) + ) + val lines = + raw.getLines.asScala.filter(line => nodes.contains(line.getNodeA)) + + val t2w = + raw.getTransformer2Ws.asScala.filter(t => nodes.contains(t.getNodeB)) + val t3w = + raw.getTransformer3Ws.asScala.filter(t => nodes.contains(t.getNodeB)) + + val switches = + raw.getSwitches.asScala.filter(s => nodes.contains(s.getNodeA)) + val measurementUnits = raw.getMeasurementUnits.asScala.filter(mU => + nodes.contains(mU.getNode) + ) + + val participants = grid.getSystemParticipants + .allEntitiesAsList() + .asScala + .filter(p => p.allNodes().asScala.forall(nodes.contains)) + + ( + new RawGridElements( + nodes.asJava, + lines.asJava, + t2w.asJava, + t3w.asJava, + switches.asJava, + measurementUnits.asJava, + ), + new SystemParticipants(participants.asJava), + ) + } + + val rawGrids = lvGrids.map(_._1) + val participants = lvGrids.map(_._2) + + val allRawGridElements = new RawGridElements( + List(first.getRawGrid).appendedAll(rawGrids).asJava + ) + val allParticipants = new SystemParticipants( + List(first.getSystemParticipants).appendedAll(participants).asJava + ) + + new JointGridContainer( + "", + allRawGridElements, + allParticipants, + new GraphicElements(List.empty[GraphicElements].asJava), + ) + } +} From c3fe3d8d5f394866fc5d3f98c44da73079c759e3 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 22 May 2024 19:12:39 +0200 Subject: [PATCH 25/55] Improve grid merging. --- .../edu/ie3/simona/util/CombineGrids.scala | 95 ++++++++++++++++++- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/util/CombineGrids.scala b/src/main/scala/edu/ie3/simona/util/CombineGrids.scala index b241da85c1..1780d9af8a 100644 --- a/src/main/scala/edu/ie3/simona/util/CombineGrids.scala +++ b/src/main/scala/edu/ie3/simona/util/CombineGrids.scala @@ -6,9 +6,15 @@ package edu.ie3.simona.util +import edu.ie3.datamodel.io.csv.CsvIndividualTimeSeriesMetaInformation import edu.ie3.datamodel.io.naming.FileNamingStrategy import edu.ie3.datamodel.io.sink.CsvFileSink -import edu.ie3.datamodel.io.source.csv.CsvJointGridContainerSource +import edu.ie3.datamodel.io.source.csv.{ + CsvJointGridContainerSource, + CsvTimeSeriesMappingSource, + CsvTimeSeriesMetaInformationSource, + CsvTimeSeriesSource, +} import edu.ie3.datamodel.models.input.container.{ GraphicElements, JointGridContainer, @@ -20,6 +26,7 @@ import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import java.io.File import java.nio.file.Path import scala.jdk.CollectionConverters._ +import scala.jdk.OptionConverters.RichOptional object CombineGrids { def main(args: Array[String]): Unit = { @@ -38,7 +45,14 @@ object CombineGrids { val sink: CsvFileSink = new CsvFileSink(output, new FileNamingStrategy(), ";") + sink.persistJointGrid(combinedGrid) + + combineAndSinkPrimaryData( + combinedGrid, + files.map(_.toPath.resolve("primary")), + new CsvFileSink(output.resolve("primary"), new FileNamingStrategy(), ";"), + ) } def combine( @@ -46,6 +60,8 @@ object CombineGrids { grids: List[JointGridContainer], ): JointGridContainer = { + val nodeMap = first.getRawGrid.getNodes.asScala.map(n => n.getId -> n).toMap + val lvGrids: Seq[(RawGridElements, SystemParticipants)] = grids.map { grid => val raw = grid.getRawGrid @@ -57,9 +73,20 @@ object CombineGrids { raw.getLines.asScala.filter(line => nodes.contains(line.getNodeA)) val t2w = - raw.getTransformer2Ws.asScala.filter(t => nodes.contains(t.getNodeB)) + raw.getTransformer2Ws.asScala + .filter(t => nodes.contains(t.getNodeB)) + .map { t => + val updatedNode = nodeMap(t.getNodeA.getId) + t.copy().nodeA(updatedNode).build() + } + val t3w = - raw.getTransformer3Ws.asScala.filter(t => nodes.contains(t.getNodeB)) + raw.getTransformer3Ws.asScala + .filter(t => nodes.contains(t.getNodeB)) + .map { t => + val updatedNode = nodeMap(t.getNodeA.getId) + t.copy().nodeA(updatedNode).build() + } val switches = raw.getSwitches.asScala.filter(s => nodes.contains(s.getNodeA)) @@ -102,4 +129,66 @@ object CombineGrids { new GraphicElements(List.empty[GraphicElements].asJava), ) } + + private def combineAndSinkPrimaryData( + combinedGrid: JointGridContainer, + paths: List[Path], + sink: CsvFileSink, + ): Unit = { + + val uuids = combinedGrid.getSystemParticipants + .allEntitiesAsList() + .asScala + .map(_.getUuid) + + paths.foreach { path => + val (mappingSource, metaInformation) = readPrimary(path) + + val timeSeriesUuids = mappingSource.getMapping.asScala.toMap.filter { + case (participant, _) => uuids.contains(participant) + }.values + + println(s"${timeSeriesUuids.size} of ${mappingSource.getMapping.size()}") + + timeSeriesUuids.toList.asJava + .parallelStream() + .forEach(uuid => { + val option = + metaInformation.getTimeSeriesMetaInformation(uuid).toScala + + option.foreach { + case csvIndividualTimeSeriesMetaInformation: CsvIndividualTimeSeriesMetaInformation => + val series = CsvTimeSeriesSource + .getSource( + ";", + path, + new FileNamingStrategy(), + csvIndividualTimeSeriesMetaInformation, + ) + .getTimeSeries + + sink.persistTimeSeries(series) + } + }) + } + } + + private def readPrimary( + directoryPath: Path, + csvSep: String = ";", + ): (CsvTimeSeriesMappingSource, CsvTimeSeriesMetaInformationSource) = { + val fileNamingStrategy = new FileNamingStrategy() + ( + new CsvTimeSeriesMappingSource( + csvSep, + directoryPath, + fileNamingStrategy, + ), + new CsvTimeSeriesMetaInformationSource( + csvSep, + directoryPath, + fileNamingStrategy, + ), + ) + } } From b06b11085359319be1ca4141ff788af62a18c59b Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 23 May 2024 11:19:33 +0200 Subject: [PATCH 26/55] Saving changes. --- input/ma_thesis/ma_thesis.conf | 5 +- input/samples/vn_simona/vn_simona.conf | 1 + .../resources/config/config-template.conf | 1 + .../grid/CongestionManagementParams.scala | 12 +- .../ie3/simona/agent/grid/DBFSAlgorithm.scala | 4 +- .../edu/ie3/simona/agent/grid/GridAgent.scala | 1 + .../ie3/simona/config/RefSystemParser.scala | 2 +- .../edu/ie3/simona/config/SimonaConfig.scala | 2 + .../simona/model/grid/TransformerModel.scala | 6 +- .../edu/ie3/simona/util/CombineGrids.scala | 194 ------------------ .../agent/grid/DCMAlgorithmCenGridSpec.scala | 1 + .../agent/grid/DCMAlgorithmSupGridSpec.scala | 1 + .../simona/test/common/ConfigTestData.scala | 1 + 13 files changed, 20 insertions(+), 211 deletions(-) delete mode 100644 src/main/scala/edu/ie3/simona/util/CombineGrids.scala diff --git a/input/ma_thesis/ma_thesis.conf b/input/ma_thesis/ma_thesis.conf index 5da44482da..cbfab23f5e 100644 --- a/input/ma_thesis/ma_thesis.conf +++ b/input/ma_thesis/ma_thesis.conf @@ -24,13 +24,13 @@ simona.time.schedulerReadyCheckWindow = 900 ################################################################## simona.input.primary.csvParams = { directoryPath: "input/ma_thesis/fullGrid/primary" - csvSep: ";" + csvSep: "," isHierarchic: false } simona.input.grid.datasource.id = "csv" simona.input.grid.datasource.csvParams = { directoryPath: "input/ma_thesis/fullGrid" - csvSep: ";" + csvSep: "," isHierarchic: false } @@ -181,6 +181,7 @@ simona.control.transformer = [] # Congestion Management Configuration ################################################################## +simona.congestionManagement.enable = false simona.congestionManagement.enableTransformerTapping = false simona.congestionManagement.enableTopologyChanges = false simona.congestionManagement.useFlexOptions = false diff --git a/input/samples/vn_simona/vn_simona.conf b/input/samples/vn_simona/vn_simona.conf index 1c73e5d2b2..48efda8a38 100644 --- a/input/samples/vn_simona/vn_simona.conf +++ b/input/samples/vn_simona/vn_simona.conf @@ -228,6 +228,7 @@ simona.control.transformer = [ # Congestion Management Configuration ################################################################## +simona.congestionManagement.enable = false simona.congestionManagement.enableTransformerTapping = false simona.congestionManagement.enableTopologyChanges = false simona.congestionManagement.useFlexOptions = false diff --git a/src/main/resources/config/config-template.conf b/src/main/resources/config/config-template.conf index 36a0485295..fcd39b32c3 100644 --- a/src/main/resources/config/config-template.conf +++ b/src/main/resources/config/config-template.conf @@ -376,6 +376,7 @@ simona.event.listener = [ # Congestion Management Configuration ################################################################## +simona.congestionManagement.enable = boolean | false simona.congestionManagement.enableTransformerTapping = boolean | false simona.congestionManagement.enableTopologyChanges = boolean | false simona.congestionManagement.maxOptimizationIterations = int | 1 diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala index 8de2d31144..bcc0dc0533 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementParams.scala @@ -9,10 +9,11 @@ package edu.ie3.simona.agent.grid import java.time.Duration /** Holds all congestion management configuration parameters used in - * [[edu.ie3.simona.agent.grid]]. If the parameter [[runCongestionManagement]] - * is set to false, no congestion management is run and all the other - * parameters are ignored + * [[edu.ie3.simona.agent.grid]]. If the parameter [[enabled]] is set to false, + * no congestion management is run and all the other parameters are ignored * + * @param enabled + * defines if the congestion management is active and can be run * @param transformerTapping * defines if the transformer tapping should be used for tappable * transformers @@ -23,6 +24,7 @@ import java.time.Duration * resolve congestions */ final case class CongestionManagementParams( + enabled: Boolean, transformerTapping: Boolean, topologyChanges: Boolean, flexOptions: Boolean, @@ -32,10 +34,6 @@ final case class CongestionManagementParams( hasRunTransformerTapping: Boolean = false, hasUsedFlexOptions: Boolean = false, ) { - - def runCongestionManagement: Boolean = - transformerTapping || topologyChanges || flexOptions - def runTransformerTapping: Boolean = transformerTapping && !hasRunTransformerTapping && runOptimization diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala index 5cdaa457b2..e5352e477d 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala @@ -477,9 +477,7 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { } // check if congestion management is enabled - if ( - gridAgentBaseData.congestionManagementParams.runCongestionManagement - ) { + if (gridAgentBaseData.congestionManagementParams.enabled) { // get result or build empty data val congestionManagementData = results 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 e6909234e0..6abb7989ed 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala @@ -191,6 +191,7 @@ object GridAgent extends DBFSAlgorithm with DCMAlgorithm { cfg.powerflow.stopOnFailure, ), CongestionManagementParams( + cfg.congestionManagement.enable, cfg.congestionManagement.enableTransformerTapping, cfg.congestionManagement.enableTopologyChanges, cfg.congestionManagement.useFlexOptions, diff --git a/src/main/scala/edu/ie3/simona/config/RefSystemParser.scala b/src/main/scala/edu/ie3/simona/config/RefSystemParser.scala index b69a62d679..fbe2f83362 100644 --- a/src/main/scala/edu/ie3/simona/config/RefSystemParser.scala +++ b/src/main/scala/edu/ie3/simona/config/RefSystemParser.scala @@ -60,7 +60,7 @@ object RefSystemParser { val defaultRefSystems = ConfigRefSystems( Map.empty, Map( - GermanVoltageLevelUtils.LV -> RefSystem(Kilowatts(100), Volts(400)), + GermanVoltageLevelUtils.LV -> RefSystem(Kilowatts(100), Kilovolts(0.4)), GermanVoltageLevelUtils.MV_10KV -> RefSystem( Megawatts(40), Kilovolts(10), diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index cba467637b..9501a1dc34 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -1084,6 +1084,7 @@ object SimonaConfig { ) object Simona { final case class CongestionManagement( + enable: scala.Boolean, enableTopologyChanges: scala.Boolean, enableTransformerTapping: scala.Boolean, maxOptimizationIterations: scala.Int, @@ -1097,6 +1098,7 @@ object SimonaConfig { $tsCfgValidator: $TsCfgValidator, ): SimonaConfig.Simona.CongestionManagement = { SimonaConfig.Simona.CongestionManagement( + enable = c.hasPathOrNull("enable") && c.getBoolean("enable"), enableTopologyChanges = c.hasPathOrNull("enableTopologyChanges") && c.getBoolean( "enableTopologyChanges" diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerModel.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerModel.scala index 36da5949c8..6cda2fec6d 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerModel.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerModel.scala @@ -280,12 +280,10 @@ case object TransformerModel { val vRef = refSystem.nominalVoltage if ( Math.abs( - vRef.value - .doubleValue() - trafoType.getvRatedA.getValue.doubleValue() + vRef.toKilovolts - trafoType.getvRatedA.getValue.doubleValue() ) < Math.abs( - vRef.value - .doubleValue() - trafoType.getvRatedB.getValue.doubleValue() + vRef.toKilovolts - trafoType.getvRatedB.getValue.doubleValue() ) ) throw new InvalidGridException( diff --git a/src/main/scala/edu/ie3/simona/util/CombineGrids.scala b/src/main/scala/edu/ie3/simona/util/CombineGrids.scala deleted file mode 100644 index 1780d9af8a..0000000000 --- a/src/main/scala/edu/ie3/simona/util/CombineGrids.scala +++ /dev/null @@ -1,194 +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.util - -import edu.ie3.datamodel.io.csv.CsvIndividualTimeSeriesMetaInformation -import edu.ie3.datamodel.io.naming.FileNamingStrategy -import edu.ie3.datamodel.io.sink.CsvFileSink -import edu.ie3.datamodel.io.source.csv.{ - CsvJointGridContainerSource, - CsvTimeSeriesMappingSource, - CsvTimeSeriesMetaInformationSource, - CsvTimeSeriesSource, -} -import edu.ie3.datamodel.models.input.container.{ - GraphicElements, - JointGridContainer, - RawGridElements, - SystemParticipants, -} -import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble - -import java.io.File -import java.nio.file.Path -import scala.jdk.CollectionConverters._ -import scala.jdk.OptionConverters.RichOptional - -object CombineGrids { - def main(args: Array[String]): Unit = { - val input = Path.of(".", "input", "grids") - val output = Path.of(".", "output", "grid") - - val files: List[File] = input.toFile.listFiles().toList - - files.foreach(println) - - val grids = files.map(file => - CsvJointGridContainerSource.read(file.getName, ";", file.toPath, false) - ) - - val combinedGrid = combine(grids(0), grids.drop(1)) - - val sink: CsvFileSink = - new CsvFileSink(output, new FileNamingStrategy(), ";") - - sink.persistJointGrid(combinedGrid) - - combineAndSinkPrimaryData( - combinedGrid, - files.map(_.toPath.resolve("primary")), - new CsvFileSink(output.resolve("primary"), new FileNamingStrategy(), ";"), - ) - } - - def combine( - first: JointGridContainer, - grids: List[JointGridContainer], - ): JointGridContainer = { - - val nodeMap = first.getRawGrid.getNodes.asScala.map(n => n.getId -> n).toMap - - val lvGrids: Seq[(RawGridElements, SystemParticipants)] = grids.map { - grid => - val raw = grid.getRawGrid - - val nodes = raw.getNodes.asScala.filter(node => - node.getVoltLvl.getNominalVoltage.isEquivalentTo(0.4.asKiloVolt) - ) - val lines = - raw.getLines.asScala.filter(line => nodes.contains(line.getNodeA)) - - val t2w = - raw.getTransformer2Ws.asScala - .filter(t => nodes.contains(t.getNodeB)) - .map { t => - val updatedNode = nodeMap(t.getNodeA.getId) - t.copy().nodeA(updatedNode).build() - } - - val t3w = - raw.getTransformer3Ws.asScala - .filter(t => nodes.contains(t.getNodeB)) - .map { t => - val updatedNode = nodeMap(t.getNodeA.getId) - t.copy().nodeA(updatedNode).build() - } - - val switches = - raw.getSwitches.asScala.filter(s => nodes.contains(s.getNodeA)) - val measurementUnits = raw.getMeasurementUnits.asScala.filter(mU => - nodes.contains(mU.getNode) - ) - - val participants = grid.getSystemParticipants - .allEntitiesAsList() - .asScala - .filter(p => p.allNodes().asScala.forall(nodes.contains)) - - ( - new RawGridElements( - nodes.asJava, - lines.asJava, - t2w.asJava, - t3w.asJava, - switches.asJava, - measurementUnits.asJava, - ), - new SystemParticipants(participants.asJava), - ) - } - - val rawGrids = lvGrids.map(_._1) - val participants = lvGrids.map(_._2) - - val allRawGridElements = new RawGridElements( - List(first.getRawGrid).appendedAll(rawGrids).asJava - ) - val allParticipants = new SystemParticipants( - List(first.getSystemParticipants).appendedAll(participants).asJava - ) - - new JointGridContainer( - "", - allRawGridElements, - allParticipants, - new GraphicElements(List.empty[GraphicElements].asJava), - ) - } - - private def combineAndSinkPrimaryData( - combinedGrid: JointGridContainer, - paths: List[Path], - sink: CsvFileSink, - ): Unit = { - - val uuids = combinedGrid.getSystemParticipants - .allEntitiesAsList() - .asScala - .map(_.getUuid) - - paths.foreach { path => - val (mappingSource, metaInformation) = readPrimary(path) - - val timeSeriesUuids = mappingSource.getMapping.asScala.toMap.filter { - case (participant, _) => uuids.contains(participant) - }.values - - println(s"${timeSeriesUuids.size} of ${mappingSource.getMapping.size()}") - - timeSeriesUuids.toList.asJava - .parallelStream() - .forEach(uuid => { - val option = - metaInformation.getTimeSeriesMetaInformation(uuid).toScala - - option.foreach { - case csvIndividualTimeSeriesMetaInformation: CsvIndividualTimeSeriesMetaInformation => - val series = CsvTimeSeriesSource - .getSource( - ";", - path, - new FileNamingStrategy(), - csvIndividualTimeSeriesMetaInformation, - ) - .getTimeSeries - - sink.persistTimeSeries(series) - } - }) - } - } - - private def readPrimary( - directoryPath: Path, - csvSep: String = ";", - ): (CsvTimeSeriesMappingSource, CsvTimeSeriesMetaInformationSource) = { - val fileNamingStrategy = new FileNamingStrategy() - ( - new CsvTimeSeriesMappingSource( - csvSep, - directoryPath, - fileNamingStrategy, - ), - new CsvTimeSeriesMetaInformationSource( - csvSep, - directoryPath, - fileNamingStrategy, - ), - ) - } -} diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index 6e826f9b66..bc7b64af79 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -51,6 +51,7 @@ class DCMAlgorithmCenGridSpec with TestSpawnerTyped { private val cmConfig = ConfigFactory.parseString(""" + |simona.congestionManagement.enable = true |simona.congestionManagement.enableTransformerTapping = true |""".stripMargin) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala index 3885d3dd20..a7f58e44e7 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala @@ -54,6 +54,7 @@ class DCMAlgorithmSupGridSpec with TestSpawnerTyped { private val cmConfig = ConfigFactory.parseString(""" + |simona.congestionManagement.enable = true |simona.congestionManagement.enableTransformerTapping = true |""".stripMargin) diff --git a/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala b/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala index f2b033a7c9..1d92b2bdf5 100644 --- a/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/ConfigTestData.scala @@ -138,6 +138,7 @@ trait ConfigTestData { |simona.gridConfig.refSystems = [] |simona.gridConfig.voltageLimits = [] | + |simona.congestionManagement.enable = false |simona.congestionManagement.enableTransformerTapping = false |simona.congestionManagement.enableTopologyChanges = false |simona.congestionManagement.useFlexOptions = false From 6a57f96c3486bea5ce9e508f884978d7366335eb Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 24 May 2024 13:01:55 +0200 Subject: [PATCH 27/55] Saving changes. --- input/ma_thesis/ma_thesis.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/input/ma_thesis/ma_thesis.conf b/input/ma_thesis/ma_thesis.conf index cbfab23f5e..93180c3867 100644 --- a/input/ma_thesis/ma_thesis.conf +++ b/input/ma_thesis/ma_thesis.conf @@ -15,8 +15,8 @@ simona.simulationName = "ma_thesis" ################################################################## # Time Parameters ################################################################## -simona.time.startDateTime = "2016-01-01T00:00:00Z" -simona.time.endDateTime = "2016-01-01T02:00:00Z" +simona.time.startDateTime = "2016-01-01T05:00:00Z" +simona.time.endDateTime = "2016-01-01T11:00:00Z" simona.time.schedulerReadyCheckWindow = 900 ################################################################## @@ -170,7 +170,7 @@ simona.event.listener = [] # Power Flow Configuration ################################################################## simona.powerflow.maxSweepPowerDeviation = 1E-5 // the maximum allowed deviation in power between two sweeps, before overall convergence is assumed -simona.powerflow.newtonraphson.epsilon = [1E-12] +simona.powerflow.newtonraphson.epsilon = [1E-9] simona.powerflow.newtonraphson.iterations = 50 simona.powerflow.resolution = "3600s" simona.powerflow.stopOnFailure = true From 30257e03dfe9ca5cc7ec876e015b25952be1b78c Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 31 May 2024 14:06:12 +0200 Subject: [PATCH 28/55] Adding more comments and explanations to parts of the code. Updating `scalaDoc` and tests. --- .../grid/CongestionManagementSupport.scala | 83 +++++++++++++-- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 41 ++++--- .../CongestionManagementSupportSpec.scala | 34 +++--- .../agent/grid/DBFSMockGridAgents.scala | 7 +- .../agent/grid/DCMAlgorithmCenGridSpec.scala | 100 ++++++++++++------ .../agent/grid/DCMAlgorithmSupGridSpec.scala | 53 +++++++--- .../simona/agent/grid/GridAgentDataSpec.scala | 5 +- 7 files changed, 238 insertions(+), 85 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index d4f062e3b2..23855b1beb 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -12,6 +12,7 @@ import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.exceptions.{GridInconsistencyException, ResultException} import edu.ie3.simona.model.grid.GridModel.GridComponents +import edu.ie3.simona.model.grid.Transformer3wPowerFlowCase.PowerFlowCaseA import edu.ie3.simona.model.grid.{ Transformer3wModel, TransformerModel, @@ -32,19 +33,49 @@ import javax.measure.quantity.Dimensionless */ trait CongestionManagementSupport { + /** Method for grouping transformers with their [[ActorRef]]s. A group consist + * of all transformers connecting another grid with this grid and the + * [[ActorRef]] of the other grid. + * + *

If the other grid is connected by a port of [[Transformer3wModel]], + * only the model with [[PowerFlowCaseA]] is inside the returned map, due to + * the way the tapping works. Because the tapping also effects the other port + * of the [[Transformer3wModel]], the [[ActorRef]] of that grid needs to be + * in the same group and also all of its other connecting transformers, + * + *

Examples:

- grid 0 -> grid 1: [[TransformerModel]]

- grid 0 -> + * grid 1: [[Transformer3wModel]] port B

- grid 0 -> grid 2: + * [[Transformer3wModel]] port C

- grid 0 -> grid 3: [[TransformerModel]] + *

- grid 0 -> grid 4: two [[TransformerModel]] + * + *

Result:

- Group 1: one [[TransformerModel]] and one + * [[Transformer3wModel]] to [[ActorRef]]s of grid 1 and 2

- Group 2: one + * [[TransformerModel]] to [[ActorRef]] of grid 3

- Group 3: two + * [[TransformerModel]] to [[ActorRef]] of grid 4 + * + * @param receivedData + * map: actor ref to connecting transformers + * @param transformer3ws + * set of [[Transformer3wModel]] with [[PowerFlowCaseA]] + * @return + * a map: set of transformers to set of [[ActorRef]]s + */ def groupTappingModels( receivedData: Map[ActorRef[GridAgent.Request], Seq[TransformerTapping]], transformer3ws: Set[Transformer3wModel], ): Map[Set[TransformerTapping], Set[ActorRef[GridAgent.Request]]] = { val transformer3wMap = transformer3ws.map(t => t.uuid -> t).toMap + // builds all groups receivedData.foldLeft( Map.empty[Set[TransformerTapping], Set[ActorRef[GridAgent.Request]]] ) { case (combined, (ref, tappings)) => + // get all transformer models val updated: Set[TransformerTapping] = tappings.map { case transformerModel: TransformerModel => transformerModel case transformer3wModel: Transformer3wModel => + // in case of a three winding transformer, we need the model of the port A transformer3wMap.getOrElse( transformer3wModel.uuid, throw new GridInconsistencyException( @@ -57,10 +88,13 @@ trait CongestionManagementSupport { ) }.toSet + // find a group that already contains one of the given transformer models val keyOption = combined.keySet.find { keys => updated.exists(key => keys.contains(key)) } + // if a key is found, add the current transformer models and the ref to that group + // else add a new group keyOption .map { key => val refs = combined(key) @@ -77,24 +111,44 @@ trait CongestionManagementSupport { } } + /** Method for calculating the tap pos changes for all given transformers and + * the voltage delta. + * + *

NOTE: This method currently only support tapping of the higher voltage + * side of a given transformer. + * + * @param suggestion + * given delta suggestion + * @param tappings + * a set of all transformers + * @return + * a map: model to tap pos change and resulting voltage delta + */ def calculateTapAndVoltage( suggestion: ComparableQuantity[Dimensionless], tappings: Seq[TransformerTapping], ): (Map[TransformerTapping, Int], ComparableQuantity[Dimensionless]) = { + // inverting the suggestion, because it is given for the lv side val inverted = suggestion.multiply(-1) + // check if all transformer have the tapping at the hv side + // lv side tapping for transformer2ws is currently not supported if (tappings.exists(_.getTapSide != ConnectorPort.A)) { // for now only work if all transformers have the tapping at the hv side return (tappings.map(t => t -> 0).toMap, 0.asPu) } + // calculate a tap option for each transformer val option = if (tappings.size == 1) { + // if only one transformer is given, we only need to calculate one result val tapping = tappings(0) val taps = tapping.computeDeltaTap(inverted) val delta = tapping.deltaV.getValue.doubleValue() * taps / -100 Some(Map(tapping -> taps), delta.asPu) } else { + // if multiple transformers are used, we need to find an option, that works + // for all transformers val possibleChange = tappings.map { tapping => val taps = tapping.computeDeltaTap(inverted) val delta = tapping.deltaV.getValue.doubleValue() * taps / 100 @@ -102,16 +156,18 @@ trait CongestionManagementSupport { }.toMap // finds the smallest possible delta, because we are limited by that transformer - val option = if (inverted.isGreaterThan(0.asPu)) { + val possibleOption = if (inverted.isGreaterThan(0.asPu)) { possibleChange.minByOption(_._2._2) } else { possibleChange.maxByOption(_._2._2) } - option.map(_._2._2) match { + // adapt all tapping the the possible option + possibleOption.map(_._2._2) match { case Some(maxValue) => val max = maxValue.asPu + // calculate tap changes val changes = tappings.map { tapping => val taps = tapping.computeDeltaTap(max) val delta = tapping.deltaV.getValue.doubleValue() * taps / -100 @@ -135,10 +191,11 @@ trait CongestionManagementSupport { } } + // return the option ore no tapping option.getOrElse((tappings.map(t => t -> 0).toMap, 0.asPu)) } - /** Method to calculate the range of possible voltage changes. + /** Method to calculate the possible range of voltage changes. * * @param powerFlowResultEvent * results from simulating the grid @@ -150,7 +207,7 @@ trait CongestionManagementSupport { * map: inferior grid to [[VoltageRange]] and [[TransformerTappingModel]] * @return */ - def calculateVoltageOptions( + def calculatePossibleVoltageRange( powerFlowResultEvent: PowerFlowResultEvent, voltageLimits: VoltageLimits, gridComponents: GridComponents, @@ -216,7 +273,7 @@ trait CongestionManagementSupport { val lineMap = gridComponents.lines.map(line => line.uuid -> line).toMap - // calculates the + // calculates the utilisation of each line val lineUtilisation = lineResMap.map { case (uuid, res) => val iNom = lineMap(uuid).iNom val diffA = Amperes(res.getiAMag().getValue.doubleValue()) / iNom @@ -225,6 +282,7 @@ trait CongestionManagementSupport { uuid -> Math.max(diffA, diffB) } + // find the maximale utilisation val maxUtilisation = lineUtilisation .maxByOption(_._2) .getOrElse(throw new ResultException(s"No line result found!")) @@ -235,6 +293,7 @@ trait CongestionManagementSupport { val resA = res.getiAMag() val resB = res.getiBMag() + // calculate the voltage change limits val deltaV = if (resA.isGreaterThan(resB)) { val nodeRes = nodeResults(line.nodeAUuid).getValue.doubleValue() val current = resA.getValue.doubleValue() @@ -256,7 +315,19 @@ trait CongestionManagementSupport { object CongestionManagementSupport { - case class VoltageRange( + /** Object that contains information about possible voltage changes.

If + * the delta plus is negative -> upper voltage violation

If the delta + * minus is positive -> lower voltage violation

If both above cases + * happen at the same time the the suggestion is set to the delta plus, + * because having a too high voltage is more severe + * @param deltaPlus + * maximale possible voltage increase + * @param deltaMinus + * maximale possible voltage decrease + * @param suggestion + * for voltage change + */ + final case class VoltageRange( deltaPlus: ComparableQuantity[Dimensionless], deltaMinus: ComparableQuantity[Dimensionless], suggestion: ComparableQuantity[Dimensionless], diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 91a7b978dc..3532fddfca 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -82,7 +82,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { val congestions = stateData.congestions if (congestions.any) { - ctx.log.warn( + ctx.log.info( s"In the grid ${stateData.gridAgentBaseData.gridEnv.gridModel.subnetNo}, the following congestions were found: $congestions" ) } @@ -114,7 +114,9 @@ trait DCMAlgorithm extends CongestionManagementSupport { ctx.self ! GotoIdle checkForCongestion(stateData, updatedData) } else { - ctx.log.warn(s"Congestions: $congestions") + ctx.log.warn( + s"Congestion overall: $congestions" + ) // TODO: Change to debug val steps = stateData.gridAgentBaseData.congestionManagementParams @@ -190,7 +192,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { ) case (ctx, msg) => - ctx.log.error(s"Received unsupported msg: $msg. Stash away!") + ctx.log.debug(s"Received unsupported msg: $msg. Stash away!") buffer.stash(msg) Behaviors.same } @@ -240,6 +242,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { val gridModel = gridEnv.gridModel val gridComponents = gridModel.gridComponents + // map all known transformers to their uuid val transformers = (gridComponents.transformers ++ gridComponents.transformers3w) .map(t => t.uuid -> t) @@ -247,6 +250,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { val voltage = gridModel.mainRefSystem.nominalVoltage.value.asKiloVolt + // find all transformers to superior grids val transformerToSup = gridEnv.subgridGateToActorRef .filter( _._1 @@ -258,7 +262,8 @@ trait DCMAlgorithm extends CongestionManagementSupport { .map(_._1.link().getUuid) val transformer = transformerToSup.map(transformers).toSeq - val range = calculateVoltageOptions( + // calculate the voltage range with the received data + val range = calculatePossibleVoltageRange( stateData.powerFlowResults, gridModel.voltageLimits, gridModel.gridComponents, @@ -266,7 +271,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { ) ctx.log.warn( - s"For Grid ${stateData.gridAgentBaseData.gridEnv.gridModel.subnetNo}, range: $range" + s"For Grid ${stateData.gridAgentBaseData.gridEnv.gridModel.subnetNo}, voltage range: $range" ) sender ! VoltageRangeResponse( @@ -298,26 +303,33 @@ trait DCMAlgorithm extends CongestionManagementSupport { ctx.log.warn( s"Grid ${stateData.gridAgentBaseData.gridEnv.gridModel.subnetNo}, received delta: $delta" - ) + ) // TODO: Change to debug if (stateData.inferiorRefs.nonEmpty) { // we calculate a voltage delta for all inferior grids val receivedData = awaitingData.mappedValues + // map the actor ref to the possible voltage range val refMap = receivedData.map { case (ref, (range, _)) => ref -> range } - val tappingModels = + // groups all tapping models + // necessary, because to make sure the tapping is change by the same value between two grids, + // we need to know all transformers that are relevant as well as all actor refs to check their + // possible voltage ranges + val groups = groupTappingModels( receivedData.map { case (ref, (_, tappings)) => ref -> tappings }, stateData.gridAgentBaseData.gridEnv.gridModel.gridComponents.transformers3w, ) - tappingModels.foreach { case (tappingModels, refs) => + groups.foreach { case (tappingModels, refs) => + // get all possible voltage ranges of the inferior grids val inferiorRanges = refs.map(refMap) + // check if all transformers support tapping if (tappingModels.forall(_.hasAutoTap)) { // the given transformer can be tapped, calculate the new tap pos @@ -326,11 +338,13 @@ trait DCMAlgorithm extends CongestionManagementSupport { .combineSuggestions(inferiorRanges) .subtract(delta) + // calculating the tap changes for all transformers and the resulting voltage delta val (tapChange, deltaV) = calculateTapAndVoltage( suggestion, tappingModels.toSeq, ) + // change the tap pos of all transformers tapChange.foreach { case (tapping, tapChange) => if (tapChange > 0) { tapping.incrTapPos(tapChange) @@ -343,8 +357,9 @@ trait DCMAlgorithm extends CongestionManagementSupport { ctx.log.warn( s"For inferior grids $refs, suggestion: $suggestion, delta: $deltaV" - ) + ) // TODO: Change to debug + // send the resulting voltage delta to all inferior grids refs.foreach(_ ! VoltageDeltaResponse(deltaV.add(delta))) } else { // no tapping possible, just send the delta to the inferior grid @@ -353,7 +368,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { } } - // all work is done in this grid, finish this step + // all work is done in this grid, therefore finish this step // simulate grid after changing the transformer tapping buffer.unstashAll( clearAndGotoSimulateGrid( @@ -364,7 +379,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { ) case (ctx, msg) => - ctx.log.error(s"Received unsupported msg: $msg. Stash away!") + ctx.log.debug(s"Received unsupported msg: $msg. Stash away!") buffer.stash(msg) Behaviors.same } @@ -401,7 +416,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { ) case (ctx, msg) => - ctx.log.error(s"Received unsupported msg: $msg. Stash away!") + ctx.log.debug(s"Received unsupported msg: $msg. Stash away!") buffer.stash(msg) Behaviors.same } @@ -438,7 +453,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { ) case (ctx, msg) => - ctx.log.error(s"Received unsupported msg: $msg. Stash away!") + ctx.log.debug(s"Received unsupported msg: $msg. Stash away!") buffer.stash(msg) Behaviors.same } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index 4b4d72f10b..9d9136dc05 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -42,11 +42,6 @@ class CongestionManagementSupportSpec "CongestionManagementSupport" should { "group transformers correctly" in { - // grid 1 is connected via a transformer2w and one port of a transformer3w - // grid 2 is connected via one port of a transformer3w - // grid 3 is connected via a transformer2w - // grid 4 is connected via two transformer2ws - val (transformer3wA, transformer3wB, transformer3wC) = mockTransformer3wModel() val transformer1 = mockTransformerModel() @@ -60,6 +55,10 @@ class CongestionManagementSupportSpec val transformer4_1 = mockTransformerModel() val transformer4_2 = mockTransformerModel() + // grid 1 is connected via a transformer2w and one port of a transformer3w + // grid 2 is connected via one port of a transformer3w + // grid 3 is connected via a transformer2w + // grid 4 is connected via two transformer2ws val receivedData = Map( ref1 -> Seq( transformer1, @@ -78,6 +77,14 @@ class CongestionManagementSupportSpec Set(transformer3wA), ) + // explanation for the expected groups: + // since both grid 1 and grid 2 are connected by the same transformer3w they must be tapped by the same voltage delta + // since grid 1 is also connected by transformer 1, both transformer are building a group together + // the group contain the refs for both grids + // + // since grid 3 is only connected by a transformer2w, the group contains only this transformer and one ref + // + // since grid 4 is connected by two transformer2w, the group contains both transformers and the ref of grid 4 grouped shouldBe Map( Set(transformer1, transformer3wA) -> Set(ref1, ref2), Set(transformer3) -> Set(ref3), @@ -250,7 +257,7 @@ class CongestionManagementSupportSpec mockLineResult(line13.uuid, 11.asAmpere, 10.9.asAmpere), ), ), - 0.093.asPu, + 0.093.asPu, // min voltage increase to resolve line congestion ), ( buildPowerFlowResultEvent( @@ -264,7 +271,7 @@ class CongestionManagementSupportSpec mockLineResult(line13.uuid, 8.asAmpere, 8.asAmpere), ), ), - (-0.0651).asPu, + (-0.0651).asPu, // max voltage decrease until line congestion occur ), ) @@ -313,7 +320,7 @@ class CongestionManagementSupportSpec ), ) - val range = calculateVoltageOptions( + val range = calculatePossibleVoltageRange( powerFlowResult, VoltageLimits(0.9, 1.1), gridComponents, @@ -365,7 +372,9 @@ class CongestionManagementSupportSpec ), ) - val range = calculateVoltageOptions( + // the voltage range of the given grid is limited by the voltage range + // of the inferior grids and the possible transformer tapping + val range = calculatePossibleVoltageRange( powerFlowResult, VoltageLimits(0.9, 1.1), gridComponents, @@ -409,17 +418,17 @@ class CongestionManagementSupportSpec (-0.01).asPu, (-0.02).asPu, (-0.015).asPu, - ), // upper voltage limit violation, decreasing voltage + ), // upper voltage limit violation (both are negative), decreasing voltage ( 0.02.asPu, 0.01.asPu, 0.015.asPu, - ), // lower voltage limit violation, increasing voltage + ), // lower voltage limit violation (both are positive), increasing voltage ( (-0.01).asPu, 0.01.asPu, (-0.01).asPu, - ), // violation of both voltage limits, decreasing voltage + ), // violation of both voltage limits (upper negative, lower positive), decreasing voltage ) forAll(cases) { (deltaPlus, deltaMinus, expected) => @@ -430,7 +439,6 @@ class CongestionManagementSupportSpec suggestion should equalWithTolerance(expected) } - } "be updated with a line voltage delta correctly" in { diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala index dcdba05e2c..2765a386dc 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala @@ -15,6 +15,7 @@ import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.{ ExchangeVoltage, } import edu.ie3.simona.agent.grid.GridAgentMessages._ +import edu.ie3.simona.model.grid.TransformerTapping import edu.ie3.simona.test.common.UnitSpec import edu.ie3.util.scala.quantities.{Megavars, ReactivePower} import org.apache.pekko.actor.testkit.typed.scaladsl.TestProbe @@ -171,13 +172,13 @@ trait DBFSMockGridAgents extends UnitSpec { def expectVoltageRangeResponse( voltageRange: VoltageRange, maxDuration: FiniteDuration = 30 seconds, - ): ActorRef[GridAgent.Request] = { + ): (ActorRef[GridAgent.Request], Seq[TransformerTapping]) = { gaProbe.expectMessageType[VoltageRangeResponse](maxDuration) match { - case VoltageRangeResponse(sender, (range, _)) => + case VoltageRangeResponse(sender, (range, tappings)) => range.deltaPlus shouldBe voltageRange.deltaPlus range.deltaMinus shouldBe voltageRange.deltaMinus - sender + (sender, tappings) } } } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index bc7b64af79..ba05cdbf66 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -50,12 +50,14 @@ class DCMAlgorithmCenGridSpec with DbfsTestGrid with TestSpawnerTyped { - private val cmConfig = ConfigFactory.parseString(""" + private val tappingEnabledConfig = ConfigFactory.parseString(""" |simona.congestionManagement.enable = true |simona.congestionManagement.enableTransformerTapping = true |""".stripMargin) - private val config = SimonaConfig(cmConfig.withFallback(typesafeConfig)) + private val configWithTransformerTapping = SimonaConfig( + tappingEnabledConfig.withFallback(typesafeConfig) + ) private val scheduler: TestProbe[SchedulerMessage] = TestProbe("scheduler") private val runtimeEvents: TestProbe[RuntimeEvent] = @@ -99,9 +101,13 @@ class DCMAlgorithmCenGridSpec val voltageCongestions = noCongestions.copy(voltageCongestions = true) s"simulate grid and check for congestions correctly if no congestions occurred" in { - val centerGridAgent = initAgentAndGotoSimulateGrid() + // init grid agent and simulate the grid + val centerGridAgent = + initAgentAndGotoSimulateGrid(configWithTransformerTapping) simulateGrid(centerGridAgent) + // after the simulation ends the center grid should receive a CongestionCheckRequest + // from the superior grid centerGridAgent ! CongestionCheckRequest(superiorGridAgent.ref) // we expect a request for grid congestion values here @@ -112,7 +118,8 @@ class DCMAlgorithmCenGridSpec val congestionCheckRequestSender13 = inferiorGrid13.expectCongestionCheckRequest() - // send congestions + // send the center grid messages that indicate that no congestion occurred + // in the inferior grids congestionCheckRequestSender11 ! CongestionResponse( inferiorGrid11.ref, noCongestions, @@ -126,11 +133,11 @@ class DCMAlgorithmCenGridSpec noCongestions, ) - // check the received congestions + // after the center grid agent has processed all received congestions + // the superior grid will receive a congestion response superiorGridAgent.expectCongestionResponse(noCongestions) - // there are no congestions - // tell all inferior grids to go back to idle + // since there are no congestions tell all inferior grids to go back to idle centerGridAgent ! GotoIdle // inferior should receive a next state message to go to the idle state @@ -149,10 +156,13 @@ class DCMAlgorithmCenGridSpec } s"simulate grid and update transformer tapping correctly" in { - val centerGridAgent = initAgentAndGotoSimulateGrid() + // init grid agent and simulate the grid + val centerGridAgent = + initAgentAndGotoSimulateGrid(configWithTransformerTapping) simulateGrid(centerGridAgent) - // aks for congestion check + // after the simulation ends the center grid should receive a CongestionCheckRequest + // from the superior grid centerGridAgent ! CongestionCheckRequest(superiorGridAgent.ref) // we expect a request for grid congestion values here @@ -163,43 +173,55 @@ class DCMAlgorithmCenGridSpec val congestionCheckRequestSender13 = inferiorGrid13.expectCongestionCheckRequest() - // send congestions + // send the center grid messages that indicate that congestions occurred + // in some inferior grids congestionCheckRequestSender11 ! CongestionResponse( inferiorGrid11.ref, voltageCongestions, ) congestionCheckRequestSender12 ! CongestionResponse( inferiorGrid12.ref, - voltageCongestions, + noCongestions, ) congestionCheckRequestSender13 ! CongestionResponse( inferiorGrid13.ref, voltageCongestions, ) - // check the received congestions + // after the center grid agent has processed all received congestions + // the superior grid will receive a congestion response superiorGridAgent.expectCongestionResponse(voltageCongestions) - // found voltage congestions in the grid - // initiate transformer tapping + // since the superior grid receives a message the shows that there is at + // least one congestion in the grid, it starts the congestions management + // because there are voltage congestions and the transformet tapping has + // not run yet, the next step is the transformer tapping centerGridAgent ! NextStepRequest( TransformerTapping ) - // inferior grids should receive a next state message to go to a congestion management step - inferiorGrid11.gaProbe.expectMessageType[NextStepRequest] - inferiorGrid12.gaProbe.expectMessageType[NextStepRequest] - inferiorGrid13.gaProbe.expectMessageType[NextStepRequest] + // inferior grids should receive a next state message to go to the transformer tapping step + inferiorGrid11.gaProbe + .expectMessageType[NextStepRequest] + .nextStep shouldBe TransformerTapping + inferiorGrid12.gaProbe + .expectMessageType[NextStepRequest] + .nextStep shouldBe TransformerTapping + inferiorGrid13.gaProbe + .expectMessageType[NextStepRequest] + .nextStep shouldBe TransformerTapping - // ask the center grid for the voltage options + // since the transformer tapping was started, the superior grid + // requests the possible voltage range from the center grid centerGridAgent ! RequestVoltageOptions(superiorGridAgent.ref) - // the inferior grids should receive a VoltageRangeRequest + // the center grid will request the voltage ranges from its inferior grid + // therefore the inferior grids should receive a VoltageRangeRequest val voltageRangeRequester11 = inferiorGrid11.expectVoltageRangeRequest() val voltageRangeRequester12 = inferiorGrid12.expectVoltageRangeRequest() val voltageRangeRequester13 = inferiorGrid13.expectVoltageRangeRequest() - // each inferior grid will send its voltage range to the center grid + // each inferior grid will send its possible voltage range to the center grid voltageRangeRequester11 ! VoltageRangeResponse( inferiorGrid11.ref, ( @@ -225,24 +247,38 @@ class DCMAlgorithmCenGridSpec ), ) + // after the center grid received all voltage ranges // the superior grid should receive a voltage range from the center grid - val voltageDeltaRequest = superiorGridAgent.expectVoltageRangeResponse( - VoltageRange(0.06.asPu, 0.01.asPu, 0.03.asPu) - ) + val (voltageDeltaRequest, tappingModels) = + superiorGridAgent.expectVoltageRangeResponse( + VoltageRange(0.06.asPu, 0.01.asPu, 0.03.asPu) + ) - // send a new delta to the center grid - voltageDeltaRequest ! VoltageDeltaResponse(0.04.asPu) + // the superior grid will update the transformer tappings + // and send the the resulting voltage delta to the center grid + tappingModels.foreach(_.decrTapPos(2)) + voltageDeltaRequest ! VoltageDeltaResponse(0.03.asPu) + // the inferior grids should receive a voltage delta from the center grid inferiorGrid11.gaProbe .expectMessageType[VoltageDeltaResponse] .delta should equalWithTolerance((-0.01).asPu) inferiorGrid12.gaProbe .expectMessageType[VoltageDeltaResponse] - .delta should equalWithTolerance(0.04.asPu) + .delta should equalWithTolerance(0.03.asPu) inferiorGrid13.gaProbe .expectMessageType[VoltageDeltaResponse] - .delta should equalWithTolerance(0.04.asPu) + .delta should equalWithTolerance(0.03.asPu) + + // this transformer can e tapped + mvTransformers(transformer11.getUuid).currentTapPos shouldBe 4 + + // these transformers can't be tapped and should keep their default tap pos + mvTransformers(transformer12.getUuid).currentTapPos shouldBe 0 + mvTransformers(transformer13_1.getUuid).currentTapPos shouldBe 0 + mvTransformers(transformer13_1.getUuid).currentTapPos shouldBe 0 + // skipping this simulation step skipSimulation(centerGridAgent) // aks for congestion check @@ -270,11 +306,11 @@ class DCMAlgorithmCenGridSpec noCongestions, ) - // check the received congestions + // after the simulation ends the center grid should receive a CongestionCheckRequest + // from the superior grid superiorGridAgent.expectCongestionResponse(noCongestions) - // there are no congestions - // tell all inferior grids to go back to idle + // since there are no congestions tell all inferior grids to go back to idle centerGridAgent ! GotoIdle // inferior should receive a next state message to go to the idle state @@ -303,7 +339,7 @@ class DCMAlgorithmCenGridSpec * the [[ActorRef]] of the created superior grid agent */ def initAgentAndGotoSimulateGrid( - simonaConfig: SimonaConfig = config + simonaConfig: SimonaConfig ): ActorRef[GridAgent.Request] = { val centerGridAgent = testKit.spawn( diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala index a7f58e44e7..b3906c6b31 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala @@ -53,12 +53,15 @@ class DCMAlgorithmSupGridSpec with DbfsTestGrid with TestSpawnerTyped { - private val cmConfig = ConfigFactory.parseString(""" + // config with congestion management and transformer tapping enabled + private val tappingEnabledConfig = ConfigFactory.parseString(""" |simona.congestionManagement.enable = true |simona.congestionManagement.enableTransformerTapping = true |""".stripMargin) - private val config = SimonaConfig(cmConfig.withFallback(typesafeConfig)) + private val configWithTransformerTapping = SimonaConfig( + tappingEnabledConfig.withFallback(typesafeConfig) + ) private val scheduler: TestProbe[SchedulerMessage] = TestProbe("scheduler") private val runtimeEvents: TestProbe[RuntimeEvent] = @@ -96,10 +99,12 @@ class DCMAlgorithmSupGridSpec ) s"skip simulate grid and check for congestions correctly if no congestions occurred" in { - val superiorGridAgent = initAgentAndGotoSimulateGrid() + val superiorGridAgent = + initAgentAndGotoSimulateGrid(configWithTransformerTapping) val lastSender = skipSimulationAndGetNextStep(superiorGridAgent) + // send the requesting grid agent a CongestionResponse with no congestions lastSender ! CongestionResponse( hvGrid.ref, Congestions( @@ -109,7 +114,8 @@ class DCMAlgorithmSupGridSpec ), ) - // inferior should receive a next state message to go to the idle state + // since their are no congestions the inferior should receive + // a next state message to go to the idle state hvGrid.expectMessageType[GotoIdle.type] // expect a completion message from the superior grid @@ -123,12 +129,13 @@ class DCMAlgorithmSupGridSpec } s"skip simulate grid and handle unresolvable congestions correctly" in { - val superiorGridAgent = initAgentAndGotoSimulateGrid() + val superiorGridAgent = + initAgentAndGotoSimulateGrid(configWithTransformerTapping) - // transformer congestion cannot be resolved, because using flex options is not - // enable by the provided config val lastSender = skipSimulationAndGetNextStep(superiorGridAgent) + // voltage congestion cannot be resolved, because using flex options is not + // enable by the provided config lastSender ! CongestionResponse( hvGrid.ref, Congestions( @@ -138,7 +145,8 @@ class DCMAlgorithmSupGridSpec ), ) - // inferior should receive a next state message to go to the idle state + // since the congestion management can't resolve the congestions + // the inferior should receive a next state message to go to the idle state hvGrid.expectMessageType[GotoIdle.type] // expect a completion message from the superior grid @@ -152,11 +160,12 @@ class DCMAlgorithmSupGridSpec } s"skip simulate grid and update transformer tapping correctly" in { - val superiorGridAgent = initAgentAndGotoSimulateGrid() + val superiorGridAgent = + initAgentAndGotoSimulateGrid(configWithTransformerTapping) val lastSender1 = skipSimulationAndGetNextStep(superiorGridAgent) - // send congestions + // sending the superior grid a solvable congestion lastSender1 ! CongestionResponse( hvGrid.ref, Congestions( @@ -166,9 +175,16 @@ class DCMAlgorithmSupGridSpec ), ) - // inferior should receive a next state message to go to a congestion management step + // since the received congestion can be resolved the inferior should + // receive a next state message to go to a congestion management step hvGrid.expectMessageType[NextStepRequest] + // both transformer models should have their default tap pos + tappingModel.currentTapPos shouldBe 0 + tappingModel2.currentTapPos shouldBe 0 + + // the inferior will receive a request to send the possible voltage range + // and send a VoltageRangeResponse to the superior grid hvGrid.expectMessageType[RequestVoltageOptions] match { case RequestVoltageOptions(sender) => sender ! VoltageRangeResponse( @@ -180,17 +196,23 @@ class DCMAlgorithmSupGridSpec ) } + // the inferior will receive a voltage delta from the superior grid + // after the superior grid change the transformer tapping hvGrid.expectMessageType[VoltageDeltaResponse](120.seconds) match { case VoltageDeltaResponse(delta) => delta should equalWithTolerance(0.015.asPu) } + // both transformer models tap pos should have changed by the same amount + tappingModel.currentTapPos shouldBe -1 + tappingModel2.currentTapPos shouldBe -1 + // skipping the simulation hvGrid.expectMessageType[RequestGridPower] val lastSender2 = skipSimulationAndGetNextStep(superiorGridAgent) - // send congestions + // sending the superior grid that no more congestions are present lastSender2 ! CongestionResponse( hvGrid.ref, Congestions( @@ -200,7 +222,8 @@ class DCMAlgorithmSupGridSpec ), ) - // inferior should receive a next state message to go to the idle state + // since their are no congestions the inferior should receive + // a next state message to go to the idle state hvGrid.expectMessageType[GotoIdle.type] // expect a completion message from the superior grid @@ -254,8 +277,10 @@ class DCMAlgorithmSupGridSpec * the [[ActorRef]] of the created superior grid agent */ def initAgentAndGotoSimulateGrid( - simonaConfig: SimonaConfig = config + simonaConfig: SimonaConfig ): ActorRef[GridAgent.Request] = { + // init a superior grid agent with the given config + // that enabled certain congestion management options val superiorGridAgent: ActorRef[GridAgent.Request] = testKit.spawn( GridAgent( environmentRefs, diff --git a/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala index 4a6d3caad8..5e0e9ff58d 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala @@ -12,8 +12,8 @@ import edu.ie3.datamodel.models.result.connector.{ LineResult, Transformer2WResult, } -import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData import edu.ie3.simona.agent.grid.CongestionManagementSupport.Congestions +import edu.ie3.simona.agent.grid.GridAgentData.CongestionManagementData import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.model.grid.{GridModel, RefSystem, VoltageLimits} import edu.ie3.simona.test.common.model.grid.DbfsTestGrid @@ -205,9 +205,6 @@ class GridAgentDataSpec extends UnitSpec with DbfsTestGrid with ConfigTestData { transformerCongestions = true, ) } - - // TODO: Add cleaning method tests - } } From 9da36a28a34df2a33db1d9715f570a1789193869 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 4 Jun 2024 12:04:53 +0200 Subject: [PATCH 29/55] Small improvements. --- input/ma_thesis/ma_thesis.conf | 17 ++++++++++-- .../grid/CongestionManagementSupport.scala | 8 +++--- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 21 +++------------ .../simona/agent/grid/GridAgentMessages.scala | 8 +++--- .../CongestionManagementSupportSpec.scala | 26 ++++++++++--------- .../agent/grid/DBFSMockGridAgents.scala | 2 +- .../agent/grid/DCMAlgorithmCenGridSpec.scala | 9 ++++--- .../agent/grid/DCMAlgorithmSupGridSpec.scala | 2 +- 8 files changed, 47 insertions(+), 46 deletions(-) diff --git a/input/ma_thesis/ma_thesis.conf b/input/ma_thesis/ma_thesis.conf index 93180c3867..c295a62869 100644 --- a/input/ma_thesis/ma_thesis.conf +++ b/input/ma_thesis/ma_thesis.conf @@ -164,7 +164,20 @@ simona.event.listener = [] # Grid Configuration ################################################################## - +simona.gridConfig.voltageLimits = [ + { + vMin = 0.97, + vMax = 1.03, + voltLvls = [ + {id = "lv", vNom = "0.4 kV"}, + {id = "mv", vNom = "10 kV"}, + {id = "mv", vNom = "20 kV"}, + {id = "mv", vNom = "30 kV"}, + {id = "hv", vNom = "110 kV"}, + ]}, + {vMin = 0.9, vMax = 1.118, voltLvls = [{id = "EHV", vNom = "220 kV"}]}, + {vMin = 0.9, vMax = 1.05, voltLvls = [{id = "EHV", vNom = "380 kV"}]}, +] ################################################################## # Power Flow Configuration @@ -181,7 +194,7 @@ simona.control.transformer = [] # Congestion Management Configuration ################################################################## -simona.congestionManagement.enable = false +simona.congestionManagement.enable = true simona.congestionManagement.enableTransformerTapping = false simona.congestionManagement.enableTopologyChanges = false simona.congestionManagement.useFlexOptions = false diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 23855b1beb..092421aa4d 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -61,7 +61,7 @@ trait CongestionManagementSupport { * a map: set of transformers to set of [[ActorRef]]s */ def groupTappingModels( - receivedData: Map[ActorRef[GridAgent.Request], Seq[TransformerTapping]], + receivedData: Map[ActorRef[GridAgent.Request], Set[TransformerTapping]], transformer3ws: Set[Transformer3wModel], ): Map[Set[TransformerTapping], Set[ActorRef[GridAgent.Request]]] = { val transformer3wMap = transformer3ws.map(t => t.uuid -> t).toMap @@ -213,7 +213,7 @@ trait CongestionManagementSupport { gridComponents: GridComponents, inferiorData: Map[ActorRef[ GridAgent.Request - ], (VoltageRange, Seq[TransformerTapping])], + ], (VoltageRange, Set[TransformerTapping])], ): VoltageRange = { // calculate voltage range val nodeResMap = powerFlowResultEvent.nodeResults @@ -370,7 +370,7 @@ object CongestionManagementSupport { def updateWithInferiorRanges( inferiorData: Map[ActorRef[ GridAgent.Request - ], (VoltageRange, Seq[TransformerTapping])] + ], (VoltageRange, Set[TransformerTapping])] ): VoltageRange = { inferiorData.foldLeft(this) { case (range, (_, (infRange, tappings))) => @@ -385,7 +385,7 @@ object CongestionManagementSupport { val decrease = deltaV.multiply(tapping.tapMin - currentPos) (increase, decrease) - } + }.toSeq val (possiblePlus, possibleMinus) = if (tappings.size == 1) { tappingRanges(0) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 3532fddfca..d43385eab5 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -211,7 +211,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { */ private[grid] def updateTransformerTapping( stateData: CongestionManagementData, - awaitingData: AwaitingData[(VoltageRange, Seq[TransformerTapping])], + awaitingData: AwaitingData[(VoltageRange, Set[TransformerTapping])], )(implicit constantData: GridAgentConstantData, buffer: StashBuffer[GridAgent.Request], @@ -245,22 +245,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { // map all known transformers to their uuid val transformers = (gridComponents.transformers ++ gridComponents.transformers3w) - .map(t => t.uuid -> t) - .toMap - - val voltage = gridModel.mainRefSystem.nominalVoltage.value.asKiloVolt - - // find all transformers to superior grids - val transformerToSup = gridEnv.subgridGateToActorRef - .filter( - _._1 - .superiorNode() - .getVoltLvl - .getNominalVoltage - .isGreaterThan(voltage) - ) - .map(_._1.link().getUuid) - val transformer = transformerToSup.map(transformers).toSeq + .map(_.asInstanceOf[TransformerTapping]) // calculate the voltage range with the received data val range = calculatePossibleVoltageRange( @@ -276,7 +261,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { sender ! VoltageRangeResponse( ctx.self, - (range, transformer), + (range, transformers), ) } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala index 0a77cc91fc..c44e5b28bf 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala @@ -301,14 +301,14 @@ object GridAgentMessages { case class VoltageRangeResponse( override val sender: ActorRef[GridAgent.Request], - override val value: (VoltageRange, Seq[TransformerTapping]), - ) extends CMReceiveResponse[(VoltageRange, Seq[TransformerTapping])] + override val value: (VoltageRange, Set[TransformerTapping]), + ) extends CMReceiveResponse[(VoltageRange, Set[TransformerTapping])] case class ReceivedVoltageRange( override val values: Vector[ - (ActorRef[GridAgent.Request], (VoltageRange, Seq[TransformerTapping])) + (ActorRef[GridAgent.Request], (VoltageRange, Set[TransformerTapping])) ] - ) extends CMResponse[(VoltageRange, Seq[TransformerTapping])] + ) extends CMResponse[(VoltageRange, Set[TransformerTapping])] case class VoltageDeltaResponse( delta: ComparableQuantity[Dimensionless] diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index 9d9136dc05..5064d31636 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -11,7 +11,7 @@ import edu.ie3.datamodel.models.result.connector.LineResult import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.model.grid.GridModel.GridComponents -import edu.ie3.simona.model.grid.VoltageLimits +import edu.ie3.simona.model.grid.{TransformerTapping, VoltageLimits} import edu.ie3.simona.test.common.UnitSpec import edu.ie3.simona.test.common.model.grid.{ GridComponentsMokka, @@ -23,6 +23,7 @@ import org.apache.pekko.actor.testkit.typed.scaladsl.{ ScalaTestWithActorTestKit, TestProbe, } +import org.apache.pekko.actor.typed.ActorRef class CongestionManagementSupportSpec extends ScalaTestWithActorTestKit @@ -59,14 +60,15 @@ class CongestionManagementSupportSpec // grid 2 is connected via one port of a transformer3w // grid 3 is connected via a transformer2w // grid 4 is connected via two transformer2ws - val receivedData = Map( - ref1 -> Seq( + val receivedData + : Map[ActorRef[GridAgent.Request], Set[TransformerTapping]] = Map( + ref1 -> Set( transformer1, transformer3wB, ), // connected with both transformer2w and transformer3w - ref2 -> Seq(transformer3wC), // connected with a transformer3w - ref3 -> Seq(transformer3), // connected with just one transformer model - ref4 -> Seq( + ref2 -> Set(transformer3wC), // connected with a transformer3w + ref3 -> Set(transformer3), // connected with just one transformer model + ref4 -> Set( transformer4_1, transformer4_2, ), // connected with two transformer2w @@ -379,10 +381,10 @@ class CongestionManagementSupportSpec VoltageLimits(0.9, 1.1), gridComponents, Map( - inferior1.ref -> (VoltageRange(0.1.asPu, 0.01.asPu), Seq( + inferior1.ref -> (VoltageRange(0.1.asPu, 0.01.asPu), Set( tappingModel )), - inferior2.ref -> (VoltageRange(0.01.asPu, (-0.04).asPu), Seq( + inferior2.ref -> (VoltageRange(0.01.asPu, (-0.04).asPu), Set( tappingModel )), ), @@ -531,8 +533,8 @@ class CongestionManagementSupportSpec forAll(cases) { (range1, range2, expected) => val updatedRange = range.updateWithInferiorRanges( Map( - inferior1.ref -> (range1, Seq(tappingModel)), - inferior2.ref -> (range2, Seq(tappingModel)), + inferior1.ref -> (range1, Set(tappingModel)), + inferior2.ref -> (range2, Set(tappingModel)), ) ) @@ -579,8 +581,8 @@ class CongestionManagementSupportSpec forAll(cases) { (range1, range2, expected) => val updatedRange = range.updateWithInferiorRanges( Map( - inferior1.ref -> (range1, Seq(tappingModel)), - inferior2.ref -> (range2, Seq(tappingModel)), + inferior1.ref -> (range1, Set(tappingModel)), + inferior2.ref -> (range2, Set(tappingModel)), ) ) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala index 2765a386dc..4cc455cf76 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala @@ -172,7 +172,7 @@ trait DBFSMockGridAgents extends UnitSpec { def expectVoltageRangeResponse( voltageRange: VoltageRange, maxDuration: FiniteDuration = 30 seconds, - ): (ActorRef[GridAgent.Request], Seq[TransformerTapping]) = { + ): (ActorRef[GridAgent.Request], Set[TransformerTapping]) = { gaProbe.expectMessageType[VoltageRangeResponse](maxDuration) match { case VoltageRangeResponse(sender, (range, tappings)) => range.deltaPlus shouldBe voltageRange.deltaPlus diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index ba05cdbf66..62c1cc1012 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -226,21 +226,21 @@ class DCMAlgorithmCenGridSpec inferiorGrid11.ref, ( VoltageRange((-0.01).asPu, (-0.02).asPu), - Seq(mvTransformers(transformer11.getUuid)), + Set(mvTransformers(transformer11.getUuid)), ), ) voltageRangeRequester12 ! VoltageRangeResponse( inferiorGrid12.ref, ( VoltageRange(0.07.asPu, 0.01.asPu), - Seq(mvTransformers(transformer12.getUuid)), + Set(mvTransformers(transformer12.getUuid)), ), ) voltageRangeRequester13 ! VoltageRangeResponse( inferiorGrid13.ref, ( VoltageRange(0.06.asPu, 0.asPu), - Seq( + Set( mvTransformers(transformer13_1.getUuid), mvTransformers(transformer13_2.getUuid), ), @@ -256,6 +256,7 @@ class DCMAlgorithmCenGridSpec // the superior grid will update the transformer tappings // and send the the resulting voltage delta to the center grid + tappingModels.size shouldBe 2 tappingModels.foreach(_.decrTapPos(2)) voltageDeltaRequest ! VoltageDeltaResponse(0.03.asPu) @@ -270,7 +271,7 @@ class DCMAlgorithmCenGridSpec .expectMessageType[VoltageDeltaResponse] .delta should equalWithTolerance(0.03.asPu) - // this transformer can e tapped + // this transformer can be tapped mvTransformers(transformer11.getUuid).currentTapPos shouldBe 4 // these transformers can't be tapped and should keep their default tap pos diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala index b3906c6b31..bc271433e2 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala @@ -191,7 +191,7 @@ class DCMAlgorithmSupGridSpec hvGrid.ref, ( VoltageRange(0.04.asPu, (-0.01).asPu), - Seq(tappingModel, tappingModel2), + Set(tappingModel, tappingModel2), ), ) } From 4bca4b58105c91587e9f4d10692850c9db6a3fb5 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 4 Jun 2024 16:34:18 +0200 Subject: [PATCH 30/55] Small improvements to `CongestionManagementSupport`. --- .../grid/CongestionManagementSupport.scala | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 092421aa4d..4b30e86f24 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -375,7 +375,7 @@ object CongestionManagementSupport { inferiorData.foldLeft(this) { case (range, (_, (infRange, tappings))) => // allow tapping only if all transformers support tapping - if (tappings.forall(_.hasAutoTap)) { + val (plus, minus) = if (tappings.forall(_.hasAutoTap)) { // TODO: Enhance tests, to tests these changes val tappingRanges = tappings.map { tapping => @@ -406,15 +406,13 @@ object CongestionManagementSupport { .isGreaterThanOrEqualTo(infRange.deltaMinus), ) match { case (true, true) => - range + (range.deltaPlus, range.deltaMinus) case (true, false) => - range.copy(deltaMinus = - infRange.deltaMinus.subtract(possiblePlus) - ) + (range.deltaPlus, infRange.deltaMinus.subtract(possiblePlus)) case (false, true) => - range.copy(deltaPlus = infRange.deltaPlus.subtract(possibleMinus)) + (infRange.deltaPlus.subtract(possibleMinus), range.deltaMinus) case (false, false) => - infRange + (infRange.deltaPlus, infRange.deltaMinus) } } else { // no tapping possible, just update the range @@ -424,15 +422,17 @@ object CongestionManagementSupport { range.deltaMinus.isLessThanOrEqualTo(infRange.deltaMinus), ) match { case (true, true) => - infRange + (infRange.deltaPlus, infRange.deltaMinus) case (true, false) => - range.copy(deltaPlus = infRange.deltaPlus) + (infRange.deltaPlus, range.deltaMinus) case (false, true) => - range.copy(deltaMinus = infRange.deltaMinus) + (range.deltaPlus, infRange.deltaMinus) case (false, false) => - range + (range.deltaPlus, range.deltaMinus) } } + + VoltageRange(plus, minus) } } } From b313c0636e20e5d97158c72b3bce8597142942ec Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 6 Jun 2024 17:37:00 +0200 Subject: [PATCH 31/55] Enhancing the selection of linking transformers in `DCMAlgorithm`. --- .../ie3/simona/agent/grid/DBFSAlgorithm.scala | 2 +- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 32 ++++++++++++++++--- .../edu/ie3/simona/model/grid/NodeModel.scala | 2 ++ .../model/grid/NodeInputModelSpec.scala | 2 ++ .../model/grid/FiveLinesWithNodes.scala | 1 + 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala index 065157082b..61700ec9a4 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala @@ -243,7 +243,7 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { /* Determine the slack node voltage under consideration of the target voltage set point */ val vTarget = gridAgentBaseData.gridEnv.gridModel.gridComponents.nodes - .find { case NodeModel(uuid, _, _, isSlack, _, _) => + .find { case NodeModel(uuid, _, _, isSlack, _, _, _) => uuid == nodeUuid && isSlack } .map(_.vTarget) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index d43385eab5..6a12075d9c 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.agent.grid +import edu.ie3.datamodel.exceptions.InvalidGridException import edu.ie3.simona.agent.grid.CongestionManagementSupport.CongestionManagementSteps._ import edu.ie3.simona.agent.grid.CongestionManagementSupport.{ Congestions, @@ -242,10 +243,31 @@ trait DCMAlgorithm extends CongestionManagementSupport { val gridModel = gridEnv.gridModel val gridComponents = gridModel.gridComponents - // map all known transformers to their uuid - val transformers = - (gridComponents.transformers ++ gridComponents.transformers3w) - .map(_.asInstanceOf[TransformerTapping]) + // get the subnet number of the upper grid + val supGridNr = gridEnv.subgridGateToActorRef + .find(_._2 == sender) + .getOrElse( + throw new InvalidGridException( + s"No SubGridGate found for superior grid of $sender!" + ) + ) + ._1 + .superiorNode() + .getSubnet + + // filter all transformers that are connecting this grid to the superior grid + val nodesInSuperiorGrid = + gridComponents.nodes.filter(_.subnet == supGridNr).map(_.uuid) + val transformers = gridComponents.transformers.filter(t => + nodesInSuperiorGrid.contains(t.hvNodeUuid) + ) + val transformers3w = gridComponents.transformers3w.filter(t => + nodesInSuperiorGrid.contains(t.hvNodeUuid) + ) + + val allTransformers = (transformers ++ transformers3w).map( + _.asInstanceOf[TransformerTapping] + ) // calculate the voltage range with the received data val range = calculatePossibleVoltageRange( @@ -261,7 +283,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { sender ! VoltageRangeResponse( ctx.self, - (range, transformers), + (range, allTransformers), ) } diff --git a/src/main/scala/edu/ie3/simona/model/grid/NodeModel.scala b/src/main/scala/edu/ie3/simona/model/grid/NodeModel.scala index 506ea804a1..7a96e155ab 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/NodeModel.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/NodeModel.scala @@ -40,6 +40,7 @@ final case class NodeModel( isSlack: Boolean, vTarget: squants.Dimensionless, voltLvl: VoltageLevel, + subnet: Int, ) extends SystemComponent( uuid, id, @@ -70,6 +71,7 @@ case object NodeModel { nodeInput.isSlack, Each(nodeInput.getvTarget.to(PowerSystemUnits.PU).getValue.doubleValue()), nodeInput.getVoltLvl, + nodeInput.getSubnet, ) /* Checks, if the participant is in operation right from the start */ diff --git a/src/test/scala/edu/ie3/simona/model/grid/NodeInputModelSpec.scala b/src/test/scala/edu/ie3/simona/model/grid/NodeInputModelSpec.scala index 140ecec17f..bba7ab411a 100644 --- a/src/test/scala/edu/ie3/simona/model/grid/NodeInputModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/grid/NodeInputModelSpec.scala @@ -38,6 +38,7 @@ class NodeInputModelSpec extends UnitSpec with NodeInputTestData { isSlack, vTarget, voltLvl, + subnet, ) => uuid shouldBe nodeInputNoSlackNs04KvA.getUuid id shouldBe nodeInputNoSlackNs04KvA.getId @@ -47,6 +48,7 @@ class NodeInputModelSpec extends UnitSpec with NodeInputTestData { Each(nodeInputNoSlackNs04KvA.getvTarget.getValue.doubleValue()) ) voltLvl shouldBe nodeInputNoSlackNs04KvA.getVoltLvl + subnet shouldBe -1 } } diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/FiveLinesWithNodes.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/FiveLinesWithNodes.scala index 615be682f4..25dad51c98 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/FiveLinesWithNodes.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/FiveLinesWithNodes.scala @@ -60,6 +60,7 @@ trait FiveLinesWithNodes { isSlack, Each(1.0d), GermanVoltageLevelUtils.parse(vNominal), + -1, ) } From 195f9fe9056c9429af2f7174942a9a60f936314c Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 6 Jun 2024 20:25:59 +0200 Subject: [PATCH 32/55] Fixing issue. --- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 21 +++++-------------- .../simona/agent/grid/GridAgentMessages.scala | 3 ++- .../agent/grid/DCMAlgorithmCenGridSpec.scala | 2 +- .../agent/grid/DCMAlgorithmSupGridSpec.scala | 4 +++- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 6a12075d9c..9b88132f71 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -6,7 +6,6 @@ package edu.ie3.simona.agent.grid -import edu.ie3.datamodel.exceptions.InvalidGridException import edu.ie3.simona.agent.grid.CongestionManagementSupport.CongestionManagementSteps._ import edu.ie3.simona.agent.grid.CongestionManagementSupport.{ Congestions, @@ -218,17 +217,19 @@ trait DCMAlgorithm extends CongestionManagementSupport { buffer: StashBuffer[GridAgent.Request], ): Behavior[GridAgent.Request] = Behaviors.receivePartial { case (ctx, StartStep) => + val subnet = stateData.gridAgentBaseData.gridEnv.gridModel.subnetNo + // request congestion check if we have inferior grids askInferior( stateData, - RequestVoltageOptions, + ref => RequestVoltageOptions(ref, subnet), ReceivedVoltageRange, ctx, ) Behaviors.same - case (ctx, voltageRangeRequest @ RequestVoltageOptions(sender)) => + case (ctx, voltageRangeRequest @ RequestVoltageOptions(sender, subnet)) => // check if waiting for inferior data is needed if (awaitingData.notDone) { ctx.log.debug( @@ -243,21 +244,9 @@ trait DCMAlgorithm extends CongestionManagementSupport { val gridModel = gridEnv.gridModel val gridComponents = gridModel.gridComponents - // get the subnet number of the upper grid - val supGridNr = gridEnv.subgridGateToActorRef - .find(_._2 == sender) - .getOrElse( - throw new InvalidGridException( - s"No SubGridGate found for superior grid of $sender!" - ) - ) - ._1 - .superiorNode() - .getSubnet - // filter all transformers that are connecting this grid to the superior grid val nodesInSuperiorGrid = - gridComponents.nodes.filter(_.subnet == supGridNr).map(_.uuid) + gridComponents.nodes.filter(_.subnet == subnet).map(_.uuid) val transformers = gridComponents.transformers.filter(t => nodesInSuperiorGrid.contains(t.hvNodeUuid) ) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala index c44e5b28bf..15a70b4ab5 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala @@ -296,7 +296,8 @@ object GridAgentMessages { // transformer tapping messages case class RequestVoltageOptions( - override val sender: ActorRef[GridAgent.Request] + override val sender: ActorRef[GridAgent.Request], + subnet: Int, ) extends CMRequest case class VoltageRangeResponse( diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index 62c1cc1012..e45cd16474 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -213,7 +213,7 @@ class DCMAlgorithmCenGridSpec // since the transformer tapping was started, the superior grid // requests the possible voltage range from the center grid - centerGridAgent ! RequestVoltageOptions(superiorGridAgent.ref) + centerGridAgent ! RequestVoltageOptions(superiorGridAgent.ref, 1000) // the center grid will request the voltage ranges from its inferior grid // therefore the inferior grids should receive a VoltageRangeRequest diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala index bc271433e2..5e44b45209 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala @@ -186,7 +186,9 @@ class DCMAlgorithmSupGridSpec // the inferior will receive a request to send the possible voltage range // and send a VoltageRangeResponse to the superior grid hvGrid.expectMessageType[RequestVoltageOptions] match { - case RequestVoltageOptions(sender) => + case RequestVoltageOptions(sender, subnet) => + subnet shouldBe 1000 + sender ! VoltageRangeResponse( hvGrid.ref, ( From 2b1aaf3452b5aa9dee3004c61c91afba1d7c262e Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Sun, 9 Jun 2024 20:58:05 +0200 Subject: [PATCH 33/55] Improvements and fixes. --- .../grid/CongestionManagementSupport.scala | 55 +++++++++++-------- .../model/grid/TransformerTapping.scala | 55 +++++++++++++++++-- .../CongestionManagementSupportSpec.scala | 6 +- .../agent/grid/DCMAlgorithmSupGridSpec.scala | 2 +- .../model/grid/Transformer3wModelSpec.scala | 3 +- .../model/grid/TransformerModelSpec.scala | 2 +- .../model/grid/GridComponentsMokka.scala | 3 + 7 files changed, 93 insertions(+), 33 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 4b30e86f24..3c259e9820 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -114,9 +114,6 @@ trait CongestionManagementSupport { /** Method for calculating the tap pos changes for all given transformers and * the voltage delta. * - *

NOTE: This method currently only support tapping of the higher voltage - * side of a given transformer. - * * @param suggestion * given delta suggestion * @param tappings @@ -128,9 +125,6 @@ trait CongestionManagementSupport { suggestion: ComparableQuantity[Dimensionless], tappings: Seq[TransformerTapping], ): (Map[TransformerTapping, Int], ComparableQuantity[Dimensionless]) = { - // inverting the suggestion, because it is given for the lv side - val inverted = suggestion.multiply(-1) - // check if all transformer have the tapping at the hv side // lv side tapping for transformer2ws is currently not supported if (tappings.exists(_.getTapSide != ConnectorPort.A)) { @@ -143,20 +137,18 @@ trait CongestionManagementSupport { // if only one transformer is given, we only need to calculate one result val tapping = tappings(0) - val taps = tapping.computeDeltaTap(inverted) - val delta = tapping.deltaV.getValue.doubleValue() * taps / -100 - Some(Map(tapping -> taps), delta.asPu) + val (taps, delta) = tapping.computeDeltas(suggestion, ConnectorPort.B) + Some(Map(tapping -> taps), delta) } else { // if multiple transformers are used, we need to find an option, that works // for all transformers val possibleChange = tappings.map { tapping => - val taps = tapping.computeDeltaTap(inverted) - val delta = tapping.deltaV.getValue.doubleValue() * taps / 100 + val (taps, delta) = tapping.computeDeltas(suggestion, ConnectorPort.B) tapping -> (taps, delta) }.toMap // finds the smallest possible delta, because we are limited by that transformer - val possibleOption = if (inverted.isGreaterThan(0.asPu)) { + val possibleOption = if (suggestion.isGreaterThan(0.asPu)) { possibleChange.minByOption(_._2._2) } else { possibleChange.maxByOption(_._2._2) @@ -165,23 +157,24 @@ trait CongestionManagementSupport { // adapt all tapping the the possible option possibleOption.map(_._2._2) match { case Some(maxValue) => - val max = maxValue.asPu + val maxAsDouble = maxValue.getValue.doubleValue() // calculate tap changes val changes = tappings.map { tapping => - val taps = tapping.computeDeltaTap(max) - val delta = tapping.deltaV.getValue.doubleValue() * taps / -100 + val (taps, delta) = tapping.computeDeltas(maxValue, ConnectorPort.B) tapping -> (taps, delta) }.toMap val check = changes.forall { case (_, (_, delta)) => // check if all deltas are in a range of plus minus 0.1 % - Math.abs(Math.abs(maxValue) - Math.abs(delta)) < 1e-3 + Math.abs( + Math.abs(maxAsDouble) - Math.abs(delta.getValue.doubleValue()) + ) < 1e-3 } if (check) { - Some(changes.map(t => t._1 -> t._2._1), max.multiply(-1)) + Some(changes.map(t => t._1 -> t._2._1), maxValue) } else { None } @@ -244,7 +237,6 @@ trait CongestionManagementSupport { updatedRange } else { // if there are inferior grids, update the voltage range - updatedRange.updateWithInferiorRanges(inferiorData) } } @@ -333,6 +325,13 @@ object CongestionManagementSupport { suggestion: ComparableQuantity[Dimensionless], ) { + def isInRange( + delta: ComparableQuantity[Dimensionless] + ): Boolean = + delta.isGreaterThanOrEqualTo(deltaMinus) && delta.isLessThanOrEqualTo( + deltaPlus + ) + /** Method to update this voltage range with line voltage delta. * @param deltaV * to consider @@ -460,11 +459,21 @@ object CongestionManagementSupport { (value * factor).ceil / factor } - VoltageRange( - deltaPlus, - deltaMinus, - suggestion.asPu, - ) + // check if tapping is required + if (plus < 0 || minus > 0) { + VoltageRange( + deltaPlus, + deltaMinus, + suggestion.asPu, + ) + } else { + // the voltage in this range is fine, set the suggested voltage change to zero + VoltageRange( + deltaPlus, + deltaMinus, + 0.asPu, + ) + } } def combineSuggestions( diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala index a893f85709..da06d8ab5d 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala @@ -74,12 +74,15 @@ trait TransformerTapping { tapRatio = transformerTappingModel.decrTapPos(deltaTap) /** Determine the amount of tap positions to increase oder decrease in order - * to meet the desired change in voltage magnitude. For details on the - * implementation see [[TransformerTappingModel.computeDeltaTap()]] + * to meet the desired change in voltage magnitude at the given transformer + * side. For details on the implementation see + * [[TransformerTappingModel.computeDeltaTap()]] * * @param vChangeRequest * desired change in voltage magnitude (> 0 --> increase voltage, < 0 --> * decrease voltage) + * @param tapSide + * the side of the transformer at which the given voltage change is desired * @param deadBand * as a portion of the transformer voltage ratio per tap, it defaults to 75 * % of the deltaV of a tap @@ -89,8 +92,52 @@ trait TransformerTapping { */ def computeDeltaTap( vChangeRequest: Quantity[Dimensionless], + tapSide: ConnectorPort = ConnectorPort.A, deadBand: Quantity[Dimensionless] = Quantities.getQuantity(0.75, PU), - ): Int = - transformerTappingModel.computeDeltaTap(vChangeRequest, deadBand) + ): Int = { + if (tapSide == transformerTappingModel.tapSide) { + transformerTappingModel.computeDeltaTap(vChangeRequest, deadBand) + } else { + transformerTappingModel.computeDeltaTap( + vChangeRequest.multiply(-1), + deadBand, + ) + } + } + + /** Determine the amount of tap positions to increase oder decrease in order + * to meet the desired change in voltage magnitude at the given transformer + * side. For details on the implementation see + * [[TransformerTappingModel.computeDeltaTap()]] and the resulting voltage + * delta. + * + * @param vChangeRequest + * desired change in voltage magnitude (> 0 --> increase voltage, < 0 --> + * decrease voltage) + * @param tapSide + * the side of the transformer at which the given voltage change is desired + * @param deadBand + * as a portion of the transformer voltage ratio per tap, it defaults to 75 + * % of the deltaV of a tap + * @return + * the needed in- or decrease of the transformer tap position to reach the + * desired change in voltage magnitude or zero if not possible and the + * resulting voltage delta + */ + def computeDeltas( + vChangeRequest: Quantity[Dimensionless], + tapSide: ConnectorPort = ConnectorPort.A, + deadBand: Quantity[Dimensionless] = Quantities.getQuantity(0.75, PU), + ): (Int, ComparableQuantity[Dimensionless]) = { + val taps = computeDeltaTap(vChangeRequest, tapSide, deadBand) + val deltaV = + transformerTappingModel.deltaV.to(PU).getValue.doubleValue() * taps + + if (tapSide == transformerTappingModel.tapSide) { + (taps, deltaV.asPu) + } else { + (taps, deltaV.asPu.multiply(-1)) + } + } } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index 5064d31636..43640957ea 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -331,7 +331,7 @@ class CongestionManagementSupportSpec range.deltaPlus should equalWithTolerance(0.05.asPu) range.deltaMinus should equalWithTolerance((-0.03).asPu) - range.suggestion should equalWithTolerance(0.011.asPu) + range.suggestion should equalWithTolerance(0.asPu) } "calculates the voltage range for a middle grid correctly" in { @@ -392,7 +392,7 @@ class CongestionManagementSupportSpec range.deltaPlus should equalWithTolerance(0.04.asPu) range.deltaMinus should equalWithTolerance((-0.02).asPu) - range.suggestion should equalWithTolerance(0.011.asPu) + range.suggestion should equalWithTolerance(0.asPu) } def buildPowerFlowResultEvent( @@ -415,7 +415,7 @@ class CongestionManagementSupportSpec "calculate the suggestion correctly" in { val cases = Table( ("deltaPlus", "deltaMinus", "expected"), - (0.05.asPu, (-0.03).asPu, 0.011.asPu), // no voltage limit violation + (0.05.asPu, (-0.03).asPu, 0.asPu), // no voltage limit violation ( (-0.01).asPu, (-0.02).asPu, diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala index 5e44b45209..821795a3d1 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala @@ -192,7 +192,7 @@ class DCMAlgorithmSupGridSpec sender ! VoltageRangeResponse( hvGrid.ref, ( - VoltageRange(0.04.asPu, (-0.01).asPu), + VoltageRange(0.04.asPu, 0.01.asPu), Set(tappingModel, tappingModel2), ), ) diff --git a/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala b/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala index d666f2fd6a..0b01933646 100644 --- a/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala @@ -488,7 +488,8 @@ class Transformer3wModelSpec val deadBand = Quantities.getQuantity(deadBandVal, PU) transformerModel.updateTapPos(currentTapPos) - val actual = transformerModel.computeDeltaTap(vChange, deadBand) + val actual = + transformerModel.computeDeltaTap(vChange, deadBand = deadBand) actual should be(expected) } } 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 2d00ddf804..56180837ac 100644 --- a/src/test/scala/edu/ie3/simona/model/grid/TransformerModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/grid/TransformerModelSpec.scala @@ -281,7 +281,7 @@ class TransformerModelSpec transformerModelTapHv.updateTapPos(currentTapPos) transformerModelTapHv.computeDeltaTap( vChange, - deadBand, + deadBand = deadBand, ) shouldBe expected } } diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala index 6753d007aa..2b3ff40c71 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.test.common.model.grid +import edu.ie3.datamodel.models.input.connector.ConnectorPort import edu.ie3.simona.model.grid.Transformer3wPowerFlowCase._ import edu.ie3.simona.model.grid._ import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble @@ -52,6 +53,7 @@ trait GridComponentsMokka extends MockitoSugar { tapMin: Int = -5, tapNeutr: Int = 0, autoTap: Boolean = true, + tapSide: ConnectorPort = ConnectorPort.A, ): TransformerTappingModel = TransformerTappingModel( deltaV, @@ -60,6 +62,7 @@ trait GridComponentsMokka extends MockitoSugar { tapMin, tapNeutr, autoTap, + tapSide, ) protected def dummyTransformerModel( From 3dfed3349e63398c74b3f1be3d817de19a0900d0 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Sun, 16 Jun 2024 20:55:11 +0200 Subject: [PATCH 34/55] Updating to `PSDM` version `6.0-SNAPSHOT`. --- build.gradle | 2 +- input/samples/vn_simona/vn_simona.conf | 1 + .../resources/config/config-template.conf | 1 + .../ie3/simona/agent/grid/DCMAlgorithm.scala | 8 +- .../ie3/simona/agent/grid/GridAgentData.scala | 15 ++++ .../simona/agent/grid/GridAgentMessages.scala | 23 ++--- .../edu/ie3/simona/config/SimonaConfig.scala | 3 + .../edu/ie3/simona/event/ResultEvent.scala | 5 +- .../event/listener/ResultEventListener.scala | 3 +- .../service/weather/SampleWeatherSource.scala | 5 ++ .../edu/ie3/simona/util/ConfigUtil.scala | 8 +- .../service/weather/WeatherSourceSpec.scala | 6 ++ .../test/common/input/EmInputTestData.scala | 3 - .../edu/ie3/simona/util/ConfigUtilSpec.scala | 83 +++++++++++++++++-- 14 files changed, 138 insertions(+), 28 deletions(-) diff --git a/build.gradle b/build.gradle index c18367c433..192cee24d6 100644 --- a/build.gradle +++ b/build.gradle @@ -75,7 +75,7 @@ dependencies { /* Exclude our own nested dependencies */ exclude group: 'com.github.ie3-institute' } - implementation('com.github.ie3-institute:PowerSystemDataModel:5.0.1') { + implementation('com.github.ie3-institute:PowerSystemDataModel:6.0-SNAPSHOT') { exclude group: 'org.apache.logging.log4j' exclude group: 'org.slf4j' /* Exclude our own nested dependencies */ diff --git a/input/samples/vn_simona/vn_simona.conf b/input/samples/vn_simona/vn_simona.conf index 48efda8a38..dc1b09d52b 100644 --- a/input/samples/vn_simona/vn_simona.conf +++ b/input/samples/vn_simona/vn_simona.conf @@ -60,6 +60,7 @@ simona.output.grid = { switches = false transformers2w = false transformers3w = false + congestions = true } simona.output.participant.defaultConfig = { notifier = "default" diff --git a/src/main/resources/config/config-template.conf b/src/main/resources/config/config-template.conf index fcd39b32c3..a1535d0a75 100644 --- a/src/main/resources/config/config-template.conf +++ b/src/main/resources/config/config-template.conf @@ -146,6 +146,7 @@ GridOutputConfig { switches: boolean | false transformers2w: boolean | false transformers3w: boolean | false + congestions: boolean | false } #@define diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 9b88132f71..ff0d13ef04 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -184,10 +184,16 @@ trait DCMAlgorithm extends CongestionManagementSupport { ) // clean up agent and go back to idle + val powerFlowResults = if (stateData.congestions.any) { + stateData.powerFlowResults.copy(congestionResults = + Seq(stateData.getCongestionResult(constantData.simStartTime)) + ) + } else stateData.powerFlowResults + GridAgent.gotoIdle( stateData.gridAgentBaseData, stateData.currentTick, - Some(stateData.powerFlowResults), + Some(powerFlowResults), ctx, ) 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 cd624a9295..1797d2ac9f 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -8,6 +8,7 @@ package edu.ie3.simona.agent.grid import edu.ie3.datamodel.graph.SubGridGate import edu.ie3.datamodel.models.input.container.{SubGridContainer, ThermalGrid} +import edu.ie3.datamodel.models.result.CongestionResult import edu.ie3.powerflow.model.PowerFlowResult import edu.ie3.powerflow.model.PowerFlowResult.SuccessFullPowerFlowResult.ValidNewtonRaphsonPFResult import edu.ie3.simona.agent.EnvironmentRefs @@ -522,6 +523,20 @@ object GridAgentData { inferiorGrids: Seq[ActorRef[GridAgent.Request]], ) extends GridAgentData { + def getCongestionResult(startTime: ZonedDateTime): CongestionResult = { + val gridModel = gridAgentBaseData.gridEnv.gridModel + + new CongestionResult( + startTime.plusSeconds(currentTick), + gridModel.subnetNo, + gridModel.voltageLimits.vMin, + gridModel.voltageLimits.vMax, + congestions.voltageCongestions, + congestions.lineCongestions, + congestions.transformerCongestions, + ) + } + def inferiorRefs: Set[ActorRef[GridAgent.Request]] = inferiorGrids.toSet def cleanAfterTransformerTapping: GridAgentBaseData = { diff --git a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala index 15a70b4ab5..8dd4a67f0a 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentMessages.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.agent.grid +import edu.ie3.datamodel.models.result.CongestionResult import edu.ie3.simona.agent.grid.CongestionManagementSupport.{ CongestionManagementSteps, Congestions, @@ -281,54 +282,54 @@ object GridAgentMessages { } // general congestion messages - case class CongestionCheckRequest( + final case class CongestionCheckRequest( override val sender: ActorRef[GridAgent.Request] ) extends CMRequest - case class CongestionResponse( + final case class CongestionResponse( override val sender: ActorRef[GridAgent.Request], override val value: Congestions, ) extends CMReceiveResponse[Congestions] - case class ReceivedCongestions( + final case class ReceivedCongestions( override val values: Vector[(ActorRef[GridAgent.Request], Congestions)] ) extends CMResponse[Congestions] // transformer tapping messages - case class RequestVoltageOptions( + final case class RequestVoltageOptions( override val sender: ActorRef[GridAgent.Request], subnet: Int, ) extends CMRequest - case class VoltageRangeResponse( + final case class VoltageRangeResponse( override val sender: ActorRef[GridAgent.Request], override val value: (VoltageRange, Set[TransformerTapping]), ) extends CMReceiveResponse[(VoltageRange, Set[TransformerTapping])] - case class ReceivedVoltageRange( + final case class ReceivedVoltageRange( override val values: Vector[ (ActorRef[GridAgent.Request], (VoltageRange, Set[TransformerTapping])) ] ) extends CMResponse[(VoltageRange, Set[TransformerTapping])] - case class VoltageDeltaResponse( + final case class VoltageDeltaResponse( delta: ComparableQuantity[Dimensionless] ) extends GridAgent.InternalReply - case class NextStepRequest( + final case class NextStepRequest( nextStep: CongestionManagementSteps.Value ) extends GridAgent.InternalRequest /** Message that indicates all actors that the current step is started. */ - case object StartStep extends GridAgent.InternalRequest + final case object StartStep extends GridAgent.InternalRequest /** Message that indicates all actors that the current step is finished. */ - case object FinishStep extends GridAgent.InternalRequest + final case object FinishStep extends GridAgent.InternalRequest /** Message that indicates all actors that the next state is the idle state. */ - case object GotoIdle extends GridAgent.InternalRequest + final case object GotoIdle extends GridAgent.InternalRequest } diff --git a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala index 9501a1dc34..ccb8686c26 100644 --- a/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala +++ b/src/main/scala/edu/ie3/simona/config/SimonaConfig.scala @@ -278,6 +278,7 @@ object SimonaConfig { } final case class GridOutputConfig( + congestions: scala.Boolean, lines: scala.Boolean, nodes: scala.Boolean, notifier: java.lang.String, @@ -292,6 +293,8 @@ object SimonaConfig { $tsCfgValidator: $TsCfgValidator, ): SimonaConfig.GridOutputConfig = { SimonaConfig.GridOutputConfig( + congestions = + c.hasPathOrNull("congestions") && c.getBoolean("congestions"), lines = c.hasPathOrNull("lines") && c.getBoolean("lines"), nodes = c.hasPathOrNull("nodes") && c.getBoolean("nodes"), notifier = $_reqStr(parentPath, c, "notifier", $tsCfgValidator), diff --git a/src/main/scala/edu/ie3/simona/event/ResultEvent.scala b/src/main/scala/edu/ie3/simona/event/ResultEvent.scala index d81242c608..3a2465245c 100644 --- a/src/main/scala/edu/ie3/simona/event/ResultEvent.scala +++ b/src/main/scala/edu/ie3/simona/event/ResultEvent.scala @@ -6,7 +6,7 @@ package edu.ie3.simona.event -import edu.ie3.datamodel.models.result.NodeResult +import edu.ie3.datamodel.models.result.{CongestionResult, NodeResult} import edu.ie3.datamodel.models.result.connector.{ LineResult, SwitchResult, @@ -59,6 +59,8 @@ object ResultEvent { * the power flow results for two winding transformers * @param transformer3wResults * the partial power flow results for three winding transformers + * @param congestionResults + * the congestion found by the congestion managements (default: empty) */ final case class PowerFlowResultEvent( nodeResults: Iterable[NodeResult], @@ -66,6 +68,7 @@ object ResultEvent { lineResults: Iterable[LineResult], transformer2wResults: Iterable[Transformer2WResult], transformer3wResults: Iterable[PartialTransformer3wResult], + congestionResults: Iterable[CongestionResult] = Iterable.empty, ) extends ResultEvent /** Event that holds the flexibility options result of a diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index d5acf17912..d460f63b8c 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -291,10 +291,11 @@ object ResultEventListener extends Transformer3wResultSupport { lineResults, transformer2wResults, transformer3wResults, + congestionResults, ), ) => val updatedBaseData = - (nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ transformer3wResults) + (nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ transformer3wResults ++ congestionResults) .foldLeft(baseData) { case (currentBaseData, resultEntity: ResultEntity) => handleResult(resultEntity, currentBaseData, ctx.log) diff --git a/src/main/scala/edu/ie3/simona/service/weather/SampleWeatherSource.scala b/src/main/scala/edu/ie3/simona/service/weather/SampleWeatherSource.scala index 5788aca327..80e969bf86 100644 --- a/src/main/scala/edu/ie3/simona/service/weather/SampleWeatherSource.scala +++ b/src/main/scala/edu/ie3/simona/service/weather/SampleWeatherSource.scala @@ -168,6 +168,11 @@ object SampleWeatherSource { else Vector.empty[CoordinateDistance].asJava } + + override def findCornerPoints( + point: Point, + comparableQuantity: ComparableQuantity[Length], + ): util.List[CoordinateDistance] = Vector.empty[CoordinateDistance].asJava } // these lists contain the hourly weather values for each first of the month of 2011 + january of diff --git a/src/main/scala/edu/ie3/simona/util/ConfigUtil.scala b/src/main/scala/edu/ie3/simona/util/ConfigUtil.scala index b041129c90..9994619c37 100644 --- a/src/main/scala/edu/ie3/simona/util/ConfigUtil.scala +++ b/src/main/scala/edu/ie3/simona/util/ConfigUtil.scala @@ -18,7 +18,11 @@ import edu.ie3.datamodel.models.result.connector.{ Transformer2WResult, Transformer3WResult, } -import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} +import edu.ie3.datamodel.models.result.{ + CongestionResult, + NodeResult, + ResultEntity, +} import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.config.SimonaConfig._ import edu.ie3.simona.event.notifier.{Notifier, NotifierConfig} @@ -271,6 +275,8 @@ object ConfigUtil { entities += classOf[Transformer2WResult] if (subConfig.transformers3w) entities += classOf[Transformer3WResult] + if (subConfig.congestions) + entities += classOf[CongestionResult] entities.toSet } diff --git a/src/test/scala/edu/ie3/simona/service/weather/WeatherSourceSpec.scala b/src/test/scala/edu/ie3/simona/service/weather/WeatherSourceSpec.scala index 2da8e6c63e..eed62feacd 100644 --- a/src/test/scala/edu/ie3/simona/service/weather/WeatherSourceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/weather/WeatherSourceSpec.scala @@ -450,5 +450,11 @@ case object WeatherSourceSpec { ): util.List[CoordinateDistance] = { calculateCoordinateDistances(coordinate, n, coordinateToId.keySet.asJava) } + + override def findCornerPoints( + coordinate: Point, + distance: ComparableQuantity[Length], + ): util.List[CoordinateDistance] = + calculateCoordinateDistances(coordinate, 4, coordinateToId.keySet.asJava) } } diff --git a/src/test/scala/edu/ie3/simona/test/common/input/EmInputTestData.scala b/src/test/scala/edu/ie3/simona/test/common/input/EmInputTestData.scala index 4c349c603e..7eeb67574c 100644 --- a/src/test/scala/edu/ie3/simona/test/common/input/EmInputTestData.scala +++ b/src/test/scala/edu/ie3/simona/test/common/input/EmInputTestData.scala @@ -78,9 +78,6 @@ trait EmInputTestData Quantities.getQuantity(5d, KILOWATT), Quantities.getQuantity(0.03, PU_PER_HOUR), Quantities.getQuantity(0.95, PU), - Quantities.getQuantity(20d, PERCENT), - Quantities.getQuantity(50000d, HOUR), - 100000, ) protected val householdStorageInput = new StorageInput( diff --git a/src/test/scala/edu/ie3/simona/util/ConfigUtilSpec.scala b/src/test/scala/edu/ie3/simona/util/ConfigUtilSpec.scala index 1356fef9cb..e62d65379e 100644 --- a/src/test/scala/edu/ie3/simona/util/ConfigUtilSpec.scala +++ b/src/test/scala/edu/ie3/simona/util/ConfigUtilSpec.scala @@ -14,7 +14,11 @@ import edu.ie3.datamodel.models.result.connector.{ Transformer3WResult, } import edu.ie3.datamodel.models.result.system.{ChpResult, LoadResult} -import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} +import edu.ie3.datamodel.models.result.{ + CongestionResult, + NodeResult, + ResultEntity, +} import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.config.SimonaConfig.{apply => _, _} import edu.ie3.simona.event.notifier.NotifierConfig @@ -24,8 +28,8 @@ import edu.ie3.simona.util.ConfigUtil.NotifierIdentifier._ import edu.ie3.simona.util.ConfigUtil.{ GridOutputConfigUtil, NotifierIdentifier, - ParticipantConfigUtil, OutputConfigUtil, + ParticipantConfigUtil, } import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor2} @@ -571,37 +575,98 @@ class ConfigUtilSpec Table( ("config", "expected"), ( - new GridOutputConfig(false, false, "grid", false, false, false), + new GridOutputConfig( + false, + false, + false, + "grid", + false, + false, + false, + ), Set.empty[Class[_ <: ResultEntity]], ), ( - new GridOutputConfig(true, false, "grid", false, false, false), + new GridOutputConfig( + false, + true, + false, + "grid", + false, + false, + false, + ), Set(classOf[LineResult]), ), ( - new GridOutputConfig(false, true, "grid", false, false, false), + new GridOutputConfig( + false, + false, + true, + "grid", + false, + false, + false, + ), Set(classOf[NodeResult]), ), ( - new GridOutputConfig(false, false, "grid", true, false, false), + new GridOutputConfig( + false, + false, + false, + "grid", + true, + false, + false, + ), Set(classOf[SwitchResult]), ), ( - new GridOutputConfig(false, false, "grid", false, true, false), + new GridOutputConfig( + false, + false, + false, + "grid", + false, + true, + false, + ), Set(classOf[Transformer2WResult]), ), ( - new GridOutputConfig(false, false, "grid", false, false, true), + new GridOutputConfig( + false, + false, + false, + "grid", + false, + false, + true, + ), Set(classOf[Transformer3WResult]), ), ( - new GridOutputConfig(true, true, "grid", true, true, true), + new GridOutputConfig( + true, + false, + false, + "grid", + false, + false, + false, + ), + Set(classOf[CongestionResult]), + ), + ( + new GridOutputConfig(true, true, true, "grid", true, true, true), Set( classOf[LineResult], classOf[NodeResult], classOf[SwitchResult], classOf[Transformer2WResult], classOf[Transformer3WResult], + classOf[CongestionResult], ), ), ) From 784bf665ccc3ea783bc21e9e02f484f052c88f1a Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 17 Jun 2024 12:42:10 +0200 Subject: [PATCH 35/55] Improving transformer tapping calculation. --- .../grid/CongestionManagementSupport.scala | 103 ++++++++---------- .../model/grid/TransformerTapping.scala | 24 ++++ .../CongestionManagementSupportSpec.scala | 23 ---- 3 files changed, 71 insertions(+), 79 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 3c259e9820..c5e2c65f05 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -13,13 +13,7 @@ import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.exceptions.{GridInconsistencyException, ResultException} import edu.ie3.simona.model.grid.GridModel.GridComponents import edu.ie3.simona.model.grid.Transformer3wPowerFlowCase.PowerFlowCaseA -import edu.ie3.simona.model.grid.{ - Transformer3wModel, - TransformerModel, - TransformerTapping, - TransformerTappingModel, - VoltageLimits, -} +import edu.ie3.simona.model.grid._ import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import org.apache.pekko.actor.typed.ActorRef import squants.electro.Amperes @@ -125,67 +119,64 @@ trait CongestionManagementSupport { suggestion: ComparableQuantity[Dimensionless], tappings: Seq[TransformerTapping], ): (Map[TransformerTapping, Int], ComparableQuantity[Dimensionless]) = { - // check if all transformer have the tapping at the hv side - // lv side tapping for transformer2ws is currently not supported - if (tappings.exists(_.getTapSide != ConnectorPort.A)) { - // for now only work if all transformers have the tapping at the hv side - return (tappings.map(t => t -> 0).toMap, 0.asPu) - } + val noTapping = (tappings.map(t => t -> 0).toMap, 0.asPu) // calculate a tap option for each transformer - val option = if (tappings.size == 1) { - // if only one transformer is given, we only need to calculate one result - val tapping = tappings(0) + if (tappings.forall(_.hasAutoTap)) { + val possibleDeltas = tappings.map(_.possibleDeltas(ConnectorPort.B)) - val (taps, delta) = tapping.computeDeltas(suggestion, ConnectorPort.B) - Some(Map(tapping -> taps), delta) - } else { - // if multiple transformers are used, we need to find an option, that works - // for all transformers - val possibleChange = tappings.map { tapping => - val (taps, delta) = tapping.computeDeltas(suggestion, ConnectorPort.B) - tapping -> (taps, delta) - }.toMap - - // finds the smallest possible delta, because we are limited by that transformer - val possibleOption = if (suggestion.isGreaterThan(0.asPu)) { - possibleChange.minByOption(_._2._2) + if (possibleDeltas.exists(_.size < 2)) { + // there is a transformer that cannot be taped + noTapping } else { - possibleChange.maxByOption(_._2._2) - } - // adapt all tapping the the possible option - possibleOption.map(_._2._2) match { - case Some(maxValue) => - val maxAsDouble = maxValue.getValue.doubleValue() + // reduce all possible deltas + val reducedOptions = possibleDeltas.map { deltas => + if (deltas.exists(_.isEquivalentTo(suggestion))) { + List(suggestion) + } else { + val minOption = deltas.filter(_.isLessThan(suggestion)).lastOption + val maxOption = deltas.find(_.isGreaterThan(suggestion)) + + // check possible deltas + (minOption, maxOption) match { + case (Some(min), Some(max)) => List(min, max) + case (Some(min), _) => List(min) + case (_, Some(max)) => List(max) + case _ => List() + } + } + } + + // filter the possible options + val filteredOptions = reducedOptions.flatten + .groupBy(identity) + .filter(_._2.size == reducedOptions.size) + .flatMap(_._2.headOption) - // calculate tap changes - val changes = tappings.map { tapping => - val (taps, delta) = tapping.computeDeltas(maxValue, ConnectorPort.B) + // find the best suitable delta + val deltaOption = if (suggestion.isGreaterThan(0.asPu)) { + filteredOptions.minByOption(_.getValue.doubleValue()) + } else { + filteredOptions.maxByOption(_.getValue.doubleValue()) + } - tapping -> (taps, delta) - }.toMap + // the actual delta that can be used for all transformers + val delta = deltaOption.getOrElse(0.asPu) - val check = changes.forall { case (_, (_, delta)) => - // check if all deltas are in a range of plus minus 0.1 % - Math.abs( - Math.abs(maxAsDouble) - Math.abs(delta.getValue.doubleValue()) - ) < 1e-3 - } + val deltas = tappings + .map(model => model -> model.computeDeltas(delta, ConnectorPort.B)) + .toMap - if (check) { - Some(changes.map(t => t._1 -> t._2._1), maxValue) - } else { - None - } + val taps = deltas.map { case (tapping, (tap, _)) => tapping -> tap } + val actualDelta = deltas.map(_._2._2).toSeq(0) - case None => - None + (taps, actualDelta) } + } else { + // return no tappings if there is at least one transformer that cannot be taped + noTapping } - - // return the option ore no tapping - option.getOrElse((tappings.map(t => t -> 0).toMap, 0.asPu)) } /** Method to calculate the possible range of voltage changes. diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala index da06d8ab5d..c1da89b8d4 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala @@ -105,6 +105,30 @@ trait TransformerTapping { } } + /** Determines all possible voltage deltas that can be achieved by tapping. + * @param tapSide + * side of the tapping + * @return + * a list of possible voltage deltas + */ + def possibleDeltas( + tapSide: ConnectorPort = ConnectorPort.A + ): List[ComparableQuantity[Dimensionless]] = { + if (hasAutoTap) { + val plus = tapMax - currentTapPos + val minus = tapMin - currentTapPos + + val range = + Range.inclusive(minus, plus).map(deltaV.multiply(_).divide(100)).toList + + if (tapSide == transformerTappingModel.tapSide) { + range + } else { + range.map(_.multiply(-1)).sortBy(_.getValue.doubleValue()) + } + } else List(0.asPu) + } + /** Determine the amount of tap positions to increase oder decrease in order * to meet the desired change in voltage magnitude at the given transformer * side. For details on the implementation see diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index 43640957ea..df32d8cdee 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -125,7 +125,6 @@ class CongestionManagementSupportSpec tapMin = -3, currentTapPos = 0, ) - val tappingModel3 = dummyTappingModel(deltaV = 1.49.asPercent) val transformer11 = dummyTransformerModel(tappingModel1) val transformer12 = dummyTransformerModel(tappingModel1) @@ -136,13 +135,9 @@ class CongestionManagementSupportSpec val transformer31 = dummyTransformerModel(tappingModel1) val transformer32 = dummyTransformer3wModel(tappingModel2) - val transformer41 = dummyTransformerModel(tappingModel1) - val transformer42 = dummyTransformer3wModel(tappingModel3) - val modelCase1 = Seq(transformer11, transformer12) val modelCase2 = Seq(transformer21, transformer22) val modelCase3 = Seq(transformer31, transformer32) - val modelCase4 = Seq(transformer41, transformer42) val cases = Table( ("suggestion", "models", "expectedTaps", "expectedDelta"), @@ -200,24 +195,6 @@ class CongestionManagementSupportSpec Map(transformer31 -> 4, transformer32 -> 5), (-0.06).asPu, ), - ( - 0.02.asPu, - modelCase4, - Map(transformer41 -> -1, transformer42 -> -1), - 0.0149.asPu, - ), - ( - 0.038.asPu, - modelCase4, - Map(transformer41 -> -2, transformer42 -> -2), - 0.0298.asPu, - ), - ( - (-0.06).asPu, - modelCase4, - Map(transformer41 -> 4, transformer42 -> 4), - (-0.0596).asPu, - ), ) forAll(cases) { (suggestion, models, expectedTaps, expectedDelta) => From fc423f53e5690d3a24ba42e9ef7941f4ddcc9cf7 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 18 Jun 2024 16:11:59 +0200 Subject: [PATCH 36/55] Adding `TappingGroup`. Improving suggestion calculation in `VoltageRange`. --- .../grid/CongestionManagementSupport.scala | 114 +++++++++++------- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 3 +- .../CongestionManagementSupportSpec.scala | 30 +++-- 3 files changed, 93 insertions(+), 54 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index c5e2c65f05..90cdd99593 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -8,7 +8,10 @@ package edu.ie3.simona.agent.grid import edu.ie3.datamodel.models.input.connector.ConnectorPort import edu.ie3.datamodel.models.result.connector.LineResult -import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange +import edu.ie3.simona.agent.grid.CongestionManagementSupport.{ + TappingGroup, + VoltageRange, +} import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.exceptions.{GridInconsistencyException, ResultException} import edu.ie3.simona.model.grid.GridModel.GridComponents @@ -52,57 +55,62 @@ trait CongestionManagementSupport { * @param transformer3ws * set of [[Transformer3wModel]] with [[PowerFlowCaseA]] * @return - * a map: set of transformers to set of [[ActorRef]]s + * a set of [[TappingGroup]]s */ def groupTappingModels( receivedData: Map[ActorRef[GridAgent.Request], Set[TransformerTapping]], transformer3ws: Set[Transformer3wModel], - ): Map[Set[TransformerTapping], Set[ActorRef[GridAgent.Request]]] = { + ): Set[TappingGroup] = { val transformer3wMap = transformer3ws.map(t => t.uuid -> t).toMap // builds all groups - receivedData.foldLeft( - Map.empty[Set[TransformerTapping], Set[ActorRef[GridAgent.Request]]] - ) { case (combined, (ref, tappings)) => - // get all transformer models - val updated: Set[TransformerTapping] = tappings.map { - case transformerModel: TransformerModel => - transformerModel - case transformer3wModel: Transformer3wModel => - // in case of a three winding transformer, we need the model of the port A - transformer3wMap.getOrElse( - transformer3wModel.uuid, - throw new GridInconsistencyException( - s"No three winding transformer found." - ), - ) - case unsupported => - throw new IllegalArgumentException( - s"The transformer type ${unsupported.getClass} is not supported." - ) - }.toSet - - // find a group that already contains one of the given transformer models - val keyOption = combined.keySet.find { keys => - updated.exists(key => keys.contains(key)) - } + receivedData + .foldLeft( + Map.empty[Set[TransformerTapping], Set[ActorRef[GridAgent.Request]]] + ) { case (combined, (ref, tappings)) => + // get all transformer models + val updated: Set[TransformerTapping] = tappings.map { + case transformerModel: TransformerModel => + transformerModel + case transformer3wModel: Transformer3wModel => + // in case of a three winding transformer, we need the model of the port A + transformer3wMap.getOrElse( + transformer3wModel.uuid, + throw new GridInconsistencyException( + s"No three winding transformer found." + ), + ) + case unsupported => + throw new IllegalArgumentException( + s"The transformer type ${unsupported.getClass} is not supported." + ) + }.toSet - // if a key is found, add the current transformer models and the ref to that group - // else add a new group - keyOption - .map { key => - val refs = combined(key) - val updatedMap = combined.removed(key) + // find a group that already contains one of the given transformer models + val keyOption = combined.keySet.find { keys => + updated.exists(key => keys.contains(key)) + } - val newKey = key ++ updated - val newValue = refs ++ Set(ref) + // if a key is found, add the current transformer models and the ref to that group + // else add a new group + keyOption + .map { key => + val refs = combined(key) + val updatedMap = combined.removed(key) - updatedMap ++ Map(newKey -> newValue) - } - .getOrElse { - combined ++ Map(updated -> Set(ref)) - } - } + val newKey = key ++ updated + val newValue = refs ++ Set(ref) + + updatedMap ++ Map(newKey -> newValue) + } + .getOrElse { + combined ++ Map(updated -> Set(ref)) + } + } + .map { case (tappingModels, refs) => + TappingGroup(refs, tappingModels) + } + .toSet } /** Method for calculating the tap pos changes for all given transformers and @@ -298,6 +306,17 @@ trait CongestionManagementSupport { object CongestionManagementSupport { + /** A group of [[TransformerTapping]] with all associated [[ActorRef]]s. + * @param refs + * a set of [[ActorRef]]s + * @param tappingModels + * a set of [[TransformerTapping]] + */ + final case class TappingGroup( + refs: Set[ActorRef[GridAgent.Request]], + tappingModels: Set[TransformerTapping], + ) + /** Object that contains information about possible voltage changes.

If * the delta plus is negative -> upper voltage violation

If the delta * minus is positive -> lower voltage violation

If both above cases @@ -437,10 +456,17 @@ object CongestionManagementSupport { val minus = deltaMinus.getValue.doubleValue() val value = if (plus > minus) { + // we could have a voltage violation of one limit (plus + minus) / 2 - } else { + } else if (plus > 0 && minus > 0) { + // we have a voltage violation of the lower limit + // since the upper limit is fine, we can increase the voltage a bit plus - } + } else if (plus < 0 && minus < 0) { + // we have a voltage violation of the upper limit + // since the lower limit is fine, we can decrease the voltage a bit + minus + } else 0 // we have a voltage violation of both limits, we can't fix this val factor = 1e3 diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index ff0d13ef04..6e7caa77ab 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -9,6 +9,7 @@ package edu.ie3.simona.agent.grid import edu.ie3.simona.agent.grid.CongestionManagementSupport.CongestionManagementSteps._ import edu.ie3.simona.agent.grid.CongestionManagementSupport.{ Congestions, + TappingGroup, VoltageRange, } import edu.ie3.simona.agent.grid.GridAgent.pipeToSelf @@ -327,7 +328,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { stateData.gridAgentBaseData.gridEnv.gridModel.gridComponents.transformers3w, ) - groups.foreach { case (tappingModels, refs) => + groups.foreach { case TappingGroup(refs, tappingModels) => // get all possible voltage ranges of the inferior grids val inferiorRanges = refs.map(refMap) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index df32d8cdee..c57aec48d1 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -8,7 +8,10 @@ package edu.ie3.simona.agent.grid import edu.ie3.datamodel.models.result.NodeResult import edu.ie3.datamodel.models.result.connector.LineResult -import edu.ie3.simona.agent.grid.CongestionManagementSupport.VoltageRange +import edu.ie3.simona.agent.grid.CongestionManagementSupport.{ + TappingGroup, + VoltageRange, +} import edu.ie3.simona.event.ResultEvent.PowerFlowResultEvent import edu.ie3.simona.model.grid.GridModel.GridComponents import edu.ie3.simona.model.grid.{TransformerTapping, VoltageLimits} @@ -74,7 +77,7 @@ class CongestionManagementSupportSpec ), // connected with two transformer2w ) - val grouped = groupTappingModels( + val groups = groupTappingModels( receivedData, Set(transformer3wA), ) @@ -87,12 +90,11 @@ class CongestionManagementSupportSpec // since grid 3 is only connected by a transformer2w, the group contains only this transformer and one ref // // since grid 4 is connected by two transformer2w, the group contains both transformers and the ref of grid 4 - grouped shouldBe Map( - Set(transformer1, transformer3wA) -> Set(ref1, ref2), - Set(transformer3) -> Set(ref3), - Set(transformer4_1, transformer4_2) -> Set(ref4), + groups shouldBe Set( + TappingGroup(Set(ref1, ref2), Set(transformer1, transformer3wA)), + TappingGroup(Set(ref3), Set(transformer3)), + TappingGroup(Set(ref4), Set(transformer4_1, transformer4_2)), ) - } "calculate the tap and voltage change for one transformer" in { @@ -404,10 +406,20 @@ class CongestionManagementSupportSpec 0.015.asPu, ), // lower voltage limit violation (both are positive), increasing voltage ( - (-0.01).asPu, 0.01.asPu, + 0.02.asPu, + 0.01.asPu, + ), // violation of both lower limit, upper > 0, increase voltage to the upper limit + ( + (-0.02).asPu, + (-0.01).asPu, + (-0.01).asPu, + ), // violation of both upper limit, lower < 0, decrease voltage to the lower limit + ( (-0.01).asPu, - ), // violation of both voltage limits (upper negative, lower positive), decreasing voltage + 0.01.asPu, + 0.asPu, + ), // violation of both voltage limits (upper negative, lower positive), do nothing ) forAll(cases) { (deltaPlus, deltaMinus, expected) => From 458d2368caff7a102ec54d09959b780580dbf0a9 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 18 Jun 2024 20:38:53 +0200 Subject: [PATCH 37/55] Updating the default config. --- input/ma_thesis/ma_thesis.conf | 90 ++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/input/ma_thesis/ma_thesis.conf b/input/ma_thesis/ma_thesis.conf index c295a62869..4cd425b3dd 100644 --- a/input/ma_thesis/ma_thesis.conf +++ b/input/ma_thesis/ma_thesis.conf @@ -1,5 +1,7 @@ include "../samples/common/pekko.conf" +pekko.loglevel = "info" + ######### # ATTENTION: Do not change this config file directly but use it as a base for your personal delta config for the # vn_simona scenario! Delta configs can be created by including the config you want to change @@ -10,15 +12,42 @@ include "../samples/common/pekko.conf" ################################################################## # Simulation Parameters ################################################################## -simona.simulationName = "ma_thesis" +simona.simulationName = "szenario-0" ################################################################## # Time Parameters ################################################################## -simona.time.startDateTime = "2016-01-01T05:00:00Z" -simona.time.endDateTime = "2016-01-01T11:00:00Z" +simona.time.startDateTime = "2016-07-24T00:00:00Z" +simona.time.endDateTime = "2016-07-30T00:00:00Z" simona.time.schedulerReadyCheckWindow = 900 +################################################################## +# Grid Configuration +################################################################## + +simona.gridConfig.voltageLimits = [ + { + vMin = 0.95, + vMax = 1.05, + voltLvls = [ + {id = "lv", vNom = "0.4 kV"}, + {id = "mv", vNom = "10 kV"}, + {id = "mv", vNom = "20 kV"}, + {id = "mv", vNom = "30 kV"}, + {id = "hv", vNom = "110 kV"}, + ]} +] + +################################################################## +# Congestion Management Configuration +################################################################## + +simona.congestionManagement.enable = true +simona.congestionManagement.enableTransformerTapping = false +simona.congestionManagement.enableTopologyChanges = false +simona.congestionManagement.useFlexOptions = false + + ################################################################## # Input Parameters ################################################################## @@ -45,7 +74,7 @@ simona.input.weather.datasource = { # Output Parameters ################################################################## simona.output.base.dir = "output/ma_thesis" -simona.output.base.addTimestampToOutputDir = true +simona.output.base.addTimestampToOutputDir = false simona.output.sink.csv { fileFormat = ".csv" @@ -59,26 +88,33 @@ simona.output.grid = { lines = true switches = true transformers2w = true - transformers3w = true + transformers3w = false + congestions = true } simona.output.participant.defaultConfig = { notifier = "default" powerRequestReply = false - simulationResult = true + simulationResult = false } -simona.output.participant.individualConfigs = [] +simona.output.participant.individualConfigs = [ + { + notifier = "fixedfeedin" + powerRequestReply = false + simulationResult = true + }, + { + notifier = "load" + powerRequestReply = false + simulationResult = true + } +] simona.output.thermal = { defaultConfig = { notifier = "default", simulationResult = false } - individualConfigs = [ - { - notifier = "house", - simulationResult = true - } - ] + individualConfigs = [] } ################################################################## @@ -160,25 +196,6 @@ simona.runtime.participant.em = { ################################################################## simona.event.listener = [] -################################################################## -# Grid Configuration -################################################################## - -simona.gridConfig.voltageLimits = [ - { - vMin = 0.97, - vMax = 1.03, - voltLvls = [ - {id = "lv", vNom = "0.4 kV"}, - {id = "mv", vNom = "10 kV"}, - {id = "mv", vNom = "20 kV"}, - {id = "mv", vNom = "30 kV"}, - {id = "hv", vNom = "110 kV"}, - ]}, - {vMin = 0.9, vMax = 1.118, voltLvls = [{id = "EHV", vNom = "220 kV"}]}, - {vMin = 0.9, vMax = 1.05, voltLvls = [{id = "EHV", vNom = "380 kV"}]}, -] - ################################################################## # Power Flow Configuration ################################################################## @@ -189,12 +206,3 @@ simona.powerflow.resolution = "3600s" simona.powerflow.stopOnFailure = true simona.control.transformer = [] - -################################################################## -# Congestion Management Configuration -################################################################## - -simona.congestionManagement.enable = true -simona.congestionManagement.enableTransformerTapping = false -simona.congestionManagement.enableTopologyChanges = false -simona.congestionManagement.useFlexOptions = false From d4c20fb6fca58ea715eedfe6f3915f02876bdbb8 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 18 Jun 2024 21:24:45 +0200 Subject: [PATCH 38/55] Fixing some issues. --- input/ma_thesis/ma_thesis.conf | 21 ++++++++----------- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 8 +++---- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/input/ma_thesis/ma_thesis.conf b/input/ma_thesis/ma_thesis.conf index 4cd425b3dd..0510b4208e 100644 --- a/input/ma_thesis/ma_thesis.conf +++ b/input/ma_thesis/ma_thesis.conf @@ -21,6 +21,15 @@ simona.time.startDateTime = "2016-07-24T00:00:00Z" simona.time.endDateTime = "2016-07-30T00:00:00Z" simona.time.schedulerReadyCheckWindow = 900 +################################################################## +# Congestion Management Configuration +################################################################## + +simona.congestionManagement.enable = true +simona.congestionManagement.enableTransformerTapping = false +simona.congestionManagement.enableTopologyChanges = false +simona.congestionManagement.useFlexOptions = false + ################################################################## # Grid Configuration ################################################################## @@ -31,23 +40,11 @@ simona.gridConfig.voltageLimits = [ vMax = 1.05, voltLvls = [ {id = "lv", vNom = "0.4 kV"}, - {id = "mv", vNom = "10 kV"}, {id = "mv", vNom = "20 kV"}, - {id = "mv", vNom = "30 kV"}, {id = "hv", vNom = "110 kV"}, ]} ] -################################################################## -# Congestion Management Configuration -################################################################## - -simona.congestionManagement.enable = true -simona.congestionManagement.enableTransformerTapping = false -simona.congestionManagement.enableTopologyChanges = false -simona.congestionManagement.useFlexOptions = false - - ################################################################## # Input Parameters ################################################################## diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 6e7caa77ab..daf006b895 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -185,11 +185,9 @@ trait DCMAlgorithm extends CongestionManagementSupport { ) // clean up agent and go back to idle - val powerFlowResults = if (stateData.congestions.any) { - stateData.powerFlowResults.copy(congestionResults = - Seq(stateData.getCongestionResult(constantData.simStartTime)) - ) - } else stateData.powerFlowResults + val powerFlowResults = stateData.powerFlowResults.copy(congestionResults = + Seq(stateData.getCongestionResult(constantData.simStartTime)) + ) GridAgent.gotoIdle( stateData.gridAgentBaseData, From 5c8b7ceded3c0fb32f3f444b263b0ec98704a7d1 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 19 Jun 2024 12:36:37 +0200 Subject: [PATCH 39/55] Fixing some issues. --- .../ie3/simona/agent/grid/CongestionManagementSupport.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 90cdd99593..ee193160c7 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -129,6 +129,10 @@ trait CongestionManagementSupport { ): (Map[TransformerTapping, Int], ComparableQuantity[Dimensionless]) = { val noTapping = (tappings.map(t => t -> 0).toMap, 0.asPu) + if (suggestion.isEquivalentTo(0.asPu)) { + return noTapping + } + // calculate a tap option for each transformer if (tappings.forall(_.hasAutoTap)) { val possibleDeltas = tappings.map(_.possibleDeltas(ConnectorPort.B)) From bbaca15e6ae806f3cde28c79992f464ae460094c Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 20 Jun 2024 21:32:57 +0200 Subject: [PATCH 40/55] Improvements. --- .../grid/CongestionManagementSupport.scala | 2 +- .../ie3/simona/agent/grid/GridAgentData.scala | 31 +++++++++++---- .../model/grid/Transformer3wModel.scala | 38 +++++++++++++------ .../simona/model/grid/TransformerModel.scala | 14 ++++--- .../simona/agent/grid/GridAgentDataSpec.scala | 28 ++++++++++++-- .../agent/grid/GridResultsSupportSpec.scala | 5 ++- .../model/grid/Transformer3wModelSpec.scala | 7 ++++ .../model/grid/TransformerModelSpec.scala | 7 ++-- .../test/common/model/grid/BasicGrid.scala | 6 ++- .../model/grid/GridComponentsMokka.scala | 3 ++ 10 files changed, 105 insertions(+), 36 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index ee193160c7..06b279e9de 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -530,7 +530,7 @@ object CongestionManagementSupport { } } - case class Congestions( + final case class Congestions( voltageCongestions: Boolean, lineCongestions: Boolean, transformerCongestions: Boolean, 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 1797d2ac9f..efc3be7403 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -6,6 +6,7 @@ package edu.ie3.simona.agent.grid +import breeze.numerics.sqrt import edu.ie3.datamodel.graph.SubGridGate import edu.ie3.datamodel.models.input.container.{SubGridContainer, ThermalGrid} import edu.ie3.datamodel.models.result.CongestionResult @@ -23,6 +24,7 @@ import edu.ie3.simona.model.grid.GridModel.GridComponents import edu.ie3.simona.model.grid.{GridModel, RefSystem, VoltageLimits} import edu.ie3.simona.ontology.messages.Activation import org.apache.pekko.actor.typed.ActorRef +import squants.electro.ElectricPotential import java.time.ZonedDateTime import java.util.UUID @@ -580,6 +582,7 @@ object GridAgentData { powerFlowResults, gridModel.gridComponents, gridModel.voltageLimits, + gridModel.mainRefSystem.nominalVoltage, ) // extracting one inferior ref for all inferior grids @@ -629,10 +632,14 @@ object GridAgentData { powerFlowResults: PowerFlowResultEvent, gridComponents: GridComponents, voltageLimits: VoltageLimits, + vNom: ElectricPotential, ): Congestions = { + val nodeRes = + powerFlowResults.nodeResults.map(res => res.getInputModel -> res).toMap + // checking for voltage congestions - val voltageCongestion = powerFlowResults.nodeResults.exists { res => + val voltageCongestion = nodeRes.values.exists { res => !voltageLimits.isInLimits(res.getvMag()) } @@ -656,12 +663,14 @@ object GridAgentData { powerFlowResults.transformer2wResults.exists { res => val transformer = transformer2w(res.getInputModel) - val iA = - res.getiAMag().getValue.doubleValue() > transformer.iNomHv.value - val iB = - res.getiBMag().getValue.doubleValue() > transformer.iNomLv.value + val vMag = nodeRes( + transformer.lvNodeUuid + ).getvMag().getValue.doubleValue() * vNom.toKilovolts - iA || iB + sqrt(3.0) * res + .getiBMag() + .getValue + .doubleValue() * vMag > transformer.sRated.toKilowatts } val transformer3w = gridComponents.transformers3w.map { transformer => @@ -669,7 +678,15 @@ object GridAgentData { }.toMap val transformer3wCongestion = powerFlowResults.transformer3wResults.exists { res => - res.currentMagnitude > transformer3w(res.input).iNom + val transformer = transformer3w(res.input) + + val vMag = nodeRes( + transformer.lvNodeUuid + ).getvMag().getValue.doubleValue() * vNom.toKilovolts + + sqrt( + 3.0 + ) * res.currentMagnitude.value * vMag > transformer.sRated.toKilowatts } Congestions( diff --git a/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala b/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala index 1e383abbd7..c265ee5c50 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala @@ -27,8 +27,9 @@ import edu.ie3.simona.model.grid.Transformer3wPowerFlowCase.{ import edu.ie3.simona.util.SimonaConstants import edu.ie3.util.quantities.PowerSystemUnits._ import edu.ie3.util.scala.OperationInterval +import squants.Power import squants.electro.{Kilovolts, Ohms, Siemens} -import squants.energy.{Megawatts, Watts} +import squants.energy.{Kilowatts, Megawatts, Watts} import tech.units.indriya.AbstractUnit import tech.units.indriya.quantity.Quantities import tech.units.indriya.unit.Units.{OHM, SIEMENS} @@ -69,6 +70,9 @@ import scala.math.BigDecimal.RoundingMode * @param iNom * the nominal current at the port that is defined by the * [[Transformer3wPowerFlowCase]] + * @param sRated + * the rated power at the port that is defined by the + * [[Transformer3wPowerFlowCase]] * @param r * resistance r, real part of the transformer impedance z (referenced to the * nominal impedance of the grid) in p.u. @@ -95,6 +99,7 @@ final case class Transformer3wModel( amount: Int, powerFlowCase: Transformer3wPowerFlowCase, iNom: squants.electro.ElectricCurrent, + sRated: Power, protected val r: squants.Dimensionless, protected val x: squants.Dimensionless, protected val g: squants.Dimensionless, @@ -166,10 +171,6 @@ final case class Transformer3wModel( ) } } - - /** Returns a copy of the [[TransformerTappingModel]] - */ - def tappingModelCopy: TransformerTappingModel = transformerTappingModel.copy() } case object Transformer3wModel extends LazyLogging { @@ -266,25 +267,37 @@ case object Transformer3wModel extends LazyLogging { .setScale(5, RoundingMode.HALF_UP) } - val iNom = powerFlowCase match { + val (iNom, sRated) = powerFlowCase match { case PowerFlowCaseA => - Watts( + val power = Watts( trafo3wType.getsRatedA().to(VOLTAMPERE).getValue.doubleValue() - ) / Math.sqrt(3) / Kilovolts( + ) + + val current = power / Math.sqrt(3) / Kilovolts( trafo3wType.getvRatedA().to(KILOVOLT).getValue.doubleValue() ) + + (current, power) case PowerFlowCaseB => - Watts( + val power = Watts( trafo3wType.getsRatedB().to(VOLTAMPERE).getValue.doubleValue() - ) / Math.sqrt(3) / Kilovolts( + ) + + val current = power / Math.sqrt(3) / Kilovolts( trafo3wType.getvRatedB().to(KILOVOLT).getValue.doubleValue() ) + + (current, power) case PowerFlowCaseC => - Watts( + val power = Watts( trafo3wType.getsRatedC().to(VOLTAMPERE).getValue.doubleValue() - ) / Math.sqrt(3) / Kilovolts( + ) + + val current = power / Math.sqrt(3) / Kilovolts( trafo3wType.getvRatedC().to(KILOVOLT).getValue.doubleValue() ) + + (current, power) } val operationInterval = @@ -307,6 +320,7 @@ case object Transformer3wModel extends LazyLogging { transformer3wInput.getParallelDevices, powerFlowCase, iNom, + sRated, r, x, g, diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerModel.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerModel.scala index 86711ef610..aef6c8abdc 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerModel.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerModel.scala @@ -17,9 +17,9 @@ import edu.ie3.simona.model.SystemComponent import edu.ie3.simona.util.SimonaConstants import edu.ie3.util.quantities.PowerSystemUnits._ import edu.ie3.util.scala.OperationInterval -import squants.Each +import squants.{Each, Power} import squants.electro.{Kilovolts, Ohms, Siemens} -import squants.energy.Watts +import squants.energy.{Kilowatts, Watts} import tech.units.indriya.unit.Units._ import java.time.ZonedDateTime @@ -50,6 +50,8 @@ import scala.math.BigDecimal.RoundingMode * nominal current on the high voltage side of the transformer * @param iNomLv * nominal current on the low voltage side of the transformer + * @param sRated + * the rated power of the transformer * @param r * resistance r, real part of the transformer impedance z (referenced to the * nominal impedance of the grid) in p.u. @@ -74,6 +76,7 @@ final case class TransformerModel( voltRatioNominal: BigDecimal, iNomHv: squants.electro.ElectricCurrent, iNomLv: squants.electro.ElectricCurrent, + sRated: Power, protected val r: squants.Dimensionless, protected val x: squants.Dimensionless, protected val g: squants.Dimensionless, @@ -87,10 +90,6 @@ final case class TransformerModel( with TransformerTapping { private val tapSide = transformerTappingModel.tapSide - - /** Returns a copy of the [[TransformerTappingModel]] - */ - def tappingModelCopy: TransformerTappingModel = transformerTappingModel.copy() } case object TransformerModel { @@ -241,6 +240,9 @@ case object TransformerModel { voltRatioNominal, iNomHv, iNomLv, + Kilowatts( + trafoType.getsRated().to(KILOVOLTAMPERE).getValue.doubleValue() + ), r, x, g, diff --git a/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala index 5e0e9ff58d..c946f9a0fa 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala @@ -19,6 +19,7 @@ import edu.ie3.simona.model.grid.{GridModel, RefSystem, VoltageLimits} import edu.ie3.simona.test.common.model.grid.DbfsTestGrid import edu.ie3.simona.test.common.{ConfigTestData, UnitSpec} import edu.ie3.util.quantities.PowerSystemUnits.PU +import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import squants.electro.Kilovolts import squants.energy.Kilowatts import tech.units.indriya.quantity.Quantities @@ -54,6 +55,7 @@ class GridAgentDataSpec extends UnitSpec with DbfsTestGrid with ConfigTestData { emptyResults, gridModel.gridComponents, gridModel.voltageLimits, + gridModel.mainRefSystem.nominalVoltage, ) shouldBe Congestions( voltageCongestions = false, lineCongestions = false, @@ -102,6 +104,7 @@ class GridAgentDataSpec extends UnitSpec with DbfsTestGrid with ConfigTestData { results, gridModel.gridComponents, gridModel.voltageLimits, + gridModel.mainRefSystem.nominalVoltage, ) shouldBe Congestions( voltageCongestions = true, lineCongestions = false, @@ -158,6 +161,7 @@ class GridAgentDataSpec extends UnitSpec with DbfsTestGrid with ConfigTestData { results, gridModel.gridComponents, gridModel.voltageLimits, + gridModel.mainRefSystem.nominalVoltage, ) shouldBe Congestions( voltageCongestions = false, lineCongestions = true, @@ -166,13 +170,28 @@ class GridAgentDataSpec extends UnitSpec with DbfsTestGrid with ConfigTestData { } "find transformer2w congestions correctly" in { + + val nodeResult1 = new NodeResult( + startTime, + node1.getUuid, + 0.9.asPu, + 0.asDegreeGeom, + ) + + val nodeResult2 = new NodeResult( + startTime, + node2.getUuid, + 1.0.asPu, + 0.asDegreeGeom, + ) + val transformerResult1 = new Transformer2WResult( startTime, transformer1.getUuid, - Quantities.getQuantity(300d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(308d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), Quantities - .getQuantity(1036.3, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + .getQuantity(1064, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), 0, ) @@ -182,13 +201,13 @@ class GridAgentDataSpec extends UnitSpec with DbfsTestGrid with ConfigTestData { transformer2.getUuid, Quantities.getQuantity(310d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), - Quantities.getQuantity(1070d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(1071d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), Quantities.getQuantity(0, StandardUnits.VOLTAGE_ANGLE), 0, ) val results = PowerFlowResultEvent( - Seq.empty, + Seq(nodeResult1, nodeResult2), Seq.empty, Seq.empty, Seq(transformerResult1, transformerResult2), @@ -199,6 +218,7 @@ class GridAgentDataSpec extends UnitSpec with DbfsTestGrid with ConfigTestData { results, gridModel.gridComponents, gridModel.voltageLimits, + gridModel.mainRefSystem.nominalVoltage, ) shouldBe Congestions( voltageCongestions = false, lineCongestions = false, diff --git a/src/test/scala/edu/ie3/simona/agent/grid/GridResultsSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/GridResultsSupportSpec.scala index ad69aabff0..9a9238a872 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/GridResultsSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/GridResultsSupportSpec.scala @@ -44,8 +44,8 @@ import edu.ie3.util.scala.OperationInterval import edu.ie3.util.scala.quantities.{QuantityUtil => ScalaQuantityUtil} import org.scalatest.prop.TableDrivenPropertyChecks import squants.Each -import squants.electro.{Amperes, ElectricCurrent, Volts} -import squants.energy.Kilowatts +import squants.electro.{Amperes, Volts} +import squants.energy.{Kilowatts, Watts} import squants.space.Degrees import tech.units.indriya.quantity.Quantities import tech.units.indriya.unit.Units @@ -445,6 +445,7 @@ class GridResultsSupportSpec 1, PowerFlowCaseA, Amperes(100), + Watts(10), Each(0.1d), Each(0.2d), Each(0.3d), diff --git a/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala b/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala index 0b01933646..ca92f97e40 100644 --- a/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala @@ -17,6 +17,7 @@ import edu.ie3.simona.test.common.UnitSpec import edu.ie3.simona.test.common.input.Transformer3wTestData import edu.ie3.util.quantities.PowerSystemUnits._ import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor4} +import squants.energy.Megawatts import squants.{Amperes, Each} import tech.units.indriya.quantity.Quantities @@ -70,6 +71,7 @@ class Transformer3wModelSpec amount, powerFlowCase, iNom, + sRated, r, x, g, @@ -89,6 +91,7 @@ class Transformer3wModelSpec amount shouldBe transformer3wInput.getParallelDevices powerFlowCase shouldBe PowerFlowCaseA iNom shouldBe Amperes(182.3211376388292) + sRated shouldBe Megawatts(120) r should approximate(Each(1.03878e-3)) x should approximate(Each(166.34349e-3)) g should approximate(Each(1.874312e-6)) @@ -147,6 +150,7 @@ class Transformer3wModelSpec amount, powerFlowCase, iNom, + sRated, r, x, g, @@ -166,6 +170,7 @@ class Transformer3wModelSpec amount shouldBe transformer3wInput.getParallelDevices powerFlowCase shouldBe PowerFlowCaseB iNom shouldBe Amperes(314.9183286488868) + sRated shouldBe Megawatts(60) r should approximate(Each(240.9972299e-6)) x should approximate(Each(24.99307479224e-3)) g should approximate(Each(0d)) @@ -224,6 +229,7 @@ class Transformer3wModelSpec amount, powerFlowCase, iNom, + sRated, r, x, g, @@ -243,6 +249,7 @@ class Transformer3wModelSpec amount shouldBe transformer3wInput.getParallelDevices powerFlowCase shouldBe PowerFlowCaseC iNom shouldBe Amperes(1154.7005383792516) + sRated shouldBe Megawatts(40) r should approximate(Each(3.185595567e-6)) x should approximate(Each(556.0941828e-6)) g should approximate(Each(0d)) 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 56180837ac..67ff272e29 100644 --- a/src/test/scala/edu/ie3/simona/model/grid/TransformerModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/grid/TransformerModelSpec.scala @@ -18,19 +18,18 @@ import edu.ie3.powerflow.model.NodeData.{PresetData, StateData} import edu.ie3.powerflow.model.StartData.WithForcedStartVoltages import edu.ie3.powerflow.model.enums.NodeType import edu.ie3.powerflow.model.{NodeData, PowerFlowResult} -import edu.ie3.simona.test.common.{ConfigTestData, UnitSpec} import edu.ie3.simona.test.common.model.grid.{ TapTestData, TransformerTestData, TransformerTestGrid, } +import edu.ie3.simona.test.common.{ConfigTestData, UnitSpec} import edu.ie3.util.quantities.PowerSystemUnits._ import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor4} import squants.Each -import squants.electro.{Amperes, Kilovolts} +import squants.electro.Amperes import squants.energy.Kilowatts import tech.units.indriya.quantity.Quantities -import tech.units.indriya.unit.Units._ import java.time.ZonedDateTime import java.time.temporal.ChronoUnit @@ -96,6 +95,7 @@ class TransformerModelSpec voltRatioNominal, iNomHv, iNomLv, + sRated, r, x, g, @@ -127,6 +127,7 @@ class TransformerModelSpec voltRatioNominal shouldBe BigDecimal("25") iNomHv should approximate(Amperes(36.373066958946424d)) iNomLv should approximate(Amperes(909.3266739736606d)) + sRated shouldBe Kilowatts(630) r should approximate(Each(7.357e-3)) x should approximate(Each(24.30792e-3)) g should approximate(Each(0.0)) diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/BasicGrid.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/BasicGrid.scala index 959a3074f7..db89b516ab 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/BasicGrid.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/BasicGrid.scala @@ -13,7 +13,8 @@ import edu.ie3.simona.model.grid.{ } import edu.ie3.simona.test.common.DefaultTestData import edu.ie3.util.quantities.PowerSystemUnits._ -import squants.{Amperes, Each} +import squants.energy.Watts +import squants.{Amperes, Each, Power} import tech.units.indriya.ComparableQuantity import tech.units.indriya.quantity.Quantities import tech.units.indriya.unit.Units._ @@ -114,6 +115,8 @@ trait BasicGrid extends FiveLinesWithNodes with DefaultTestData { protected val iNomLv: squants.electro.ElectricCurrent = Amperes(2309.401076758503d) + protected val sRated: Power = Watts(1) + // / transformer protected val transformer2wModel = new TransformerModel( UUID.fromString("a28eb631-2c26-4831-9d05-aa1b3f90b96a"), @@ -126,6 +129,7 @@ trait BasicGrid extends FiveLinesWithNodes with DefaultTestData { BigDecimal("11"), iNomHv, iNomLv, + sRated, transformerRInPu, transformerXInPu, transformerGInPu, diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala index 2b3ff40c71..9a80f5f5d9 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala @@ -13,6 +13,7 @@ import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble import edu.ie3.util.scala.OperationInterval import org.mockito.Mockito.when import org.scalatestplus.mockito.MockitoSugar +import squants.energy.Watts import squants.{Amperes, Each} import tech.units.indriya.ComparableQuantity @@ -79,6 +80,7 @@ trait GridComponentsMokka extends MockitoSugar { voltRatioNominal = BigDecimal(110), iNomHv = Amperes(1), iNomLv = Amperes(10), + sRated = Watts(1), r = Each(1), x = Each(1), g = Each(1), @@ -101,6 +103,7 @@ trait GridComponentsMokka extends MockitoSugar { amount = 1, powerFlowCase = PowerFlowCaseA, iNom = Amperes(1), + sRated = Watts(1), r = Each(1), x = Each(1), g = Each(1), From 77211ff2ad17532bb1fc00db965356fbe38c7067 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 21 Jun 2024 21:06:09 +0200 Subject: [PATCH 41/55] Improvements. --- .../edu/ie3/simona/agent/grid/GridAgentData.scala | 14 +++++++++++--- .../ie3/simona/agent/grid/GridAgentDataSpec.scala | 4 ++++ 2 files changed, 15 insertions(+), 3 deletions(-) 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 efc3be7403..db2673e7df 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -583,6 +583,7 @@ object GridAgentData { gridModel.gridComponents, gridModel.voltageLimits, gridModel.mainRefSystem.nominalVoltage, + gridModel.subnetNo, ) // extracting one inferior ref for all inferior grids @@ -633,15 +634,22 @@ object GridAgentData { gridComponents: GridComponents, voltageLimits: VoltageLimits, vNom: ElectricPotential, + subnetNo: Int, ): Congestions = { val nodeRes = powerFlowResults.nodeResults.map(res => res.getInputModel -> res).toMap + // filter nodes in subnet + val nodesInSubnet = + gridComponents.nodes.filter(_.subnet == subnetNo).map(_.uuid) + // checking for voltage congestions - val voltageCongestion = nodeRes.values.exists { res => - !voltageLimits.isInLimits(res.getvMag()) - } + val voltageCongestion = nodeRes.values + .filter(res => nodesInSubnet.contains(res.getInputModel)) + .exists { res => + !voltageLimits.isInLimits(res.getvMag()) + } // checking for line congestions val linesLimits = gridComponents.lines.map { line => diff --git a/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala index c946f9a0fa..3cdda238c5 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/GridAgentDataSpec.scala @@ -56,6 +56,7 @@ class GridAgentDataSpec extends UnitSpec with DbfsTestGrid with ConfigTestData { gridModel.gridComponents, gridModel.voltageLimits, gridModel.mainRefSystem.nominalVoltage, + gridModel.subnetNo, ) shouldBe Congestions( voltageCongestions = false, lineCongestions = false, @@ -105,6 +106,7 @@ class GridAgentDataSpec extends UnitSpec with DbfsTestGrid with ConfigTestData { gridModel.gridComponents, gridModel.voltageLimits, gridModel.mainRefSystem.nominalVoltage, + gridModel.subnetNo, ) shouldBe Congestions( voltageCongestions = true, lineCongestions = false, @@ -162,6 +164,7 @@ class GridAgentDataSpec extends UnitSpec with DbfsTestGrid with ConfigTestData { gridModel.gridComponents, gridModel.voltageLimits, gridModel.mainRefSystem.nominalVoltage, + gridModel.subnetNo, ) shouldBe Congestions( voltageCongestions = false, lineCongestions = true, @@ -219,6 +222,7 @@ class GridAgentDataSpec extends UnitSpec with DbfsTestGrid with ConfigTestData { gridModel.gridComponents, gridModel.voltageLimits, gridModel.mainRefSystem.nominalVoltage, + gridModel.subnetNo, ) shouldBe Congestions( voltageCongestions = false, lineCongestions = false, From 88e4ec8bb5dc08deed9c2ef773f3828ee35d39cc Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 24 Jun 2024 14:00:36 +0200 Subject: [PATCH 42/55] Updating PSDM. --- build.gradle | 2 +- .../edu/ie3/simona/service/weather/SampleWeatherSource.scala | 2 ++ .../edu/ie3/simona/service/weather/WeatherSourceSpec.scala | 2 ++ .../ie3/simona/service/weather/WeatherSourceWrapperSpec.scala | 4 +--- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 192cee24d6..68ca2f3287 100644 --- a/build.gradle +++ b/build.gradle @@ -75,7 +75,7 @@ dependencies { /* Exclude our own nested dependencies */ exclude group: 'com.github.ie3-institute' } - implementation('com.github.ie3-institute:PowerSystemDataModel:6.0-SNAPSHOT') { + implementation('com.github.ie3-institute:PowerSystemDataModel:5.1.0') { exclude group: 'org.apache.logging.log4j' exclude group: 'org.slf4j' /* Exclude our own nested dependencies */ diff --git a/src/main/scala/edu/ie3/simona/service/weather/SampleWeatherSource.scala b/src/main/scala/edu/ie3/simona/service/weather/SampleWeatherSource.scala index 80e969bf86..8b39aa153d 100644 --- a/src/main/scala/edu/ie3/simona/service/weather/SampleWeatherSource.scala +++ b/src/main/scala/edu/ie3/simona/service/weather/SampleWeatherSource.scala @@ -173,6 +173,8 @@ object SampleWeatherSource { point: Point, comparableQuantity: ComparableQuantity[Length], ): util.List[CoordinateDistance] = Vector.empty[CoordinateDistance].asJava + + override def validate(): Unit = ??? } // these lists contain the hourly weather values for each first of the month of 2011 + january of diff --git a/src/test/scala/edu/ie3/simona/service/weather/WeatherSourceSpec.scala b/src/test/scala/edu/ie3/simona/service/weather/WeatherSourceSpec.scala index eed62feacd..a0b212bd37 100644 --- a/src/test/scala/edu/ie3/simona/service/weather/WeatherSourceSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/weather/WeatherSourceSpec.scala @@ -456,5 +456,7 @@ case object WeatherSourceSpec { distance: ComparableQuantity[Length], ): util.List[CoordinateDistance] = calculateCoordinateDistances(coordinate, 4, coordinateToId.keySet.asJava) + + override def validate(): Unit = ??? } } diff --git a/src/test/scala/edu/ie3/simona/service/weather/WeatherSourceWrapperSpec.scala b/src/test/scala/edu/ie3/simona/service/weather/WeatherSourceWrapperSpec.scala index ab00e32bdf..fcad677f91 100644 --- a/src/test/scala/edu/ie3/simona/service/weather/WeatherSourceWrapperSpec.scala +++ b/src/test/scala/edu/ie3/simona/service/weather/WeatherSourceWrapperSpec.scala @@ -318,9 +318,7 @@ object WeatherSourceWrapperSpec { ), ) - override def getSourceFields[C <: WeatherValue]( - entityClass: Class[C] - ): Optional[util.Set[String]] = + override def getSourceFields: Optional[util.Set[String]] = // only required for validation Optional.empty From 84e68294e849d415f2572484aad37b66547785f0 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 2 Jul 2024 17:04:33 +0200 Subject: [PATCH 43/55] Improving the tap calculation. --- .../grid/CongestionManagementSupport.scala | 171 +++++++++++++----- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 5 +- .../model/grid/TransformerTapping.scala | 12 +- .../CongestionManagementSupportSpec.scala | 116 +++++++++--- .../agent/grid/DCMAlgorithmSupGridSpec.scala | 2 +- 5 files changed, 231 insertions(+), 75 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 06b279e9de..1dd6580043 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -116,18 +116,19 @@ trait CongestionManagementSupport { /** Method for calculating the tap pos changes for all given transformers and * the voltage delta. * - * @param suggestion - * given delta suggestion + * @param range + * given voltage range * @param tappings * a set of all transformers * @return * a map: model to tap pos change and resulting voltage delta */ def calculateTapAndVoltage( - suggestion: ComparableQuantity[Dimensionless], + range: VoltageRange, tappings: Seq[TransformerTapping], ): (Map[TransformerTapping, Int], ComparableQuantity[Dimensionless]) = { val noTapping = (tappings.map(t => t -> 0).toMap, 0.asPu) + val suggestion = range.suggestion if (suggestion.isEquivalentTo(0.asPu)) { return noTapping @@ -135,55 +136,42 @@ trait CongestionManagementSupport { // calculate a tap option for each transformer if (tappings.forall(_.hasAutoTap)) { - val possibleDeltas = tappings.map(_.possibleDeltas(ConnectorPort.B)) + val possibleDeltas = tappings.map( + _.possibleDeltas(range.deltaPlus, range.deltaMinus, ConnectorPort.B) + ) - if (possibleDeltas.exists(_.size < 2)) { - // there is a transformer that cannot be taped - noTapping - } else { + val deltaOption = if (possibleDeltas.exists(_.isEmpty)) { + // there is a transformer that cannot be tapped + None + } else if (possibleDeltas.exists(_.size == 1)) { + // there is a transformer that can only be tapped by one delta + val delta = possibleDeltas.flatten.toSet - // reduce all possible deltas - val reducedOptions = possibleDeltas.map { deltas => - if (deltas.exists(_.isEquivalentTo(suggestion))) { - List(suggestion) - } else { - val minOption = deltas.filter(_.isLessThan(suggestion)).lastOption - val maxOption = deltas.find(_.isGreaterThan(suggestion)) - - // check possible deltas - (minOption, maxOption) match { - case (Some(min), Some(max)) => List(min, max) - case (Some(min), _) => List(min) - case (_, Some(max)) => List(max) - case _ => List() - } - } - } + if (delta.size == 1) { + // all transformer have the same delta + Some(delta.toSeq(0)) + } else None - // filter the possible options - val filteredOptions = reducedOptions.flatten - .groupBy(identity) - .filter(_._2.size == reducedOptions.size) - .flatMap(_._2.headOption) + } else { + // the actual delta that can be used for all transformers + val delta = findCommonDelta(suggestion, possibleDeltas) - // find the best suitable delta - val deltaOption = if (suggestion.isGreaterThan(0.asPu)) { - filteredOptions.minByOption(_.getValue.doubleValue()) - } else { - filteredOptions.maxByOption(_.getValue.doubleValue()) - } + Some(delta) + } - // the actual delta that can be used for all transformers - val delta = deltaOption.getOrElse(0.asPu) + deltaOption match { + case Some(delta) => + val deltas = tappings + .map(model => model -> model.computeDeltas(delta, ConnectorPort.B)) + .toMap - val deltas = tappings - .map(model => model -> model.computeDeltas(delta, ConnectorPort.B)) - .toMap + val taps = deltas.map { case (tapping, (tap, _)) => tapping -> tap } + val actualDelta = deltas.map(_._2._2).toSeq(0) - val taps = deltas.map { case (tapping, (tap, _)) => tapping -> tap } - val actualDelta = deltas.map(_._2._2).toSeq(0) + (taps, actualDelta) - (taps, actualDelta) + case None => + noTapping } } else { // return no tappings if there is at least one transformer that cannot be taped @@ -191,6 +179,85 @@ trait CongestionManagementSupport { } } + /** Method for finding a common delta that can be applied to all transformers. + * @param suggestion + * the given suggestion + * @param possibleDeltas + * the possible deltas for each transformer + * @return + * either a common delta or zero + */ + def findCommonDelta( + suggestion: ComparableQuantity[Dimensionless], + possibleDeltas: Seq[List[ComparableQuantity[Dimensionless]]], + ): ComparableQuantity[Dimensionless] = { + // reduce all possible deltas + val reducedOptions = possibleDeltas.map { deltas => + if (deltas.exists(_.isEquivalentTo(suggestion))) { + List(suggestion) + } else { + val minOption = + deltas.filter(_.isLessThan(suggestion)).sorted.lastOption + val maxOption = deltas.sorted.find(_.isGreaterThan(suggestion)) + + // check possible deltas + (minOption, maxOption) match { + case (Some(min), Some(max)) => List(min, max) + case (Some(min), _) => List(min) + case (_, Some(max)) => List(max) + case _ => List() + } + } + } + + // filter the possible options + val filteredOptions: Set[ComparableQuantity[Dimensionless]] = + reducedOptions.flatten + .groupBy(identity) + .filter(_._2.size == reducedOptions.size) + .keySet + + // find the best suitable delta + filteredOptions.size match { + case 0 => 0.asPu + case 1 => filteredOptions.toSeq(0) + case _ => + if (filteredOptions.exists(_.isEquivalentTo(suggestion))) { + suggestion + } else { + + val minOption = filteredOptions + .filter(_.isLessThan(suggestion)) + .lastOption + .map(_.getValue.doubleValue()) + val maxOption = filteredOptions + .find(_.isGreaterThan(suggestion)) + .map(_.getValue.doubleValue()) + + (minOption, maxOption) match { + case (Some(min), Some(max)) => + val suggestionDouble = suggestion.getValue.doubleValue() + + if ( + Math.abs(suggestionDouble - min) < Math.abs( + suggestionDouble - max + ) + ) { + min.asPu + } else max.asPu + + case (Some(min), _) => + min.asPu + case (_, Some(max)) => + max.asPu + case _ => + 0.asPu + + } + } + } + } + /** Method to calculate the possible range of voltage changes. * * @param powerFlowResultEvent @@ -528,6 +595,22 @@ object CongestionManagementSupport { 0.asPu } } + + def combine( + ranges: Iterable[VoltageRange], + offset: ComparableQuantity[Dimensionless], + ): VoltageRange = { + val minPlus = ranges.minByOption(_.deltaPlus).map(_.deltaPlus) + val maxMinus = ranges.maxByOption(_.deltaMinus).map(_.deltaMinus) + + (minPlus, maxMinus) match { + case (Some(plus), Some(minus)) => + VoltageRange(plus.subtract(offset), minus.subtract(offset)) + case _ => + VoltageRange(0.asPu, 0.asPu) + } + + } } final case class Congestions( diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index daf006b895..5ef1ed15cf 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -334,10 +334,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { if (tappingModels.forall(_.hasAutoTap)) { // the given transformer can be tapped, calculate the new tap pos - val suggestion = - VoltageRange - .combineSuggestions(inferiorRanges) - .subtract(delta) + val suggestion = VoltageRange.combine(inferiorRanges, delta) // calculating the tap changes for all transformers and the resulting voltage delta val (tapChange, deltaV) = calculateTapAndVoltage( diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala index c1da89b8d4..e35dcda361 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala @@ -112,7 +112,9 @@ trait TransformerTapping { * a list of possible voltage deltas */ def possibleDeltas( - tapSide: ConnectorPort = ConnectorPort.A + maxIncrease: ComparableQuantity[Dimensionless], + maxDecrease: ComparableQuantity[Dimensionless], + tapSide: ConnectorPort = ConnectorPort.A, ): List[ComparableQuantity[Dimensionless]] = { if (hasAutoTap) { val plus = tapMax - currentTapPos @@ -121,11 +123,17 @@ trait TransformerTapping { val range = Range.inclusive(minus, plus).map(deltaV.multiply(_).divide(100)).toList - if (tapSide == transformerTappingModel.tapSide) { + val values = if (tapSide == transformerTappingModel.tapSide) { range } else { range.map(_.multiply(-1)).sortBy(_.getValue.doubleValue()) } + + values.filter(value => + value.isLessThanOrEqualTo(maxIncrease) && value.isGreaterThanOrEqualTo( + maxDecrease + ) + ) } else List(0.asPu) } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index c57aec48d1..cbc183f31a 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -102,18 +102,30 @@ class CongestionManagementSupportSpec val tapping = dummyTransformerModel(tappingModel) val cases = Table( - ("suggestion", "expectedTap", "expectedDelta"), - (0.02.asPu, -1, 0.015.asPu), - ((-0.02).asPu, 1, (-0.015).asPu), - (0.031.asPu, -2, 0.03.asPu), - (0.05.asPu, -3, 0.045.asPu), - ((-0.1).asPu, 4, (-0.06).asPu), // max tap increase - (0.1.asPu, -6, 0.09.asPu), // max tap decrease + ("range", "expectedTap", "expectedDelta"), + (VoltageRange(0.025.asPu, 0.015.asPu, 0.02.asPu), -1, 0.015.asPu), + ( + VoltageRange((-0.015).asPu, (-0.025).asPu, (-0.02).asPu), + 1, + (-0.015).asPu, + ), + (VoltageRange(0.041.asPu, 0.021.asPu, 0.031.asPu), -2, 0.03.asPu), + (VoltageRange(0.05.asPu, 0.03.asPu, 0.05.asPu), -3, 0.045.asPu), + ( + VoltageRange(0.asPu, (-0.2).asPu, (-0.1).asPu), + 4, + (-0.06).asPu, + ), // max tap increase + ( + VoltageRange(0.2.asPu, 0.asPu, 0.1.asPu), + -6, + 0.09.asPu, + ), // max tap decrease ) - forAll(cases) { (suggestion, expectedTap, expectedDelta) => + forAll(cases) { (range, expectedTap, expectedDelta) => val (actualTap, actualDelta) = - calculateTapAndVoltage(suggestion, Seq(tapping)) + calculateTapAndVoltage(range, Seq(tapping)) actualTap shouldBe Map(tapping -> expectedTap) actualDelta should equalWithTolerance(expectedDelta) @@ -144,63 +156,63 @@ class CongestionManagementSupportSpec val cases = Table( ("suggestion", "models", "expectedTaps", "expectedDelta"), ( - 0.02.asPu, + VoltageRange(0.1.asPu, (-0.1).asPu, 0.02.asPu), modelCase1, Map(transformer11 -> -1, transformer12 -> -1), 0.015.asPu, ), ( - 0.038.asPu, + VoltageRange(0.1.asPu, (-0.1).asPu, 0.038.asPu), modelCase1, - Map(transformer11 -> -2, transformer12 -> -2), - 0.03.asPu, + Map(transformer11 -> -3, transformer12 -> -3), + 0.045.asPu, ), ( - (-0.06).asPu, + VoltageRange(0.1.asPu, (-0.1).asPu, (-0.06).asPu), modelCase1, Map(transformer11 -> 4, transformer12 -> 4), (-0.06).asPu, ), ( - 0.02.asPu, + VoltageRange(0.1.asPu, (-0.1).asPu, 0.02.asPu), modelCase2, - Map(transformer21 -> -1, transformer22 -> -1), - 0.012.asPu, + Map(transformer21 -> -2, transformer22 -> -2), + 0.024.asPu, ), ( - 0.038.asPu, + VoltageRange(0.1.asPu, (-0.1).asPu, 0.038.asPu), modelCase2, Map(transformer21 -> -3, transformer22 -> -3), 0.036.asPu, ), ( - (-0.06).asPu, + VoltageRange(0.1.asPu, (-0.1).asPu, (-0.06).asPu), modelCase2, Map(transformer21 -> 5, transformer22 -> 5), (-0.06).asPu, ), ( - 0.02.asPu, + VoltageRange(0.1.asPu, (-0.1).asPu, 0.02.asPu), modelCase3, Map(transformer31 -> 0, transformer32 -> 0), 0.asPu, ), ( - 0.038.asPu, + VoltageRange(0.1.asPu, (-0.1).asPu, 0.038.asPu), modelCase3, Map(transformer31 -> 0, transformer32 -> 0), 0.asPu, ), ( - (-0.06).asPu, + VoltageRange(0.1.asPu, (-0.1).asPu, (-0.06).asPu), modelCase3, Map(transformer31 -> 4, transformer32 -> 5), (-0.06).asPu, ), ) - forAll(cases) { (suggestion, models, expectedTaps, expectedDelta) => - val (tapChanges, delta) = calculateTapAndVoltage(suggestion, models) + forAll(cases) { (range, models, expectedTaps, expectedDelta) => + val (tapChanges, delta) = calculateTapAndVoltage(range, models) tapChanges shouldBe expectedTaps delta should equalWithTolerance(expectedDelta) @@ -208,6 +220,62 @@ class CongestionManagementSupportSpec } + "calculate the common delta correctly" in { + + val cases = Table( + ("suggestion", "possibleDeltas", "expected"), + (0.015.asPu, Seq(List(0.03.asPu, 0.015.asPu, 0.asPu)), 0.015.asPu), + ( + 0.012.asPu, + Seq(List(0.03.asPu, 0.02.asPu, 0.01.asPu, 0.asPu)), + 0.01.asPu, + ), + (0.006.asPu, Seq(List(0.03.asPu, 0.015.asPu, 0.asPu)), 0.asPu), + ( + 0.03.asPu, + Seq( + List(0.06.asPu, 0.03.asPu, 0.asPu), + List(0.045.asPu, 0.03.asPu, 0.015.asPu, 0.asPu), + ), + 0.03.asPu, + ), + ( + 0.03.asPu, + Seq(List(0.06.asPu, 0.03.asPu), List(0.03.asPu, 0.015.asPu)), + 0.03.asPu, + ), + ( + 0.035.asPu, + Seq( + List(0.06.asPu, 0.03.asPu, 0.asPu), + List(0.045.asPu, 0.03.asPu, 0.015.asPu, 0.asPu), + ), + 0.03.asPu, + ), + ( + 0.02.asPu, + Seq(List(0.06.asPu, 0.03.asPu), List(0.03.asPu, 0.015.asPu)), + 0.03.asPu, + ), + ( + 0.06.asPu, + Seq(List(0.06.asPu, 0.03.asPu), List(0.03.asPu, 0.015.asPu)), + 0.asPu, + ), + ( + (-0.02).asPu, + Seq(List(0.06.asPu, 0.03.asPu), List(0.03.asPu, 0.015.asPu)), + 0.asPu, + ), + ) + + forAll(cases) { (suggestion, possibleDeltas, expected) => + val delta = findCommonDelta(suggestion, possibleDeltas) + + delta should equalWithTolerance(expected) + } + } + "calculates the possible voltage delta for lines correctly" in { val node1 = nodeModel() val node2 = nodeModel() diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala index 821795a3d1..8f71feb744 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmSupGridSpec.scala @@ -192,7 +192,7 @@ class DCMAlgorithmSupGridSpec sender ! VoltageRangeResponse( hvGrid.ref, ( - VoltageRange(0.04.asPu, 0.01.asPu), + VoltageRange(0.025.asPu, 0.01.asPu), Set(tappingModel, tappingModel2), ), ) From ef30d7a4b7045a333333b86ef7eb911ab2cb122f Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 2 Jul 2024 20:01:34 +0200 Subject: [PATCH 44/55] Fixing two bugs. --- input/ma_thesis/ma_thesis.conf | 13 ++---- .../edu/ie3/simona/PrimaryDataFilter.java | 41 +++++++++++++++++++ .../grid/CongestionManagementSupport.scala | 16 +++++++- .../ie3/simona/agent/grid/DCMAlgorithm.scala | 1 + .../CongestionManagementSupportSpec.scala | 2 + .../model/grid/GridComponentsMokka.scala | 4 +- 6 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/PrimaryDataFilter.java diff --git a/input/ma_thesis/ma_thesis.conf b/input/ma_thesis/ma_thesis.conf index 0510b4208e..ba1a56137b 100644 --- a/input/ma_thesis/ma_thesis.conf +++ b/input/ma_thesis/ma_thesis.conf @@ -12,7 +12,7 @@ pekko.loglevel = "info" ################################################################## # Simulation Parameters ################################################################## -simona.simulationName = "szenario-0" +simona.simulationName = "Szenario-0" ################################################################## # Time Parameters @@ -35,14 +35,9 @@ simona.congestionManagement.useFlexOptions = false ################################################################## simona.gridConfig.voltageLimits = [ - { - vMin = 0.95, - vMax = 1.05, - voltLvls = [ - {id = "lv", vNom = "0.4 kV"}, - {id = "mv", vNom = "20 kV"}, - {id = "hv", vNom = "110 kV"}, - ]} + {vMin = 0.95, vMax = 1.05, voltLvls = [{id = "lv", vNom = "0.4 kV"}]}, + {vMin = 0.95, vMax = 1.05, voltLvls = [{id = "mv", vNom = "20 kV"}]}, + {vMin = 0.95, vMax = 1.05, voltLvls = [{id = "hv", vNom = "110 kV"}]}, ] ################################################################## diff --git a/src/main/scala/edu/ie3/simona/PrimaryDataFilter.java b/src/main/scala/edu/ie3/simona/PrimaryDataFilter.java new file mode 100644 index 0000000000..65111d9e47 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/PrimaryDataFilter.java @@ -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; + +import java.io.*; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class PrimaryDataFilter { + public static void main(String[] args) { + + Path path = Path.of(".", "input", "ma_thesis", "fullGrid", "primary"); + List files = Arrays.stream(Objects.requireNonNull(path.toFile().listFiles())).toList(); + + files.forEach( + file -> { + try { + BufferedReader reader = new BufferedReader(new FileReader(file)); + String headline = reader.readLine(); + List lines = reader.lines().toList(); + + String txt = + lines.stream() + .filter(line -> line.contains("2016-07-") || line.contains("2016-08-01")) + .reduce(headline, (a, b) -> a + "\n" + b); + + Writer writer = new OutputStreamWriter(new FileOutputStream(file)); + writer.write(txt); + writer.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } +} diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 1dd6580043..d1d2dc73a1 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -277,9 +277,15 @@ trait CongestionManagementSupport { inferiorData: Map[ActorRef[ GridAgent.Request ], (VoltageRange, Set[TransformerTapping])], + subnetNo: Int, ): VoltageRange = { + // filter nodes in subnet + val nodesInSubnet = + gridComponents.nodes.filter(_.subnet == subnetNo).map(_.uuid) + // calculate voltage range val nodeResMap = powerFlowResultEvent.nodeResults + .filter(res => nodesInSubnet.contains(res.getInputModel)) .map(res => res.getInputModel -> res.getvMag()) .toMap val minVoltage = nodeResMap @@ -365,7 +371,7 @@ trait CongestionManagementSupport { val nodeRes = nodeResults(line.nodeBUuid).getValue.doubleValue() val current = resB.getValue.doubleValue() val deltaI = line.iNom.value - current - (nodeRes * current) / (current + deltaI) * -1 + (nodeRes * deltaI) / (current + deltaI) * -1 } // deltaV < 0 => tapping down possible @@ -604,8 +610,14 @@ object CongestionManagementSupport { val maxMinus = ranges.maxByOption(_.deltaMinus).map(_.deltaMinus) (minPlus, maxMinus) match { + case (Some(plus), Some(minus)) if offset.isEquivalentTo(0.asPu) => + VoltageRange(plus, minus) case (Some(plus), Some(minus)) => - VoltageRange(plus.subtract(offset), minus.subtract(offset)) + VoltageRange( + plus.subtract(offset), + minus.subtract(offset), + offset.multiply(-1), + ) case _ => VoltageRange(0.asPu, 0.asPu) } diff --git a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala index 5ef1ed15cf..1a9d82e248 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DCMAlgorithm.scala @@ -269,6 +269,7 @@ trait DCMAlgorithm extends CongestionManagementSupport { gridModel.voltageLimits, gridModel.gridComponents, awaitingData.mappedValues, + gridModel.subnetNo, ) ctx.log.warn( diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index cbc183f31a..26e8fe5df4 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -374,6 +374,7 @@ class CongestionManagementSupportSpec VoltageLimits(0.9, 1.1), gridComponents, Map.empty, + subnetNo = 1, ) range.deltaPlus should equalWithTolerance(0.05.asPu) @@ -435,6 +436,7 @@ class CongestionManagementSupportSpec tappingModel )), ), + subnetNo = 1, ) range.deltaPlus should equalWithTolerance(0.04.asPu) diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala index 9a80f5f5d9..66d53cad16 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala @@ -25,10 +25,12 @@ import javax.measure.quantity.Dimensionless trait GridComponentsMokka extends MockitoSugar { protected def nodeModel( - uuid: UUID = UUID.randomUUID() + uuid: UUID = UUID.randomUUID(), + subnetNo: Int = 1, ): NodeModel = { val node = mock[NodeModel] when(node.uuid).thenReturn(uuid) + when(node.subnet).thenReturn(subnetNo) node } From b0b616ffd58054f5593138e022e47a14c7e5b0e0 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Wed, 3 Jul 2024 18:40:31 +0200 Subject: [PATCH 45/55] Saving changes. --- .../edu/ie3/simona/PrimaryDataFilter.java | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/PrimaryDataFilter.java b/src/main/scala/edu/ie3/simona/PrimaryDataFilter.java index 65111d9e47..f362c89cfa 100644 --- a/src/main/scala/edu/ie3/simona/PrimaryDataFilter.java +++ b/src/main/scala/edu/ie3/simona/PrimaryDataFilter.java @@ -11,26 +11,36 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; public class PrimaryDataFilter { public static void main(String[] args) { + Path base = Path.of(".", "input", "ma_thesis", "fullGrid"); - Path path = Path.of(".", "input", "ma_thesis", "fullGrid", "primary"); - List files = Arrays.stream(Objects.requireNonNull(path.toFile().listFiles())).toList(); + Path input = base.resolve("primary"); + Path output = base.resolve( "primary-x1.5"); + List files = Arrays.stream(Objects.requireNonNull(input.toFile().listFiles())).toList(); + + AtomicInteger i = new AtomicInteger(0); + + files.forEach(file -> { + i.addAndGet(1); + System.out.println(i.get()); - files.forEach( - file -> { try { BufferedReader reader = new BufferedReader(new FileReader(file)); String headline = reader.readLine(); List lines = reader.lines().toList(); - String txt = - lines.stream() - .filter(line -> line.contains("2016-07-") || line.contains("2016-08-01")) - .reduce(headline, (a, b) -> a + "\n" + b); - Writer writer = new OutputStreamWriter(new FileOutputStream(file)); + Stream stream = lines.stream() + //.filter(line ->line.contains("2016-07-") || line.contains("2016-08-01")) + .map(line -> multiply(line, 1.5)); + + String txt = stream.reduce(headline, (a, b) -> a + "\n" + b); + + Writer writer = new OutputStreamWriter(new FileOutputStream(output.resolve(file.getName()).toFile())); writer.write(txt); writer.close(); } catch (Exception e) { @@ -38,4 +48,21 @@ public static void main(String[] args) { } }); } + + public static String multiply(String str, double factor) { + String[] arr = str.split(","); + + if (arr.length == 2) { + double modified = Double.parseDouble(arr[0]) * factor; + + return modified +","+arr[1]; + } else if (arr.length == 3) { + double modified1 = Double.parseDouble(arr[0]) * factor; + double modified2 = Double.parseDouble(arr[1]) * factor; + + return modified1 +","+ modified2 +","+arr[2]; + } else { + throw new RuntimeException("Size:"+arr.length); + } + } } From 8a8aab5879ecd7d8c8ba0c227d2b055614f4dc5a Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 4 Jul 2024 11:33:20 +0200 Subject: [PATCH 46/55] Update base config. --- input/ma_thesis/ma_thesis.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/input/ma_thesis/ma_thesis.conf b/input/ma_thesis/ma_thesis.conf index ba1a56137b..9b9c3a2732 100644 --- a/input/ma_thesis/ma_thesis.conf +++ b/input/ma_thesis/ma_thesis.conf @@ -12,13 +12,13 @@ pekko.loglevel = "info" ################################################################## # Simulation Parameters ################################################################## -simona.simulationName = "Szenario-0" +simona.simulationName = "Szenario-1-0" ################################################################## # Time Parameters ################################################################## simona.time.startDateTime = "2016-07-24T00:00:00Z" -simona.time.endDateTime = "2016-07-30T00:00:00Z" +simona.time.endDateTime = "2016-07-31T00:00:00Z" simona.time.schedulerReadyCheckWindow = 900 ################################################################## From deb12262293cf96a84c555f6e7fefd12b9a624ec Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Sat, 6 Jul 2024 10:52:39 +0200 Subject: [PATCH 47/55] Fixing bug in `possibleDeltas()`. --- input/ma_thesis/ma_thesis.conf | 2 +- .../edu/ie3/simona/PrimaryDataFilter.java | 45 ++++++++++--------- .../model/grid/TransformerTapping.scala | 25 +++++++++-- .../CongestionManagementSupportSpec.scala | 22 +++++++++ 4 files changed, 68 insertions(+), 26 deletions(-) diff --git a/input/ma_thesis/ma_thesis.conf b/input/ma_thesis/ma_thesis.conf index 9b9c3a2732..3221c08e0d 100644 --- a/input/ma_thesis/ma_thesis.conf +++ b/input/ma_thesis/ma_thesis.conf @@ -12,7 +12,7 @@ pekko.loglevel = "info" ################################################################## # Simulation Parameters ################################################################## -simona.simulationName = "Szenario-1-0" +simona.simulationName = "Szenario-1" ################################################################## # Time Parameters diff --git a/src/main/scala/edu/ie3/simona/PrimaryDataFilter.java b/src/main/scala/edu/ie3/simona/PrimaryDataFilter.java index f362c89cfa..23d6b11845 100644 --- a/src/main/scala/edu/ie3/simona/PrimaryDataFilter.java +++ b/src/main/scala/edu/ie3/simona/PrimaryDataFilter.java @@ -19,28 +19,31 @@ public static void main(String[] args) { Path base = Path.of(".", "input", "ma_thesis", "fullGrid"); Path input = base.resolve("primary"); - Path output = base.resolve( "primary-x1.5"); + Path output = base.resolve("primary-x1.5"); List files = Arrays.stream(Objects.requireNonNull(input.toFile().listFiles())).toList(); - AtomicInteger i = new AtomicInteger(0); + AtomicInteger i = new AtomicInteger(0); - files.forEach(file -> { - i.addAndGet(1); - System.out.println(i.get()); + files.forEach( + file -> { + i.addAndGet(1); + System.out.println(i.get()); try { BufferedReader reader = new BufferedReader(new FileReader(file)); String headline = reader.readLine(); List lines = reader.lines().toList(); - - Stream stream = lines.stream() - //.filter(line ->line.contains("2016-07-") || line.contains("2016-08-01")) - .map(line -> multiply(line, 1.5)); + Stream stream = + lines.stream() + // .filter(line ->line.contains("2016-07-") || line.contains("2016-08-01")) + .map(line -> multiply(line, 1.5)); String txt = stream.reduce(headline, (a, b) -> a + "\n" + b); - Writer writer = new OutputStreamWriter(new FileOutputStream(output.resolve(file.getName()).toFile())); + Writer writer = + new OutputStreamWriter( + new FileOutputStream(output.resolve(file.getName()).toFile())); writer.write(txt); writer.close(); } catch (Exception e) { @@ -50,19 +53,19 @@ public static void main(String[] args) { } public static String multiply(String str, double factor) { - String[] arr = str.split(","); + String[] arr = str.split(","); - if (arr.length == 2) { - double modified = Double.parseDouble(arr[0]) * factor; + if (arr.length == 2) { + double modified = Double.parseDouble(arr[0]) * factor; - return modified +","+arr[1]; - } else if (arr.length == 3) { - double modified1 = Double.parseDouble(arr[0]) * factor; - double modified2 = Double.parseDouble(arr[1]) * factor; + return modified + "," + arr[1]; + } else if (arr.length == 3) { + double modified1 = Double.parseDouble(arr[0]) * factor; + double modified2 = Double.parseDouble(arr[1]) * factor; - return modified1 +","+ modified2 +","+arr[2]; - } else { - throw new RuntimeException("Size:"+arr.length); - } + return modified1 + "," + modified2 + "," + arr[2]; + } else { + throw new RuntimeException("Size:" + arr.length); + } } } diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala index e35dcda361..9233764c01 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala @@ -129,11 +129,28 @@ trait TransformerTapping { range.map(_.multiply(-1)).sortBy(_.getValue.doubleValue()) } - values.filter(value => - value.isLessThanOrEqualTo(maxIncrease) && value.isGreaterThanOrEqualTo( - maxDecrease + if (maxIncrease.isGreaterThan(maxDecrease)) { + values.filter(value => + value.isLessThanOrEqualTo(maxIncrease) && value + .isGreaterThanOrEqualTo( + maxDecrease + ) ) - ) + } else { + + if (maxDecrease.isGreaterThan(0.asPu)) { + values.filter(value => + value.isLessThanOrEqualTo(maxIncrease) && value + .isGreaterThanOrEqualTo(0.asPu) + ) + } else if (maxIncrease.isLessThan(0.asPu)) { + values.filter(value => + value.isLessThanOrEqualTo(0.asPu) && value.isGreaterThanOrEqualTo( + maxDecrease + ) + ) + } else List(0.asPu) + } } else List(0.asPu) } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index 26e8fe5df4..4638a0f295 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -121,6 +121,16 @@ class CongestionManagementSupportSpec -6, 0.09.asPu, ), // max tap decrease + ( + VoltageRange(0.015.asPu, 0.03.asPu, 0.15.asPu), + -1, + 0.015.asPu, + ), + ( + VoltageRange((-0.04).asPu, (-0.03).asPu, (-0.03).asPu), + 2, + (-0.03).asPu, + ), ) forAll(cases) { (range, expectedTap, expectedDelta) => @@ -209,6 +219,18 @@ class CongestionManagementSupportSpec Map(transformer31 -> 4, transformer32 -> 5), (-0.06).asPu, ), + ( + VoltageRange(0.015.asPu, 0.05.asPu, 0.015.asPu), + modelCase1, + Map(transformer11 -> -1, transformer12 -> -1), + 0.015.asPu, + ), + ( + VoltageRange((-0.05).asPu, (-0.03).asPu, (-0.03).asPu), + modelCase1, + Map(transformer11 -> 2, transformer12 -> 2), + (-0.03).asPu, + ), ) forAll(cases) { (range, models, expectedTaps, expectedDelta) => From 44a1a1034d7ca1fc70ee9b7c2c86d2f186bb5063 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Sat, 6 Jul 2024 16:12:14 +0200 Subject: [PATCH 48/55] Fixing bugs in `updateWithInferiorRanges()`. --- .../grid/CongestionManagementSupport.scala | 26 +++++++++--------- .../model/grid/TransformerTapping.scala | 27 +++++++++---------- .../CongestionManagementSupportSpec.scala | 10 +++---- .../agent/grid/DBFSMockGridAgents.scala | 1 + .../agent/grid/DCMAlgorithmCenGridSpec.scala | 2 +- 5 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index d1d2dc73a1..92b886e40a 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -366,12 +366,12 @@ trait CongestionManagementSupport { val nodeRes = nodeResults(line.nodeAUuid).getValue.doubleValue() val current = resA.getValue.doubleValue() val deltaI = line.iNom.value - current - (nodeRes * deltaI) / (current + deltaI) * -1 + (nodeRes * deltaI) / line.iNom.value * -1 } else { val nodeRes = nodeResults(line.nodeBUuid).getValue.doubleValue() val current = resB.getValue.doubleValue() val deltaI = line.iNom.value - current - (nodeRes * deltaI) / (current + deltaI) * -1 + (nodeRes * deltaI) / line.iNom.value * -1 } // deltaV < 0 => tapping down possible @@ -466,9 +466,9 @@ object CongestionManagementSupport { // TODO: Enhance tests, to tests these changes val tappingRanges = tappings.map { tapping => val currentPos = tapping.currentTapPos - val deltaV = tapping.deltaV - val increase = deltaV.multiply(tapping.tapMax - currentPos) - val decrease = deltaV.multiply(tapping.tapMin - currentPos) + val deltaV = tapping.deltaV.divide(-100) + val increase = deltaV.multiply(tapping.tapMin - currentPos) + val decrease = deltaV.multiply(tapping.tapMax - currentPos) (increase, decrease) }.toSeq @@ -483,14 +483,14 @@ object CongestionManagementSupport { ) } - ( - range.deltaPlus - .add(possibleMinus) - .isLessThanOrEqualTo(infRange.deltaPlus), - range.deltaMinus - .add(possiblePlus) - .isGreaterThanOrEqualTo(infRange.deltaMinus), - ) match { + val increase = range.deltaPlus + .add(possibleMinus) + .isLessThanOrEqualTo(infRange.deltaPlus) + val decrease = range.deltaMinus + .add(possiblePlus) + .isGreaterThanOrEqualTo(infRange.deltaMinus) + + (increase, decrease) match { case (true, true) => (range.deltaPlus, range.deltaMinus) case (true, false) => diff --git a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala index 9233764c01..7b82483660 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/TransformerTapping.scala @@ -129,27 +129,24 @@ trait TransformerTapping { range.map(_.multiply(-1)).sortBy(_.getValue.doubleValue()) } - if (maxIncrease.isGreaterThan(maxDecrease)) { + if (maxIncrease.isLessThan(0.asPu)) { + values.filter(value => + value.isLessThanOrEqualTo(0.asPu) && value.isGreaterThanOrEqualTo( + maxDecrease + ) + ) + } else if (maxDecrease.isGreaterThan(0.asPu)) { values.filter(value => value.isLessThanOrEqualTo(maxIncrease) && value - .isGreaterThanOrEqualTo( - maxDecrease - ) + .isGreaterThanOrEqualTo(0.asPu) ) } else { - - if (maxDecrease.isGreaterThan(0.asPu)) { - values.filter(value => - value.isLessThanOrEqualTo(maxIncrease) && value - .isGreaterThanOrEqualTo(0.asPu) - ) - } else if (maxIncrease.isLessThan(0.asPu)) { - values.filter(value => - value.isLessThanOrEqualTo(0.asPu) && value.isGreaterThanOrEqualTo( + values.filter(value => + value.isLessThanOrEqualTo(maxIncrease) && value + .isGreaterThanOrEqualTo( maxDecrease ) - ) - } else List(0.asPu) + ) } } else List(0.asPu) } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index 4638a0f295..aafacba03d 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -427,7 +427,7 @@ class CongestionManagementSupportSpec currentTapPos = 0, tapMax = 3, tapMin = -3, - deltaV = 0.01.asPu, + deltaV = 1.asPu, ) val powerFlowResult = buildPowerFlowResultEvent( @@ -584,7 +584,7 @@ class CongestionManagementSupportSpec currentTapPos = 0, tapMax = 10, tapMin = -10, - deltaV = 0.01.asPu, + deltaV = 1.asPu, ) val cases = Table( @@ -632,7 +632,7 @@ class CongestionManagementSupportSpec currentTapPos = 7, tapMax = 10, tapMin = -10, - deltaV = 0.01.asPu, + deltaV = 1.asPu, ) val cases = Table( @@ -650,12 +650,12 @@ class CongestionManagementSupportSpec ( VoltageRange(0.asPu, (-0.01).asPu), VoltageRange(0.02.asPu, (-0.03).asPu), - VoltageRange(0.05.asPu, (-0.04).asPu), + VoltageRange(0.03.asPu, (-0.05).asPu), ), ( VoltageRange(0.02.asPu, 0.01.asPu), VoltageRange(0.04.asPu, (-0.01).asPu), - VoltageRange(0.05.asPu, (-0.02).asPu), + VoltageRange(0.05.asPu, (-0.05).asPu), ), ) diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala b/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala index 4cc455cf76..a0fd6d1b74 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSMockGridAgents.scala @@ -177,6 +177,7 @@ trait DBFSMockGridAgents extends UnitSpec { case VoltageRangeResponse(sender, (range, tappings)) => range.deltaPlus shouldBe voltageRange.deltaPlus range.deltaMinus shouldBe voltageRange.deltaMinus + range.suggestion should equalWithTolerance(voltageRange.suggestion) (sender, tappings) } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index e45cd16474..a98fe2f9a4 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -251,7 +251,7 @@ class DCMAlgorithmCenGridSpec // the superior grid should receive a voltage range from the center grid val (voltageDeltaRequest, tappingModels) = superiorGridAgent.expectVoltageRangeResponse( - VoltageRange(0.06.asPu, 0.01.asPu, 0.03.asPu) + VoltageRange(0.04.asPu, 0.01.asPu, 0.025.asPu) ) // the superior grid will update the transformer tappings From 8dbccd74667d6fe2c9c67217d2e74fbae933eec9 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 11 Jul 2024 21:59:10 +0200 Subject: [PATCH 49/55] Small improvement. --- .../grid/CongestionManagementSupport.scala | 61 ++++++++----------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala index 92b886e40a..e3216f233d 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/CongestionManagementSupport.scala @@ -337,46 +337,37 @@ trait CongestionManagementSupport { lineResults: Iterable[LineResult], gridComponents: GridComponents, ): ComparableQuantity[Dimensionless] = { - val lineResMap = lineResults.map(res => res.getInputModel -> res).toMap - val lineMap = gridComponents.lines.map(line => line.uuid -> line).toMap - // calculates the utilisation of each line - val lineUtilisation = lineResMap.map { case (uuid, res) => - val iNom = lineMap(uuid).iNom - val diffA = Amperes(res.getiAMag().getValue.doubleValue()) / iNom - val diffB = Amperes(res.getiBMag().getValue.doubleValue()) / iNom + // calculate the voltage change that ensures there is no line congestion + val voltageChanges = + lineResults.map(res => res.getInputModel -> res).map { case (uuid, res) => + val line = lineMap(uuid) - uuid -> Math.max(diffA, diffB) - } + val (voltage, deltaI) = + if (res.getiAMag().isGreaterThan(res.getiBMag())) { + ( + nodeResults(line.nodeAUuid).getValue.doubleValue(), + line.iNom.value - res.getiAMag().getValue.doubleValue(), + ) + } else { + ( + nodeResults(line.nodeBUuid).getValue.doubleValue(), + line.iNom.value - res.getiBMag().getValue.doubleValue(), + ) + } - // find the maximale utilisation - val maxUtilisation = lineUtilisation - .maxByOption(_._2) - .getOrElse(throw new ResultException(s"No line result found!")) - ._1 - - val line = lineMap(maxUtilisation) - val res = lineResMap(maxUtilisation) - val resA = res.getiAMag() - val resB = res.getiBMag() - - // calculate the voltage change limits - val deltaV = if (resA.isGreaterThan(resB)) { - val nodeRes = nodeResults(line.nodeAUuid).getValue.doubleValue() - val current = resA.getValue.doubleValue() - val deltaI = line.iNom.value - current - (nodeRes * deltaI) / line.iNom.value * -1 - } else { - val nodeRes = nodeResults(line.nodeBUuid).getValue.doubleValue() - val current = resB.getValue.doubleValue() - val deltaI = line.iNom.value - current - (nodeRes * deltaI) / line.iNom.value * -1 - } + (voltage * deltaI) / line.iNom.value * -1 + } + + // determine the actual possible voltage change + val change = voltageChanges.maxOption.getOrElse( + throw new ResultException(s"No line result found!") + ) - // deltaV < 0 => tapping down possible - // deltaV > 0 => tapping up is necessary - deltaV.asPu + // change < 0 => tapping down possible + // change > 0 => tapping up is necessary + change.asPu } } From 4779598a481a9c2ee182f4e05ea98c7719809ed0 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 29 Jul 2024 07:39:07 +0200 Subject: [PATCH 50/55] Saving changes. --- .../edu/ie3/simona/TimeseriesBuilder.scala | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/main/scala/edu/ie3/simona/TimeseriesBuilder.scala diff --git a/src/main/scala/edu/ie3/simona/TimeseriesBuilder.scala b/src/main/scala/edu/ie3/simona/TimeseriesBuilder.scala new file mode 100644 index 0000000000..559ab5dbab --- /dev/null +++ b/src/main/scala/edu/ie3/simona/TimeseriesBuilder.scala @@ -0,0 +1,106 @@ +package edu.ie3.simona + +import edu.ie3.datamodel.io.sink.CsvFileSink +import edu.ie3.datamodel.io.source.TimeSeriesMappingSource.MappingEntry +import edu.ie3.datamodel.io.source.csv.CsvJointGridContainerSource + +import java.nio.file.Path +import java.time.format.DateTimeFormatter +import java.time.{LocalDateTime, ZoneId, ZonedDateTime} +import java.util.UUID +import scala.jdk.CollectionConverters._ + +object TimeseriesBuilder { + def main(args: Array[String]): Unit = { + val dateTime = LocalDateTime.of(2016, 1, 1, 0, 0, 0) + val start = ZonedDateTime.of(dateTime, ZoneId.of("UTC")) + + val loadString = load(start) + val feedInNoCM = noCM(start) + val feedInTwoPerDayString = twoPerDay(start) + val feedInEachHour = eachHour(start) + + println("finished") + } + + def load(start: ZonedDateTime): String = { + val hours = (0 until 366 * 24 + 1).map(start.plusHours(_)) + + val loadBuilder = new StringBuilder() + hours.map(hour => s"2.7,1.31,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n").foreach(loadBuilder.append) + loadBuilder.toString().replaceAll("\\[UTC]", "") + } + + def noCM(start: ZonedDateTime): String = { + val hours = (0 until 366 * 24 + 1).map(start.plusHours(_)) + val stringBuilder = new StringBuilder() + hours.map(hour => s"0.0,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n").foreach(stringBuilder.append) + stringBuilder.toString().replaceAll("\\[UTC]", "") + } + + def twoPerDay(start: ZonedDateTime): String = { + val stringBuilder = new StringBuilder() + + (0 until 366).map(start.plusDays(_)).foreach { day => + stringBuilder.append(s"0.0,${day.format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"0.0,${day.plusHours(1).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"0.0,${day.plusHours(2).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"0.0,${day.plusHours(3).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"0.0,${day.plusHours(4).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"0.0,${day.plusHours(5).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"-20.0,${day.plusHours(6).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"-20.0,${day.plusHours(7).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"-20.0,${day.plusHours(8).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"-20.0,${day.plusHours(9).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"-20.0,${day.plusHours(10).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"-20.0,${day.plusHours(11).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"-20.0,${day.plusHours(12).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"-20.0,${day.plusHours(13).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"-20.0,${day.plusHours(14).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"-20.0,${day.plusHours(15).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"-20.0,${day.plusHours(16).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"-20.0,${day.plusHours(17).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"0.0,${day.plusHours(18).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"0.0,${day.plusHours(19).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"0.0,${day.plusHours(20).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"0.0,${day.plusHours(21).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"0.0,${day.plusHours(22).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append(s"0.0,${day.plusHours(23).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + } + + stringBuilder.append(s"0.0,${start.plusYears(1).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + + stringBuilder.toString().replaceAll("\\[UTC]", "") + } + + def eachHour(start: ZonedDateTime): String = { + val hours = (0 until 366 * 24 + 1).map(start.plusHours(_)) + + val feedInBuilder = new StringBuilder() + hours.zipWithIndex.map { case (hour, i) => + if (i % 2 == 0) { + s"-20.0,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n" + } else s"5.0,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n" + }.foreach(feedInBuilder.append) + + feedInBuilder.toString().replaceAll("\\[UTC]", "") + } + + + def mapping: Unit = { + val feedInSeries = UUID.fromString("0ac0477b-cee6-4026-91c9-864d1c6871c9") + val loadSeries = UUID.fromString("0c24f963-67b0-40c8-997e-0091fcfae00a") + + + val jointGridContainer = CsvJointGridContainerSource.read("grid", ",", Path.of(".", "input", "ma_thesis", "fullGrid"), false) + val participants = jointGridContainer.getSystemParticipants + + val loads = participants.getLoads.asScala.map(_.getUuid) + val feedIns = participants.getFixedFeedIns.asScala.map(_.getUuid) + + val mappingEntries = loads.map(load => new MappingEntry(load, loadSeries)) ++ feedIns.map(feedIn => new MappingEntry(feedIn, feedInSeries)) + + val sink = new CsvFileSink(Path.of(".", "input", "ma_thesis", "fullGrid", "primary-x")) + sink.persistAll(mappingEntries.asJava) + } +} From 70bdc5468a7a0afa534606a259a41ac5d3828f36 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 12 Aug 2024 10:18:37 +0200 Subject: [PATCH 51/55] Adding helper class. --- .../scala/edu/ie3/simona/ResultFilter.scala | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/main/scala/edu/ie3/simona/ResultFilter.scala diff --git a/src/main/scala/edu/ie3/simona/ResultFilter.scala b/src/main/scala/edu/ie3/simona/ResultFilter.scala new file mode 100644 index 0000000000..ebb2f92257 --- /dev/null +++ b/src/main/scala/edu/ie3/simona/ResultFilter.scala @@ -0,0 +1,120 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + +package edu.ie3.simona + +import edu.ie3.datamodel.io.naming.FileNamingStrategy +import edu.ie3.datamodel.io.source.ResultEntitySource +import edu.ie3.datamodel.io.source.csv.{ + CsvDataSource, + CsvJointGridContainerSource, +} +import edu.ie3.datamodel.models.result.NodeResult +import edu.ie3.datamodel.models.result.connector.LineResult + +import java.io.{File, FileOutputStream, OutputStreamWriter} +import java.nio.file.Path +import java.time.ZonedDateTime +import java.util.UUID +import scala.jdk.CollectionConverters.CollectionHasAsScala + +object ResultFilter { + def main(args: Array[String]): Unit = { + val grid = Path.of("input", "grid") + val jointGrid = CsvJointGridContainerSource.read("grid", ";", grid, false) + val results = Path.of("output", "grid") + + val source = new CsvDataSource(",", results, new FileNamingStrategy()) + val resultSource = new ResultEntitySource(source) + + val nodeSubnetRes = results.resolve("subnetVoltages.csv").toFile + write("time,subnet,v_max,v_min", nodeSubnetRes, append = false) + + val lineSubnetRes = results.resolve("subnetLineUtilisation.csv").toFile + write("time,subnet,max", lineSubnetRes, append = false) + + val nodeToSubnet = jointGrid.getRawGrid.getNodes.asScala + .map(n => n.getUuid -> n.getSubnet) + .toMap + + val lineToSubnet = jointGrid.getRawGrid.getLines.asScala.map { l => + l.getUuid -> l.getNodeA.getSubnet + }.toMap + + subnetVoltages(nodeToSubnet, resultSource, nodeSubnetRes) + subnetLineUtilisation(lineToSubnet, resultSource, lineSubnetRes) + } + + private def subnetVoltages( + nodeToSubnet: Map[UUID, Int], + resultSource: ResultEntitySource, + file: File, + ): Unit = { + val results: Map[ZonedDateTime, Iterable[NodeResult]] = + resultSource.getNodeResults.asScala + .map(res => res.getTime -> res) + .groupBy(_._1) + .map { case (time, tuples) => time -> tuples.map(_._2) } + + results.foreach { case (time, results) => + println(time) + + results + .map(res => + nodeToSubnet(res.getInputModel) -> res + .getvMag() + .getValue + .doubleValue() + ) + .groupBy(_._1) + .map { case (i, tuples) => + val set = tuples.map(_._2).toSet + val str = s"$time,$i,${set.max},${set.min}" + + write(str, file) + } + } + } + + private def subnetLineUtilisation( + lineToSubnet: Map[UUID, Int], + resultSource: ResultEntitySource, + file: File, + ): Unit = { + val results: Map[ZonedDateTime, Iterable[LineResult]] = + resultSource.getLineResults.asScala + .map(res => res.getTime -> res) + .groupBy(_._1) + .map { case (time, tuples) => time -> tuples.map(_._2) } + + results.foreach { case (time, results) => + println(time) + + results + .map(res => + lineToSubnet(res.getInputModel) -> Math.max( + res.getiAMag().getValue.doubleValue(), + res.getiBMag().getValue.doubleValue(), + ) + ) + .groupBy(_._1) + .map { case (i, tuples) => + val set = tuples.map(_._2).toSet + val str = s"$time,$i,${set.max}" + + write(str, file) + } + } + } + + def write(str: String, subnetVoltages: File, append: Boolean = true): Unit = { + val writer = new OutputStreamWriter( + new FileOutputStream(subnetVoltages, append) + ) + writer.write(str) + writer.close() + } +} From 01b43a0de7b8a7e72bb79e8867e5748104f6f1a3 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 12 Aug 2024 11:34:35 +0200 Subject: [PATCH 52/55] Adding helper class. --- .../ie3/simona/ExtendedCongestionResult.java | 56 +++++++++++++++++++ .../ie3/simona/agent/grid/GridAgentData.scala | 9 ++- .../event/listener/ResultEventListener.scala | 39 +++++++++---- 3 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 src/main/scala/edu/ie3/simona/ExtendedCongestionResult.java diff --git a/src/main/scala/edu/ie3/simona/ExtendedCongestionResult.java b/src/main/scala/edu/ie3/simona/ExtendedCongestionResult.java new file mode 100644 index 0000000000..0956155d8a --- /dev/null +++ b/src/main/scala/edu/ie3/simona/ExtendedCongestionResult.java @@ -0,0 +1,56 @@ +package edu.ie3.simona; + +import edu.ie3.datamodel.models.result.CongestionResult; +import tech.units.indriya.ComparableQuantity; + +import javax.measure.quantity.Dimensionless; +import java.time.ZonedDateTime; + +public class ExtendedCongestionResult extends CongestionResult { + private final double vMaxVal; + private final double vMinVal; + private final double getLineMax; + + + public ExtendedCongestionResult( + ZonedDateTime time, + int subgrid, + ComparableQuantity vMin, + ComparableQuantity vMax, + boolean voltage, + boolean line, + boolean transformer, + double vMaxVal, + double vMinVal, + double getLineMax + ) { + super(time, subgrid, vMin, vMax, voltage, line, transformer); + this.vMaxVal = vMaxVal; + this.vMinVal = vMinVal; + this.getLineMax = getLineMax; + } + + public double getvMaxVal() { + return vMaxVal; + } + + public double getvMinVal() { + return vMinVal; + } + + public double getLineMax() { + return getLineMax; + } + + public CongestionResult to() { + return new CongestionResult( + getTime(), + getSubgrid(), + getVMin(), + getVMax(), + getVoltage(), + getLine(), + getTransformer() + ); + } +} 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 db2673e7df..07cee1ba59 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -12,6 +12,7 @@ import edu.ie3.datamodel.models.input.container.{SubGridContainer, ThermalGrid} import edu.ie3.datamodel.models.result.CongestionResult import edu.ie3.powerflow.model.PowerFlowResult import edu.ie3.powerflow.model.PowerFlowResult.SuccessFullPowerFlowResult.ValidNewtonRaphsonPFResult +import edu.ie3.simona.ExtendedCongestionResult import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.CongestionManagementSupport.Congestions import edu.ie3.simona.agent.grid.GridAgentMessages._ @@ -528,7 +529,10 @@ object GridAgentData { def getCongestionResult(startTime: ZonedDateTime): CongestionResult = { val gridModel = gridAgentBaseData.gridEnv.gridModel - new CongestionResult( + val node = powerFlowResults.nodeResults.map(n => n.getvMag().getValue.doubleValue()) + val line = powerFlowResults.lineResults.map(l => Math.max(l.getiAMag().getValue.doubleValue(), l.getiBMag().getValue.doubleValue())) + + new ExtendedCongestionResult( startTime.plusSeconds(currentTick), gridModel.subnetNo, gridModel.voltageLimits.vMin, @@ -536,6 +540,9 @@ object GridAgentData { congestions.voltageCongestions, congestions.lineCongestions, congestions.transformerCongestions, + node.maxOption.getOrElse(0), + node.minOption.getOrElse(0), + line.maxOption.getOrElse(0) ) } diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index d460f63b8c..00d0fe02b1 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -6,25 +6,21 @@ package edu.ie3.simona.event.listener +import edu.ie3.datamodel.io.csv.BufferedCsvWriter import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.{Behavior, PostStop} import edu.ie3.datamodel.io.processor.result.ResultEntityProcessor import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} +import edu.ie3.simona.ExtendedCongestionResult import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult -import edu.ie3.simona.event.ResultEvent.{ - FlexOptionsResultEvent, - ParticipantResultEvent, - PowerFlowResultEvent, - ThermalResultEvent, -} -import edu.ie3.simona.exceptions.{ - FileHierarchyException, - ProcessResultEventException, -} +import edu.ie3.simona.event.ResultEvent.{FlexOptionsResultEvent, ParticipantResultEvent, PowerFlowResultEvent, ThermalResultEvent} +import edu.ie3.simona.exceptions.{FileHierarchyException, ProcessResultEventException} import edu.ie3.simona.io.result._ import edu.ie3.simona.util.ResultFileHierarchy import org.slf4j.Logger +import java.io.BufferedWriter +import java.nio.file.Path import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration.DurationInt import scala.concurrent.{Await, Future} @@ -32,6 +28,8 @@ import scala.util.{Failure, Success, Try} object ResultEventListener extends Transformer3wResultSupport { + private var subnetResultWriter: BufferedWriter = _ + trait Request private final case class SinkResponse( @@ -69,6 +67,15 @@ object ResultEventListener extends Transformer3wResultSupport { ): Iterable[Future[(Class[_], ResultEntitySink)]] = { resultFileHierarchy.resultSinkType match { case _: ResultSinkType.Csv => + subnetResultWriter = new BufferedCsvWriter( + Path.of(resultFileHierarchy.rawOutputDataDir, "subnet.csv"), + Array("time", "subnet", "v_max", "v_min", "line_max"), + ",", + true + ) + subnetResultWriter.write("time,subnet,v_max,v_min,line_max\n") + + resultFileHierarchy.resultEntitiesToConsider .map(resultClass => { resultFileHierarchy.rawOutputDataFilePaths @@ -294,8 +301,16 @@ object ResultEventListener extends Transformer3wResultSupport { congestionResults, ), ) => + + val cRes = congestionResults.map { + case extended: ExtendedCongestionResult => + subnetResultWriter.write(s"${extended.getTime},${extended.getSubgrid},${extended.getvMaxVal},${extended.getvMinVal},${extended.getLineMax}\n") + extended.to() + case c => c + } + val updatedBaseData = - (nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ transformer3wResults ++ congestionResults) + (nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ transformer3wResults ++ cRes) .foldLeft(baseData) { case (currentBaseData, resultEntity: ResultEntity) => handleResult(resultEntity, currentBaseData, ctx.log) @@ -331,6 +346,8 @@ object ResultEventListener extends Transformer3wResultSupport { .mkString("\n\t\t"), ) + subnetResultWriter.close() + // close sinks concurrently to speed up closing (closing calls might be blocking) Await.ready( Future.sequence( From fe36cfa196d32fc355883933138eb95e4489092a Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 29 Aug 2024 18:39:36 +0200 Subject: [PATCH 53/55] Adapting some parts. --- .../ie3/simona/ExtendedCongestionResult.java | 94 ++++++----- .../edu/ie3/simona/TimeseriesBuilder.scala | 146 +++++++++++++----- .../ie3/simona/agent/grid/GridAgentData.scala | 13 +- .../event/listener/ResultEventListener.scala | 20 ++- .../edu/ie3/simona/model/grid/GridModel.scala | 18 ++- .../DBFSAlgorithmFailedPowerFlowSpec.scala | 1 + .../edu/ie3/simona/model/grid/GridSpec.scala | 5 +- .../model/grid/BasicGridWithSwitches.scala | 1 + 8 files changed, 199 insertions(+), 99 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/ExtendedCongestionResult.java b/src/main/scala/edu/ie3/simona/ExtendedCongestionResult.java index 0956155d8a..55039cdfb3 100644 --- a/src/main/scala/edu/ie3/simona/ExtendedCongestionResult.java +++ b/src/main/scala/edu/ie3/simona/ExtendedCongestionResult.java @@ -1,56 +1,52 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + package edu.ie3.simona; import edu.ie3.datamodel.models.result.CongestionResult; -import tech.units.indriya.ComparableQuantity; - -import javax.measure.quantity.Dimensionless; import java.time.ZonedDateTime; +import javax.measure.quantity.Dimensionless; +import tech.units.indriya.ComparableQuantity; public class ExtendedCongestionResult extends CongestionResult { - private final double vMaxVal; - private final double vMinVal; - private final double getLineMax; - - - public ExtendedCongestionResult( - ZonedDateTime time, - int subgrid, - ComparableQuantity vMin, - ComparableQuantity vMax, - boolean voltage, - boolean line, - boolean transformer, - double vMaxVal, - double vMinVal, - double getLineMax - ) { - super(time, subgrid, vMin, vMax, voltage, line, transformer); - this.vMaxVal = vMaxVal; - this.vMinVal = vMinVal; - this.getLineMax = getLineMax; - } - - public double getvMaxVal() { - return vMaxVal; - } - - public double getvMinVal() { - return vMinVal; - } - - public double getLineMax() { - return getLineMax; - } - - public CongestionResult to() { - return new CongestionResult( - getTime(), - getSubgrid(), - getVMin(), - getVMax(), - getVoltage(), - getLine(), - getTransformer() - ); - } + private final double vMaxVal; + private final double vMinVal; + private final double getLineMax; + + public ExtendedCongestionResult( + ZonedDateTime time, + int subgrid, + ComparableQuantity vMin, + ComparableQuantity vMax, + boolean voltage, + boolean line, + boolean transformer, + double vMaxVal, + double vMinVal, + double getLineMax) { + super(time, subgrid, vMin, vMax, voltage, line, transformer); + this.vMaxVal = vMaxVal; + this.vMinVal = vMinVal; + this.getLineMax = getLineMax; + } + + public double getvMaxVal() { + return vMaxVal; + } + + public double getvMinVal() { + return vMinVal; + } + + public double getLineMax() { + return getLineMax; + } + + public CongestionResult to() { + return new CongestionResult( + getTime(), getSubgrid(), getVMin(), getVMax(), getVoltage(), getLine(), getTransformer()); + } } diff --git a/src/main/scala/edu/ie3/simona/TimeseriesBuilder.scala b/src/main/scala/edu/ie3/simona/TimeseriesBuilder.scala index 559ab5dbab..784cb89912 100644 --- a/src/main/scala/edu/ie3/simona/TimeseriesBuilder.scala +++ b/src/main/scala/edu/ie3/simona/TimeseriesBuilder.scala @@ -1,3 +1,9 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ + package edu.ie3.simona import edu.ie3.datamodel.io.sink.CsvFileSink @@ -27,14 +33,20 @@ object TimeseriesBuilder { val hours = (0 until 366 * 24 + 1).map(start.plusHours(_)) val loadBuilder = new StringBuilder() - hours.map(hour => s"2.7,1.31,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n").foreach(loadBuilder.append) + hours + .map(hour => + s"2.7,1.31,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + .foreach(loadBuilder.append) loadBuilder.toString().replaceAll("\\[UTC]", "") } def noCM(start: ZonedDateTime): String = { val hours = (0 until 366 * 24 + 1).map(start.plusHours(_)) val stringBuilder = new StringBuilder() - hours.map(hour => s"0.0,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n").foreach(stringBuilder.append) + hours + .map(hour => s"0.0,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n") + .foreach(stringBuilder.append) stringBuilder.toString().replaceAll("\\[UTC]", "") } @@ -42,33 +54,82 @@ object TimeseriesBuilder { val stringBuilder = new StringBuilder() (0 until 366).map(start.plusDays(_)).foreach { day => - stringBuilder.append(s"0.0,${day.format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"0.0,${day.plusHours(1).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"0.0,${day.plusHours(2).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"0.0,${day.plusHours(3).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"0.0,${day.plusHours(4).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"0.0,${day.plusHours(5).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"-20.0,${day.plusHours(6).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"-20.0,${day.plusHours(7).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"-20.0,${day.plusHours(8).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"-20.0,${day.plusHours(9).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"-20.0,${day.plusHours(10).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"-20.0,${day.plusHours(11).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"-20.0,${day.plusHours(12).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"-20.0,${day.plusHours(13).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"-20.0,${day.plusHours(14).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"-20.0,${day.plusHours(15).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"-20.0,${day.plusHours(16).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"-20.0,${day.plusHours(17).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"0.0,${day.plusHours(18).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"0.0,${day.plusHours(19).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"0.0,${day.plusHours(20).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"0.0,${day.plusHours(21).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"0.0,${day.plusHours(22).format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append(s"0.0,${day.plusHours(23).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder + .append(s"0.0,${day.format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append( + s"0.0,${day.plusHours(1).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"0.0,${day.plusHours(2).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"0.0,${day.plusHours(3).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"0.0,${day.plusHours(4).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"0.0,${day.plusHours(5).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"-20.0,${day.plusHours(6).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"-20.0,${day.plusHours(7).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"-20.0,${day.plusHours(8).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"-20.0,${day.plusHours(9).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"-20.0,${day.plusHours(10).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"-20.0,${day.plusHours(11).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"-20.0,${day.plusHours(12).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"-20.0,${day.plusHours(13).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"-20.0,${day.plusHours(14).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"-20.0,${day.plusHours(15).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"-20.0,${day.plusHours(16).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"-20.0,${day.plusHours(17).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"0.0,${day.plusHours(18).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"0.0,${day.plusHours(19).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"0.0,${day.plusHours(20).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"0.0,${day.plusHours(21).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"0.0,${day.plusHours(22).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) + stringBuilder.append( + s"0.0,${day.plusHours(23).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) } - stringBuilder.append(s"0.0,${start.plusYears(1).format(DateTimeFormatter.ISO_DATE_TIME)}\n") + stringBuilder.append( + s"0.0,${start.plusYears(1).format(DateTimeFormatter.ISO_DATE_TIME)}\n" + ) stringBuilder.toString().replaceAll("\\[UTC]", "") } @@ -77,30 +138,39 @@ object TimeseriesBuilder { val hours = (0 until 366 * 24 + 1).map(start.plusHours(_)) val feedInBuilder = new StringBuilder() - hours.zipWithIndex.map { case (hour, i) => - if (i % 2 == 0) { - s"-20.0,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n" - } else s"5.0,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n" - }.foreach(feedInBuilder.append) + hours.zipWithIndex + .map { case (hour, i) => + if (i % 2 == 0) { + s"-20.0,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n" + } else s"5.0,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n" + } + .foreach(feedInBuilder.append) feedInBuilder.toString().replaceAll("\\[UTC]", "") } - - def mapping: Unit = { + def mapping(): Unit = { val feedInSeries = UUID.fromString("0ac0477b-cee6-4026-91c9-864d1c6871c9") val loadSeries = UUID.fromString("0c24f963-67b0-40c8-997e-0091fcfae00a") - - val jointGridContainer = CsvJointGridContainerSource.read("grid", ",", Path.of(".", "input", "ma_thesis", "fullGrid"), false) + val jointGridContainer = CsvJointGridContainerSource.read( + "grid", + ",", + Path.of(".", "input", "ma_thesis", "fullGrid"), + false, + ) val participants = jointGridContainer.getSystemParticipants val loads = participants.getLoads.asScala.map(_.getUuid) val feedIns = participants.getFixedFeedIns.asScala.map(_.getUuid) - val mappingEntries = loads.map(load => new MappingEntry(load, loadSeries)) ++ feedIns.map(feedIn => new MappingEntry(feedIn, feedInSeries)) + val mappingEntries = loads.map(load => + new MappingEntry(load, loadSeries) + ) ++ feedIns.map(feedIn => new MappingEntry(feedIn, feedInSeries)) - val sink = new CsvFileSink(Path.of(".", "input", "ma_thesis", "fullGrid", "primary-x")) + val sink = new CsvFileSink( + Path.of(".", "input", "ma_thesis", "fullGrid", "primary-x") + ) sink.persistAll(mappingEntries.asJava) } } 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 07cee1ba59..576344dec0 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -529,8 +529,15 @@ object GridAgentData { def getCongestionResult(startTime: ZonedDateTime): CongestionResult = { val gridModel = gridAgentBaseData.gridEnv.gridModel - val node = powerFlowResults.nodeResults.map(n => n.getvMag().getValue.doubleValue()) - val line = powerFlowResults.lineResults.map(l => Math.max(l.getiAMag().getValue.doubleValue(), l.getiBMag().getValue.doubleValue())) + val node = powerFlowResults.nodeResults.map(n => + n.getvMag().getValue.doubleValue() + ) + val line = powerFlowResults.lineResults.map(l => + Math.max( + l.getiAMag().getValue.doubleValue(), + l.getiBMag().getValue.doubleValue(), + ) + ) new ExtendedCongestionResult( startTime.plusSeconds(currentTick), @@ -542,7 +549,7 @@ object GridAgentData { congestions.transformerCongestions, node.maxOption.getOrElse(0), node.minOption.getOrElse(0), - line.maxOption.getOrElse(0) + line.maxOption.getOrElse(0), ) } diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index 00d0fe02b1..8b1d737f3e 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -13,8 +13,16 @@ import edu.ie3.datamodel.io.processor.result.ResultEntityProcessor import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} import edu.ie3.simona.ExtendedCongestionResult import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult -import edu.ie3.simona.event.ResultEvent.{FlexOptionsResultEvent, ParticipantResultEvent, PowerFlowResultEvent, ThermalResultEvent} -import edu.ie3.simona.exceptions.{FileHierarchyException, ProcessResultEventException} +import edu.ie3.simona.event.ResultEvent.{ + FlexOptionsResultEvent, + ParticipantResultEvent, + PowerFlowResultEvent, + ThermalResultEvent, +} +import edu.ie3.simona.exceptions.{ + FileHierarchyException, + ProcessResultEventException, +} import edu.ie3.simona.io.result._ import edu.ie3.simona.util.ResultFileHierarchy import org.slf4j.Logger @@ -71,11 +79,10 @@ object ResultEventListener extends Transformer3wResultSupport { Path.of(resultFileHierarchy.rawOutputDataDir, "subnet.csv"), Array("time", "subnet", "v_max", "v_min", "line_max"), ",", - true + true, ) subnetResultWriter.write("time,subnet,v_max,v_min,line_max\n") - resultFileHierarchy.resultEntitiesToConsider .map(resultClass => { resultFileHierarchy.rawOutputDataFilePaths @@ -301,10 +308,11 @@ object ResultEventListener extends Transformer3wResultSupport { congestionResults, ), ) => - val cRes = congestionResults.map { case extended: ExtendedCongestionResult => - subnetResultWriter.write(s"${extended.getTime},${extended.getSubgrid},${extended.getvMaxVal},${extended.getvMinVal},${extended.getLineMax}\n") + subnetResultWriter.write( + s"${extended.getTime},${extended.getSubgrid},${extended.getvMaxVal},${extended.getvMinVal},${extended.getLineMax}\n" + ) extended.to() case c => c } 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 f9fdccb48c..e72350f216 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/GridModel.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/GridModel.scala @@ -15,6 +15,7 @@ import edu.ie3.simona.config.SimonaConfig import edu.ie3.simona.exceptions.GridInconsistencyException import edu.ie3.simona.exceptions.agent.GridAgentInitializationException import edu.ie3.simona.model.SystemComponent +import edu.ie3.simona.model.control.{GridControls, TransformerControlGroupModel} import edu.ie3.simona.model.grid.GridModel.GridComponents import edu.ie3.simona.model.grid.Transformer3wPowerFlowCase.{ PowerFlowCaseA, @@ -39,6 +40,7 @@ final case class GridModel( mainRefSystem: RefSystem, gridComponents: GridComponents, voltageLimits: VoltageLimits, + gridControls: GridControls, ) { // init nodeUuidToIndexMap @@ -451,7 +453,6 @@ object GridModel { * @param maybeControlConfig * Config of ControlGroup */ - @deprecated private def validateControlGroups( subGridContainer: SubGridContainer, maybeControlConfig: Option[SimonaConfig.Simona.Control], @@ -589,11 +590,22 @@ object GridModel { switches, ) + /* Build transformer control groups */ + val transformerControlGroups = simonaConfig.simona.control + .map { controlConfig => + TransformerControlGroupModel.buildControlGroups( + subGridContainer.getRawGrid.getMeasurementUnits.asScala.toSet, + controlConfig.transformer, + ) + } + .getOrElse(Set.empty) + val gridModel = GridModel( subGridContainer.getSubnet, refSystem, gridComponents, voltageLimits, + GridControls(transformerControlGroups), ) /** Check and validates the grid. Especially the consistency of the grid @@ -605,6 +617,10 @@ object GridModel { // validate validateConsistency(gridModel) validateConnectivity(gridModel) + validateControlGroups( + subGridContainer, + simonaConfig.simona.control, + ) // return gridModel 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 267ecb5dd6..a6ad983dc8 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DBFSAlgorithmFailedPowerFlowSpec.scala @@ -313,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/model/grid/GridSpec.scala b/src/test/scala/edu/ie3/simona/model/grid/GridSpec.scala index 211160211b..3d7d81d6c8 100644 --- a/src/test/scala/edu/ie3/simona/model/grid/GridSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/grid/GridSpec.scala @@ -232,7 +232,7 @@ class GridSpec nodes.foreach(_.enable()) // remove a line from the grid - val adaptedLines = lines - line3To4 + val adaptedLines: Set[LineModel] = lines - line3To4 adaptedLines.foreach(_.enable()) // enable transformer @@ -464,13 +464,14 @@ class GridSpec Set.empty[Transformer3wModel], switches, ), + defaultVoltageLimits, GridControls.empty, ) updateUuidToIndexMap(gridModel) // nodes 1, 13 and 14 should map to the same node - val node1Index = gridModel.nodeUuidToIndexMap + val node1Index: Int = gridModel.nodeUuidToIndexMap .get(node1.uuid) .value gridModel.nodeUuidToIndexMap.get(node13.uuid).value shouldBe node1Index 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 d20e19b26a..495fd134db 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 @@ -232,6 +232,7 @@ trait BasicGridWithSwitches extends BasicGrid { gridSwitches, ), defaultVoltageLimits, + GridControls.empty, ) } From 9eaaae8be8509fa1f04e2ab9675b94d954cb920e Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 29 Aug 2024 19:05:10 +0200 Subject: [PATCH 54/55] Adapting some parts. --- CHANGELOG.md | 2 +- .../ie3/simona/ExtendedCongestionResult.java | 52 ------------------- .../ie3/simona/agent/grid/GridAgentData.scala | 6 +-- .../event/listener/ResultEventListener.scala | 27 +--------- .../model/grid/Transformer3wModel.scala | 31 ++--------- .../agent/grid/GridResultsSupportSpec.scala | 1 - .../model/grid/Transformer3wModelSpec.scala | 6 --- .../model/grid/GridComponentsMokka.scala | 1 - 8 files changed, 7 insertions(+), 119 deletions(-) delete mode 100644 src/main/scala/edu/ie3/simona/ExtendedCongestionResult.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 57499fb283..781aba25a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,7 +78,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated `Gradle` to version V8.10 [#829](https://github.com/ie3-institute/simona/issues/829) - Updated AUTHORS.md [#905](https://github.com/ie3-institute/simona/issues/905) - Rewrote BMModelTest from groovy to scala [#646](https://github.com/ie3-institute/simona/issues/646) -- Changed implementation of actor naming for unique name generation [#103](https://github.com/ie3-institute/simona/issues/103) ### Fixed - Removed a repeated line in the documentation of vn_simona config [#658](https://github.com/ie3-institute/simona/issues/658) @@ -167,6 +166,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changed format of example grid `vn_simona` [#216](https://github.com/ie3-institute/simona/issues/216) - Renamed ChpData to ChpRelevantData [#494](https://github.com/ie3-institute/simona/issues/494) - Updated gradle to 8.2.1, cleaned up `build.gradle` and `Jenkinsfile` [#572](https://github.com/ie3-institute/simona/issues/572) +- Changed implementation of actor naming for unique name generation [#103](https://github.com/ie3-institute/simona/issues/103) ### Fixed - Location of `vn_simona` test grid (was partially in Berlin and Dortmund) [#72](https://github.com/ie3-institute/simona/issues/72) diff --git a/src/main/scala/edu/ie3/simona/ExtendedCongestionResult.java b/src/main/scala/edu/ie3/simona/ExtendedCongestionResult.java deleted file mode 100644 index 55039cdfb3..0000000000 --- a/src/main/scala/edu/ie3/simona/ExtendedCongestionResult.java +++ /dev/null @@ -1,52 +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; - -import edu.ie3.datamodel.models.result.CongestionResult; -import java.time.ZonedDateTime; -import javax.measure.quantity.Dimensionless; -import tech.units.indriya.ComparableQuantity; - -public class ExtendedCongestionResult extends CongestionResult { - private final double vMaxVal; - private final double vMinVal; - private final double getLineMax; - - public ExtendedCongestionResult( - ZonedDateTime time, - int subgrid, - ComparableQuantity vMin, - ComparableQuantity vMax, - boolean voltage, - boolean line, - boolean transformer, - double vMaxVal, - double vMinVal, - double getLineMax) { - super(time, subgrid, vMin, vMax, voltage, line, transformer); - this.vMaxVal = vMaxVal; - this.vMinVal = vMinVal; - this.getLineMax = getLineMax; - } - - public double getvMaxVal() { - return vMaxVal; - } - - public double getvMinVal() { - return vMinVal; - } - - public double getLineMax() { - return getLineMax; - } - - public CongestionResult to() { - return new CongestionResult( - getTime(), getSubgrid(), getVMin(), getVMax(), getVoltage(), getLine(), getTransformer()); - } -} 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 576344dec0..79f3efe61f 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/GridAgentData.scala @@ -12,7 +12,6 @@ import edu.ie3.datamodel.models.input.container.{SubGridContainer, ThermalGrid} import edu.ie3.datamodel.models.result.CongestionResult import edu.ie3.powerflow.model.PowerFlowResult import edu.ie3.powerflow.model.PowerFlowResult.SuccessFullPowerFlowResult.ValidNewtonRaphsonPFResult -import edu.ie3.simona.ExtendedCongestionResult import edu.ie3.simona.agent.EnvironmentRefs import edu.ie3.simona.agent.grid.CongestionManagementSupport.Congestions import edu.ie3.simona.agent.grid.GridAgentMessages._ @@ -539,7 +538,7 @@ object GridAgentData { ) ) - new ExtendedCongestionResult( + new CongestionResult( startTime.plusSeconds(currentTick), gridModel.subnetNo, gridModel.voltageLimits.vMin, @@ -547,9 +546,6 @@ object GridAgentData { congestions.voltageCongestions, congestions.lineCongestions, congestions.transformerCongestions, - node.maxOption.getOrElse(0), - node.minOption.getOrElse(0), - line.maxOption.getOrElse(0), ) } diff --git a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala index 8b1d737f3e..d460f63b8c 100644 --- a/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala +++ b/src/main/scala/edu/ie3/simona/event/listener/ResultEventListener.scala @@ -6,12 +6,10 @@ package edu.ie3.simona.event.listener -import edu.ie3.datamodel.io.csv.BufferedCsvWriter import org.apache.pekko.actor.typed.scaladsl.Behaviors import org.apache.pekko.actor.typed.{Behavior, PostStop} import edu.ie3.datamodel.io.processor.result.ResultEntityProcessor import edu.ie3.datamodel.models.result.{NodeResult, ResultEntity} -import edu.ie3.simona.ExtendedCongestionResult import edu.ie3.simona.agent.grid.GridResultsSupport.PartialTransformer3wResult import edu.ie3.simona.event.ResultEvent.{ FlexOptionsResultEvent, @@ -27,8 +25,6 @@ import edu.ie3.simona.io.result._ import edu.ie3.simona.util.ResultFileHierarchy import org.slf4j.Logger -import java.io.BufferedWriter -import java.nio.file.Path import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration.DurationInt import scala.concurrent.{Await, Future} @@ -36,8 +32,6 @@ import scala.util.{Failure, Success, Try} object ResultEventListener extends Transformer3wResultSupport { - private var subnetResultWriter: BufferedWriter = _ - trait Request private final case class SinkResponse( @@ -75,14 +69,6 @@ object ResultEventListener extends Transformer3wResultSupport { ): Iterable[Future[(Class[_], ResultEntitySink)]] = { resultFileHierarchy.resultSinkType match { case _: ResultSinkType.Csv => - subnetResultWriter = new BufferedCsvWriter( - Path.of(resultFileHierarchy.rawOutputDataDir, "subnet.csv"), - Array("time", "subnet", "v_max", "v_min", "line_max"), - ",", - true, - ) - subnetResultWriter.write("time,subnet,v_max,v_min,line_max\n") - resultFileHierarchy.resultEntitiesToConsider .map(resultClass => { resultFileHierarchy.rawOutputDataFilePaths @@ -308,17 +294,8 @@ object ResultEventListener extends Transformer3wResultSupport { congestionResults, ), ) => - val cRes = congestionResults.map { - case extended: ExtendedCongestionResult => - subnetResultWriter.write( - s"${extended.getTime},${extended.getSubgrid},${extended.getvMaxVal},${extended.getvMinVal},${extended.getLineMax}\n" - ) - extended.to() - case c => c - } - val updatedBaseData = - (nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ transformer3wResults ++ cRes) + (nodeResults ++ switchResults ++ lineResults ++ transformer2wResults ++ transformer3wResults ++ congestionResults) .foldLeft(baseData) { case (currentBaseData, resultEntity: ResultEntity) => handleResult(resultEntity, currentBaseData, ctx.log) @@ -354,8 +331,6 @@ object ResultEventListener extends Transformer3wResultSupport { .mkString("\n\t\t"), ) - subnetResultWriter.close() - // close sinks concurrently to speed up closing (closing calls might be blocking) Await.ready( Future.sequence( diff --git a/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala b/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala index c265ee5c50..c7e34ac09d 100644 --- a/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala +++ b/src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala @@ -67,9 +67,6 @@ import scala.math.BigDecimal.RoundingMode * number of parallel transformers * @param powerFlowCase * the [[Transformer3wPowerFlowCase]] - * @param iNom - * the nominal current at the port that is defined by the - * [[Transformer3wPowerFlowCase]] * @param sRated * the rated power at the port that is defined by the * [[Transformer3wPowerFlowCase]] @@ -98,7 +95,6 @@ final case class Transformer3wModel( override protected val transformerTappingModel: TransformerTappingModel, amount: Int, powerFlowCase: Transformer3wPowerFlowCase, - iNom: squants.electro.ElectricCurrent, sRated: Power, protected val r: squants.Dimensionless, protected val x: squants.Dimensionless, @@ -267,37 +263,19 @@ case object Transformer3wModel extends LazyLogging { .setScale(5, RoundingMode.HALF_UP) } - val (iNom, sRated) = powerFlowCase match { + val sRated = powerFlowCase match { case PowerFlowCaseA => - val power = Watts( + Watts( trafo3wType.getsRatedA().to(VOLTAMPERE).getValue.doubleValue() ) - - val current = power / Math.sqrt(3) / Kilovolts( - trafo3wType.getvRatedA().to(KILOVOLT).getValue.doubleValue() - ) - - (current, power) case PowerFlowCaseB => - val power = Watts( + Watts( trafo3wType.getsRatedB().to(VOLTAMPERE).getValue.doubleValue() ) - - val current = power / Math.sqrt(3) / Kilovolts( - trafo3wType.getvRatedB().to(KILOVOLT).getValue.doubleValue() - ) - - (current, power) case PowerFlowCaseC => - val power = Watts( + Watts( trafo3wType.getsRatedC().to(VOLTAMPERE).getValue.doubleValue() ) - - val current = power / Math.sqrt(3) / Kilovolts( - trafo3wType.getvRatedC().to(KILOVOLT).getValue.doubleValue() - ) - - (current, power) } val operationInterval = @@ -319,7 +297,6 @@ case object Transformer3wModel extends LazyLogging { transformerTappingModel, transformer3wInput.getParallelDevices, powerFlowCase, - iNom, sRated, r, x, diff --git a/src/test/scala/edu/ie3/simona/agent/grid/GridResultsSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/GridResultsSupportSpec.scala index 9a9238a872..57fa4fa049 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/GridResultsSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/GridResultsSupportSpec.scala @@ -444,7 +444,6 @@ class GridResultsSupportSpec ), 1, PowerFlowCaseA, - Amperes(100), Watts(10), Each(0.1d), Each(0.2d), diff --git a/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala b/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala index ca92f97e40..e386cd4ca0 100644 --- a/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala +++ b/src/test/scala/edu/ie3/simona/model/grid/Transformer3wModelSpec.scala @@ -70,7 +70,6 @@ class Transformer3wModelSpec transformerTappingModel, amount, powerFlowCase, - iNom, sRated, r, x, @@ -90,7 +89,6 @@ class Transformer3wModelSpec transformerTappingModel shouldBe expectedTappingModel amount shouldBe transformer3wInput.getParallelDevices powerFlowCase shouldBe PowerFlowCaseA - iNom shouldBe Amperes(182.3211376388292) sRated shouldBe Megawatts(120) r should approximate(Each(1.03878e-3)) x should approximate(Each(166.34349e-3)) @@ -149,7 +147,6 @@ class Transformer3wModelSpec transformerTappingModel, amount, powerFlowCase, - iNom, sRated, r, x, @@ -169,7 +166,6 @@ class Transformer3wModelSpec transformerTappingModel shouldBe expectedTappingModel amount shouldBe transformer3wInput.getParallelDevices powerFlowCase shouldBe PowerFlowCaseB - iNom shouldBe Amperes(314.9183286488868) sRated shouldBe Megawatts(60) r should approximate(Each(240.9972299e-6)) x should approximate(Each(24.99307479224e-3)) @@ -228,7 +224,6 @@ class Transformer3wModelSpec transformerTappingModel, amount, powerFlowCase, - iNom, sRated, r, x, @@ -248,7 +243,6 @@ class Transformer3wModelSpec transformerTappingModel shouldBe expectedTappingModel amount shouldBe transformer3wInput.getParallelDevices powerFlowCase shouldBe PowerFlowCaseC - iNom shouldBe Amperes(1154.7005383792516) sRated shouldBe Megawatts(40) r should approximate(Each(3.185595567e-6)) x should approximate(Each(556.0941828e-6)) diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala index 66d53cad16..f5ff6885f0 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/GridComponentsMokka.scala @@ -104,7 +104,6 @@ trait GridComponentsMokka extends MockitoSugar { tappingModel, amount = 1, powerFlowCase = PowerFlowCaseA, - iNom = Amperes(1), sRated = Watts(1), r = Each(1), x = Each(1), From beda9a189740ea48fb5e23bc2e8ff937f29dda68 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Sun, 1 Sep 2024 19:10:06 +0200 Subject: [PATCH 55/55] Adapting some parts. --- .../edu/ie3/simona/PrimaryDataFilter.java | 71 ------- .../scala/edu/ie3/simona/ResultFilter.scala | 120 ------------ .../edu/ie3/simona/TimeseriesBuilder.scala | 176 ------------------ .../CongestionManagementSupportSpec.scala | 10 +- .../agent/grid/DCMAlgorithmCenGridSpec.scala | 8 +- .../test/common/model/grid/DbfsTestGrid.scala | 22 +-- 6 files changed, 20 insertions(+), 387 deletions(-) delete mode 100644 src/main/scala/edu/ie3/simona/PrimaryDataFilter.java delete mode 100644 src/main/scala/edu/ie3/simona/ResultFilter.scala delete mode 100644 src/main/scala/edu/ie3/simona/TimeseriesBuilder.scala diff --git a/src/main/scala/edu/ie3/simona/PrimaryDataFilter.java b/src/main/scala/edu/ie3/simona/PrimaryDataFilter.java deleted file mode 100644 index 23d6b11845..0000000000 --- a/src/main/scala/edu/ie3/simona/PrimaryDataFilter.java +++ /dev/null @@ -1,71 +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; - -import java.io.*; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Stream; - -public class PrimaryDataFilter { - public static void main(String[] args) { - Path base = Path.of(".", "input", "ma_thesis", "fullGrid"); - - Path input = base.resolve("primary"); - Path output = base.resolve("primary-x1.5"); - List files = Arrays.stream(Objects.requireNonNull(input.toFile().listFiles())).toList(); - - AtomicInteger i = new AtomicInteger(0); - - files.forEach( - file -> { - i.addAndGet(1); - System.out.println(i.get()); - - try { - BufferedReader reader = new BufferedReader(new FileReader(file)); - String headline = reader.readLine(); - List lines = reader.lines().toList(); - - Stream stream = - lines.stream() - // .filter(line ->line.contains("2016-07-") || line.contains("2016-08-01")) - .map(line -> multiply(line, 1.5)); - - String txt = stream.reduce(headline, (a, b) -> a + "\n" + b); - - Writer writer = - new OutputStreamWriter( - new FileOutputStream(output.resolve(file.getName()).toFile())); - writer.write(txt); - writer.close(); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } - - public static String multiply(String str, double factor) { - String[] arr = str.split(","); - - if (arr.length == 2) { - double modified = Double.parseDouble(arr[0]) * factor; - - return modified + "," + arr[1]; - } else if (arr.length == 3) { - double modified1 = Double.parseDouble(arr[0]) * factor; - double modified2 = Double.parseDouble(arr[1]) * factor; - - return modified1 + "," + modified2 + "," + arr[2]; - } else { - throw new RuntimeException("Size:" + arr.length); - } - } -} diff --git a/src/main/scala/edu/ie3/simona/ResultFilter.scala b/src/main/scala/edu/ie3/simona/ResultFilter.scala deleted file mode 100644 index ebb2f92257..0000000000 --- a/src/main/scala/edu/ie3/simona/ResultFilter.scala +++ /dev/null @@ -1,120 +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 - -import edu.ie3.datamodel.io.naming.FileNamingStrategy -import edu.ie3.datamodel.io.source.ResultEntitySource -import edu.ie3.datamodel.io.source.csv.{ - CsvDataSource, - CsvJointGridContainerSource, -} -import edu.ie3.datamodel.models.result.NodeResult -import edu.ie3.datamodel.models.result.connector.LineResult - -import java.io.{File, FileOutputStream, OutputStreamWriter} -import java.nio.file.Path -import java.time.ZonedDateTime -import java.util.UUID -import scala.jdk.CollectionConverters.CollectionHasAsScala - -object ResultFilter { - def main(args: Array[String]): Unit = { - val grid = Path.of("input", "grid") - val jointGrid = CsvJointGridContainerSource.read("grid", ";", grid, false) - val results = Path.of("output", "grid") - - val source = new CsvDataSource(",", results, new FileNamingStrategy()) - val resultSource = new ResultEntitySource(source) - - val nodeSubnetRes = results.resolve("subnetVoltages.csv").toFile - write("time,subnet,v_max,v_min", nodeSubnetRes, append = false) - - val lineSubnetRes = results.resolve("subnetLineUtilisation.csv").toFile - write("time,subnet,max", lineSubnetRes, append = false) - - val nodeToSubnet = jointGrid.getRawGrid.getNodes.asScala - .map(n => n.getUuid -> n.getSubnet) - .toMap - - val lineToSubnet = jointGrid.getRawGrid.getLines.asScala.map { l => - l.getUuid -> l.getNodeA.getSubnet - }.toMap - - subnetVoltages(nodeToSubnet, resultSource, nodeSubnetRes) - subnetLineUtilisation(lineToSubnet, resultSource, lineSubnetRes) - } - - private def subnetVoltages( - nodeToSubnet: Map[UUID, Int], - resultSource: ResultEntitySource, - file: File, - ): Unit = { - val results: Map[ZonedDateTime, Iterable[NodeResult]] = - resultSource.getNodeResults.asScala - .map(res => res.getTime -> res) - .groupBy(_._1) - .map { case (time, tuples) => time -> tuples.map(_._2) } - - results.foreach { case (time, results) => - println(time) - - results - .map(res => - nodeToSubnet(res.getInputModel) -> res - .getvMag() - .getValue - .doubleValue() - ) - .groupBy(_._1) - .map { case (i, tuples) => - val set = tuples.map(_._2).toSet - val str = s"$time,$i,${set.max},${set.min}" - - write(str, file) - } - } - } - - private def subnetLineUtilisation( - lineToSubnet: Map[UUID, Int], - resultSource: ResultEntitySource, - file: File, - ): Unit = { - val results: Map[ZonedDateTime, Iterable[LineResult]] = - resultSource.getLineResults.asScala - .map(res => res.getTime -> res) - .groupBy(_._1) - .map { case (time, tuples) => time -> tuples.map(_._2) } - - results.foreach { case (time, results) => - println(time) - - results - .map(res => - lineToSubnet(res.getInputModel) -> Math.max( - res.getiAMag().getValue.doubleValue(), - res.getiBMag().getValue.doubleValue(), - ) - ) - .groupBy(_._1) - .map { case (i, tuples) => - val set = tuples.map(_._2).toSet - val str = s"$time,$i,${set.max}" - - write(str, file) - } - } - } - - def write(str: String, subnetVoltages: File, append: Boolean = true): Unit = { - val writer = new OutputStreamWriter( - new FileOutputStream(subnetVoltages, append) - ) - writer.write(str) - writer.close() - } -} diff --git a/src/main/scala/edu/ie3/simona/TimeseriesBuilder.scala b/src/main/scala/edu/ie3/simona/TimeseriesBuilder.scala deleted file mode 100644 index 784cb89912..0000000000 --- a/src/main/scala/edu/ie3/simona/TimeseriesBuilder.scala +++ /dev/null @@ -1,176 +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 - -import edu.ie3.datamodel.io.sink.CsvFileSink -import edu.ie3.datamodel.io.source.TimeSeriesMappingSource.MappingEntry -import edu.ie3.datamodel.io.source.csv.CsvJointGridContainerSource - -import java.nio.file.Path -import java.time.format.DateTimeFormatter -import java.time.{LocalDateTime, ZoneId, ZonedDateTime} -import java.util.UUID -import scala.jdk.CollectionConverters._ - -object TimeseriesBuilder { - def main(args: Array[String]): Unit = { - val dateTime = LocalDateTime.of(2016, 1, 1, 0, 0, 0) - val start = ZonedDateTime.of(dateTime, ZoneId.of("UTC")) - - val loadString = load(start) - val feedInNoCM = noCM(start) - val feedInTwoPerDayString = twoPerDay(start) - val feedInEachHour = eachHour(start) - - println("finished") - } - - def load(start: ZonedDateTime): String = { - val hours = (0 until 366 * 24 + 1).map(start.plusHours(_)) - - val loadBuilder = new StringBuilder() - hours - .map(hour => - s"2.7,1.31,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - .foreach(loadBuilder.append) - loadBuilder.toString().replaceAll("\\[UTC]", "") - } - - def noCM(start: ZonedDateTime): String = { - val hours = (0 until 366 * 24 + 1).map(start.plusHours(_)) - val stringBuilder = new StringBuilder() - hours - .map(hour => s"0.0,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n") - .foreach(stringBuilder.append) - stringBuilder.toString().replaceAll("\\[UTC]", "") - } - - def twoPerDay(start: ZonedDateTime): String = { - val stringBuilder = new StringBuilder() - - (0 until 366).map(start.plusDays(_)).foreach { day => - stringBuilder - .append(s"0.0,${day.format(DateTimeFormatter.ISO_DATE_TIME)}\n") - stringBuilder.append( - s"0.0,${day.plusHours(1).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"0.0,${day.plusHours(2).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"0.0,${day.plusHours(3).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"0.0,${day.plusHours(4).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"0.0,${day.plusHours(5).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"-20.0,${day.plusHours(6).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"-20.0,${day.plusHours(7).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"-20.0,${day.plusHours(8).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"-20.0,${day.plusHours(9).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"-20.0,${day.plusHours(10).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"-20.0,${day.plusHours(11).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"-20.0,${day.plusHours(12).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"-20.0,${day.plusHours(13).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"-20.0,${day.plusHours(14).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"-20.0,${day.plusHours(15).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"-20.0,${day.plusHours(16).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"-20.0,${day.plusHours(17).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"0.0,${day.plusHours(18).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"0.0,${day.plusHours(19).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"0.0,${day.plusHours(20).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"0.0,${day.plusHours(21).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"0.0,${day.plusHours(22).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - stringBuilder.append( - s"0.0,${day.plusHours(23).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - } - - stringBuilder.append( - s"0.0,${start.plusYears(1).format(DateTimeFormatter.ISO_DATE_TIME)}\n" - ) - - stringBuilder.toString().replaceAll("\\[UTC]", "") - } - - def eachHour(start: ZonedDateTime): String = { - val hours = (0 until 366 * 24 + 1).map(start.plusHours(_)) - - val feedInBuilder = new StringBuilder() - hours.zipWithIndex - .map { case (hour, i) => - if (i % 2 == 0) { - s"-20.0,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n" - } else s"5.0,${hour.format(DateTimeFormatter.ISO_DATE_TIME)}\n" - } - .foreach(feedInBuilder.append) - - feedInBuilder.toString().replaceAll("\\[UTC]", "") - } - - def mapping(): Unit = { - val feedInSeries = UUID.fromString("0ac0477b-cee6-4026-91c9-864d1c6871c9") - val loadSeries = UUID.fromString("0c24f963-67b0-40c8-997e-0091fcfae00a") - - val jointGridContainer = CsvJointGridContainerSource.read( - "grid", - ",", - Path.of(".", "input", "ma_thesis", "fullGrid"), - false, - ) - val participants = jointGridContainer.getSystemParticipants - - val loads = participants.getLoads.asScala.map(_.getUuid) - val feedIns = participants.getFixedFeedIns.asScala.map(_.getUuid) - - val mappingEntries = loads.map(load => - new MappingEntry(load, loadSeries) - ) ++ feedIns.map(feedIn => new MappingEntry(feedIn, feedInSeries)) - - val sink = new CsvFileSink( - Path.of(".", "input", "ma_thesis", "fullGrid", "primary-x") - ) - sink.persistAll(mappingEntries.asJava) - } -} diff --git a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala index aafacba03d..96a05f33a4 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/CongestionManagementSupportSpec.scala @@ -56,8 +56,8 @@ class CongestionManagementSupportSpec val transformer3 = mockTransformerModel() val ref4 = TestProbe[GridAgent.Request]("ref4").ref - val transformer4_1 = mockTransformerModel() - val transformer4_2 = mockTransformerModel() + val transformer4a = mockTransformerModel() + val transformer4b = mockTransformerModel() // grid 1 is connected via a transformer2w and one port of a transformer3w // grid 2 is connected via one port of a transformer3w @@ -72,8 +72,8 @@ class CongestionManagementSupportSpec ref2 -> Set(transformer3wC), // connected with a transformer3w ref3 -> Set(transformer3), // connected with just one transformer model ref4 -> Set( - transformer4_1, - transformer4_2, + transformer4a, + transformer4b, ), // connected with two transformer2w ) @@ -93,7 +93,7 @@ class CongestionManagementSupportSpec groups shouldBe Set( TappingGroup(Set(ref1, ref2), Set(transformer1, transformer3wA)), TappingGroup(Set(ref3), Set(transformer3)), - TappingGroup(Set(ref4), Set(transformer4_1, transformer4_2)), + TappingGroup(Set(ref4), Set(transformer4a, transformer4b)), ) } diff --git a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala index a98fe2f9a4..168a352e0f 100644 --- a/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala +++ b/src/test/scala/edu/ie3/simona/agent/grid/DCMAlgorithmCenGridSpec.scala @@ -241,8 +241,8 @@ class DCMAlgorithmCenGridSpec ( VoltageRange(0.06.asPu, 0.asPu), Set( - mvTransformers(transformer13_1.getUuid), - mvTransformers(transformer13_2.getUuid), + mvTransformers(transformer13a.getUuid), + mvTransformers(transformer13b.getUuid), ), ), ) @@ -276,8 +276,8 @@ class DCMAlgorithmCenGridSpec // these transformers can't be tapped and should keep their default tap pos mvTransformers(transformer12.getUuid).currentTapPos shouldBe 0 - mvTransformers(transformer13_1.getUuid).currentTapPos shouldBe 0 - mvTransformers(transformer13_1.getUuid).currentTapPos shouldBe 0 + mvTransformers(transformer13a.getUuid).currentTapPos shouldBe 0 + mvTransformers(transformer13a.getUuid).currentTapPos shouldBe 0 // skipping this simulation step skipSimulation(centerGridAgent) diff --git a/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala b/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala index 47d5e02905..eb9df24c3c 100644 --- a/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala +++ b/src/test/scala/edu/ie3/simona/test/common/model/grid/DbfsTestGrid.scala @@ -127,7 +127,7 @@ trait DbfsTestGrid extends SubGridGateMokka with GridComponentsMokka { * MS1_01 @ 11 -> 1676e48c-5353-4f06-b671-c579cf6a7072 @ 11 * MS3_01 @ 13 -> 9237e237-01e9-446f-899f-c3b5cf69d288 @ 13 */ - protected val node13_1: NodeInput = mockNode( + protected val node13a: NodeInput = mockNode( UUID.fromString("1129b00d-3d89-4a4a-8ae1-2a56041b95aa"), 13, GermanVoltageLevelUtils.MV_10KV, @@ -142,7 +142,7 @@ trait DbfsTestGrid extends SubGridGateMokka with GridComponentsMokka { 11, GermanVoltageLevelUtils.MV_10KV, ) - protected val node13_2: NodeInput = mockNode( + protected val node13b: NodeInput = mockNode( UUID.fromString("9237e237-01e9-446f-899f-c3b5cf69d288"), 13, GermanVoltageLevelUtils.MV_10KV, @@ -151,7 +151,7 @@ trait DbfsTestGrid extends SubGridGateMokka with GridComponentsMokka { // 5 lines between the nodes protected val lineType1 = new LineTypeInput( UUID.randomUUID(), - "Freileitung_110kV_1 ", + "Freileitung_110kV_1", Quantities.getQuantity(0.0, SIEMENS_PER_KILOMETRE), Quantities.getQuantity(0.0, SIEMENS_PER_KILOMETRE), Quantities.getQuantity(0.1094, OHM_PER_KILOMETRE), @@ -347,25 +347,25 @@ trait DbfsTestGrid extends SubGridGateMokka with GridComponentsMokka { 0, false, ) - protected val transformer13_1 = new Transformer2WInput( + protected val transformer13a = new Transformer2WInput( UUID.randomUUID(), "HV-MV-Trafo_13_1", OperatorInput.NO_OPERATOR_ASSIGNED, OperationTime.notLimited(), node4, - node13_1, + node13a, 1, trafoType10kV, 0, false, ) - protected val transformer13_2 = new Transformer2WInput( + protected val transformer13b = new Transformer2WInput( UUID.randomUUID(), "HV-MV-Trafo_13_2", OperatorInput.NO_OPERATOR_ASSIGNED, OperationTime.notLimited(), node4, - node13_2, + node13b, 1, trafoType10kV, 0, @@ -378,8 +378,8 @@ trait DbfsTestGrid extends SubGridGateMokka with GridComponentsMokka { protected val mvTransformers: Map[UUID, TransformerModel] = Seq( transformer11, transformer12, - transformer13_1, - transformer13_2, + transformer13a, + transformer13b, ).map { model => model.getUuid -> TransformerModel( model, @@ -415,10 +415,10 @@ trait DbfsTestGrid extends SubGridGateMokka with GridComponentsMokka { SubGridGate.fromTransformer3W(transformer, ConnectorPort.C), ) ) ++ Seq( - new SubGridGate(transformer13_1, node4, node13_1), + new SubGridGate(transformer13a, node4, node13a), new SubGridGate(transformer12, node2, node12), new SubGridGate(transformer11, node1, node11), - new SubGridGate(transformer13_2, node3, node13_2), + new SubGridGate(transformer13b, node3, node13b), ) (