Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix NRSE line measurement implementation #478

Merged
merged 45 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8d5e631
fix multiplication
nitbharambe Jan 24, 2024
6a2802f
rearrange branch injection
nitbharambe Jan 25, 2024
44d3f15
Merge branch 'feature/NRSE-general-structure' into fix/nrse-line-meas…
nitbharambe Jan 25, 2024
c018a17
refactor big blocks
nitbharambe Jan 25, 2024
1fbec29
fix adding to both
nitbharambe Jan 26, 2024
03d5101
remove commentted code
nitbharambe Jan 26, 2024
4562c35
change condition
nitbharambe Jan 26, 2024
3c62f60
rename to workout
nitbharambe Jan 26, 2024
6ad0718
rename del to delta
nitbharambe Jan 26, 2024
2f49229
pre-merge to comments
nitbharambe Jan 26, 2024
38c1b0f
add docstring
nitbharambe Jan 26, 2024
0d6e780
merge fixes to angle
nitbharambe Jan 29, 2024
cb21b9f
add newton raphson state estimation unit tests to math solver
mgovers Jan 29, 2024
632412a
add shunt tests
nitbharambe Jan 29, 2024
098c961
Merge branch 'feature/NRSE-general-structure' into fix/nrse-line-meas…
nitbharambe Jan 29, 2024
cbcba36
fix injection variance
nitbharambe Jan 29, 2024
6aea7f5
Merge branch 'feature/NRSE-general-structure' into fix/nrse-line-meas…
mgovers Jan 30, 2024
de343e4
minor
mgovers Jan 30, 2024
00d4325
format
mgovers Jan 30, 2024
c6cec66
Merge branch 'feature/NRSE-general-structure' into fix/nrse-line-meas…
mgovers Jan 30, 2024
b54487c
fix branch logic
nitbharambe Jan 30, 2024
5ddaa96
remove rough branch logic
nitbharambe Jan 30, 2024
12cfb32
improve readability
nitbharambe Jan 31, 2024
6d397e1
Merge branch 'fix/nrse-line-measurements' of https://github.com/Power…
nitbharambe Jan 31, 2024
e54b85a
refactor zero voltage angle
nitbharambe Jan 31, 2024
307bf50
fix spell
nitbharambe Jan 31, 2024
fab7277
change injection uj to ui
nitbharambe Jan 31, 2024
a706503
fix zero injection fx addition
nitbharambe Feb 1, 2024
4c471ca
revert line order change
nitbharambe Feb 2, 2024
0027661
correct lagrange multiplier
nitbharambe Feb 4, 2024
061d592
fix tau
nitbharambe Feb 5, 2024
1cac73b
remove unused variable
nitbharambe Feb 5, 2024
5e273ac
fix shunt
nitbharambe Feb 5, 2024
ec04c22
xfail distribution and sensor update
nitbharambe Feb 5, 2024
7b37e15
refactor, fix any zero to all zero
nitbharambe Feb 5, 2024
5247ea4
fix NRSE validation jsons
mgovers Feb 6, 2024
b46998b
fix compiler warning
mgovers Feb 6, 2024
1dcccea
Merge branch 'feature/NRSE-general-structure' into fix/nrse-line-meas…
mgovers Feb 6, 2024
84b3e65
Merge branch 'fix/nrse-line-measurements' of https://github.com/Power…
mgovers Feb 6, 2024
9940af1
minor resolve comments
mgovers Feb 6, 2024
bada724
refactor iteration logic
mgovers Feb 6, 2024
451a420
refactor
mgovers Feb 6, 2024
be53049
replace inline comments by multiline docstrings
mgovers Feb 6, 2024
f7e2cf0
fix bug in measured values
mgovers Feb 6, 2024
cdcd535
fix clang tidy
mgovers Feb 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ template <bool sym> class IterativeLinearSESolver {
// shunt
if (type == YBusElementType::shunt) {
if (measured_value.has_shunt(obj)) {
// G += Ys^H * (variance^-1) * Ys
// G += (-Ys)^H * (variance^-1) * (-Ys)
auto const& shunt_power = measured_value.shunt_power(obj);
block.g() += dot(hermitian_transpose(param.shunt_param[obj]),
diagonal_inverse(shunt_power.p_variance + shunt_power.q_variance),
Expand Down Expand Up @@ -282,7 +282,7 @@ template <bool sym> class IterativeLinearSESolver {
if (type == YBusElementType::shunt) {
if (measured_value.has_shunt(obj)) {
PowerSensorCalcParam<sym> const& m = measured_value.shunt_power(obj);
// eta -= Ys^H * (variance^-1) * i_shunt
// eta += (-Ys)^H * (variance^-1) * i_shunt
rhs_block.eta() -= dot(hermitian_transpose(param.shunt_param[obj]),
diagonal_inverse(m.p_variance + m.q_variance), conj(m.value / u[bus]));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ template <bool sym> class MeasuredValues {

// assign a meaningful mean angle shift, if at least one voltage has angle measurement
if (has_angle()) {
mean_angle_shift_ = angle_cum / static_cast<RealValue<sym>>(n_voltage_angle_measurements_);
mean_angle_shift_ = angle_cum / static_cast<RealValue<sym> const>(n_voltage_angle_measurements_);
}

static constexpr auto const is_measured = [](auto const& value) { return value >= 0; };
Expand Down
mgovers marked this conversation as resolved.
Show resolved Hide resolved
mgovers marked this conversation as resolved.
Show resolved Hide resolved
mgovers marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,6 @@ template <bool sym> class NewtonRaphsonSESolver {
this->dQ_dv += other.dQ_dv;
return *this;
}
NRSEJacobian& operator-=(NRSEJacobian const& other) {
this->dP_dt -= other.dP_dt;
this->dP_dv -= other.dP_dv;
this->dQ_dt -= other.dQ_dt;
this->dQ_dv -= other.dQ_dv;
return *this;
}
};

public:
Expand Down Expand Up @@ -221,7 +214,7 @@ template <bool sym> class NewtonRaphsonSESolver {
std::ranges::fill(x_, default_unknown);
}

void prepare_matrix_and_rhs(YBus<sym> const& y_bus, MeasuredValues<sym> const& measured_value,
void prepare_matrix_and_rhs(YBus<sym> const& y_bus, MeasuredValues<sym> const& measured_values,
ComplexValueVector<sym> const& current_u) {
MathModelParam<sym> const& param = y_bus.math_model_param();
IdxVector const& row_indptr = y_bus.row_indptr_lu();
Expand Down Expand Up @@ -261,78 +254,89 @@ template <bool sym> class NewtonRaphsonSESolver {

// get a reference and reset block to zero
NRSEGainBlock<sym>& block = data_gain_[data_idx_lu];
// Diagonal block is being cleared outside this loop
if (row != col) {
block.clear();
}
// fill block with voltage measurement, only diagonal
if (row == col) {
process_voltage_measurements(block, rhs_block, measured_value, row);
// fill block with voltage measurement, only diagonal
process_voltage_measurements(diag_block, rhs_block, measured_values, row);
} else {
// Diagonal block is being cleared outside this loop
block.clear();
}

// fill block with branch, shunt measurement
for (Idx element_idx = y_bus.y_bus_entry_indptr()[data_idx];
element_idx != y_bus.y_bus_entry_indptr()[data_idx + 1]; ++element_idx) {
Idx const obj = y_bus.y_bus_element()[element_idx].idx;
YBusElementType const type = y_bus.y_bus_element()[element_idx].element_type;
if (type == YBusElementType::shunt) {
if (measured_value.has_shunt(obj)) {
auto const& yii = param.shunt_param[obj];
auto const& measured_power = measured_value.shunt_power(obj);
process_shunt_measurement(block, rhs_block, yii, u_state, measured_power);

switch (type) {
case YBusElementType::shunt:
if (measured_values.has_shunt(obj)) {
auto const& ys = param.shunt_param[obj];
auto const& measured_power = measured_values.shunt_power(obj);
process_shunt_measurement(block, rhs_block, ys, u_state, measured_power);
}
} else if (type == YBusElementType::bft || type == YBusElementType::btf) {
break;
case YBusElementType::bft:
[[fallthrough]];
case YBusElementType::btf: {
auto const& y_branch = param.branch_param[obj];
if (measured_value.has_branch_from(obj)) {
if (measured_values.has_branch_from(obj)) {
auto const ij_voltage_order = (type == YBusElementType::bft);
process_branch_measurement(block, diag_block, rhs_block, y_branch.yff(), y_branch.yft(),
u_state, ij_voltage_order,
measured_value.branch_from_power(obj));
measured_values.branch_from_power(obj));
}
if (measured_value.has_branch_to(obj)) {
if (measured_values.has_branch_to(obj)) {
auto const ij_voltage_order = (type == YBusElementType::btf);
process_branch_measurement(block, diag_block, rhs_block, y_branch.ytt(), y_branch.ytf(),
u_state, ij_voltage_order, measured_value.branch_to_power(obj));
u_state, ij_voltage_order, measured_values.branch_to_power(obj));
}
} else {
break;
}
default:
assert(type == YBusElementType::bff || type == YBusElementType::btt);
break;
}
}

// fill block with injection measurement constraints
if (measured_value.has_bus_injection(row)) {
if (measured_values.has_bus_injection(row)) {
auto const& yij = y_bus.admittance()[data_idx];
process_injection_row(block, diag_block, rhs_block, yij, u_state);

// R_ii = -variance, only diagonal
if (row == col) {
auto const& injection = measured_value.bus_injection(row);
// assign variance to diagonal of 3x3 tensor, for asym
rhs_block.tau_p() += injection.value.real();
rhs_block.tau_q() += injection.value.imag();
block.r_P_theta() = RealTensor<sym>{RealValue<sym>{-injection.p_variance}};
block.r_Q_v() = RealTensor<sym>{RealValue<sym>{-injection.q_variance}};
process_injection_diagonal(block, rhs_block, measured_values.bus_injection(row));
}
} else {
// virtually remove constraints from equation
// Q_ij = 0
// R_ii = -1.0, only diagonal
// assign -1.0 to diagonal of 3x3 tensor, for asym
if (row == col) {
mgovers marked this conversation as resolved.
Show resolved Hide resolved
block.r_P_theta() = RealTensor<sym>{-1.0};
block.r_Q_v() = RealTensor<sym>{-1.0};
virtually_remove_constraints(block);
}
}
}
}

// loop all transpose entry for QT
// assign the transpose of the transpose entry of Q
fill_qt_process_lagrange_multiplier(y_bus);

// prefactorize
sparse_solver_.prefactorize(data_gain_, perm_);
}

void virtually_remove_constraints(NRSEGainBlock<sym>& block) {
// Q_ij = 0
// R_ii = -1.0, only diagonal
// assign -1.0 to diagonal of 3x3 tensor, for asym
block.r_P_theta() = RealTensor<sym>{-1.0};
block.r_Q_v() = RealTensor<sym>{-1.0};
}

void process_injection_diagonal(NRSEGainBlock<sym>& block, NRSERhs<sym>& rhs_block, auto const& injection) {
// R_ii = -variance, only diagonal
// assign variance to diagonal of 3x3 tensor, for asym
rhs_block.tau_p() += injection.value.real();
rhs_block.tau_q() += injection.value.imag();
block.r_P_theta() = RealTensor<sym>{RealValue<sym>{-injection.p_variance}};
block.r_Q_v() = RealTensor<sym>{RealValue<sym>{-injection.q_variance}};
}

/**
* @brief Processes common part of all elements to fill from an injection measurement.
* Also includes zero injection constraint.
Expand All @@ -342,8 +346,8 @@ template <bool sym> class NewtonRaphsonSESolver {
* @param block LHS(r, c)
* @param diag_block LHS(r, r)
* @param rhs_block RHS(r)
* @param yij
* @param u_state
* @param yij admittance of (row with injection, c)
* @param u_state Voltage state of iteration
*/
void process_injection_row(NRSEGainBlock<sym>& block, NRSEGainBlock<sym>& diag_block, NRSERhs<sym>& rhs_block,
auto const& yij, auto const& u_state) {
Expand All @@ -361,15 +365,25 @@ template <bool sym> class NewtonRaphsonSESolver {
rhs_block.tau_q() -= imag(f_x_complex_row);
}

void process_shunt_measurement(NRSEGainBlock<sym>& block, NRSERhs<sym>& rhs_block, auto const& yii,
/**
* @brief Adds contribution of G(i, i) form the shunt.
* Note: The sign of Y_s is inverted per injection direction.
*
* @param block LHS(i, i)
* @param rhs_block RHS(i)
* @param ys shunt admittance related to the shunt measurement
* @param u_state Voltage state of iteration Voltage state of iteration
* @param measured_power measured shunt power
*/
void process_shunt_measurement(NRSEGainBlock<sym>& block, NRSERhs<sym>& rhs_block, auto const& ys,
auto const& u_state, auto const& measured_power) {
auto const hm_ui_ui_yii = hm_complex_form(yii, u_state.ui_ui_conj);
auto const nl_ui_ui_yii = dot(hm_ui_ui_yii, u_state.abs_ui_inv);
auto const f_x_complex = sum_row(hm_ui_ui_yii);
auto const f_x_complex_abs_ui_inv = sum_row(nl_ui_ui_yii);
auto const hm_ui_ui_ys = hm_complex_form(-ys, u_state.ui_ui_conj);
auto const nl_ui_ui_ys = dot(hm_ui_ui_ys, u_state.abs_ui_inv);
auto const f_x_complex = sum_row(hm_ui_ui_ys);
auto const f_x_complex_abs_ui_inv = sum_row(nl_ui_ui_ys);

auto jac_block = calculate_jacobian(hm_ui_ui_yii, nl_ui_ui_yii);
jac_block -= jacobian_diagonal_component(f_x_complex_abs_ui_inv, f_x_complex);
auto jac_block = calculate_jacobian(hm_ui_ui_ys, nl_ui_ui_ys);
jac_block += jacobian_diagonal_component(f_x_complex_abs_ui_inv, f_x_complex);
auto const& block_F_T_k_w = transpose_multiply_weight(jac_block, measured_power);
multiply_add_jacobian_blocks_lhs(block, block_F_T_k_w, jac_block);
multiply_add_jacobian_blocks_rhs(rhs_block, block_F_T_k_w, measured_power, f_x_complex);
Expand Down Expand Up @@ -398,9 +412,9 @@ template <bool sym> class NewtonRaphsonSESolver {
* @param block G_(r, c)
* @param diag_block G_(r, r)
* @param rhs_block RHS(r)
* @param y_xi_xi
* @param y_xi_mu
* @param u_state voltage state vector
* @param y_xi_xi shunt admittance near to branch measurement
* @param y_xi_mu admittance from the branch measurement to other bus
* @param u_state Voltage state of iteration voltage state vector
* @param order bool to determine if (chi, psi) = (row, col) or (col, row)
* @param measured_power
*/
Expand Down Expand Up @@ -481,31 +495,31 @@ template <bool sym> class NewtonRaphsonSESolver {
*
* @param block LHS(row, col), ie. LHS(row, row)
* @param rhs_block RHS(row)
* @param measured_value
* @param bus
* @param measured_values
* @param bus bus with voltage measurement
*/
void process_voltage_measurements(NRSEGainBlock<sym>& block, NRSERhs<sym>& rhs_block,
MeasuredValues<sym> const& measured_value, Idx const& bus) {
if (!measured_value.has_voltage(bus)) {
MeasuredValues<sym> const& measured_values, Idx const& bus) {
if (!measured_values.has_voltage(bus)) {
return;
}

// G += 1.0 / variance
// for 3x3 tensor, fill diagonal
auto const w_v = RealTensor<sym>{1.0 / measured_value.voltage_var(bus)};
auto const abs_measured_v = detail::cabs_or_real<sym>(measured_value.voltage(bus));
auto const w_v = RealTensor<sym>{1.0 / measured_values.voltage_var(bus)};
auto const abs_measured_v = detail::cabs_or_real<sym>(measured_values.voltage(bus));
auto const delta_v = abs_measured_v - x_[bus].v();

auto const virtual_angle_measurement_bus = measured_value.has_voltage(math_topo_->slack_bus)
auto const virtual_angle_measurement_bus = measured_values.has_voltage(math_topo_->slack_bus)
? math_topo_->slack_bus
: measured_value.first_voltage_measurement();
: measured_values.first_voltage_measurement();

RealTensor<sym> w_theta{};
RealValue<sym> delta_theta{};
if (measured_value.has_angle_measurement(bus)) {
delta_theta = RealValue<sym>{arg(measured_value.voltage(bus))} - RealValue<sym>{x_[bus].theta()};
if (measured_values.has_angle_measurement(bus)) {
delta_theta = RealValue<sym>{arg(measured_values.voltage(bus))} - RealValue<sym>{x_[bus].theta()};
w_theta = RealTensor<sym>{1.0};
} else if (bus == virtual_angle_measurement_bus && !measured_value.has_angle()) {
} else if (bus == virtual_angle_measurement_bus && !measured_values.has_angle()) {
delta_theta = arg(ComplexValue<sym>{1.0}) - RealValue<sym>{x_[bus].theta()};
w_theta = RealTensor<sym>{1.0};
}
Expand Down Expand Up @@ -643,10 +657,10 @@ template <bool sym> class NewtonRaphsonSESolver {
// accumulate the unknown variable
x_[bus].theta() += delta_x_rhs_[bus].theta() - RealValue<sym>{angle_offset};
x_[bus].v() += delta_x_rhs_[bus].v();
if (measured_values.has_bus_injection(bus) && any_zero(measured_values.bus_injection(bus).p_variance)) {
if (measured_values.has_bus_injection(bus) && all_zero(measured_values.bus_injection(bus).p_variance)) {
mgovers marked this conversation as resolved.
Show resolved Hide resolved
x_[bus].phi_p() += delta_x_rhs_[bus].phi_p();
}
if (measured_values.has_bus_injection(bus) && any_zero(measured_values.bus_injection(bus).q_variance)) {
if (measured_values.has_bus_injection(bus) && all_zero(measured_values.bus_injection(bus).q_variance)) {
x_[bus].phi_q() += delta_x_rhs_[bus].phi_q();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ inline auto is_inf(RealValue<false> const& value) { return is_inf(value(0)) || i
inline auto any_zero(std::floating_point auto value) { return value == 0.0; }
inline auto any_zero(RealValue<false> const& value) { return (value == RealValue<false>{0.0}).any(); }

// all_zero
inline auto all_zero(std::floating_point auto value) { return value == 0.0; }
inline auto all_zero(RealValue<false> const& value) { return (value == RealValue<false>{0.0}).all(); }

// update real values
//
// RealValue is only updated when the update value is not nan
Expand Down
32 changes: 18 additions & 14 deletions tests/cpp_unit_tests/test_main_model_se.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,20 +236,24 @@ TEST_CASE_TEMPLATE("Test main model - state estimation", CalculationMethod, Iter
main_model.output_result<Line>(math_output, line_output.begin());
main_model.output_result<SymPowerSensor>(math_output, power_sensor_output.begin());

CHECK(shunt_output[0].p == doctest::Approx(1800.0).scale(1e3));
CHECK(shunt_output[0].q == doctest::Approx(180.0).scale(1e3));

CHECK(line_output[0].p_from == doctest::Approx(1800.0).scale(1e3));
CHECK(line_output[0].q_from == doctest::Approx(180.0).scale(1e3));
CHECK(line_output[0].p_to == doctest::Approx(-1800.0).scale(1e3));
CHECK(line_output[0].q_to == doctest::Approx(-180.0).scale(1e3));

CHECK(power_sensor_output[0].p_residual == doctest::Approx(0.0).scale(1e3)); // shunt
CHECK(power_sensor_output[0].q_residual == doctest::Approx(0.0).scale(1e3)); // shunt
CHECK(power_sensor_output[1].p_residual == doctest::Approx(0.0).scale(1e3)); // branch_from
CHECK(power_sensor_output[1].q_residual == doctest::Approx(0.0).scale(1e3)); // branch_from
CHECK(power_sensor_output[2].p_residual == doctest::Approx(0.0).scale(1e3)); // branch_to
CHECK(power_sensor_output[2].q_residual == doctest::Approx(0.0).scale(1e3)); // branch_to
CHECK(shunt_output[0].p == doctest::Approx(1800.0).epsilon(0.01).scale(1e3));
CHECK(shunt_output[0].q == doctest::Approx(180.0).epsilon(0.01).scale(1e3));

CHECK(line_output[0].p_from == doctest::Approx(1800.0).epsilon(0.01).scale(1e3));
CHECK(line_output[0].q_from == doctest::Approx(180.0).epsilon(0.01).scale(1e3));
CHECK(line_output[0].p_to == doctest::Approx(-1800.0).epsilon(0.01).scale(1e3));
CHECK(line_output[0].q_to == doctest::Approx(-180.0).epsilon(0.01).scale(1e3));

CHECK(power_sensor_output[0].p_residual == doctest::Approx(0.0).epsilon(0.01).scale(1e3)); // shunt
CHECK(power_sensor_output[0].q_residual == doctest::Approx(0.0).epsilon(0.01).scale(1e3)); // shunt
CHECK(power_sensor_output[1].p_residual ==
doctest::Approx(0.0).epsilon(0.01).scale(1e3)); // branch_from
CHECK(power_sensor_output[1].q_residual ==
doctest::Approx(0.0).epsilon(0.01).scale(1e3)); // branch_from
CHECK(power_sensor_output[2].p_residual ==
doctest::Approx(0.0).epsilon(0.01).scale(1e3)); // branch_to
CHECK(power_sensor_output[2].q_residual ==
doctest::Approx(0.0).epsilon(0.01).scale(1e3)); // branch_to
mgovers marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Expand Down
11 changes: 1 addition & 10 deletions tests/data/state_estimation/1os2msr-no-angle/params.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
{
"calculation_method": ["iterative_linear", "newton_raphson"],
"rtol": 1e-8,
"atol": 1e-8,
"extra_params": {
"newton_raphson": {
"experimental_features": "enabled",
"fail": {
"reason": "Experimental feature under development",
"raises": "PowerGridError"
}
}
}
"atol": 1e-8
mgovers marked this conversation as resolved.
Show resolved Hide resolved
}
9 changes: 0 additions & 9 deletions tests/data/state_estimation/1os2msr/params.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,5 @@
"atol": {
"default": 1e-8,
".+_residual": 5e-4
},
"extra_params": {
"newton_raphson": {
"experimental_features": "enabled",
"fail": {
"reason": "Experimental feature under development",
"raises": "PowerGridError"
}
}
mgovers marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading
Loading