Skip to content

Commit 3d630d5

Browse files
authored
Merge pull request #180 from ie3-institute/to/#178-custom-node-uuids
To/#178 custom node uuids
2 parents f5c098b + 7758132 commit 3d630d5

24 files changed

+323
-84
lines changed

.gitignore

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,36 @@ dmypy.json
146146
# profiling data
147147
.prof
148148

149+
### macOS ###
150+
# General
151+
.DS_Store
152+
.AppleDouble
153+
.LSOverride
154+
155+
# Icon must end with two \r
156+
Icon
157+
158+
159+
# Thumbnails
160+
._*
161+
162+
# Files that might appear in the root of a volume
163+
.DocumentRevisions-V100
164+
.fseventsd
165+
.Spotlight-V100
166+
.TemporaryItems
167+
.Trashes
168+
.VolumeIcon.icns
169+
.com.apple.timemachine.donotpresent
170+
171+
# Directories potentially created on remote AFP share
172+
.AppleDB
173+
.AppleDesktop
174+
Network Trash Folder
175+
Temporary Items
176+
.apdisk
177+
178+
149179
# End of https://www.toptal.com/developers/gitignore/api/python
150180

151181
# Ignore Gradle project-specific cache directory

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ dependencies {
9292
// Linter Scala //
9393
implementation "com.sksamuel.scapegoat:scalac-scapegoat-plugin_${scalaBinaryVersion}:1.4.11" // scala scapegoat
9494
scalaCompilerPlugin "com.sksamuel.scapegoat:scalac-scapegoat-plugin_${scalaBinaryVersion}:1.4.11" // scala scapegoat
95+
96+
implementation 'tech.units:indriya:2.1.3-SNAPSHOT'
9597
}
9698

9799
/* scapegoat hook configuration

src/main/python/powerFactory2json/pf2jsonUtils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
],
2525
'conElms': [],
2626
'nodes': [
27+
"loc_name",
2728
"vtarget",
2829
"uknom",
2930
"GPSlat",

src/main/scala/edu/ie3/powerFactory2psdm/config/ConversionConfig.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package edu.ie3.powerFactory2psdm.config
88

99
import edu.ie3.powerFactory2psdm.config.ConversionConfig.{
10+
NodeUuidMappingInformation,
1011
OutputConfig,
1112
StatGenModelConfigs
1213
}
@@ -19,6 +20,7 @@ import edu.ie3.powerFactory2psdm.config.model.{
1920
final case class ConversionConfig(
2021
gridName: String,
2122
modelConfigs: StatGenModelConfigs,
23+
nodeMapping: Option[NodeUuidMappingInformation],
2224
output: OutputConfig
2325
)
2426

@@ -42,6 +44,11 @@ object ConversionConfig {
4244
cosPhiSource: ParameterSource
4345
)
4446

47+
final case class NodeUuidMappingInformation(
48+
filePath: String,
49+
csvSeparator: String
50+
)
51+
4552
final case class OutputConfig(
4653
targetFolder: String,
4754
csvConfig: CsvConfig

src/main/scala/edu/ie3/powerFactory2psdm/converter/ConversionHelper.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,21 @@ object ConversionHelper {
8080
)
8181
}
8282

83+
/** Get all duplicates of the sequence.
84+
*
85+
* @param items
86+
* items to check for duplicates
87+
* @tparam T
88+
* type of the items
89+
* @return
90+
* sequence of duplicates
91+
*/
92+
def getDuplicates[T](items: Seq[T]): Seq[T] = {
93+
val uniqueItems = items.distinct
94+
if (uniqueItems.size == items.size) {
95+
return Seq.empty[T]
96+
}
97+
items.diff(uniqueItems)
98+
}
99+
83100
}

src/main/scala/edu/ie3/powerFactory2psdm/converter/GridConverter.scala

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,17 @@ import edu.ie3.datamodel.models.input.system.{
2727
StorageInput
2828
}
2929
import edu.ie3.powerFactory2psdm.config.ConversionConfig
30-
import edu.ie3.powerFactory2psdm.config.ConversionConfig.StatGenModelConfigs
30+
import edu.ie3.powerFactory2psdm.config.ConversionConfig.{
31+
NodeUuidMappingInformation,
32+
StatGenModelConfigs
33+
}
34+
import edu.ie3.powerFactory2psdm.converter.NodeConverter.getNodeNameMapping
3135
import edu.ie3.powerFactory2psdm.converter.types.{
3236
LineTypeConverter,
3337
Transformer2WTypeConverter
3438
}
3539
import edu.ie3.powerFactory2psdm.model.{PreprocessedPfGridModel, RawPfGridModel}
36-
40+
import java.util.UUID
3741
import scala.jdk.CollectionConverters.SetHasAsJava
3842

3943
/** Functionalities to transform an exported and then parsed PowerFactory grid
@@ -50,7 +54,8 @@ case object GridConverter {
5054
config.modelConfigs.sRatedSource,
5155
config.modelConfigs.cosPhiSource
5256
)
53-
val (gridElements, convertedNodes) = convertGridElements(grid)
57+
val (gridElements, convertedNodes) =
58+
convertGridElements(grid, config.nodeMapping)
5459
val participants =
5560
convertParticipants(grid, convertedNodes, config.modelConfigs)
5661
new JointGridContainer(
@@ -70,13 +75,16 @@ case object GridConverter {
7075
* the raw parsed PowerFactoryGrid
7176
*/
7277
def convertGridElements(
73-
grid: PreprocessedPfGridModel
78+
grid: PreprocessedPfGridModel,
79+
nodeUuidMapping: Option[NodeUuidMappingInformation]
7480
): (RawGridElements, Map[String, NodeInput]) = {
7581
val graph =
7682
GridGraphBuilder.build(grid.nodes, grid.lines ++ grid.switches)
7783
val nodeId2node = grid.nodes.map(node => (node.id, node)).toMap
7884
val subnets = SubnetBuilder.buildSubnets(graph, nodeId2node)
79-
val nodes = NodeConverter.convertNodesOfSubnets(subnets)
85+
val nodeId2Uuid =
86+
nodeUuidMapping.map(getNodeNameMapping).getOrElse(Map.empty[String, UUID])
87+
val nodes = NodeConverter.convertNodesOfSubnets(subnets, nodeId2Uuid)
8088
val lineTypes = grid.lineTypes
8189
.map(lineType => (lineType.id, LineTypeConverter.convert(lineType)))
8290
.toMap
@@ -112,7 +120,6 @@ case object GridConverter {
112120
),
113121
nodes
114122
)
115-
116123
}
117124

118125
/** Convert system participants of the power factory grid.

src/main/scala/edu/ie3/powerFactory2psdm/converter/NodeConverter.scala

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,17 @@ package edu.ie3.powerFactory2psdm.converter
99
import edu.ie3.datamodel.models.OperationTime
1010
import edu.ie3.datamodel.models.input.{NodeInput, OperatorInput}
1111
import edu.ie3.datamodel.models.voltagelevels.VoltageLevel
12-
import edu.ie3.powerFactory2psdm.exception.pf.{
13-
ConversionException,
14-
GridConfigurationException
15-
}
16-
import edu.ie3.powerFactory2psdm.model.entity.{Node, Subnet}
17-
import edu.ie3.util.quantities.PowerSystemUnits.PU
12+
import edu.ie3.powerFactory2psdm.config.ConversionConfig.NodeUuidMappingInformation
13+
import edu.ie3.powerFactory2psdm.exception.pf.ConversionException
14+
import edu.ie3.powerFactory2psdm.model.entity.Subnet
1815
import edu.ie3.powerFactory2psdm.exception.pf.GridConfigurationException
1916
import edu.ie3.powerFactory2psdm.model.entity.Node
2017
import edu.ie3.powerFactory2psdm.util.QuantityUtils.RichQuantityDouble
2118
import org.locationtech.jts.geom.Point
22-
import tech.units.indriya.quantity.Quantities
2319

20+
import java.io.IOException
2421
import java.util.UUID
22+
import scala.io.Source
2523
import scala.util.{Failure, Success, Try}
2624

2725
object NodeConverter {
@@ -30,26 +28,44 @@ object NodeConverter {
3028
*
3129
* @param subnets
3230
* subnets of the grid
31+
* @param unsafeNodeId2Uuid
32+
* mapping form human readable identifier set in pf to the generated UUID
3333
* @return
3434
* Map of node id to PSDM [[NodeInput]]
3535
*/
36-
def convertNodesOfSubnets(subnets: List[Subnet]): Map[String, NodeInput] = {
37-
subnets.flatMap(subnet => convertNodesOfSubnet(subnet)).toMap
36+
def convertNodesOfSubnets(
37+
subnets: List[Subnet],
38+
unsafeNodeId2Uuid: Map[String, UUID]
39+
): Map[String, NodeInput] = {
40+
subnets
41+
.flatMap(subnet => convertNodesOfSubnet(subnet, unsafeNodeId2Uuid))
42+
.toMap
3843
}
3944

4045
/** Converts all nodes within a subnet to PSDM [[NodeInput]] s
4146
*
4247
* @param subnet
4348
* the subnet with reference to all PF nodes that live within
49+
* @param unsafeNodeId2Uuid
50+
* mapping form human readable identifier set in pf to the generated UUID
4451
* @return
4552
* list of all converted [[NodeInput]]
4653
*/
4754
def convertNodesOfSubnet(
48-
subnet: Subnet
55+
subnet: Subnet,
56+
unsafeNodeId2Uuid: Map[String, UUID]
4957
): Set[(String, NodeInput)] =
5058
subnet.nodes
5159
.map(node =>
52-
(node.id, NodeConverter.convertNode(node, subnet.id, subnet.voltLvl))
60+
(
61+
node.id,
62+
NodeConverter.convertNode(
63+
node,
64+
subnet.id,
65+
subnet.voltLvl,
66+
unsafeNodeId2Uuid
67+
)
68+
)
5369
)
5470

5571
/** Converts a PowerFactory node into a PSDM node.
@@ -60,18 +76,22 @@ object NodeConverter {
6076
* subnet id the node is assigned to
6177
* @param voltLvl
6278
* voltage level of the node
79+
* @param unsafeNodeId2Uuid
80+
* mapping form human readable identifier set in pf to the generated UUID
6381
* @return
6482
* a PSDM [[NodeInput]]
6583
*/
6684
def convertNode(
6785
node: Node,
6886
subnetId: Int,
69-
voltLvl: VoltageLevel
87+
voltLvl: VoltageLevel,
88+
unsafeNodeId2Uuid: Map[String, UUID]
7089
): NodeInput = {
7190
val geoPosition: Point = CoordinateConverter.convert(node.lat, node.lon)
7291
val slack = isSlack(node)
92+
val uuid = unsafeNodeId2Uuid.getOrElse(node.unsafeId, UUID.randomUUID())
7393
new NodeInput(
74-
UUID.randomUUID(),
94+
uuid,
7595
node.id,
7696
OperatorInput.NO_OPERATOR_ASSIGNED,
7797
OperationTime.notLimited(),
@@ -83,6 +103,69 @@ object NodeConverter {
83103
)
84104
}
85105

106+
/** Creates a Map that maps from unsafe node ids to uuids from a csv file that
107+
* is used for node conversion. This can be used to keep uuids of the nodes
108+
* consistent between original data and the converted grid.
109+
*
110+
* @param nodeUuidMappingInformation
111+
* necessary mapping information
112+
* @return
113+
* Map from unsafe node id to uuid
114+
*/
115+
def getNodeNameMapping(
116+
nodeUuidMappingInformation: NodeUuidMappingInformation
117+
): Map[String, UUID] = {
118+
// parse csv file
119+
val bufferedSource = Source.fromFile(nodeUuidMappingInformation.filePath)
120+
val lines = bufferedSource.getLines()
121+
122+
lines.take(1).next.split(nodeUuidMappingInformation.csvSeparator) match {
123+
case Array("uuid", "id") =>
124+
case _ =>
125+
throw new IOException(
126+
"Invalid CSV header. We expect the header of the node name mapping file to be \"uuid\" [CSV Sep.] \"id\"."
127+
)
128+
}
129+
130+
val idsAndUuids = lines.map { line =>
131+
line
132+
.split(nodeUuidMappingInformation.csvSeparator)
133+
.map(_.trim) match {
134+
case Array(uuidString, id) =>
135+
val uuid = Try(UUID.fromString(uuidString)) match {
136+
case Failure(exception) =>
137+
throw new IllegalArgumentException(
138+
s"UUID: $uuidString on line: $line is not a valid UUID.",
139+
exception
140+
)
141+
case Success(uuid) => uuid
142+
}
143+
(id, uuid)
144+
case Array(_) =>
145+
throw new IOException(
146+
s"Invalid format on csv line INDEX_HERE. Every line should have exactly two elements."
147+
)
148+
}
149+
}.toVector
150+
151+
// check for duplicates in ids or uuids
152+
val (ids, uuids) = idsAndUuids.unzip
153+
val duplicateIds = ConversionHelper.getDuplicates(ids)
154+
if (duplicateIds.nonEmpty) {
155+
throw ConversionException(
156+
f"There are the following duplicate ids in the node id to uuid mapping: $duplicateIds"
157+
)
158+
}
159+
val duplicateUuids = ConversionHelper.getDuplicates(uuids)
160+
if (duplicateIds.nonEmpty) {
161+
throw ConversionException(
162+
f"There are the following duplicate uuids in the node id to uuid mapping: $duplicateUuids"
163+
)
164+
}
165+
bufferedSource.close
166+
idsAndUuids.toMap
167+
}
168+
86169
/** Checks if a node is a slack node by checking if there is an external grid
87170
* connected to the node.
88171
*

src/main/scala/edu/ie3/powerFactory2psdm/converter/types/LineTypeConverter.scala

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ object LineTypeConverter extends LazyLogging {
6262

6363
// sanity check of total line length versus aggregated line length of all corresponding line sections
6464
lineLength - aggregatedLineSectionLength match {
65-
case x if abs(x) < 1e-9 =>
65+
case _ if abs(aggregatedLineSectionLength / lineLength - 1) < 1e-6 =>
6666
case x if x < 0 =>
6767
logger.error(
6868
s"The line length of line: $lineId is smaller than the aggregated length of line sections by ${(1 - (lineLength / aggregatedLineSectionLength)) * 100}% which distorts results. This should be prevented by PF and therefore not happen."
@@ -94,20 +94,20 @@ object LineTypeConverter extends LazyLogging {
9494
)
9595

9696
weightedLineTypes.foldLeft(emptyLineType)((averageType, current) => {
97-
val currentLine = current._2
97+
val currentLineType = current._2
9898
val weightingFactor = current._1 / lineLength
9999
new LineTypeInput(
100100
averageType.getUuid,
101101
averageType.getId,
102-
averageType.getB.add(currentLine.getB.multiply(weightingFactor)),
103-
averageType.getG.add(currentLine.getG.multiply(weightingFactor)),
104-
averageType.getR.add(currentLine.getR.multiply(weightingFactor)),
105-
averageType.getX.add(currentLine.getX.multiply(weightingFactor)),
106-
if (averageType.getiMax().isLessThan(currentLine.getiMax()))
102+
averageType.getB.add(currentLineType.getB.multiply(weightingFactor)),
103+
averageType.getG.add(currentLineType.getG.multiply(weightingFactor)),
104+
averageType.getR.add(currentLineType.getR.multiply(weightingFactor)),
105+
averageType.getX.add(currentLineType.getX.multiply(weightingFactor)),
106+
if (averageType.getiMax().isLessThan(currentLineType.getiMax()))
107107
averageType.getiMax()
108-
else currentLine.getiMax(),
108+
else currentLineType.getiMax(),
109109
if (averageType.getvRated().equals(Double.MaxValue.asKiloVolt))
110-
currentLine.getvRated()
110+
currentLineType.getvRated()
111111
else averageType.getvRated()
112112
)
113113
})

src/main/scala/edu/ie3/powerFactory2psdm/exception/pf/TestException.scala

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)