Skip to content

Commit

Permalink
Merge pull request #1104 from ie3-institute/ms/#1099-allow-for-topolo…
Browse files Browse the repository at this point in the history
…gies-without-transformers

Added support for topologies without transformers and slack grids with multiple nodes
  • Loading branch information
danielfeismann authored Jan 28, 2025
2 parents fbea5e8 + d3f99a2 commit 4036f4b
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 79 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Introduce ThermalDemandWrapper [#1049](https://github.com/ie3-institute/simona/issues/1049)
- Added Marius Staudt to list of reviewers [#1057](https://github.com/ie3-institute/simona/issues/1057)
- Throw exception if the slack node is not directly conected to a transformer. [#525](https://github.com/ie3-institute/simona/issues/525)
- Added support for topologies without transformers and slack grids with multiple nodes [#1099](https://github.com/ie3-institute/simona/issues/1099)

### Changed
- Adapted to changed data source in PSDM [#435](https://github.com/ie3-institute/simona/issues/435)
Expand Down
51 changes: 6 additions & 45 deletions src/main/scala/edu/ie3/simona/agent/grid/DBFSAlgorithm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

package edu.ie3.simona.agent.grid

import breeze.linalg.{DenseMatrix, DenseVector}
import breeze.linalg.DenseVector
import breeze.math.Complex
import edu.ie3.datamodel.graph.SubGridGate
import edu.ie3.powerflow.model.FailureCause.CalculationFailed
Expand Down Expand Up @@ -806,7 +806,7 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport {
* @return
* a [[Behavior]]
*/
private def checkPowerDifferences(
private[grid] def checkPowerDifferences(
gridAgentBaseData: GridAgentBaseData
)(implicit
constantData: GridAgentConstantData,
Expand All @@ -817,50 +817,11 @@ trait DBFSAlgorithm extends PowerFlowSupport with GridResultsSupport {
ctx.log.debug("Starting the power differences check ...")
val currentSweepNo = gridAgentBaseData.currentSweepNo

val gridModel = gridAgentBaseData.gridEnv.gridModel

/* This is the highest grid agent, therefore no data is received for the slack node. Suppress, that it is looked
* up in the empty store. */
val (operationPoint, slackNodeVoltages) = composeOperatingPoint(
gridModel.gridComponents.nodes,
gridModel.gridComponents.transformers,
gridModel.gridComponents.transformers3w,
gridModel.nodeUuidToIndexMap,
slackGridPF(
gridAgentBaseData.gridEnv.gridModel,
gridAgentBaseData.receivedValueStore,
gridModel.mainRefSystem,
targetVoltageFromReceivedData = false,
)

/* Regarding the power flow result of this grid, there are two cases. If this is the "highest" grid in a
* simulation without a three winding transformer, the grid consists of only one node, and we can mock the power
* flow results. If there is a three winding transformer apparent, we actually have to perform power flow
* calculations, as the high voltage branch of the transformer is modeled here. */
(if (gridModel.gridComponents.transformers3w.isEmpty) {
val nodeData = operationPoint.map(StateData(_))
ValidNewtonRaphsonPFResult(-1, nodeData, DenseMatrix(0d, 0d))
} else {
ctx.log.debug(
"This grid contains a three winding transformer. Perform power flow calculations before assessing the power deviations."
)
newtonRaphsonPF(
gridModel,
gridAgentBaseData.powerFlowParams.maxIterations,
operationPoint,
slackNodeVoltages,
)(gridAgentBaseData.powerFlowParams.epsilon)(ctx.log) match {
case validPowerFlowResult: ValidNewtonRaphsonPFResult =>
ctx.log.debug(
"{}",
composeValidNewtonRaphsonPFResultVoltagesDebugString(
validPowerFlowResult,
gridModel,
),
)
validPowerFlowResult
case result: PowerFlowResult.FailedPowerFlowResult =>
result
}
}) match {
gridAgentBaseData.powerFlowParams,
)(ctx.log) match {
case validResult: ValidNewtonRaphsonPFResult =>
val updatedGridAgentBaseData: GridAgentBaseData =
gridAgentBaseData
Expand Down
14 changes: 10 additions & 4 deletions src/main/scala/edu/ie3/simona/agent/grid/GridAgent.scala
Original file line number Diff line number Diff line change
Expand Up @@ -81,25 +81,30 @@ object GridAgent extends DBFSAlgorithm {
simonaConfig: SimonaConfig,
): Behavior[Request] =
Behaviors.receiveMessagePartial {
case CreateGridAgent(gridAgentInitData, unlockKey) =>
case CreateGridAgent(gridAgentInitData, unlockKey, onlyOneSubGrid) =>
constantData.environmentRefs.scheduler ! ScheduleActivation(
constantData.activationAdapter,
INIT_SIM_TICK,
Some(unlockKey),
)
initializing(gridAgentInitData, simonaConfig)
initializing(gridAgentInitData, simonaConfig, onlyOneSubGrid)
}

private def initializing(
gridAgentInitData: GridAgentInitData,
simonaConfig: SimonaConfig,
onlyOneSubGrid: Boolean,
)(implicit
constantData: GridAgentConstantData,
buffer: StashBuffer[Request],
): Behavior[Request] = Behaviors.receivePartial {
case (ctx, WrappedActivation(Activation(INIT_SIM_TICK))) =>
// fail fast sanity checks
failFast(gridAgentInitData, SimonaActorNaming.actorName(ctx.self))
failFast(
gridAgentInitData,
SimonaActorNaming.actorName(ctx.self),
onlyOneSubGrid,
)

ctx.log.debug(
s"Inferior Subnets: {}; Inferior Subnet Nodes: {}",
Expand Down Expand Up @@ -227,9 +232,10 @@ object GridAgent extends DBFSAlgorithm {
private def failFast(
gridAgentInitData: GridAgentInitData,
actorName: String,
onlyOneSubGrid: Boolean,
): Unit = {
if (
gridAgentInitData.superiorGridGates.isEmpty && gridAgentInitData.inferiorGridGates.isEmpty
gridAgentInitData.superiorGridGates.isEmpty && gridAgentInitData.inferiorGridGates.isEmpty && !onlyOneSubGrid
)
throw new GridAgentInitializationException(
s"$actorName has neither superior nor inferior grids! This can either " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ object GridAgentMessages {
final case class CreateGridAgent(
gridAgentInitData: GridAgentInitData,
unlockKey: ScheduleKey,
onlyOneSubGrid: Boolean = false,
) extends GridAgent.InternalRequest

/** Trigger used inside of [[edu.ie3.simona.agent.grid.DBFSAlgorithm]] to
Expand Down
73 changes: 73 additions & 0 deletions src/main/scala/edu/ie3/simona/agent/grid/PowerFlowSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

package edu.ie3.simona.agent.grid

import breeze.linalg.DenseMatrix
import breeze.math.Complex
import edu.ie3.powerflow.NewtonRaphsonPF
import edu.ie3.powerflow.model.NodeData.{PresetData, StateData}
Expand Down Expand Up @@ -611,4 +612,76 @@ trait PowerFlowSupport {
)
}
}

/** Calculates the power flow for the grid that contains the slack node.
* @param gridModel
* model of the slack grid
* @param receivedValueStore
* received values
* @param powerFlowParams
* parameters for the power flow calculation
* @param log
* for logging
* @return
* power flow results
*/
protected final def slackGridPF(
gridModel: GridModel,
receivedValueStore: ReceivedValuesStore,
powerFlowParams: PowerFlowParams,
)(implicit log: Logger): PowerFlowResult = {
/* This is the highest grid agent, therefore no data is received for the slack node. Suppress, that it is looked
* up in the empty store. */
val (operationPoint, slackNodeVoltages) = composeOperatingPoint(
gridModel.gridComponents.nodes,
gridModel.gridComponents.transformers,
gridModel.gridComponents.transformers3w,
gridModel.nodeUuidToIndexMap,
receivedValueStore,
gridModel.mainRefSystem,
targetVoltageFromReceivedData = false,
)

def superiorPowerFlow: PowerFlowResult =
newtonRaphsonPF(
gridModel,
powerFlowParams.maxIterations,
operationPoint,
slackNodeVoltages,
)(powerFlowParams.epsilon) match {
case validPowerFlowResult: ValidNewtonRaphsonPFResult =>
log.debug(
"{}",
composeValidNewtonRaphsonPFResultVoltagesDebugString(
validPowerFlowResult,
gridModel,
),
)
validPowerFlowResult
case result: PowerFlowResult.FailedPowerFlowResult =>
result
}

/* Regarding the power flow result of this grid, there are two cases. If this is the "highest" grid in a
* simulation without a three winding transformer, the grid consists of only one node, and we can mock the power
* flow results. If there is a three winding transformer apparent, we actually have to perform power flow
* calculations, as the high voltage branch of the transformer is modeled here. */
gridModel.gridComponents.transformers3w.isEmpty match {
case true if gridModel.gridComponents.nodes.size == 1 =>
val nodeData = operationPoint.map(StateData(_))
ValidNewtonRaphsonPFResult(-1, nodeData, DenseMatrix(0d, 0d))

case true =>
log.warn(
"This grid contains a more than just a slack node. Perform power flow calculations before assessing the power deviations."
)
superiorPowerFlow

case false =>
log.debug(
"This grid contains a three winding transformer. Perform power flow calculations before assessing the power deviations."
)
superiorPowerFlow
}
}
}
23 changes: 0 additions & 23 deletions src/main/scala/edu/ie3/simona/io/grid/GridProvider.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
package edu.ie3.simona.io.grid

import com.typesafe.scalalogging.LazyLogging
import edu.ie3.datamodel.exceptions.{InvalidGridException, SourceException}
import edu.ie3.datamodel.io.naming.FileNamingStrategy
import edu.ie3.datamodel.io.source.csv.{
CsvJointGridContainerSource,
Expand All @@ -23,7 +22,6 @@ import edu.ie3.simona.config.SimonaConfig

import java.nio.file.Path
import scala.jdk.CollectionConverters._
import scala.util.{Failure, Success, Try}

/** Takes [[edu.ie3.simona.config.SimonaConfig.Simona.Input.Grid.Datasource]] as
* input and provides a [[JointGridContainer]] based on the configuration incl.
Expand Down Expand Up @@ -52,27 +50,6 @@ object GridProvider extends LazyLogging {
// checks the grid container and throws exception if there is an error
ValidationUtils.check(jointGridContainer)

// check slack node location
val slackSubGrid = jointGridContainer.getSubGridTopologyGraph
.vertexSet()
.asScala
.filter(_.getRawGrid.getNodes.asScala.exists(_.isSlack))
.maxByOption(
_.getPredominantVoltageLevel.getNominalVoltage.getValue
.doubleValue()
)
.getOrElse(
throw new InvalidGridException(
"There is no slack node present in the grid."
)
)

if (slackSubGrid.getRawGrid.getNodes.size() > 1) {
throw new SourceException(
"There are too many nodes in the slack grid. This is currently not support."
)
}

jointGridContainer
case None =>
throw new RuntimeException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,13 @@ class SimonaStandaloneSetup(
)

/* build the initialization data */
subGridTopologyGraph
val subGrids = subGridTopologyGraph
.vertexSet()
.asScala

val onlyOneSubGrid = subGrids.size == 1

subGrids
.zip(keys)
.map { case (subGridContainer, key) =>
/* Get all connections to superior and inferior sub grids */
Expand Down Expand Up @@ -140,7 +144,11 @@ class SimonaStandaloneSetup(
thermalGrids,
)

currentActorRef ! CreateGridAgent(gridAgentInitData, key)
currentActorRef ! CreateGridAgent(
gridAgentInitData,
key,
onlyOneSubGrid,
)

currentActorRef
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ trait DBFSMockGridAgents extends UnitSpec {
def expectSlackVoltageRequest(
expectedSweepNo: Int
): ActorRef[GridAgent.Request] =
gaProbe.expectMessageType[GridAgent.Request] match {
gaProbe.expectMessageType[GridAgent.Request](10.seconds) match {
case requestSlackVoltageMessage: SlackVoltageRequest =>
requestSlackVoltageMessage.currentSweepNo shouldBe expectedSweepNo
requestSlackVoltageMessage.nodeUuids should have size nodeUuids.size
Expand Down
Loading

0 comments on commit 4036f4b

Please sign in to comment.