From 2d04f3e43ea1475834179eeb66304d184c9d3d43 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Wed, 29 May 2024 12:30:57 +0200 Subject: [PATCH 1/5] Refactor simulateGrid method of DBFSAlgorithm to reduce complexity --- CHANGELOG.md | 1 + .../ie3/simona/agent/grid/DBFSAlgorithm.scala | 746 ++++++++++-------- 2 files changed, 426 insertions(+), 321 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa9ff87da1..e63c47a0d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refactoring of `GridAgent` messages [#736](https://github.com/ie3-institute/simona/issues/736) - Rewrote PVModelTest from groovy to scala [#646](https://github.com/ie3-institute/simona/issues/646) - Making configuration of `RefSystem` via config optional [#769](https://github.com/ie3-institute/simona/issues/769) +- Refactor `simulateGrid` method of DBFSAlgorithm to reduce complexity [#817](https://github.com/ie3-institute/simona/issues/817) ### Fixed - Removed a repeated line in the documentation of vn_simona config [#658](https://github.com/ie3-institute/simona/issues/658) 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 24236f43d3..8e4d7dc1e5 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala @@ -88,56 +88,19 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { gridAgentBaseData.currentSweepNo, ) - // we start the grid simulation by requesting the p/q values of all the nodes we are responsible for - // as well as the slack voltage power from our superior grid - // 1. assets p/q values - askForAssetPowers( - currentTick, - gridAgentBaseData.sweepValueStores - .get(gridAgentBaseData.currentSweepNo), - gridAgentBaseData.gridEnv.nodeToAssetAgents, - gridAgentBaseData.gridEnv.gridModel.mainRefSystem, - gridAgentBaseData.powerFlowParams.sweepTimeout, - )(ctx) - - // 2. inferior grids p/q values - askInferiorGridsForPowers( - gridAgentBaseData.currentSweepNo, - gridAgentBaseData.gridEnv.subgridGateToActorRef, - gridAgentBaseData.inferiorGridGates, - gridAgentBaseData.powerFlowParams.sweepTimeout, - )(ctx) - - // 3. superior grids slack voltage - askSuperiorGridsForSlackVoltages( - gridAgentBaseData.currentSweepNo, - gridAgentBaseData.gridEnv.subgridGateToActorRef, - gridAgentBaseData.superiorGridGates, - gridAgentBaseData.powerFlowParams.sweepTimeout, - )(ctx) + // Request data from child assets and grids + requestInitialData(gridAgentBaseData, currentTick, ctx) simulateGrid(gridAgentBaseData, activation.tick) // if we receive power values as response on our request, we process them here + // Handle received power and voltage values case ( receivedValues: ReceivedValues, gridAgentBaseData: GridAgentBaseData, ) => - // we just received either all provided slack voltage values or all provided power values - val updatedGridAgentBaseData: GridAgentBaseData = - receivedValues match { - case receivedPowers: ReceivedPowerValues => - /* Can be a message from an asset or a message from an inferior grid */ - gridAgentBaseData.updateWithReceivedPowerValues(receivedPowers) - case receivedSlacks: ReceivedSlackVoltageValues => - gridAgentBaseData.updateWithReceivedSlackVoltages( - receivedSlacks - ) - case unknownReceivedValues => - throw new DBFSAlgorithmException( - s"Received unknown values: $unknownReceivedValues" - ) - } + val updatedGridAgentBaseData = + updateWithReceivedValues(receivedValues, gridAgentBaseData) // check if we have enough data for a power flow calculation or a // power differences check (if the grid agent is a superior agent) @@ -145,15 +108,14 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { // if there are failing ones, escalate the failure to the superior grid (if any), // if not go to power flow or power differences check // if we haven't received everything yet, stay and wait + val allValuesReceived = updatedGridAgentBaseData.allRequestedDataReceived - ctx.log.debug( - "{}", if (allValuesReceived) "Got answers for all my requests for Slack Voltages and Power Values." else - "Still waiting for answers my requests for Slack Voltages and Power Values.", + "Still waiting for answers to my requests for Slack Voltages and Power Values." ) if (gridAgentBaseData.isSuperior) { @@ -172,142 +134,35 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { )(ctx, constantData, buffer) } - // if we receive a request for slack voltages from our inferior grids we want to answer it + // Handle slack voltage requests case ( - SlackVoltageRequest( - currentSweepNo, - nodeUuids, - sender, - ), + SlackVoltageRequest(currentSweepNo, nodeUuids, sender), gridAgentBaseData: GridAgentBaseData, ) => - ctx.log.debug( - s"Received Slack Voltages request from {} for nodes {} and sweepNo: {}", - sender, + handleSlackVoltageRequest( + currentSweepNo, nodeUuids, - gridAgentBaseData.currentSweepNo, + sender, + gridAgentBaseData, + ctx, ) - nodeUuids.map { nodeUuid => - // we either have voltages ready calculated (not the first sweep) or we don't have them here - // -> return calculated value or target voltage as physical value - (gridAgentBaseData.sweepValueStores.get(currentSweepNo) match { - case Some(result) => - Some(result, currentSweepNo) - case None => - // this happens if this agent is either a) the superior grid agent, because it will always get a request for - // the next sweep, as it triggers calculations for the next sweep or b) at all other - // (non last downstream grid agents) in sweep 0 - ctx.log.debug( - "Unable to find slack voltage for nodes '{}' in sweep '{}'. Try to get voltage of previous sweep.", - nodeUuids, - currentSweepNo, - ) - gridAgentBaseData.sweepValueStores - .get(currentSweepNo - 1) - .map((_, currentSweepNo - 1)) - }).map { case (result, sweepNo) => - // get nodeUUID - result.sweepData.find(_.nodeUuid == nodeUuid) match { - case Some(sweepValueStoreData) => - val slackVoltageInPu = sweepValueStoreData.stateData.voltage - val mainRefSystem = - gridAgentBaseData.gridEnv.gridModel.mainRefSystem - ( - mainRefSystem.vInSi(slackVoltageInPu.real), - mainRefSystem.vInSi(slackVoltageInPu.imag), - ) - case None => - throw new DBFSAlgorithmException( - s"Requested nodeUuid $nodeUuid " + - s"not found in sweep value store data for sweepNo: $sweepNo. This indicates" + - s"either a wrong processing of a previous sweep result or inconsistencies in grid model data!" - ) - } - }.getOrElse { - ctx.log.debug( - "Unable to get slack voltage for node '{}' in sweeps '{}' and '{}'. Returning target voltage.", - nodeUuid, - currentSweepNo, - currentSweepNo - 1, - ) - - val refSystem = - gridAgentBaseData.gridEnv.gridModel.mainRefSystem - - /* 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, _, _) => - uuid == nodeUuid && isSlack - } - .map(_.vTarget) - .getOrElse(Each(1d)) - val vSlack = - refSystem.nominalVoltage.multiplyWithDimensionles(vTarget) - - ( - vSlack, - refSystem.vInSi(0d), - ) - } match { - case (slackE, slackF) => - ctx.log.debug( - s"Provide {} to {} for node {} and sweepNo: {}", - s"$slackE, $slackF", - sender, - nodeUuid, - gridAgentBaseData.currentSweepNo, - ) - - ExchangeVoltage(nodeUuid, slackE, slackF) - } - } match { - case exchangeVoltages => - sender ! SlackVoltageResponse( - currentSweepNo, - exchangeVoltages, - ) - Behaviors.same - } - // receive grid power values message request from superior grids // before power flow calc for this sweep we either have to stash() the message to answer it later (in current sweep) // or trigger a new run for the next sweepNo case ( - msg @ RequestGridPower( - requestSweepNo, - _, - _, - ), + msg @ RequestGridPower(requestSweepNo, _, _), gridAgentBaseData: GridAgentBaseData, ) => - if (gridAgentBaseData.currentSweepNo == requestSweepNo) { - ctx.log.debug( - s"Received request for grid power values for sweepNo {} before my first power flow calc. Stashing away.", - requestSweepNo, - ) - - buffer.stash(msg) - - Behaviors.same - } else { - ctx.log.debug( - s"Received request for grid power values for a NEW sweep (request: {}, my: {})", - requestSweepNo, - gridAgentBaseData.currentSweepNo, - ) - ctx.self ! PrepareNextSweepTrigger(currentTick) - - buffer.stash(msg) - - simulateGrid( - gridAgentBaseData.copy(currentSweepNo = requestSweepNo), - currentTick, - ) - } + handleGridPowerRequestBeforeCalculation( + requestSweepNo, + msg, + gridAgentBaseData, + currentTick, + ctx, + ) - // after power flow calc for this sweepNo + // Handle grid power requests after power flow calculation for this sweepNo case ( RequestGridPower(_, requestedNodeUuids, sender), powerFlowDoneData @ PowerFlowDoneData( @@ -316,184 +171,376 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { pendingRequestAnswers, ), ) => - /* Determine the subgrid number of the grid agent, that has sent the request */ - val firstRequestedNodeUuid = requestedNodeUuids.headOption.getOrElse( - throw new DBFSAlgorithmException( - "Did receive a grid power request but without specified nodes" - ) + handleGridPowerRequestAfterCalculation( + requestedNodeUuids, + sender, + powerFlowDoneData, + currentTick, + ctx, ) - gridAgentBaseData.gridEnv.subgridGateToActorRef - .map { case (subGridGate, _) => subGridGate.superiorNode } - .find(_.getUuid == firstRequestedNodeUuid) - .map(_.getSubnet) match { - case Some(requestingSubgridNumber) => - powerFlowResult match { - case validNewtonRaphsonPFResult: ValidNewtonRaphsonPFResult => - val exchangePowers = requestedNodeUuids - .map { nodeUuid => - /* Figure out the node index for each requested node */ - nodeUuid -> gridAgentBaseData.gridEnv.gridModel.nodeUuidToIndexMap - .get(nodeUuid) - .flatMap { nodeIndex => - /* Find matching node result */ - validNewtonRaphsonPFResult.nodeData.find(stateData => - stateData.index == nodeIndex - ) - } - .map { - case StateData(_, nodeType, _, power) - if nodeType == NodeType.SL => - val refSystem = - gridAgentBaseData.gridEnv.gridModel.mainRefSystem - val (pInPu, qInPu) = - (power.real, power.imag) - // The power flow result data provides the nodal residual power at the slack node. - // A feed-in case from the inferior grid TO the superior grid leads to positive residual power at the - // inferior grid's *slack node* (superior grid seems to be a load to the inferior grid). - // To model the exchanged power from the superior grid's point of view, -1 has to be multiplied. - // (Inferior grid is a feed in facility to superior grid, which is negative then). Analogously for load case. - ( - refSystem.pInSi(pInPu) * (-1), - refSystem.qInSi(qInPu) * (-1), - ) - case _ => - /* TODO: As long as there are no multiple slack nodes, provide "real" power only for the slack node */ - ( - zeroMW, - zeroMVAr, - ) - } - .getOrElse { - throw new DBFSAlgorithmException( - s"Got a request for power @ node with uuid $requestedNodeUuids but cannot find it in my result data!" - ) - } - } - .map { case (nodeUuid, (p, q)) => - Responses.ExchangePower( - nodeUuid, - p, - q, - ) - } - - /* Determine the remaining replies */ - val stillPendingRequestAnswers = - pendingRequestAnswers.filterNot( - _ == requestingSubgridNumber - ) - - // update the sweep value store and clear all received maps - // note: normally it is expected that this has to be done after power flow calculations but for the sake - // of having it only once in the code we put this here. Otherwise it would have to been put before EVERY - // return with a valid power flow result (currently happens already in two situations) - val updatedGridAgentBaseData = - if (stillPendingRequestAnswers.isEmpty) { - gridAgentBaseData.storeSweepDataAndClearReceiveMaps( - validNewtonRaphsonPFResult, - gridAgentBaseData.superiorGridNodeUuids, - gridAgentBaseData.inferiorGridGates, - ) - } else { - powerFlowDoneData.copy(pendingRequestAnswers = - stillPendingRequestAnswers - ) - } - - sender ! GridPowerResponse(exchangePowers) - simulateGrid(updatedGridAgentBaseData, currentTick) - - case _: FailedNewtonRaphsonPFResult => - sender ! FailedPowerFlow - simulateGrid(gridAgentBaseData, currentTick) - } - case None => - /* It is not possible to determine, who has asked */ - ctx.log.error( - "I got a grid power request from a subgrid I don't know. Can't answer it properly." - ) - - sender ! FailedPowerFlow - Behaviors.same - } - // called when a grid power values request from a superior grid is received // which is similar to a new sweep and causes a) a power flow with updated slack voltage values and // b) afterwards a request for updated power values from inferior grids and assets with updated voltage values // based on the just carried out power flow + // Handle next sweep trigger case ( PrepareNextSweepTrigger(_), gridAgentBaseData: GridAgentBaseData, ) => - // request the updated slack voltages from the superior grid - askSuperiorGridsForSlackVoltages( - gridAgentBaseData.currentSweepNo, - gridAgentBaseData.gridEnv.subgridGateToActorRef, - gridAgentBaseData.superiorGridGates, - gridAgentBaseData.powerFlowParams.sweepTimeout, - )(ctx) - - ctx.log.debug(s"Going to HandlePowerFlowCalculation") + requestUpdatedSlackVoltages( + gridAgentBaseData, + currentTick, + ctx, + ) + ctx.log.debug("Going to HandlePowerFlowCalculation") handlePowerFlowCalculations(gridAgentBaseData, currentTick) - // last step which should includes a) information on inferior grids about finish and - // b) cleanup of receiveMaps and sweepStore + // Handle finish grid simulation trigger case ( FinishGridSimulationTrigger(currentTick), gridAgentBaseData: GridAgentBaseData, ) => - // inform my child grids about the end of this grid simulation - gridAgentBaseData.inferiorGridGates - .map { - gridAgentBaseData.gridEnv.subgridGateToActorRef(_) - } - .distinct - .foreach( - _ ! FinishGridSimulationTrigger(currentTick) - ) + finishGridSimulation(gridAgentBaseData, currentTick, ctx) - // inform every system participant about the end of this grid simulation - gridAgentBaseData.gridEnv.nodeToAssetAgents.foreach { - case (_, actors) => - actors.foreach { actor => - actor ! FinishParticipantSimulation(currentTick) - } - } + case _ => + // preventing "match may not be exhaustive" + Behaviors.unhandled + } + } + + // Helper methods for different parts of the simulation + private def requestInitialData( + gridAgentBaseData: GridAgentBaseData, + currentTick: Long, + ctx: ActorContext[GridAgent.Request], + ): Unit = { + // we start the grid simulation by requesting the p/q values of all the nodes we are responsible for + // as well as the slack voltage power from our superior grid + // 1. assets p/q values + askForAssetPowers( + currentTick, + gridAgentBaseData.sweepValueStores + .get(gridAgentBaseData.currentSweepNo), + gridAgentBaseData.gridEnv.nodeToAssetAgents, + gridAgentBaseData.gridEnv.gridModel.mainRefSystem, + gridAgentBaseData.powerFlowParams.sweepTimeout, + )(ctx) + + // 2. inferior grids p/q values + askInferiorGridsForPowers( + gridAgentBaseData.currentSweepNo, + gridAgentBaseData.gridEnv.subgridGateToActorRef, + gridAgentBaseData.inferiorGridGates, + gridAgentBaseData.powerFlowParams.sweepTimeout, + )(ctx) + + // 3. superior grids slack voltage + askSuperiorGridsForSlackVoltages( + gridAgentBaseData.currentSweepNo, + gridAgentBaseData.gridEnv.subgridGateToActorRef, + gridAgentBaseData.superiorGridGates, + gridAgentBaseData.powerFlowParams.sweepTimeout, + )(ctx) + } - // notify listener about the results + private def updateWithReceivedValues( + receivedValues: ReceivedValues, + gridAgentBaseData: GridAgentBaseData, + ): GridAgentBaseData = { + + // we just received either all provided slack voltage values or all provided power values + receivedValues match { + case receivedPowers: ReceivedPowerValues => + /* Can be a message from an asset or a message from an inferior grid */ + gridAgentBaseData.updateWithReceivedPowerValues(receivedPowers) + case receivedSlacks: ReceivedSlackVoltageValues => + gridAgentBaseData.updateWithReceivedSlackVoltages( + receivedSlacks + ) + case unknownReceivedValues => + throw new DBFSAlgorithmException( + s"Received unknown values: $unknownReceivedValues" + ) + } + + } + + private def handleSlackVoltageRequest( + currentSweepNo: Int, + nodeUuids: Seq[UUID], + sender: ActorRef[SlackVoltageResponse], + gridAgentBaseData: GridAgentBaseData, + ctx: ActorContext[_], + ): Behavior[GridAgent.Request] = { + // if we receive a request for slack voltages from our inferior grids we want to answer it + ctx.log.debug( + s"Received Slack Voltages request from {} for nodes {} and sweepNo: {}", + sender, + nodeUuids, + gridAgentBaseData.currentSweepNo, + ) + + nodeUuids.map { nodeUuid => + // we either have voltages ready calculated (not the first sweep) or we don't have them here + // -> return calculated value or target voltage as physical value + (gridAgentBaseData.sweepValueStores.get(currentSweepNo) match { + case Some(result) => + Some(result, currentSweepNo) + case None => + // this happens if this agent is either a) the superior grid agent, because it will always get a request for + // the next sweep, as it triggers calculations for the next sweep or b) at all other + // (non last downstream grid agents) in sweep 0 ctx.log.debug( - "Calculate results and sending the results to the listener ..." + "Unable to find slack voltage for nodes '{}' in sweep '{}'. Try to get voltage of previous sweep.", + nodeUuids, + currentSweepNo, ) - createAndSendPowerFlowResults( - gridAgentBaseData, - currentTick.toDateTime(constantData.simStartTime), - )(ctx.log, constantData) + gridAgentBaseData.sweepValueStores + .get(currentSweepNo - 1) + .map((_, currentSweepNo - 1)) + }).map { case (result, sweepNo) => + // get nodeUUID + result.sweepData.find(_.nodeUuid == nodeUuid) match { + case Some(sweepValueStoreData) => + val slackVoltageInPu = sweepValueStoreData.stateData.voltage + val mainRefSystem = + gridAgentBaseData.gridEnv.gridModel.mainRefSystem + ( + mainRefSystem.vInSi(slackVoltageInPu.real), + mainRefSystem.vInSi(slackVoltageInPu.imag), + ) + case None => + throw new DBFSAlgorithmException( + s"Requested nodeUuid $nodeUuid " + + s"not found in sweep value store data for sweepNo: $sweepNo. This indicates" + + s"either a wrong processing of a previous sweep result or inconsistencies in grid model data!" + ) + } + }.getOrElse { + ctx.log.debug( + "Unable to get slack voltage for node '{}' in sweeps '{}' and '{}'. Returning target voltage.", + nodeUuid, + currentSweepNo, + currentSweepNo - 1, + ) - // do my cleanup stuff - ctx.log.debug("Doing my cleanup stuff") + val refSystem = + gridAgentBaseData.gridEnv.gridModel.mainRefSystem - // / clean copy of the gridAgentBaseData - val cleanedGridAgentBaseData = GridAgentBaseData.clean( - gridAgentBaseData, - gridAgentBaseData.superiorGridNodeUuids, - gridAgentBaseData.inferiorGridGates, + /* 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, _, _) => + uuid == nodeUuid && isSlack + } + .map(_.vTarget) + .getOrElse(Each(1d)) + val vSlack = + refSystem.nominalVoltage.multiplyWithDimensionles(vTarget) + + ( + vSlack, + refSystem.vInSi(0d), + ) + } match { + case (slackE, slackF) => + ctx.log.debug( + s"Provide {} to {} for node {} and sweepNo: {}", + s"$slackE, $slackF", + sender, + nodeUuid, + gridAgentBaseData.currentSweepNo, ) - // / 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), - ) + ExchangeVoltage(nodeUuid, slackE, slackF) + } + } match { + case exchangeVoltages => + sender ! SlackVoltageResponse( + currentSweepNo, + exchangeVoltages, + ) + Behaviors.same + } + } + private def handleGridPowerRequestBeforeCalculation( + requestSweepNo: Int, + msg: RequestGridPower, + gridAgentBaseData: GridAgentBaseData, + currentTick: Long, + ctx: ActorContext[GridAgent.Request], + )(implicit + constantData: GridAgentConstantData, + buffer: StashBuffer[GridAgent.Request], + ): Behavior[GridAgent.Request] = { + if (gridAgentBaseData.currentSweepNo == requestSweepNo) { + ctx.log.debug( + s"Received request for grid power values for sweepNo {} before my first power flow calc. Stashing away.", + requestSweepNo, + ) - // return to Idle - idle(cleanedGridAgentBaseData) + buffer.stash(msg) - case _ => - // preventing "match may not be exhaustive" - Behaviors.unhandled - } + Behaviors.same + } else { + ctx.log.debug( + s"Received request for grid power values for a NEW sweep (request: {}, my: {})", + requestSweepNo, + gridAgentBaseData.currentSweepNo, + ) + ctx.self ! PrepareNextSweepTrigger(currentTick) + + buffer.stash(msg) + + simulateGrid( + gridAgentBaseData.copy(currentSweepNo = requestSweepNo), + currentTick, + ) + } + } + + private def handleGridPowerRequestAfterCalculation( + requestedNodeUuids: Seq[UUID], + sender: ActorRef[GridAgentMessages.PowerResponse], + powerFlowDoneData: PowerFlowDoneData, + currentTick: Long, + ctx: ActorContext[GridAgent.Request], + )(implicit + buffer: StashBuffer[GridAgent.Request], + constantData: GridAgentConstantData, + ): Behavior[GridAgent.Request] = { + + // Extract the necessary variables + val gridAgentBaseData = powerFlowDoneData.gridAgentBaseData + val powerFlowResult = powerFlowDoneData.powerFlowResult + val pendingRequestAnswers = powerFlowDoneData.pendingRequestAnswers + + // Determine the subgrid number of the grid agent that has sent the request + val firstRequestedNodeUuid = requestedNodeUuids.headOption.getOrElse( + throw new DBFSAlgorithmException( + "Did receive a grid power request but without specified nodes" + ) + ) + + gridAgentBaseData.gridEnv.subgridGateToActorRef + .collectFirst { + case (subGridGate, _) + if subGridGate.superiorNode.getUuid == firstRequestedNodeUuid => + subGridGate.superiorNode.getSubnet + } match { + case Some(requestingSubgridNumber) => + powerFlowResult match { + case validNewtonRaphsonPFResult: ValidNewtonRaphsonPFResult => + val exchangePowers = requestedNodeUuids + .map { nodeUuid => + // Figure out the node index for each requested node + nodeUuid -> gridAgentBaseData.gridEnv.gridModel.nodeUuidToIndexMap + .get(nodeUuid) + .flatMap { nodeIndex => + // Find matching node result + validNewtonRaphsonPFResult.nodeData.find(stateData => + stateData.index == nodeIndex + ) + } + .map { + case StateData(_, nodeType, _, power) + if nodeType == NodeType.SL => + val refSystem = + gridAgentBaseData.gridEnv.gridModel.mainRefSystem + val (pInPu, qInPu) = (power.real, power.imag) + // The power flow result data provides the nodal residual power at the slack node. + // A feed-in case from the inferior grid TO the superior grid leads to positive residual power at the + // inferior grid's *slack node* (superior grid seems to be a load to the inferior grid). + // To model the exchanged power from the superior grid's point of view, -1 has to be multiplied. + // (Inferior grid is a feed in facility to superior grid, which is negative then). Analogously for load case. + ( + refSystem.pInSi(pInPu) * (-1), + refSystem.qInSi(qInPu) * (-1), + ) + case _ => + // As long as there are no multiple slack nodes, provide "real" power only for the slack node + ( + zeroMW, + zeroMVAr, + ) + } + .getOrElse { + throw new DBFSAlgorithmException( + s"Got a request for power @ node with uuid $requestedNodeUuids but cannot find it in my result data!" + ) + } + } + .map { case (nodeUuid, (p, q)) => + Responses.ExchangePower( + nodeUuid, + p, + q, + ) + } + + // Determine the remaining replies + val stillPendingRequestAnswers = + pendingRequestAnswers.filterNot( + _ == requestingSubgridNumber + ) + + // Update the sweep value store and clear all received maps + // Note: normally it is expected that this has to be done after power flow calculations but for the sake + // of having it only once in the code we put this here. Otherwise it would have to been put before EVERY + // return with a valid power flow result (currently happens already in two situations) + val updatedGridAgentBaseData = + if (stillPendingRequestAnswers.isEmpty) { + gridAgentBaseData.storeSweepDataAndClearReceiveMaps( + validNewtonRaphsonPFResult, + gridAgentBaseData.superiorGridNodeUuids, + gridAgentBaseData.inferiorGridGates, + ) + } else { + powerFlowDoneData.copy(pendingRequestAnswers = + stillPendingRequestAnswers + ) + } + + sender ! GridAgentMessages.GridPowerResponse(exchangePowers) + simulateGrid(updatedGridAgentBaseData, currentTick) + + case _: FailedNewtonRaphsonPFResult => + sender ! GridAgentMessages.FailedPowerFlow + simulateGrid(gridAgentBaseData, currentTick) + } + case None => + // It is not possible to determine who has asked + ctx.log.error( + "I got a grid power request from a subgrid I don't know. Can't answer it properly." + ) + + sender ! GridAgentMessages.FailedPowerFlow + simulateGrid(gridAgentBaseData, currentTick) + } + } + + private def requestUpdatedSlackVoltages( + gridAgentBaseData: GridAgentBaseData, + currentTick: Long, + ctx: ActorContext[_], + )(implicit + buffer: StashBuffer[GridAgent.Request], + constantData: GridAgentConstantData, + ): Unit = { + val typedCtx = ctx.asInstanceOf[ActorContext[GridAgent.Request]] + + askSuperiorGridsForSlackVoltages( + gridAgentBaseData.currentSweepNo, + gridAgentBaseData.gridEnv.subgridGateToActorRef, + gridAgentBaseData.superiorGridGates, + gridAgentBaseData.powerFlowParams.sweepTimeout, + )(typedCtx) + + ctx.log.debug("Going to HandlePowerFlowCalculation") + + handlePowerFlowCalculations(gridAgentBaseData, currentTick) } /** Method that defines the [[Behavior]] for handling the power flow @@ -808,6 +855,63 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { } } + private def finishGridSimulation( + gridAgentBaseData: GridAgentBaseData, + currentTick: Long, + ctx: ActorContext[_], + )(implicit + constantData: GridAgentConstantData, + buffer: StashBuffer[GridAgent.Request], + ): Behavior[GridAgent.Request] = { + + // last step which should includes a) information on inferior grids about finish and + // b) cleanup of receiveMaps and sweepStore + + // inform my child grids about the end of this grid simulation + gridAgentBaseData.inferiorGridGates + .map { + gridAgentBaseData.gridEnv.subgridGateToActorRef(_) + } + .distinct + .foreach( + _ ! FinishGridSimulationTrigger(currentTick) + ) + + // inform every system participant about the end of this grid simulation + gridAgentBaseData.gridEnv.nodeToAssetAgents.foreach { case (_, actors) => + actors.foreach { actor => + actor ! FinishParticipantSimulation(currentTick) + } + } + + // 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, + ) + + // / 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) + } + /** Method used for checking the power difference.

This method should only * be reached by the superior (dummy) grid agent. * @param gridAgentBaseData @@ -1113,9 +1217,9 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { if (gridAgentBaseData.powerFlowParams.stopOnFailure) { ctx.log.error("Stopping because of failed power flow.") Behaviors.stopped + } else { + simulateGrid(gridAgentBaseData, currentTick) } - - simulateGrid(gridAgentBaseData, currentTick) } /** Normally only reached by the superior (dummy) agent! From 6ee088a211a399525d52d7604a8d66b3d269ee6d Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Wed, 29 May 2024 12:41:17 +0200 Subject: [PATCH 2/5] add scaladoc to dos and remove usage of asInstanceOf --- .../ie3/simona/agent/grid/DBFSAlgorithm.scala | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 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 8e4d7dc1e5..9ba3b44332 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala @@ -211,6 +211,7 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { } // Helper methods for different parts of the simulation + // TODO ScalaDoc private def requestInitialData( gridAgentBaseData: GridAgentBaseData, currentTick: Long, @@ -245,6 +246,7 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { )(ctx) } + // TODO ScalaDoc private def updateWithReceivedValues( receivedValues: ReceivedValues, gridAgentBaseData: GridAgentBaseData, @@ -266,7 +268,7 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { } } - + // TODO ScalaDoc private def handleSlackVoltageRequest( currentSweepNo: Int, nodeUuids: Seq[UUID], @@ -365,6 +367,7 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { Behaviors.same } } + // TODO ScalaDoc private def handleGridPowerRequestBeforeCalculation( requestSweepNo: Int, msg: RequestGridPower, @@ -400,7 +403,7 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { ) } } - + // TODO ScalaDoc private def handleGridPowerRequestAfterCalculation( requestedNodeUuids: Seq[UUID], sender: ActorRef[GridAgentMessages.PowerResponse], @@ -520,23 +523,21 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { simulateGrid(gridAgentBaseData, currentTick) } } - - private def requestUpdatedSlackVoltages( + // TODO ScalaDoc + private def requestUpdatedSlackVoltages[T <: GridAgent.Request]( gridAgentBaseData: GridAgentBaseData, currentTick: Long, - ctx: ActorContext[_], + ctx: ActorContext[GridAgent.Request], )(implicit buffer: StashBuffer[GridAgent.Request], constantData: GridAgentConstantData, ): Unit = { - val typedCtx = ctx.asInstanceOf[ActorContext[GridAgent.Request]] - askSuperiorGridsForSlackVoltages( gridAgentBaseData.currentSweepNo, gridAgentBaseData.gridEnv.subgridGateToActorRef, gridAgentBaseData.superiorGridGates, gridAgentBaseData.powerFlowParams.sweepTimeout, - )(typedCtx) + )(ctx) ctx.log.debug("Going to HandlePowerFlowCalculation") From 18edf6de7ef34fa394da9786d8467d72a2211c4f Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Wed, 29 May 2024 13:00:43 +0200 Subject: [PATCH 3/5] provide scala doc --- .../ie3/simona/agent/grid/DBFSAlgorithm.scala | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 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 9ba3b44332..2a64300f52 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala @@ -210,8 +210,10 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { } } - // Helper methods for different parts of the simulation - // TODO ScalaDoc + /** Helper method for [[simulateGrid()]] Requesting for active and reactive + * power values of all nodes within this [[GridAgent]] and return it. Same + * for slack voltage power from superior grid. + */ private def requestInitialData( gridAgentBaseData: GridAgentBaseData, currentTick: Long, @@ -246,7 +248,9 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { )(ctx) } - // TODO ScalaDoc + /** Helper method for [[simulateGrid()]] Updates the values stores with + * received data. + */ private def updateWithReceivedValues( receivedValues: ReceivedValues, gridAgentBaseData: GridAgentBaseData, @@ -268,7 +272,10 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { } } - // TODO ScalaDoc + + /** Helper method for [[simulateGrid()]] Handles all requests for slack + * voltages from inferior grids. + */ private def handleSlackVoltageRequest( currentSweepNo: Int, nodeUuids: Seq[UUID], @@ -367,7 +374,10 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { Behaviors.same } } - // TODO ScalaDoc + + /** Helper method for [[simulateGrid()]] Handles all power requests **before** + * the next calculation step. + */ private def handleGridPowerRequestBeforeCalculation( requestSweepNo: Int, msg: RequestGridPower, @@ -403,7 +413,10 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { ) } } - // TODO ScalaDoc + + /** Helper method for [[simulateGrid()]] Handles all power requests **after** + * the calculation step done before. + */ private def handleGridPowerRequestAfterCalculation( requestedNodeUuids: Seq[UUID], sender: ActorRef[GridAgentMessages.PowerResponse], @@ -523,7 +536,10 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { simulateGrid(gridAgentBaseData, currentTick) } } - // TODO ScalaDoc + + /** Helper method for [[simulateGrid()]] Ask its superior grids for updated + * slack voltages and handles the necessary power flow calculations. + */ private def requestUpdatedSlackVoltages[T <: GridAgent.Request]( gridAgentBaseData: GridAgentBaseData, currentTick: Long, @@ -856,6 +872,11 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { } } + /** Helper method for [[simulateGrid()]] which contains all final steps. + * Mainly informs all entities about finishing simulation of this tick and do + * clean up. + */ + private def finishGridSimulation( gridAgentBaseData: GridAgentBaseData, currentTick: Long, From 863891a532538b43715b77e5fc7b18cad58c9845 Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Wed, 17 Jul 2024 11:58:56 +0200 Subject: [PATCH 4/5] fmt --- .../ie3/simona/agent/grid/DBFSAlgorithm.scala | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 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 3d94e0fd07..b5b055af7e 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala @@ -16,18 +16,10 @@ import edu.ie3.powerflow.model.PowerFlowResult.FailedPowerFlowResult.FailedNewto 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.GridAgentData.{ - GridAgentBaseData, - GridAgentConstantData, - PowerFlowDoneData, -} +import edu.ie3.simona.agent.grid.GridAgentData.{GridAgentBaseData, GridAgentConstantData, PowerFlowDoneData} import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.ExchangeVoltage import edu.ie3.simona.agent.grid.GridAgentMessages._ -import edu.ie3.simona.agent.participant.ParticipantAgent.{ - FinishParticipantSimulation, - ParticipantMessage, - RequestAssetPowerMessage, -} +import edu.ie3.simona.agent.participant.ParticipantAgent.{FinishParticipantSimulation, ParticipantMessage, RequestAssetPowerMessage} import edu.ie3.simona.event.RuntimeEvent.PowerFlowFailed import edu.ie3.simona.exceptions.agent.DBFSAlgorithmException import edu.ie3.simona.model.grid.{NodeModel, RefSystem} @@ -38,11 +30,7 @@ import edu.ie3.util.scala.quantities.DefaultQuantities._ import edu.ie3.util.scala.quantities.SquantsUtils.RichElectricPotential import org.apache.pekko.actor.typed.scaladsl.AskPattern._ import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps -import org.apache.pekko.actor.typed.scaladsl.{ - ActorContext, - Behaviors, - StashBuffer, -} +import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} import org.apache.pekko.actor.typed.{ActorRef, Behavior, Scheduler} import org.apache.pekko.pattern.ask import org.apache.pekko.util.{Timeout => PekkoTimeout} @@ -61,6 +49,7 @@ import scala.util.{Failure, Success} trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { /** Method that defines the [[Behavior]] for simulating the grid. + * * @param gridAgentData * state data of the actor * @param currentTick @@ -536,9 +525,10 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { "I got a grid power request from a subgrid I don't know. Can't answer it properly." ) - sender ! FailedPowerFlow - Behaviors.stopped - } + sender ! FailedPowerFlow + Behaviors.stopped + } + } /** Helper method for [[simulateGrid()]] Ask its superior grids for updated * slack voltages and handles the necessary power flow calculations. @@ -926,6 +916,7 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { /** Method used for checking the power difference.

This method should only * be reached by the superior (dummy) grid agent. + * * @param gridAgentBaseData * state data of the actor * @return @@ -1211,6 +1202,7 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport { } /** Method for handling failed power flows. + * * @param gridAgentBaseData * state data of the actor * @param currentTick From e89e5df1965a2a5aca3a6589e3fbbd3804aff17b Mon Sep 17 00:00:00 2001 From: danielfeismann Date: Wed, 17 Jul 2024 12:02:03 +0200 Subject: [PATCH 5/5] fmt --- .../ie3/simona/agent/grid/DBFSAlgorithm.scala | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 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 b5b055af7e..8928215c8d 100644 --- a/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala +++ b/src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala @@ -16,10 +16,18 @@ import edu.ie3.powerflow.model.PowerFlowResult.FailedPowerFlowResult.FailedNewto 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.GridAgentData.{GridAgentBaseData, GridAgentConstantData, PowerFlowDoneData} +import edu.ie3.simona.agent.grid.GridAgentData.{ + GridAgentBaseData, + GridAgentConstantData, + PowerFlowDoneData, +} import edu.ie3.simona.agent.grid.GridAgentMessages.Responses.ExchangeVoltage import edu.ie3.simona.agent.grid.GridAgentMessages._ -import edu.ie3.simona.agent.participant.ParticipantAgent.{FinishParticipantSimulation, ParticipantMessage, RequestAssetPowerMessage} +import edu.ie3.simona.agent.participant.ParticipantAgent.{ + FinishParticipantSimulation, + ParticipantMessage, + RequestAssetPowerMessage, +} import edu.ie3.simona.event.RuntimeEvent.PowerFlowFailed import edu.ie3.simona.exceptions.agent.DBFSAlgorithmException import edu.ie3.simona.model.grid.{NodeModel, RefSystem} @@ -30,7 +38,11 @@ import edu.ie3.util.scala.quantities.DefaultQuantities._ import edu.ie3.util.scala.quantities.SquantsUtils.RichElectricPotential import org.apache.pekko.actor.typed.scaladsl.AskPattern._ import org.apache.pekko.actor.typed.scaladsl.adapter.TypedActorRefOps -import org.apache.pekko.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import org.apache.pekko.actor.typed.scaladsl.{ + ActorContext, + Behaviors, + StashBuffer, +} import org.apache.pekko.actor.typed.{ActorRef, Behavior, Scheduler} import org.apache.pekko.pattern.ask import org.apache.pekko.util.{Timeout => PekkoTimeout}