Skip to content

Commit

Permalink
proper conversion to line-to-neutral voltages, as discussed in the pe…
Browse files Browse the repository at this point in the history
…r-unit tutorial
  • Loading branch information
mcosovic committed Nov 8, 2024
1 parent c844efb commit de8f981
Show file tree
Hide file tree
Showing 14 changed files with 158 additions and 55 deletions.
3 changes: 2 additions & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ makedocs(
"Measurement Model" => "tutorials/measurementModel.md",
"AC State Estimation" => "tutorials/acStateEstimation.md",
"PMU State Estimation" => "tutorials/pmuStateEstimation.md",
"DC State Estimation" => "tutorials/dcStateEstimation.md"
"DC State Estimation" => "tutorials/dcStateEstimation.md",
"Per-Unit System" => "tutorials/perunit.md",
],
"API Reference" =>[
"Power System Model" => "api/powerSystemModel.md",
Expand Down
7 changes: 5 additions & 2 deletions docs/src/manual/measurementModel.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ using JuliaGrid # hide
system = powerSystem()
device = measurement()
addBus!(system; label = "Bus 1", base = 135e3)
addBus!(system; label = "Bus 1", base = sqrt(3) * 135e3)
addVoltmeter!(system, device; bus = "Bus 1", magnitude = 121.5, variance = 0.0135)
addVoltmeter!(system, device; bus = "Bus 1", magnitude = 135, variance = 0.135, noise = true)
Expand All @@ -144,6 +144,9 @@ In this example, we have chosen to specify `magnitude` and `variance` in kilovol
[device.voltmeter.magnitude.mean device.voltmeter.magnitude.variance]
```

!!! note "Info"
When users choose to input data in volts, measurement values and variances are related to line-to-neutral voltages, while the base values are defined for line-to-line voltages. Therefore, a conversion using ``\sqrt{3}`` is necessary. For more information, refer to the [Per-Unit System](@ref PerUnitSystem) section.

---

##### Print Data in the REPL
Expand Down Expand Up @@ -413,7 +416,7 @@ addBus!(system; label = "Bus 1", base = 135e3)
addBus!(system; label = "Bus 2", base = 135e3)
addBranch!(system; label = "Branch 1", from = "Bus 1", to = "Bus 2", reactance = 0.12)
addPmu!(system, device; bus = "Bus 1", magnitude = 148.5, angle = 5.73, varianceAngle = 0.06)
addPmu!(system, device; bus = "Bus 1", magnitude = 85.74, angle = 5.73, varianceAngle = 0.06)
addPmu!(system, device; from = "Branch 1", magnitude = 167.35, angle = -11.46, noise = true)
addPmu!(system, device; to = "Branch 1", magnitude = 150.61, angle = 0.0)
```
Expand Down
2 changes: 1 addition & 1 deletion docs/src/manual/powerSystemModel.md
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ system.generator.cost.reactive.piecewise[1]
---

##### Customizing Input Units for Keywords
Changing input units from per-units (pu) can be particularly useful since cost functions are usually related to SI units. Let us set active powers in megawatts (MW) and reactive powers in megavolt-amperes reactive (MVAr) :
Changing input units from per-units (pu) can be particularly useful since cost functions are usually related to SI units. Let us set active powers in megawatts (MW) and reactive powers in megavolt-amperes reactive (MVAr):
```@example addActiveCost
@power(MW, MVAr, pu)
nothing # hide
Expand Down
96 changes: 96 additions & 0 deletions docs/src/tutorials/perunit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# [Per-Unit System](@id PerUnitSystem)
In power system modeling and analysis, variables and parameters are often normalized using the per-unit system. The per-unit system is particularly advantageous for analyzing networks with multiple voltage levels connected by transformers with different turns ratios.

In static scenarios, there are four key quantities of interest: voltage, current, power, and impedance or admittance. Defining a per-unit system requires selecting base values for each of these quantities. However, since these quantities are interconnected by various laws, the choice of base values cannot be arbitrary. Typically, the base quantities are chosen as voltage and power, with the base current and impedance (or admittance) then calculated accordingly. Usually, base quantities are selected as follows:
* three-phase apparent power ``S_{3\phi(\text{b})}``,
* line-to-line voltage ``V_{LL(\text{b})}``.

While the value of three-phase apparent power is unique across the entire system, multiple line-to-line base voltages are used to account for the different voltage zones created by transformers.

!!! note "Info"
Since balanced three-phase power systems are treated as a single line with a neutral return, confusion may arise regarding the relationship between the per-unit values of line voltages and phase voltages, as well as between the per-unit values of single-phase and three-phase powers [john1994power](@cite). To clarify these relationships, we will systematically explore the conversions between the per-unit and SI systems below.

---

## Powers
Let us consider the three-phase apparent power expressed in SI units, denoted as ``S_{3\phi(\text{si})}``. To convert this into the per-unit system, we divide it by the base power:
```math
S_{3\phi(\text{pu})} = \cfrac{S_{3\phi(\text{si})}}{S_{3\phi(\text{b})}}.
```

Now, let us examine the power of a single phase and convert it to the per-unit system:
```math
S_{1\phi(\text{pu})} = \cfrac{S_{1\phi(\text{si})}}{S_{1\phi(\text{b})}} = \cfrac{S_{3\phi(\text{si})}}{3} \cfrac{3}{S_{3\phi(\text{b})}} = \cfrac{S_{3\phi(\text{si})}}{S_{3\phi(\text{b})}} = S_{3\phi(\text{pu})}.
```

This indicates that in the per-unit system, there is no distinction between three-phase and single-phase powers. The type of power only becomes relevant when converting back from per-unit to SI units, and vice versa.

!!! note "Info"
As is standard practice, even if all simulations utilize a single-phase equivalent, input powers provided in SI units are assumed to represent three-phase powers. Similarly, if simulation results are displayed in SI units, they are considered to be three-phase powers, as we have selected three-phase power as the base value.

---

## Voltages
Format for input data that JuliaGrid uses required value for base voltage per each bus, and those values represent the line-to-line voltages. On the other hand, in all analyses we are working with line-to-neutral voltages. To convert a line-to-neutral voltage given in SI units ``V_{LN(\text{si})}`` to per-unit form ``V_{LN(\text{pu})}``, or vice versa, we use the formula:
```math
V_{LN(\text{pu})} = \cfrac{\sqrt{3}V_{LN(\text{si})}}{V_{LL(\text{b})}}.
```

!!! note "Info"
Similarly to power, JuliaGrid simulations use a single-phase equivalent. Voltage values specified in volts correspond to line-to-neutral values, while base voltages are expected to be provided as line-to-line values.

---

## Impedances
Let us first consider the line itself, excluding transformers. The base impedance of the line is given by:
```math
Z_{L(\text{b})} = \cfrac{V_{LL(\text{b})}^2}{S_{3\phi(\text{b})}}.
```
If the impedance is provided in ohms, its value in the per-unit system is:
```math
Z_{L(\text{pu})} = \cfrac{Z_{L(\text{si})}}{Z_{L(\text{b})}} = \cfrac{Z_{L(\text{si})} S_{3\phi(\text{b})}}{V_{LL(\text{b})}^2}.
```

A common question that arises is which base voltage should be used for the line, considering the two ends of the line (from-bus and to-bus). The key assumption here is that the base voltages correspond to the nominal voltages of the transformers. Therefore, when the user defines base voltages, JuliaGrid assumes these voltages represent the nominal voltages of the transformers, implying that the base voltages on both the from-bus and to-bus ends of the line will be the same.

Now, let us consider the transformer. The base voltages at the from-bus end (primary side) ``V_{LLF(\text{b})}``, and the to-bus end (secondary side) ``V_{LLT(\text{b})}``,
will generally be different. This requires us to define a conversion method for impedance. Typically, when impedance is given in ohms, it refers to the primary side of the transformer, denoted as ``Z_{F(\text{si})}``, while the impedance in our [unified branch model](@ref UnifiedBranchModelTutorials) refers to the secondary side, denoted as ``Z_{T(\text{si})}``. To convert the impedance from the from-bus end to the to-bus end, we use:
```math
Z_{T(\text{si})} = \cfrac{Z_{F(\text{si})}}{m^2},
```
where ``m`` is the effective turns ratio, calculated as:
```math
m = \tau \cfrac{V_{LLF(\text{b})}}{V_{LLT(\text{b})}},
```
with ``\tau`` is off-nominal turns ratio. This equation provides the impedance on the to-bus end of the branch or the secondary side of the transformer in ohms.

To convert this impedance to the per-unit system, we use the base impedance for the secondary side:
```math
Z_{T(\text{pu})} = \cfrac{Z_{T(\text{si})}}{Z_{T(\text{b})}},
```
where:
```math
Z_{T(\text{b})} = \cfrac{V_{LLT(\text{b})}^2}{S_{3\phi(\text{b})}}.
```

Substituting the previous expressions, we obtain the following formula for the impedance on the secondary side in per-unit system:
```math
Z_{T(\text{pu})} = \cfrac{Z_{F(\text{si})} S_{3\phi(\text{b})}}{\tau^2 V_{LLF(\text{b})}^2}.
```
This formula applies to both lines and transformers. For a line, where ``\tau = 1``, the formula simplifies and becomes the same as the impedance equation for a line.

!!! note "Info"
In the case of a transformer, if impedances or admittances are provided in SI units, they must be specified for the primary side (from-bus end).

---

## Currents
Once the base power and base voltage values are set, we can calculate the base current flowing through a line or branch as follows:
```math
I_{L(\text{b})} = \cfrac{S_{3\phi(\text{b})}}{\sqrt{3}V_{LL(\text{b})}}.
```

When we transform currents that are given in SI unit to per-unit, or vice versa, we use the following formula:
```math
I_{L(\text{pu})} = \cfrac{I_{L(\text{si})}}{I_{L(\text{b})}} = \cfrac{\sqrt{3}I_{L(\text{si})}V_{LL(\text{b})}}{S_{3\phi(\text{b})}}.
```
2 changes: 1 addition & 1 deletion src/definition/system.jl
Original file line number Diff line number Diff line change
Expand Up @@ -281,4 +281,4 @@ end

##### Types #####
const P = Union{Bus, Branch, Generator}
const M = Union{Voltmeter, Ammeter, Wattmeter, Varmeter, PMU}
const M = Union{Voltmeter, Ammeter, Wattmeter, Varmeter, PMU}
6 changes: 3 additions & 3 deletions src/measurement/pmu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ function addPmu!(
pfxMagnitude = pfx.voltageMagnitude
pfxAngle = pfx.voltageAngle

baseInv = 1 / (baseVoltg.value[idx] * baseVoltg.prefix)
baseInv = sqrt(3) / (baseVoltg.value[idx] * baseVoltg.prefix)
else
if fromFlag
setLabel(pmu, label, def.label, lblBrch; prefix = "From ")
Expand Down Expand Up @@ -328,7 +328,7 @@ function addPmu!(
pmu.layout.index[i] = i
pmu.layout.bus[i] = true

baseInv = 1 / (baseVoltg.prefix * baseVoltg.value[i])
baseInv = sqrt(3) / (baseVoltg.prefix * baseVoltg.value[i])

add!(
pmu.magnitude, i, noise, pfx.voltageMagnitude, analysis.voltage.magnitude[i],
Expand Down Expand Up @@ -433,7 +433,7 @@ function updatePmu!(
pfxMagnitude = pfx.voltageMagnitude
pfxAngle = pfx.voltageAngle

baseInv = 1 / (baseVoltg.value[idxBusBrch] * baseVoltg.prefix)
baseInv = sqrt(3) / (baseVoltg.value[idxBusBrch] * baseVoltg.prefix)
else
pfxMagnitude = pfx.currentMagnitude
pfxAngle = pfx.currentAngle
Expand Down
10 changes: 5 additions & 5 deletions src/measurement/voltmeter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function addVoltmeter!(
idxBus = system.bus.label[lblBus]
push!(volt.layout.index, idxBus)

baseInv = 1 / (system.base.voltage.value[idxBus] * system.base.voltage.prefix)
baseInv = sqrt(3) / (system.base.voltage.value[idxBus] * system.base.voltage.prefix)

setMeter(
volt.magnitude, magnitude, key.variance, key.status, key.noise,
Expand Down Expand Up @@ -158,7 +158,7 @@ function addVoltmeter!(

lblBus = getLabel(system.bus, label[i], "bus")
setLabel(volt, missing, def.label, lblBus)
baseInv = 1 / (baseVoltg.prefix * baseVoltg.value[i])
baseInv = sqrt(3) / (baseVoltg.prefix * baseVoltg.value[i])

add!(
volt.magnitude, i, key.noise, pfx.voltageMagnitude,
Expand Down Expand Up @@ -220,7 +220,7 @@ function updateVoltmeter!(

idx = volt.label[getLabel(volt, label, "voltmeter")]
idxBus = volt.layout.index[idx]
baseInv = 1 / (system.base.voltage.value[idxBus] * system.base.voltage.prefix)
baseInv = sqrt(3) / (system.base.voltage.value[idxBus] * system.base.voltage.prefix)

updateMeter(
volt.magnitude, idx, magnitude, key.variance,
Expand All @@ -243,7 +243,7 @@ function updateVoltmeter!(

idx = voltmeter.label[getLabel(voltmeter, label, "voltmeter")]
idxBus = voltmeter.layout.index[idx]
baseInv = 1 / (system.base.voltage.value[idxBus] * system.base.voltage.prefix)
baseInv = sqrt(3) / (system.base.voltage.value[idxBus] * system.base.voltage.prefix)

updateMeter(
voltmeter.magnitude, idx, magnitude, key.variance,
Expand Down Expand Up @@ -281,7 +281,7 @@ function updateVoltmeter!(

idx = volt.label[getLabel(volt, label, "voltmeter")]
idxBus = volt.layout.index[idx]
baseInv = 1 / (system.base.voltage.value[idxBus] * system.base.voltage.prefix)
baseInv = sqrt(3) / (system.base.voltage.value[idxBus] * system.base.voltage.prefix)

updateMeter(
volt.magnitude, idx, magnitude, key.variance,
Expand Down
6 changes: 3 additions & 3 deletions src/powerFlow/acPowerFlow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -757,10 +757,10 @@ function solve!(system::PowerSystem, analysis::ACPowerFlow{FastNewtonRaphson})
pq = pf.pq
pvpq = pf.pvpq

if system.model.ac.model != pf.acmodel
pf.acmodel = copy(system.model.ac.model)
if ac.model != pf.acmodel
pf.acmodel = copy(ac.model)
if ac.pattern != pf.pattern
pf.pattern = copy(system.model.ac.pattern)
pf.pattern = copy(ac.pattern)
active.factorization = factorization(active.jacobian, active.factorization)
reactive.factorization = factorization(reactive.jacobian, reactive.factorization)
else
Expand Down
4 changes: 2 additions & 2 deletions src/powerSystem/bus.jl
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function addBus!(system::PowerSystem; label::IntStrMiss = missing, kwargs...)
end
push!(system.base.voltage.value, baseVoltage / system.base.voltage.prefix)

baseInv = 1 / baseVoltage
baseInv = sqrt(3) / baseVoltage
add!(voltg.magnitude, key.magnitude, def.magnitude, pfx.voltageMagnitude, baseInv)
add!(voltg.minMagnitude, key.minMagnitude, def.minMagnitude, pfx.voltageMagnitude, baseInv)
add!(voltg.maxMagnitude, key.maxMagnitude, def.maxMagnitude, pfx.voltageMagnitude, baseInv)
Expand Down Expand Up @@ -211,7 +211,7 @@ function updateBus!(system::PowerSystem; label::IntStrMiss, kwargs...)
baseVoltg.value[idx] = key.base * pfx.baseVoltage / baseVoltg.prefix
end

baseInv = 1 / (baseVoltg.value[idx] * baseVoltg.prefix)
baseInv = sqrt(3) / (baseVoltg.value[idx] * baseVoltg.prefix)
update!(bus.voltage.magnitude, key.magnitude, pfx.voltageMagnitude, baseInv, idx)
update!(bus.voltage.angle, key.angle, pfx.voltageAngle, 1.0, idx)
update!(bus.voltage.minMagnitude, key.minMagnitude, pfx.voltageMagnitude, baseInv, idx)
Expand Down
8 changes: 4 additions & 4 deletions src/powerSystem/generator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ function addGenerator!(
system.bus.supply.reactive[busIdx] += gen.output.reactive[end]
end

baseInv = 1 / (system.base.voltage.value[busIdx] * system.base.voltage.prefix)
baseInv = sqrt(3) / (system.base.voltage.value[busIdx] * system.base.voltage.prefix)
add!(gen.voltage.magnitude, key.magnitude, def.magnitude, pfx.voltageMagnitude, baseInv)

push!(gen.layout.bus, busIdx)
Expand Down Expand Up @@ -324,7 +324,7 @@ function updateGenerator!(system::PowerSystem; label::IntStrMiss, kwargs...)
update!(rmp.reserve30min, key.reserve30min, pfx.activePower, baseInv, idx)
update!(rmp.reactiveRamp, key.reactiveRamp, pfx.reactivePower, baseInv, idx)

baseInv = 1 / (system.base.voltage.value[idxBus] * system.base.voltage.prefix)
baseInv = sqrt(3) / (system.base.voltage.value[idxBus] * system.base.voltage.prefix)
update!(gen.voltage.magnitude, key.magnitude, pfx.voltageMagnitude, baseInv, idx)

update!(gen.layout.area, key.area, idx)
Expand Down Expand Up @@ -752,7 +752,7 @@ The function accepts five keywords:
* `reactive`: Reactive power cost model:
* `reactive = 1`: adding or updating cost, and piecewise linear is being used,
* `reactive = 2`: adding or updating cost, and polynomial is being used.
* `piecewise`: Cost model defined by input-output points given as `Vector{Float64}`:
* `piecewise`: Cost model defined by input-output points given as `Matrix{Float64}`:
* first column (pu, W or VAr): active or reactive power output of the generator,
* second column (€/hr): cost for the specified active or reactive power output.
* `polynomial`: The n-th degree polynomial coefficients given as `Vector{Float64}`:
Expand Down Expand Up @@ -1072,4 +1072,4 @@ function generatorkwargs(;
loadFollowing = loadFollowing, reactiveRamp = reactiveRamp,
reserve10min = reserve10min, reserve30min = reserve30min,
)
end
end
2 changes: 1 addition & 1 deletion src/powerSystem/save.jl
Original file line number Diff line number Diff line change
Expand Up @@ -437,4 +437,4 @@ function savePiecewise(file::File, cost::OrderedDict{Int64, Matrix{Float64}}, na
write(file, name, matpiecewise)

return format
end
end
2 changes: 1 addition & 1 deletion src/print/utility.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ end

##### Scale Quantities #####
function scaleVoltage(system::PowerSystem, pfx::PrefixLive, i::Int64)
return (system.base.voltage.value[i] * system.base.voltage.prefix) / pfx.voltageMagnitude
return (system.base.voltage.value[i] * system.base.voltage.prefix) / (sqrt(3) * pfx.voltageMagnitude)
end

function scaleVoltage(pfx::PrefixLive, system::PowerSystem, i::Int64)
Expand Down
Loading

0 comments on commit de8f981

Please sign in to comment.