Skip to content

Commit 15848c2

Browse files
authored
Merge pull request #21 from ie3-institute/ck/#14-profileAndTypeConverters
Adding profile and type converters
2 parents b28742c + 150effb commit 15848c2

File tree

10 files changed

+1529
-3
lines changed

10 files changed

+1529
-3
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package edu.ie3.simbench.convert.profiles
2+
3+
import java.util.UUID
4+
5+
import edu.ie3.datamodel.models.timeseries.individual.{
6+
IndividualTimeSeries,
7+
TimeBasedValue
8+
}
9+
import edu.ie3.datamodel.models.value.{PValue, SValue}
10+
import edu.ie3.simbench.exception.ConversionException
11+
import edu.ie3.simbench.model.datamodel.profiles.{ProfileModel, ProfileType}
12+
import javax.measure.quantity.Power
13+
import tec.uom.se.ComparableQuantity
14+
15+
import scala.jdk.CollectionConverters._
16+
17+
case object PowerProfileConverter {
18+
19+
/**
20+
* Converts a given profile with a tuple of BigDecimal as data to a ie³'s data model time series denoting a tuple of
21+
* active and reactive power. The SimBench model gives only scaling factors for active and reactive power, which will
22+
* scale the maximum active and reactive power of a distinct entity.
23+
*
24+
* @param profileModel The profile to convert
25+
* @param pRated Reference active power to meet the peak point of the profile
26+
* @param qRated Reference reactive power to meet the peak point of the profile
27+
* @return A [[IndividualTimeSeries]] with [[SValue]] (active and reactive power) for each time step
28+
*/
29+
def convert(
30+
profileModel: ProfileModel[_ <: ProfileType, (BigDecimal, BigDecimal)],
31+
pRated: ComparableQuantity[Power],
32+
qRated: ComparableQuantity[Power]
33+
): IndividualTimeSeries[SValue] = {
34+
val values = profileModel.profile.map {
35+
case (zdt, (pScaling, qScaling)) =>
36+
new TimeBasedValue(
37+
zdt,
38+
new SValue(pRated.multiply(pScaling), qRated.multiply(qScaling))
39+
)
40+
}.toSet
41+
new IndividualTimeSeries[SValue](UUID.randomUUID(), values.asJava)
42+
}
43+
44+
/**
45+
* Converts a given profile with s single BigDecimal as data to a ie³'s data model time series denoting active power.
46+
* The SimBench model gives only scaling factors for active power, which will scale the maximum active power of a
47+
* distinct entity.
48+
*
49+
* @param profileModel The profile to convert
50+
* @param pRated Reference active power to meet the peak point of the profile
51+
* @return A [[IndividualTimeSeries]] with active and reactive power for each time step
52+
*/
53+
def convert(
54+
profileModel: ProfileModel[_ <: ProfileType, BigDecimal],
55+
pRated: ComparableQuantity[Power]
56+
): IndividualTimeSeries[PValue] = {
57+
val values = profileModel.profile.map {
58+
case (zdt, pScaling) =>
59+
new TimeBasedValue(zdt, new PValue(pRated.multiply(pScaling)))
60+
}.toSet
61+
new IndividualTimeSeries[PValue](UUID.randomUUID(), values.asJava)
62+
}
63+
64+
/**
65+
* Extract one profile from the map from profile type to profile
66+
*
67+
* @param profileType Profile type to extract
68+
* @param profiles Map from profile type to profile
69+
* @tparam T Type parameter for the profile's type
70+
* @tparam P Type parameter for the profile itself
71+
* @return The equivalent [[ProfileModel]]
72+
*/
73+
def getProfile[T <: ProfileType, P <: ProfileModel[T, _]](
74+
profileType: T,
75+
profiles: Map[T, P]
76+
): P =
77+
profiles.getOrElse(
78+
profileType,
79+
throw ConversionException(s"Cannot find profile for type $profileType")
80+
)
81+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package edu.ie3.simbench.convert.types
2+
3+
import java.util.UUID
4+
5+
import com.typesafe.scalalogging.LazyLogging
6+
import edu.ie3.datamodel.models.input.connector.`type`.LineTypeInput
7+
import edu.ie3.simbench.exception.ConversionException
8+
import edu.ie3.simbench.exception.io.SimbenchDataModelException
9+
import edu.ie3.simbench.model.datamodel.Line
10+
import edu.ie3.simbench.model.datamodel.types.LineType
11+
import edu.ie3.util.quantities.PowerSystemUnits.{
12+
KILOVOLT,
13+
OHM_PER_KILOMETRE,
14+
SIEMENS_PER_KILOMETRE
15+
}
16+
import javax.measure.quantity.ElectricPotential
17+
import tec.uom.se.ComparableQuantity
18+
import tec.uom.se.quantity.Quantities
19+
import tec.uom.se.unit.MetricPrefix
20+
import tec.uom.se.unit.Units.AMPERE
21+
22+
/**
23+
* Currently not supported by ie³'s data model:
24+
* - line style
25+
*/
26+
case object LineTypeConverter extends LazyLogging {
27+
28+
/**
29+
* Converts the [[LineType]]s of the given lines into [[LineTypeInput]]s
30+
*
31+
* @param lines [[Vector]] of SimBench [[LineType]]s
32+
* @return A [[Vector]] of [[LineTypeInput]]s
33+
*/
34+
def convert(
35+
lines: Vector[Line[_ <: LineType]]
36+
): Map[LineType, LineTypeInput] = {
37+
val ratedVoltageMapping = getRatedVoltages(lines)
38+
val lineTypes = lines.map(line => line.lineType).distinct
39+
40+
lineTypes
41+
.map(
42+
lineType =>
43+
lineType -> convert(
44+
lineType,
45+
ratedVoltageMapping.getOrElse(
46+
lineType,
47+
throw SimbenchDataModelException(
48+
s"Cannot find the rated voltage vor line type ${lineType}"
49+
)
50+
)
51+
)
52+
)
53+
.toMap
54+
}
55+
56+
/**
57+
* Converts a given SimBench [[LineType]] into a ie³ [[LineTypeInput]]. The [[LineType.DCLineType]]s are currently
58+
* not supported by the ie³ data model. Therefore an [[IllegalArgumentException]] is thrown.
59+
*
60+
* @param input SimBench [[LineType]] to convert
61+
* @param vRated Externally provided rated voltage, as the SimBench [[LineType]] does not provide this information
62+
* @param uuid UUID to use for the model generation (default: Random UUID)
63+
* @return A ie³ [[LineTypeInput]]
64+
*/
65+
def convert(
66+
input: LineType,
67+
vRated: ComparableQuantity[ElectricPotential],
68+
uuid: UUID = UUID.randomUUID()
69+
): LineTypeInput = {
70+
input match {
71+
case LineType.ACLineType(id, r, x, b, iMax, _) =>
72+
val rQty = Quantities.getQuantity(r, OHM_PER_KILOMETRE)
73+
val xQty = Quantities.getQuantity(x, OHM_PER_KILOMETRE)
74+
val gQty =
75+
Quantities.getQuantity(0d, MetricPrefix.MICRO(SIEMENS_PER_KILOMETRE))
76+
val bQty =
77+
Quantities.getQuantity(b, MetricPrefix.MICRO(SIEMENS_PER_KILOMETRE))
78+
val iMaxQty = Quantities.getQuantity(iMax, AMPERE)
79+
80+
new LineTypeInput(uuid, id, bQty, gQty, rQty, xQty, iMaxQty, vRated)
81+
case _: LineType.DCLineType =>
82+
throw ConversionException(
83+
"DC line types are currently not supported by ie³'s data model."
84+
)
85+
}
86+
}
87+
88+
/**
89+
* Extracts a mapping of [[LineType]]s to the rated voltages of those types. Unambiguousness is ensured.
90+
*
91+
* @param lines [[Vector]] of [[Line]]s
92+
* @return Mapping of [[LineType]] to [[ComparableQuantity]] of type [[ElectricPotential]]
93+
*/
94+
def getRatedVoltages(
95+
lines: Vector[Line[_ <: LineType]]
96+
): Map[LineType, ComparableQuantity[ElectricPotential]] = {
97+
val rawMapping = lines
98+
.distinctBy(line => line.lineType)
99+
.map(line => determineRatedVoltage(line))
100+
.groupMap(_._1)(_._2)
101+
102+
/* Sanity check, that there is no ambiguous mapping */
103+
rawMapping.find {
104+
case (_, ratedVoltages) if ratedVoltages.length > 1 =>
105+
true
106+
case _ =>
107+
false
108+
} match {
109+
case Some(ambiguousEntry) =>
110+
throw SimbenchDataModelException(
111+
s"Found ambiguous rated voltages for at least one entry: $ambiguousEntry"
112+
)
113+
case None =>
114+
logger.debug(
115+
"Great! Found only unambiguous line type to rated voltage mappings."
116+
)
117+
}
118+
119+
/* Mapping the line type to the rated voltage of the first entry of the Vector of each raw mapping. That nothing
120+
* is missed is ensured by the sanity check beforehand */
121+
rawMapping.map {
122+
case (lineType, lineTypeVRatedVector) =>
123+
lineType -> lineTypeVRatedVector.headOption.getOrElse(
124+
throw SimbenchDataModelException(
125+
s"Cannot receive rated voltage for line type '$lineType'."
126+
)
127+
)
128+
}
129+
}
130+
131+
/**
132+
* Maps the [[LineType]] of the specific line to it's rated voltage based on the line's nodes' rated voltages
133+
*
134+
* @param line Specific line to examine
135+
* @return The rated voltage of the used line type
136+
*/
137+
private def determineRatedVoltage(
138+
line: Line[_ <: LineType]
139+
): (LineType, ComparableQuantity[ElectricPotential]) = {
140+
val vRatedA = Quantities.getQuantity(line.nodeA.vmR, KILOVOLT)
141+
val vRatedB = Quantities.getQuantity(line.nodeB.vmR, KILOVOLT)
142+
if (vRatedA != vRatedB)
143+
throw SimbenchDataModelException(
144+
s"The line ${line.id} connects two nodes with different rated voltages, which physically is not possible"
145+
)
146+
(line.lineType, vRatedA)
147+
}
148+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package edu.ie3.simbench.convert.types
2+
3+
import java.util.UUID
4+
5+
import edu.ie3.datamodel.models.input.connector.`type`.Transformer2WTypeInput
6+
import edu.ie3.simbench.exception.ConversionException
7+
import edu.ie3.simbench.model.datamodel.enums.BranchElementPort.LV
8+
import edu.ie3.simbench.model.datamodel.types.Transformer2WType
9+
import edu.ie3.util.quantities.PowerSystemUnits.{DEGREE_GEOM, PU, VOLTAMPERE}
10+
import tec.uom.se.quantity.Quantities
11+
import tec.uom.se.unit.Units.{OHM, SIEMENS, VOLT}
12+
13+
import scala.math.sqrt
14+
15+
/**
16+
* Not covered
17+
* - Phase shifting based on the assembly style of the transformer
18+
*/
19+
case object Transformer2wTypeConverter {
20+
21+
def convert(
22+
types: Vector[Transformer2WType]
23+
): Map[Transformer2WType, Transformer2WTypeInput] =
24+
types.map(input => input -> convert(input)).toMap
25+
26+
/**
27+
* Converts a singe [[Transformer2WType]] into ie³'s [[Transformer2WTypeInput]]
28+
*
29+
* @param input Input model to use
30+
* @param uuid UUID to use for the model generation (default: Random UUID)
31+
* @return A ie³ [[Transformer2WTypeInput]]
32+
*/
33+
def convert(
34+
input: Transformer2WType,
35+
uuid: UUID = UUID.randomUUID()
36+
): Transformer2WTypeInput = {
37+
val id = input.id
38+
val tapSide = input.tapside == LV
39+
val dV = input.dVm
40+
val dPhi = input.dVa
41+
val tapNeutr = input.tapNeutr
42+
val tapMin = input.tapMin
43+
val tapMax = input.tapMax
44+
45+
/*
46+
* Conversion of parameters
47+
* ------------------------
48+
* The parameters for the equivalent circuit are given from the perspective of the high voltage side. This can be
49+
* looked up in the official SimBench documentation:
50+
* https://simbench.de/wp-content/uploads/2020/01/simbench_documentation_de_1.0.1.pdf
51+
*/
52+
val sRated = input.sR * 1e6 // Rated apparent power in VA
53+
val vmHV = input.vmHV * 1e3 // Rated voltage magnitude at high voltage port in V
54+
val vmLV = input.vmLV * 1e3 // Rated voltage magnitude at low voltage port in V
55+
val vImp = input.vmImp / 100 * vmHV // Voltage magnitude in short circuit experiment in V
56+
val pCu = input.pCu * 1e3 // Copper losses in W
57+
val pFe = input.pFe * 1e3 // Iron losses in W
58+
59+
/* Short circuit experiment */
60+
val iRated = sRated / (sqrt(3) * vmHV) // Rated current on high voltage side in Ampere
61+
val zSc = vImp / (iRated * sqrt(3)) // Short circuit impedance of the total branch in Ohm
62+
val rSc = pCu / (3 * iRated * iRated) // Short circuit resistance in Ohm
63+
if (rSc > zSc)
64+
throw ConversionException(
65+
s"Cannot convert two winding transformer type $id into ie³ type, as the conversion of short circuit parameters is not possible."
66+
)
67+
val xSc = sqrt((zSc * zSc - rSc * rSc).doubleValue) // Short circuit reactance in Ohm
68+
69+
/* No load experiment */
70+
val iNoLoad = input.iNoLoad / 100 * iRated // No load current in Ampere
71+
val vM = vmHV / sqrt(3) // Voltage at the main field admittance in V
72+
val yNoLoad = iNoLoad / vM // No load admittance in Ohm
73+
val gNoLoad = pFe / (3 * vM * vM) // No load conductance in Ohm
74+
if (gNoLoad > yNoLoad)
75+
throw ConversionException(
76+
s"Cannot convert two winding transformer type $id into ie³ type, as the conversion of no load parameters is not possible."
77+
)
78+
val bNoLoad = sqrt((yNoLoad * yNoLoad - gNoLoad * gNoLoad).doubleValue) // No load susceptance in Ohm
79+
80+
new Transformer2WTypeInput(
81+
uuid,
82+
id,
83+
Quantities.getQuantity(rSc, OHM),
84+
Quantities.getQuantity(xSc, OHM),
85+
Quantities.getQuantity(sRated, VOLTAMPERE),
86+
Quantities.getQuantity(vmHV, VOLT),
87+
Quantities.getQuantity(vmLV, VOLT),
88+
Quantities.getQuantity(gNoLoad, SIEMENS),
89+
Quantities.getQuantity(bNoLoad, SIEMENS),
90+
Quantities.getQuantity(dV, PU),
91+
Quantities.getQuantity(dPhi, DEGREE_GEOM),
92+
tapSide,
93+
tapNeutr,
94+
tapMin,
95+
tapMax
96+
)
97+
}
98+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package edu.ie3.simbench.exception
2+
3+
final case class ConversionException(
4+
private val msg: String,
5+
private val cause: Throwable = None.orNull
6+
) extends SimbenchException(msg, cause)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package edu.ie3.simbench.exception
2+
3+
final case class TestingException(
4+
private val msg: String,
5+
private val cause: Throwable = None.orNull
6+
) extends SimbenchException(msg, cause)

src/main/scala/edu/ie3/simbench/model/datamodel/enums/NodeType.scala

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ object NodeType {
1414
*/
1515
object BusBar extends NodeType
1616

17+
/**
18+
* A double bus bar
19+
*/
20+
object DoubleBusBar extends NodeType
21+
1722
/**
1823
* A simple node like a bushing
1924
*/
@@ -34,9 +39,10 @@ object NodeType {
3439
@throws[SimbenchDataModelException]
3540
def apply(typeString: String): NodeType =
3641
typeString.toLowerCase.replaceAll("[_]*", "") match {
37-
case "busbar" => BusBar
38-
case "node" => Node
39-
case "auxiliary" => Auxiliary
42+
case "busbar" => BusBar
43+
case "doublebusbar" => DoubleBusBar
44+
case "node" => Node
45+
case "auxiliary" => Auxiliary
4046
case whatever =>
4147
throw SimbenchDataModelException(
4248
s"I cannot handle the node type $whatever"

0 commit comments

Comments
 (0)