Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for topologies without transformers and slack grids with multiple nodes #1104

Merged
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