Skip to content

Commit 093e3ed

Browse files
authored
Merge pull request #64 from ie3-institute/ck/#63-threeWindingFix
Fix three winding transformer calculation
2 parents fd2f43b + 5ae9f56 commit 093e3ed

File tree

7 files changed

+216
-262
lines changed

7 files changed

+216
-262
lines changed

.scalafmt.conf

Whitespace-only changes.

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9090
- Fixed configuration reference in user's guide [#224](https://github.com/ie3-institute/simona/issues/224)
9191
- Fixed ResultEventListener exiting too early with high volumes of results [#350](https://github.com/ie3-institute/simona/issues/350)
9292
- Fixed tests that unreliably fail [#359](https://github.com/ie3-institute/simona/issues/359)
93+
- Support for three winding transformers [#63](https://github.com/ie3-institute/simona/issues/63)
94+
- Handle incoming slack voltage accordingly
95+
- Allow multiple sub grid gates at one node (also allows multiple two winding transformers at one node)
96+
- Perform power flow calculation in highest grid, if a three winding transformer is apparent
97+
- Write out results
9398

9499
### Removed
95100
- Remove workaround for tscfg tmp directory [#178](https://github.com/ie3-institute/simona/issues/178)

src/main/scala/edu/ie3/simona/agent/grid/GridResultsSupport.scala

Lines changed: 17 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package edu.ie3.simona.agent.grid
88

99
import akka.event.LoggingAdapter
1010
import breeze.math.Complex
11-
import edu.ie3.datamodel.models.StandardUnits
1211
import edu.ie3.datamodel.models.input.connector.ConnectorPort
1312
import edu.ie3.datamodel.models.result.NodeResult
1413
import edu.ie3.datamodel.models.result.connector.{
@@ -420,9 +419,8 @@ private[grid] trait GridResultsSupport {
420419
}
421420
}
422421

423-
/** NOT IMPLEMENTED YET AS NO TEST DATA IS AVAILABLE! Creates an instance of
424-
* [[Transformer3WResult]] based on the provided grid and power flow result
425-
* data
422+
/** Creates an instance of [[Transformer3WResult]] based on the provided grid
423+
* and power flow result data
426424
*
427425
* @param trafo3w
428426
* the instance of the 3 winding transformer that should be processed
@@ -445,12 +443,23 @@ private[grid] trait GridResultsSupport {
445443
iNominal: ComparableQuantity[ElectricCurrent],
446444
timestamp: ZonedDateTime
447445
): PartialTransformer3wResult = {
448-
val (iMag, iAng) = calcPortCurrent(
449-
trafo3w,
450-
nodeStateData.voltage,
446+
val (_, iComplexPu) = iIJComplexPu(
451447
internalNodeStateData.voltage,
452-
iNominal
448+
nodeStateData.voltage,
449+
yij(trafo3w),
450+
Transformer3wModel.y0(
451+
trafo3w,
452+
trafo3w.powerFlowCase match {
453+
case PowerFlowCaseA => Transformer3wModel.Transformer3wPort.A
454+
case PowerFlowCaseB => Transformer3wModel.Transformer3wPort.B
455+
case PowerFlowCaseC => Transformer3wModel.Transformer3wPort.C
456+
}
457+
),
458+
None
453459
)
460+
461+
val (iMag, iAng) = iMagAndAngle(iComplexPu, iNominal)
462+
454463
trafo3w.powerFlowCase match {
455464
case Transformer3wPowerFlowCase.PowerFlowCaseA =>
456465
PartialTransformer3wResult.PortA(
@@ -477,47 +486,6 @@ private[grid] trait GridResultsSupport {
477486
}
478487
}
479488

480-
/** Calculate the port current of the transformer
481-
*
482-
* @param transformer
483-
* The transformer model
484-
* @param v1
485-
* Nodal voltage at the port
486-
* @param v2
487-
* Nodal voltage at internal node
488-
* @param iNominal
489-
* Nominal current
490-
* @return
491-
* Magnitude and angle of the current
492-
*/
493-
def calcPortCurrent(
494-
transformer: Transformer3wModel,
495-
v1: Complex,
496-
v2: Complex,
497-
iNominal: ComparableQuantity[ElectricCurrent]
498-
): (ComparableQuantity[ElectricCurrent], ComparableQuantity[Angle]) = {
499-
val y = yij(transformer)
500-
val (de, df) = (v1, v2) match {
501-
case (Complex(e1, f1), Complex(e2, f2)) =>
502-
(e1 - e2, f1 - f2)
503-
}
504-
val iReal = (de * y.real - df * y.imag) / (pow(y.real, 2) + pow(y.imag, 2))
505-
val iImag = (de * y.imag + df * y.real) / (pow(y.real, 2) + pow(y.imag, 2))
506-
507-
val iMag = iNominal.multiply(sqrt(pow(iReal, 2) + pow(iImag, 2)))
508-
val iAng = atan(iImag / iReal) match {
509-
case angle if angle.isNaN =>
510-
Quantities
511-
.getQuantity(copySign(90d, iImag), PowerSystemUnits.DEGREE_GEOM)
512-
.to(StandardUnits.ELECTRIC_CURRENT_ANGLE)
513-
case angle =>
514-
Quantities
515-
.getQuantity(angle, Units.RADIAN)
516-
.to(StandardUnits.ELECTRIC_CURRENT_ANGLE)
517-
}
518-
(iMag, iAng)
519-
}
520-
521489
/** Calculate the current magnitude and the current angle in physical units
522490
* based on a provided electric current in p.u. and the nominal referenced
523491
* electric current. The arctangent "only" calculates the angle between the

src/main/scala/edu/ie3/simona/model/grid/Transformer3wModel.scala

Lines changed: 62 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66

77
package edu.ie3.simona.model.grid
88

9+
import breeze.linalg.max
10+
911
import java.time.ZonedDateTime
1012
import java.util.UUID
1113
import breeze.math.Complex
1214
import breeze.numerics.pow
15+
import com.typesafe.scalalogging.LazyLogging
1316
import edu.ie3.datamodel.exceptions.InvalidGridException
17+
import edu.ie3.datamodel.models.StandardUnits
1418
import edu.ie3.datamodel.models.input.connector.Transformer3WInput
1519
import edu.ie3.datamodel.models.input.connector.`type`.Transformer3WTypeInput
1620
import edu.ie3.simona.exceptions.{
@@ -29,7 +33,7 @@ import edu.ie3.util.scala.OperationInterval
2933

3034
import javax.measure.Quantity
3135
import javax.measure.quantity.{Dimensionless, ElectricPotential}
32-
import tech.units.indriya.ComparableQuantity
36+
import tech.units.indriya.{AbstractUnit, ComparableQuantity}
3337
import tech.units.indriya.quantity.Quantities
3438

3539
import scala.math.BigDecimal.RoundingMode
@@ -159,7 +163,7 @@ final case class Transformer3wModel(
159163
}
160164
}
161165

162-
case object Transformer3wModel {
166+
case object Transformer3wModel extends LazyLogging {
163167

164168
def apply(
165169
transformer3wInput: Transformer3WInput,
@@ -319,66 +323,56 @@ case object Transformer3wModel {
319323
val transformerRefSystem =
320324
RefSystem(transformerType.getsRatedA, transformerType.getvRatedA)
321325

322-
/* Extract the equivalent circuit diagram parameters from type, with the perspective of the
323-
* transformer */
326+
/* Get the physical equivalent circuit diagram parameters from type. They come with reference to the highest
327+
* voltage side, therefore, in power flow case B and C, they need to be adapted. */
324328
val (rTrafo, xTrafo, gTrafo, bTrafo) = powerFlowCase match {
325329
case PowerFlowCaseA =>
326330
(
327-
transformerRefSystem.rInPu(transformerType.getrScA),
328-
transformerRefSystem.xInPu(transformerType.getxScA),
329-
transformerRefSystem.gInPu(transformerType.getgM),
330-
transformerRefSystem.gInPu(transformerType.getbM)
331+
transformerType.getrScA,
332+
transformerType.getxScA,
333+
transformerType.getgM,
334+
transformerType.getbM
331335
)
332336
case PowerFlowCaseB =>
337+
val nominalRatio = transformerType
338+
.getvRatedA()
339+
.divide(transformerType.getvRatedB())
340+
.asType(classOf[Dimensionless])
341+
.to(AbstractUnit.ONE)
342+
.getValue
343+
.doubleValue()
333344
(
334-
transformerRefSystem.rInPu(transformerType.getrScB),
335-
transformerRefSystem.xInPu(transformerType.getxScB),
336-
Quantities.getQuantity(0d, PU),
337-
Quantities.getQuantity(0d, PU)
345+
transformerType.getrScB.divide(pow(nominalRatio, 2)),
346+
transformerType.getxScB.divide(pow(nominalRatio, 2)),
347+
Quantities.getQuantity(0d, StandardUnits.CONDUCTANCE),
348+
Quantities.getQuantity(0d, StandardUnits.SUSCEPTANCE)
338349
)
339350
case PowerFlowCaseC =>
351+
val nominalRatio = transformerType
352+
.getvRatedA()
353+
.divide(transformerType.getvRatedC())
354+
.asType(classOf[Dimensionless])
355+
.to(AbstractUnit.ONE)
356+
.getValue
357+
.doubleValue()
340358
(
341-
transformerRefSystem.rInPu(transformerType.getrScC),
342-
transformerRefSystem.xInPu(transformerType.getxScC),
343-
Quantities.getQuantity(0d, PU),
344-
Quantities.getQuantity(0d, PU)
359+
transformerType.getrScC.divide(pow(nominalRatio, 2)),
360+
transformerType.getxScC.divide(pow(nominalRatio, 2)),
361+
Quantities.getQuantity(0d, StandardUnits.CONDUCTANCE),
362+
Quantities.getQuantity(0d, StandardUnits.SUSCEPTANCE)
345363
)
346364
}
347365

348-
/* Translate the single parameters to the grid's reference system */
366+
/* Translate the single parameters to dimensionless units based on the grid's reference system */
349367
(
350368
/* r */
351-
Quantities.getQuantity(
352-
RefSystem
353-
.transferImpedance(rTrafo, transformerRefSystem, refSystem)
354-
.getValue
355-
.doubleValue(),
356-
PU
357-
),
369+
refSystem.rInPu(rTrafo),
358370
/* x */
359-
Quantities.getQuantity(
360-
RefSystem
361-
.transferImpedance(xTrafo, transformerRefSystem, refSystem)
362-
.getValue
363-
.doubleValue(),
364-
PU
365-
),
371+
refSystem.xInPu(xTrafo),
366372
/* g */
367-
Quantities.getQuantity(
368-
RefSystem
369-
.transferAdmittance(gTrafo, transformerRefSystem, refSystem)
370-
.getValue
371-
.doubleValue(),
372-
PU
373-
),
373+
refSystem.gInPu(gTrafo),
374374
/* b */
375-
Quantities.getQuantity(
376-
RefSystem
377-
.transferAdmittance(bTrafo, transformerRefSystem, refSystem)
378-
.getValue
379-
.doubleValue(),
380-
PU
381-
)
375+
refSystem.bInPu(bTrafo)
382376
)
383377
}
384378

@@ -461,15 +455,27 @@ case object Transformer3wModel {
461455
)
462456

463457
// check if nominal power of winding A is able to supply winding B and C
464-
val nomSwindingA = trafo3wType.getsRatedA.getValue.doubleValue
465-
val nomSwindingB = trafo3wType.getsRatedB.getValue.doubleValue
466-
val nomSwindingC = trafo3wType.getsRatedC.getValue.doubleValue
467-
468-
if (nomSwindingA < (nomSwindingB + nomSwindingC))
469-
throw new InvalidParameterException(
470-
s"The winding A of transformer type has a lower rating as both windings B and C together! " +
471-
s"($nomSwindingA < $nomSwindingB + $nomSwindingC)"
472-
)
458+
val sNomA = trafo3wType.getsRatedA.getValue.doubleValue
459+
val sNomB = trafo3wType.getsRatedB.getValue.doubleValue
460+
val sNomC = trafo3wType.getsRatedC.getValue.doubleValue
461+
462+
if (sNomA < (sNomB + sNomC)) {
463+
val maxSNomUnderlying = max(sNomB, sNomC)
464+
if (sNomA < maxSNomUnderlying)
465+
throw new InvalidParameterException(
466+
s"The winding A of transformer type has a lower rating ($sNomA) as winding B ($sNomB) or C ($sNomC)!"
467+
)
468+
else
469+
logger.warn(
470+
"The port A of three winding transformer type {} ({}) has lower power rating ({}) than both underlying ports together ({} + {} = {})!",
471+
trafo3wType.getUuid,
472+
trafo3wType.getId,
473+
trafo3wType.getsRatedA(),
474+
trafo3wType.getsRatedB(),
475+
trafo3wType.getsRatedC(),
476+
trafo3wType.getsRatedB().add(trafo3wType.getsRatedC())
477+
)
478+
}
473479
}
474480

475481
/** Calculates the current, tap dependent voltage ratio between the high
@@ -521,7 +527,7 @@ case object Transformer3wModel {
521527
val bij = transformer3wModel.bij().getValue.doubleValue()
522528
val gii = transformer3wModel.g0().getValue.doubleValue()
523529
val bii = transformer3wModel.b0().getValue.doubleValue()
524-
amount * ((1 - transformer3wModel.tapRatio) * Complex(
530+
amount * ((1 - 1 / transformer3wModel.tapRatio) * Complex(
525531
gij,
526532
bij
527533
) + Complex(
@@ -550,7 +556,7 @@ case object Transformer3wModel {
550556
val bij = transformer3wModel.bij().getValue.doubleValue()
551557
transformer3wModel.powerFlowCase match {
552558
case PowerFlowCaseA =>
553-
amount * pow(transformer3wModel.tapRatio, 2) * Complex(gij, bij)
559+
amount * Complex(gij, bij) / transformer3wModel.tapRatio
554560
case _ => amount * Complex(gij, bij)
555561
}
556562
}

0 commit comments

Comments
 (0)