Skip to content

Commit

Permalink
Merge branch 'refs/heads/dev' into df/#856-tap-water
Browse files Browse the repository at this point in the history
  • Loading branch information
danielfeismann committed Aug 14, 2024
2 parents 18356c3 + 5df170c commit fc93131
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 92 deletions.
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,13 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk

# End of https://www.gitignore.io/api/java,macos,windows,eclipse
# Metals
.metals/
.bloop/
project/**/metals.sbt


# End of https://www.gitignore.io/api/java,macos,windows,eclipse, metals

/target/

Expand Down
112 changes: 70 additions & 42 deletions src/main/scala/edu/ie3/simona/model/grid/GridModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import org.jgrapht.graph.{DefaultEdge, SimpleGraph}

import java.time.ZonedDateTime
import java.util.UUID
import scala.collection.immutable.ListSet
import scala.jdk.CollectionConverters._

/** Representation of one physical electrical grid. It holds the references to
Expand Down Expand Up @@ -440,19 +439,6 @@ object GridModel {
throw new InvalidGridException(
s"The grid model for subnet ${gridModel.subnetNo} has multiple nodes with the same name!"
)

// multiple switches @ one node -> not supported yet!
val switchVector = gridModel.gridComponents.switches.foldLeft(
Vector.empty[UUID]
)((vector, switch) => (vector :+ switch.nodeAUuid) :+ switch.nodeBUuid)
val uniqueSwitchNodeIds = switchVector.toSet.toList
if (switchVector.diff(uniqueSwitchNodeIds).nonEmpty) {
throw new InvalidGridException(
s"The grid model for subnet ${gridModel.subnetNo} has nodes with multiple switches. This is not supported yet! Duplicates are located @ nodes: ${switchVector
.diff(uniqueSwitchNodeIds)}"
)
}

}

/** Checks all ControlGroups if a) Transformer of ControlGroup and Measurement
Expand Down Expand Up @@ -646,40 +632,82 @@ object GridModel {
def updateUuidToIndexMap(gridModel: GridModel): Unit = {

val switches = gridModel.gridComponents.switches
val nodes = gridModel.gridComponents.nodes
val nodes = gridModel.gridComponents.nodes.distinct

// map for each node that is directly connected to a switch,
// to all nodes that are directly connected via switch
val nodeConnections: Map[UUID, Set[UUID]] =
switches.filter(_.isClosed).foldLeft(Map.empty[UUID, Set[UUID]]) {
(acc, switch) =>
acc
.updated(
switch.nodeAUuid,
acc.getOrElse(switch.nodeAUuid, Set.empty) + switch.nodeBUuid,
)
.updated(
switch.nodeBUuid,
acc.getOrElse(switch.nodeBUuid, Set.empty) + switch.nodeAUuid,
)
}

val nodesAndSwitches: ListSet[SystemComponent] = ListSet
.empty[SystemComponent] ++ switches ++ nodes
// create sets of nodes to be fused together and assign common indices
val switchConnectedNodes =
findConnectedNodes(nodeConnections).zipWithIndex.flatMap {
case (nodes, idx) => nodes.map(_ -> idx)
}.toMap

val updatedNodeToUuidMap = nodesAndSwitches
// also account for all missing nodes (not connected to a switch)
val offset = switchConnectedNodes.values.maxOption.map(_ + 1).getOrElse(0)
val (updatedNodeToUuidMap, _) = nodes
.filter(_.isInOperation)
.filter {
case switch: SwitchModel => switch.isClosed
case _: NodeModel => true
}
.zipWithIndex
.foldLeft(Map.empty[UUID, Int]) {
case (map, (gridComponent, componentId)) =>
gridComponent match {
case switchModel: SwitchModel =>
map ++ Map(
switchModel.nodeAUuid -> componentId,
switchModel.nodeBUuid -> componentId,
)

case nodeModel: NodeModel =>
if (!map.contains(nodeModel.uuid)) {
val idx = map.values.toList.sorted.lastOption
.getOrElse(
-1
) + 1 // if we didn't found anything in the list, we don't have switches and want to start @ 0
map + (nodeModel.uuid -> idx)
} else {
map
}
.foldLeft(Map.empty[UUID, Int], offset) {
case ((map, nextIdx), nodeModel) =>
switchConnectedNodes.get(nodeModel.uuid) match {
case Some(idx) => (map + (nodeModel.uuid -> idx), nextIdx)
case None =>
(map + (nodeModel.uuid -> nextIdx), nextIdx + 1)
}
}

gridModel._nodeUuidToIndexMap = updatedNodeToUuidMap
}

/** Build sets of connected nodes via depth-first search
*
* @param nodeConnections
* The connected nodes for each node
* @return
* The sets of nodes
*/
private def findConnectedNodes(
nodeConnections: Map[UUID, Set[UUID]]
): Seq[Seq[UUID]] = {

def dfs(node: UUID, visited: Set[UUID] = Set.empty): Set[UUID] = {
nodeConnections
.getOrElse(node, Set.empty)
.foldLeft(visited + node) { case (accVisited, neighbor) =>
if (accVisited.contains(neighbor))
accVisited
else
dfs(neighbor, accVisited)
}
}

val (_, components) =
nodeConnections.keys.foldLeft((Set.empty[UUID], Seq.empty[Seq[UUID]])) {
case ((visited, components), node) =>
if (visited.contains(node)) {
(visited, components)
} else {
val component = dfs(node)
val updatedVisited = visited ++ component
val updatedComponents = components :+ component.toSeq

(updatedVisited, updatedComponents)
}
}

components
}
}
178 changes: 129 additions & 49 deletions src/test/scala/edu/ie3/simona/model/grid/GridSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ package edu.ie3.simona.model.grid
import breeze.linalg.DenseMatrix
import breeze.math.Complex
import breeze.numerics.abs
import edu.ie3.datamodel.exceptions.InvalidGridException
import edu.ie3.datamodel.models.input.MeasurementUnitInput
import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils
import edu.ie3.simona.exceptions.GridInconsistencyException
import edu.ie3.simona.model.control.{GridControls, TransformerControlGroupModel}
import edu.ie3.simona.model.grid.GridModel.GridComponents
import edu.ie3.simona.model.grid.GridModel.{
GridComponents,
updateUuidToIndexMap,
}
import edu.ie3.simona.test.common.input.{GridInputTestData, LineInputTestData}
import edu.ie3.simona.test.common.model.grid.{
BasicGrid,
Expand Down Expand Up @@ -265,53 +267,6 @@ class GridSpec

}

"throw an InvalidGridException if two switches are connected @ the same node" in new BasicGridWithSwitches {
// enable nodes
override val nodes: Seq[NodeModel] = super.nodes
nodes.foreach(_.enable())

// add a second switch @ node13 (between node1 and node13)
val secondSwitch = new SwitchModel(
UUID.fromString("ebeaad04-0ee3-4b2e-ae85-8c76a583295b"),
"SecondSwitch1",
defaultOperationInterval,
node1.uuid,
node13.uuid,
)
// add the second switch + enable switches
override val switches: Set[SwitchModel] = super.switches + secondSwitch
switches.foreach(_.enable())
// open the switches
switches.foreach(_.open())

// get the grid from the raw data
val gridModel = new GridModel(
1,
default400Kva10KvRefSystem,
GridComponents(
nodes,
lines,
Set(transformer2wModel),
Set.empty[Transformer3wModel],
switches,
),
GridControls.empty,
)

// get the private method for validation
val validateConsistency: PrivateMethod[Unit] =
PrivateMethod[Unit](Symbol("validateConsistency"))

// call the validation method
val exception: InvalidGridException = intercept[InvalidGridException] {
GridModel invokePrivate validateConsistency(gridModel)
}

// expect an exception for node 13
exception.getMessage shouldBe s"The grid model for subnet 1 has nodes with multiple switches. This is not supported yet! Duplicates are located @ nodes: Vector(${node13.uuid})"

}

"update the nodeUuidToIndexMap correctly, when the given grid" must {

"contain 3 open switches" in new BasicGridWithSwitches {
Expand Down Expand Up @@ -474,6 +429,131 @@ class GridSpec
nodes.map(node => node.uuid).toVector.sorted
)
}

"contains two closed switches with a common node" in new BasicGridWithSwitches {
// enable nodes
override val nodes: Seq[NodeModel] = super.nodes
nodes.foreach(_.enable())

// add a second switch @ node13 (between node1 and node13)
val secondSwitch = new SwitchModel(
UUID.fromString("ebeaad04-0ee3-4b2e-ae85-8c76a583295b"),
"SecondSwitch1",
defaultOperationInterval,
node1.uuid,
node13.uuid,
)
// add the second switch + enable switches
override val switches: Set[SwitchModel] = super.switches + secondSwitch
switches.foreach(_.enable())
// close the switches
switches.foreach(_.close())

// get the grid from the raw data
val gridModel = new GridModel(
1,
default400Kva10KvRefSystem,
GridComponents(
nodes,
lines,
Set(transformer2wModel),
Set.empty[Transformer3wModel],
switches,
),
GridControls.empty,
)

updateUuidToIndexMap(gridModel)

// nodes 1, 13 and 14 should map to the same node
val node1Index = gridModel.nodeUuidToIndexMap
.get(node1.uuid)
.value
gridModel.nodeUuidToIndexMap.get(node13.uuid).value shouldBe node1Index
gridModel.nodeUuidToIndexMap.get(node14.uuid).value shouldBe node1Index
}

"contains closed switches in a complex pattern" in new BasicGridWithSwitches {
// just six nodes from the basic grid
override val nodes: Seq[NodeModel] =
Seq(node1, node2, node3, node4, node5, node6)
nodes.foreach(_.enable())

// nodes 1-4 connected by switches in a rectangle plus diagonal,
// nodes 5-6 connected separately
override val switches: Set[SwitchModel] = Set(
SwitchModel(
UUID.fromString("0-0-0-0-1"),
"Switch1",
defaultOperationInterval,
node1.uuid,
node2.uuid,
),
SwitchModel(
UUID.fromString("0-0-0-0-2"),
"Switch2",
defaultOperationInterval,
node2.uuid,
node3.uuid,
),
SwitchModel(
UUID.fromString("0-0-0-0-3"),
"Switch3",
defaultOperationInterval,
node3.uuid,
node4.uuid,
),
SwitchModel(
UUID.fromString("0-0-0-0-4"),
"Switch4",
defaultOperationInterval,
node1.uuid,
node4.uuid,
),
SwitchModel(
UUID.fromString("0-0-0-0-5"),
"Switch5",
defaultOperationInterval,
node2.uuid,
node4.uuid,
),
SwitchModel(
UUID.fromString("0-0-0-0-6"),
"Switch6",
defaultOperationInterval,
node5.uuid,
node6.uuid,
),
)
switches.foreach(_.enable())
// close the switches
switches.foreach(_.close())

// get the grid from the raw data
val gridModel = new GridModel(
1,
default400Kva10KvRefSystem,
GridComponents(
nodes,
Set.empty,
Set.empty,
Set.empty,
switches,
),
GridControls.empty,
)

// testing the assignment of nodes to indices
updateUuidToIndexMap(gridModel)

gridModel.nodeUuidToIndexMap.get(node1.uuid).value shouldBe 0
gridModel.nodeUuidToIndexMap.get(node2.uuid).value shouldBe 0
gridModel.nodeUuidToIndexMap.get(node3.uuid).value shouldBe 0
gridModel.nodeUuidToIndexMap.get(node4.uuid).value shouldBe 0
gridModel.nodeUuidToIndexMap.get(node5.uuid).value shouldBe 1
gridModel.nodeUuidToIndexMap.get(node6.uuid).value shouldBe 1
}

}

"build correct transformer control models" should {
Expand Down

0 comments on commit fc93131

Please sign in to comment.