|
| 1 | +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project <powergridmodel@lfenergy.org> |
| 2 | +// |
| 3 | +// SPDX-License-Identifier: MPL-2.0 |
| 4 | + |
| 5 | +#pragma once |
| 6 | + |
| 7 | +#include "common.hpp" |
| 8 | +#include "three_phase_tensor.hpp" |
| 9 | + |
| 10 | +/** |
| 11 | + * @file statistics.hpp |
| 12 | + * @brief This file contains various structures and functions for handling statistical representations of |
| 13 | + * random variables (RandVar) used in the Power Grid Model, like in the State Estimation algorithms to |
| 14 | + * handle measurements. |
| 15 | + * |
| 16 | + * The structures provided in this file are used to represent measured values of sensors |
| 17 | + * with different types of variances. These structures support both symmetric and asymmetric representations and |
| 18 | + * provide conversion operators to transform between these representations. |
| 19 | + * |
| 20 | + * A random variable in PGM can have following characteristics: |
| 21 | + * - Uniform: Single total variance for all phases |
| 22 | + * - Independent: all phases are independent from each other |
| 23 | + * - Scalar: Named as `Real` in this file, a scalar value `RealValue`, eg. real axis: RealValue (* 1), imaginary axis: |
| 24 | + * RealValue (* i). |
| 25 | + * - Complex: a complex value with real and imaginary parts. |
| 26 | + * |
| 27 | + * Based on these, we use combined variables in Decomposed/Polar forms: |
| 28 | + * - Decomposed: treat random variables individually as in cartesian co-ordinates with separated variances for both real |
| 29 | + * and imaginary part. |
| 30 | + * - Polar: random variables are in polar co-ordinates, with magnitudes and angles. |
| 31 | + * |
| 32 | + */ |
| 33 | + |
| 34 | +namespace power_grid_model { |
| 35 | +template <symmetry_tag sym_type> struct UniformRealRandVar { |
| 36 | + using sym = sym_type; |
| 37 | + |
| 38 | + RealValue<sym> value{}; |
| 39 | + double variance{}; // variance (sigma^2) of the error range |
| 40 | + |
| 41 | + explicit operator UniformRealRandVar<asymmetric_t>() const |
| 42 | + requires(is_symmetric_v<sym>) |
| 43 | + { |
| 44 | + return {.value = RealValue<asymmetric_t>{std::piecewise_construct, value}, .variance = variance}; |
| 45 | + } |
| 46 | + explicit operator UniformRealRandVar<symmetric_t>() const |
| 47 | + requires(is_asymmetric_v<sym>) |
| 48 | + { |
| 49 | + return {.value = mean_val(value), .variance = variance / 3.0}; |
| 50 | + } |
| 51 | +}; |
| 52 | + |
| 53 | +template <symmetry_tag sym_type> struct IndependentRealRandVar { |
| 54 | + using sym = sym_type; |
| 55 | + |
| 56 | + RealValue<sym> value{}; |
| 57 | + RealValue<sym> variance{}; // variance (sigma^2) of the error range |
| 58 | + |
| 59 | + explicit operator UniformRealRandVar<symmetric_t>() const { |
| 60 | + constexpr auto scale = is_asymmetric_v<sym> ? 3.0 : 1.0; |
| 61 | + return {.value = mean_val(value), .variance = mean_val(variance) / scale}; |
| 62 | + } |
| 63 | + explicit operator UniformRealRandVar<asymmetric_t>() const { |
| 64 | + return {.value = value, .variance = mean_val(variance)}; |
| 65 | + } |
| 66 | + explicit operator IndependentRealRandVar<asymmetric_t>() const |
| 67 | + requires(is_symmetric_v<sym>) |
| 68 | + { |
| 69 | + return {.value = RealValue<asymmetric_t>{std::piecewise_construct, value}, |
| 70 | + .variance = RealValue<asymmetric_t>{std::piecewise_construct, variance}}; |
| 71 | + } |
| 72 | + explicit operator IndependentRealRandVar<symmetric_t>() const |
| 73 | + requires(is_asymmetric_v<sym>) |
| 74 | + { |
| 75 | + return {.value = mean_val(value), .variance = mean_val(variance) / 3.0}; |
| 76 | + } |
| 77 | +}; |
| 78 | + |
| 79 | +// Complex measured value of a sensor with a uniform variance across all phases and axes of the complex plane |
| 80 | +// (rotationally symmetric) |
| 81 | +template <symmetry_tag sym_type> struct UniformComplexRandVar { |
| 82 | + using sym = sym_type; |
| 83 | + |
| 84 | + ComplexValue<sym> value{}; |
| 85 | + double variance{}; // variance (sigma^2) of the error range |
| 86 | +}; |
| 87 | + |
| 88 | +inline UniformComplexRandVar<symmetric_t> pos_seq(UniformComplexRandVar<asymmetric_t> const& var) { |
| 89 | + return {.value = pos_seq(var.value), .variance = var.variance / 3.0}; |
| 90 | +} |
| 91 | +inline UniformComplexRandVar<asymmetric_t> three_phase(UniformComplexRandVar<symmetric_t> const& var) { |
| 92 | + return {.value = ComplexValue<asymmetric_t>{var.value}, .variance = var.variance}; |
| 93 | +} |
| 94 | + |
| 95 | +// Complex measured value of a sensor with separate variances per phase (but rotationally symmetric in the |
| 96 | +// complex plane) |
| 97 | +template <symmetry_tag sym_type> struct IndependentComplexRandVar { |
| 98 | + using sym = sym_type; |
| 99 | + |
| 100 | + ComplexValue<sym> value{}; |
| 101 | + RealValue<sym> variance{}; // variance (sigma^2) of the error range |
| 102 | + |
| 103 | + explicit operator UniformComplexRandVar<sym>() const { |
| 104 | + return UniformComplexRandVar<sym>{.value = value, .variance = sum_val(variance)}; |
| 105 | + } |
| 106 | +}; |
| 107 | + |
| 108 | +// Complex measured value of a sensor modeled as separate real and imaginary components with independent |
| 109 | +// variances (rotationally symmetric) |
| 110 | +template <symmetry_tag sym_type> struct DecomposedComplexRandVar { |
| 111 | + using sym = sym_type; |
| 112 | + |
| 113 | + IndependentRealRandVar<sym> real_component; |
| 114 | + IndependentRealRandVar<sym> imag_component; |
| 115 | + |
| 116 | + ComplexValue<sym> value() const { return {real_component.value, imag_component.value}; } |
| 117 | + |
| 118 | + explicit operator UniformComplexRandVar<sym>() const { |
| 119 | + return static_cast<UniformComplexRandVar<sym>>(static_cast<IndependentComplexRandVar<sym>>(*this)); |
| 120 | + } |
| 121 | + explicit operator IndependentComplexRandVar<sym>() const { |
| 122 | + return IndependentComplexRandVar<sym>{.value = value(), |
| 123 | + .variance = real_component.variance + imag_component.variance}; |
| 124 | + } |
| 125 | +}; |
| 126 | + |
| 127 | +// Complex measured value of a sensor in polar coordinates (magnitude and angle) |
| 128 | +// (rotationally symmetric) |
| 129 | +template <symmetry_tag sym_type> struct PolarComplexRandVar { |
| 130 | + using sym = sym_type; |
| 131 | + |
| 132 | + UniformRealRandVar<sym> magnitude; |
| 133 | + UniformRealRandVar<sym> angle; |
| 134 | + |
| 135 | + ComplexValue<sym> value() const { return magnitude.value * exp(1.0i * angle.value); } |
| 136 | + |
| 137 | + explicit operator UniformComplexRandVar<sym>() const { |
| 138 | + return static_cast<UniformComplexRandVar<sym>>(static_cast<IndependentComplexRandVar<sym>>(*this)); |
| 139 | + } |
| 140 | + explicit operator IndependentComplexRandVar<sym>() const { |
| 141 | + return IndependentComplexRandVar<sym>{ |
| 142 | + .value = value(), .variance = magnitude.variance + magnitude.value * magnitude.value * angle.variance}; |
| 143 | + } |
| 144 | + |
| 145 | + // For sym to sym conversion: |
| 146 | + // Var(I_Re) ≈ Var(I) * cos^2(θ) + Var(θ) * I^2 * sin^2(θ) |
| 147 | + // Var(I_Im) ≈ Var(I) * sin^2(θ) + Var(θ) * I^2 * cos^2(θ) |
| 148 | + // For asym to asym conversion: |
| 149 | + // Var(I_Re,p) ≈ Var(I_p) * cos^2(θ_p) + Var(θ_p) * I_p^2 * sin^2(θ_p) |
| 150 | + // Var(I_Im,p) ≈ Var(I_p) * sin^2(θ_p) + Var(θ_p) * I_p^2 * cos^2(θ_p) |
| 151 | + explicit operator DecomposedComplexRandVar<sym>() const { |
| 152 | + auto const cos_theta = cos(angle.value); |
| 153 | + auto const sin_theta = sin(angle.value); |
| 154 | + auto const real_component = magnitude.value * cos_theta; |
| 155 | + auto const imag_component = magnitude.value * sin_theta; |
| 156 | + return DecomposedComplexRandVar<sym>{ |
| 157 | + .real_component = {.value = real_component, |
| 158 | + .variance = magnitude.variance * cos_theta * cos_theta + |
| 159 | + imag_component * imag_component * angle.variance}, |
| 160 | + .imag_component = {.value = imag_component, |
| 161 | + .variance = magnitude.variance * sin_theta * sin_theta + |
| 162 | + real_component * real_component * angle.variance}}; |
| 163 | + } |
| 164 | + |
| 165 | + // Var(I_Re,p) ≈ Var(I) * cos^2(θ - 2πp/3) + Var(θ) * I^2 * sin^2(θ - 2πp/3) |
| 166 | + // Var(I_Im,p) ≈ Var(I) * sin^2(θ - 2πp/3) + Var(θ) * I^2 * cos^2(θ - 2πp/3) |
| 167 | + explicit operator DecomposedComplexRandVar<asymmetric_t>() const |
| 168 | + requires(is_symmetric_v<sym>) |
| 169 | + { |
| 170 | + ComplexValue<asymmetric_t> const unit_complex{exp(1.0i * angle.value)}; |
| 171 | + ComplexValue<asymmetric_t> const complex = magnitude.value * unit_complex; |
| 172 | + return DecomposedComplexRandVar<asymmetric_t>{ |
| 173 | + .real_component = {.value = real(complex), |
| 174 | + .variance = magnitude.variance * real(unit_complex) * real(unit_complex) + |
| 175 | + imag(complex) * imag(complex) * angle.variance}, |
| 176 | + .imag_component = {.value = imag(complex), |
| 177 | + .variance = magnitude.variance * imag(unit_complex) * imag(unit_complex) + |
| 178 | + real(complex) * real(complex) * angle.variance}}; |
| 179 | + } |
| 180 | + |
| 181 | + // Var(I_Re) ≈ (1 / 9) * sum_p(Var(I_p) * cos^2(theta_p + 2 * pi * p / 3) + Var(theta_p) * I_p^2 * sin^2(theta_p + 2 |
| 182 | + // * pi * p / 3)) |
| 183 | + // Var(I_Im) ≈ (1 / 9) * sum_p(Var(I_p) * sin^2(theta_p + 2 * pi * p / 3) + Var(theta_p) * I_p^2 * |
| 184 | + // cos^2(theta_p + 2 * pi * p / 3)) |
| 185 | + explicit operator DecomposedComplexRandVar<symmetric_t>() const |
| 186 | + requires(is_asymmetric_v<sym>) |
| 187 | + { |
| 188 | + ComplexValue<asymmetric_t> const unit_complex{exp(1.0i * angle.value)}; |
| 189 | + ComplexValue<asymmetric_t> const unit_pos_seq_per_phase{unit_complex(0), a * unit_complex(1), |
| 190 | + a2 * unit_complex(2)}; |
| 191 | + DoubleComplex const pos_seq_value = pos_seq(magnitude.value * unit_complex); |
| 192 | + return DecomposedComplexRandVar<symmetric_t>{ |
| 193 | + .real_component = {.value = real(pos_seq_value), |
| 194 | + .variance = sum_val(magnitude.variance * real(unit_pos_seq_per_phase) * |
| 195 | + real(unit_pos_seq_per_phase) + |
| 196 | + imag(unit_pos_seq_per_phase) * imag(unit_pos_seq_per_phase) * |
| 197 | + magnitude.value * magnitude.value * angle.variance) / |
| 198 | + 9.0}, |
| 199 | + .imag_component = { |
| 200 | + .value = imag(pos_seq_value), |
| 201 | + .variance = sum_val(magnitude.variance * imag(unit_pos_seq_per_phase) * imag(unit_pos_seq_per_phase) + |
| 202 | + real(unit_pos_seq_per_phase) * real(unit_pos_seq_per_phase) * magnitude.value * |
| 203 | + magnitude.value * angle.variance) / |
| 204 | + 9.0}}; |
| 205 | + } |
| 206 | +}; |
| 207 | +} // namespace power_grid_model |
0 commit comments