Skip to content

Commit 3dd427a

Browse files
authored
Merge pull request #1028 from PowerGridModel/NRSE-local-current-sensor
Current sensor: NRSE local angle implementation
2 parents a0f5b39 + 4dd296f commit 3dd427a

File tree

5 files changed

+324
-128
lines changed

5 files changed

+324
-128
lines changed

power_grid_model_c/power_grid_model/include/power_grid_model/math_solver/iterative_linear_se_solver.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,9 @@ template <symmetry_tag sym_type> class IterativeLinearSESolver {
6666
using sym = sym_type;
6767

6868
static constexpr auto is_iterative = true;
69-
static constexpr auto has_current_sensor_implemented =
70-
true; // TODO(mgovers): for testing purposes; remove after NRSE has current sensor implemented
69+
static constexpr auto has_global_current_sensor_implemented =
70+
true; // TODO(figueroa1395): for testing purposes; remove after NRSE has global current sensor implemented
71+
static constexpr auto is_NRSE_solver = false; // for testing purposes only
7172

7273
private:
7374
// block size 2 for symmetric, 6 for asym

power_grid_model_c/power_grid_model/include/power_grid_model/math_solver/newton_raphson_se_solver.hpp

Lines changed: 94 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include "y_bus.hpp"
1414

1515
#include "../calculation_parameters.hpp"
16+
#include "../common/enum.hpp"
17+
#include "../common/exception.hpp"
1618
#include "../common/three_phase_tensor.hpp"
1719
#include "../common/timer.hpp"
1820

@@ -91,8 +93,9 @@ template <symmetry_tag sym_type> class NewtonRaphsonSESolver {
9193
using sym = sym_type;
9294

9395
static constexpr auto is_iterative = true;
94-
static constexpr auto has_current_sensor_implemented =
95-
false; // TODO(mgovers): for testing purposes; remove after NRSE has current sensor implemented
96+
static constexpr auto has_global_current_sensor_implemented =
97+
false; // TODO(figueroa1395): for testing purposes; remove after NRSE has global current sensor implemented
98+
static constexpr auto is_NRSE_solver = true; // for testing purposes only
9699

97100
private:
98101
enum class Order : IntS { row_major = 0, column_major = 1 };
@@ -311,15 +314,50 @@ template <symmetry_tag sym_type> class NewtonRaphsonSESolver {
311314
if (measured_values.has_branch_from_power(obj)) {
312315
auto const ij_voltage_order =
313316
(type == YBusElementType::bft) ? Order::row_major : Order::column_major;
314-
process_branch_measurement(block, diag_block, rhs_block, y_branch.yff(), y_branch.yft(),
315-
u_state, ij_voltage_order,
316-
measured_values.branch_from_power(obj));
317+
process_branch_power_measurement(block, diag_block, rhs_block, y_branch.yff(),
318+
y_branch.yft(), u_state, ij_voltage_order,
319+
measured_values.branch_from_power(obj));
317320
}
318321
if (measured_values.has_branch_to_power(obj)) {
319322
auto const ij_voltage_order =
320323
(type == YBusElementType::btf) ? Order::row_major : Order::column_major;
321-
process_branch_measurement(block, diag_block, rhs_block, y_branch.ytt(), y_branch.ytf(),
322-
u_state, ij_voltage_order, measured_values.branch_to_power(obj));
324+
process_branch_power_measurement(block, diag_block, rhs_block, y_branch.ytt(),
325+
y_branch.ytf(), u_state, ij_voltage_order,
326+
measured_values.branch_to_power(obj));
327+
}
328+
if (measured_values.has_branch_from_current(obj)) {
329+
auto const ij_voltage_order =
330+
(type == YBusElementType::bft) ? Order::row_major : Order::column_major;
331+
auto const& measurement = measured_values.branch_from_current(obj);
332+
switch (measurement.angle_measurement_type) {
333+
case AngleMeasurementType::local_angle:
334+
process_branch_local_current_measurement(block, diag_block, rhs_block, y_branch.yff(),
335+
y_branch.yft(), u_state, ij_voltage_order,
336+
measurement);
337+
break;
338+
case AngleMeasurementType::global_angle:
339+
throw SparseMatrixError{};
340+
default:
341+
assert(measurement.angle_measurement_type == AngleMeasurementType::local_angle ||
342+
measurement.angle_measurement_type == AngleMeasurementType::global_angle);
343+
}
344+
}
345+
if (measured_values.has_branch_to_current(obj)) {
346+
auto const ij_voltage_order =
347+
(type == YBusElementType::btf) ? Order::row_major : Order::column_major;
348+
auto const& measurement = measured_values.branch_to_current(obj);
349+
switch (measurement.angle_measurement_type) {
350+
case AngleMeasurementType::local_angle:
351+
process_branch_local_current_measurement(block, diag_block, rhs_block, y_branch.ytt(),
352+
y_branch.ytf(), u_state, ij_voltage_order,
353+
measurement);
354+
break;
355+
case AngleMeasurementType::global_angle:
356+
throw SparseMatrixError{};
357+
default:
358+
assert(measurement.angle_measurement_type == AngleMeasurementType::local_angle ||
359+
measurement.angle_measurement_type == AngleMeasurementType::global_angle);
360+
}
323361
}
324362
break;
325363
}
@@ -412,7 +450,7 @@ template <symmetry_tag sym_type> class NewtonRaphsonSESolver {
412450
multiply_add_jacobian_blocks_rhs(rhs_block, block_F_T_k_w, measured_power, f_x_complex);
413451
}
414452

415-
/// @brief Adds contribution of branch measurements to the G(r, r), G(r, c) and eta_r blocks,
453+
/// @brief Adds contribution of branch power measurements to the G(r, r), G(r, c) and eta_r blocks,
416454
/// given the iteration passes through (r, c), i.e., row, col
417455
///
418456
/// When iterating via (row, col), have 4 cases regarding branch measurements:
@@ -438,10 +476,11 @@ template <symmetry_tag sym_type> class NewtonRaphsonSESolver {
438476
/// @param y_xi_mu admittance from the branch measurement to other bus
439477
/// @param u_state Voltage state of iteration voltage state vector
440478
/// @param order Order enum to determine if (chi, psi) = (row, col) or (col, row)
441-
/// @param measured_power
442-
void process_branch_measurement(NRSEGainBlock<sym>& block, NRSEGainBlock<sym>& diag_block, NRSERhs<sym>& rhs_block,
443-
auto const& y_xi_xi, auto const& y_xi_mu, auto const& u_state, Order const order,
444-
auto const& measured_power) {
479+
/// @param power_sensor measurement
480+
void process_branch_power_measurement(NRSEGainBlock<sym>& block, NRSEGainBlock<sym>& diag_block,
481+
NRSERhs<sym>& rhs_block, auto const& y_xi_xi, auto const& y_xi_mu,
482+
auto const& u_state, Order const order,
483+
PowerSensorCalcParam<sym> const& power_sensor) {
445484
auto const hm_u_chi_u_chi_y_xi_xi = hm_complex_form(y_xi_xi, u_state.u_chi_u_chi_conj(order));
446485
auto const nl_u_chi_u_chi_y_xi_xi = dot(hm_u_chi_u_chi_y_xi_xi, u_state.abs_u_chi_inv(order));
447486

@@ -456,22 +495,52 @@ template <symmetry_tag sym_type> class NewtonRaphsonSESolver {
456495
auto const block_rc_or_cr = calculate_jacobian(hm_u_chi_u_psi_y_xi_mu, nl_u_chi_u_psi_y_xi_mu);
457496

458497
if (order == Order::row_major) {
459-
multiply_add_branch_blocks(block, diag_block, rhs_block, block_rr_or_cc, block_rc_or_cr, measured_power,
498+
multiply_add_branch_blocks(block, diag_block, rhs_block, block_rr_or_cc, block_rc_or_cr, power_sensor,
460499
f_x_complex);
461500
} else {
462-
multiply_add_branch_blocks(block, diag_block, rhs_block, block_rc_or_cr, block_rr_or_cc, measured_power,
501+
multiply_add_branch_blocks(block, diag_block, rhs_block, block_rc_or_cr, block_rr_or_cc, power_sensor,
463502
f_x_complex);
464503
}
465504
}
466505

506+
/// @brief Adds contribution of branch current measurements. Logic is similar to powermeasurements, but with an
507+
/// additional voltage division as per mathematical workout.
508+
void process_branch_local_current_measurement(NRSEGainBlock<sym>& block, NRSEGainBlock<sym>& diag_block,
509+
NRSERhs<sym>& rhs_block, auto const& y_xi_xi, auto const& y_xi_mu,
510+
auto const& u_state, Order const order,
511+
CurrentSensorCalcParam<sym> const& current_sensor) {
512+
auto const hm_u_chi_u_chi_y_xi_xi = hm_complex_form(y_xi_xi, u_state.u_chi_u_chi_conj(order));
513+
auto const hm_hat_u_chi_u_chi_y_xi_xi = dot(hm_u_chi_u_chi_y_xi_xi, u_state.abs_u_chi_inv(order));
514+
auto const nl_hat_u_chi_u_chi_y_xi_xi = dot(hm_hat_u_chi_u_chi_y_xi_xi, u_state.abs_u_chi_inv(order));
515+
516+
auto const hm_u_chi_u_psi_y_xi_mu = hm_complex_form(y_xi_mu, u_state.u_chi_u_psi_conj(order));
517+
auto const hm_hat_u_chi_u_psi_y_xi_mu = dot(hm_u_chi_u_psi_y_xi_mu, u_state.abs_u_chi_inv(order));
518+
auto const nl_hat_u_chi_u_psi_y_xi_mu = dot(hm_hat_u_chi_u_psi_y_xi_mu, u_state.abs_u_psi_inv(order));
519+
520+
auto const f_x_complex = sum_row(hm_hat_u_chi_u_chi_y_xi_xi + hm_hat_u_chi_u_psi_y_xi_mu);
521+
522+
auto block_rr_or_cc = calculate_jacobian(hm_hat_u_chi_u_chi_y_xi_xi, nl_hat_u_chi_u_chi_y_xi_xi);
523+
block_rr_or_cc.dP_dt += RealTensor<sym>{RealValue<sym>{-imag(f_x_complex)}};
524+
block_rr_or_cc.dQ_dt += RealTensor<sym>{RealValue<sym>{real(f_x_complex)}};
525+
auto const block_rc_or_cr = calculate_jacobian(hm_hat_u_chi_u_psi_y_xi_mu, nl_hat_u_chi_u_psi_y_xi_mu);
526+
527+
if (order == Order::row_major) {
528+
multiply_add_branch_blocks(block, diag_block, rhs_block, block_rr_or_cc, block_rc_or_cr,
529+
current_sensor.measurement, f_x_complex);
530+
} else {
531+
multiply_add_branch_blocks(block, diag_block, rhs_block, block_rc_or_cr, block_rr_or_cc,
532+
current_sensor.measurement, f_x_complex);
533+
}
534+
}
535+
467536
void multiply_add_branch_blocks(NRSEGainBlock<sym>& block, NRSEGainBlock<sym>& diag_block, NRSERhs<sym>& rhs_block,
468-
auto& left_block, auto const& right_block, auto const& measured_power,
469-
auto const& f_x_complex) {
470-
auto const& block_F_T_k_w = transpose_multiply_weight(left_block, measured_power);
537+
auto& left_block, auto const& right_block,
538+
DecomposedComplexRandVar<sym> const& measured_flow, auto const& f_x_complex) {
539+
auto const& block_F_T_k_w = transpose_multiply_weight(left_block, measured_flow);
471540

472541
multiply_add_jacobian_blocks_lhs(diag_block, block_F_T_k_w, left_block);
473542
multiply_add_jacobian_blocks_lhs(block, block_F_T_k_w, right_block);
474-
multiply_add_jacobian_blocks_rhs(rhs_block, block_F_T_k_w, measured_power, f_x_complex);
543+
multiply_add_jacobian_blocks_rhs(rhs_block, block_F_T_k_w, measured_flow, f_x_complex);
475544
}
476545

477546
/// @brief Fill Q^T(j,i) of LHS(i, j) from the Q(j, i) of LHS(j, i).
@@ -586,12 +655,12 @@ template <symmetry_tag sym_type> class NewtonRaphsonSESolver {
586655
/// where W_k = [[p_variance, 0], [0, q_variance]]
587656
///
588657
/// @param jac_block F_k(u1, u2, y12)
589-
/// @param power_sensor object with members p_variance and q_variance
658+
/// @param measured_flow object with members p_variance and q_variance
590659
/// @return F_k(u1, u2, y12)^T . W
591660
NRSEJacobian transpose_multiply_weight(NRSEJacobian const& jac_block,
592-
PowerSensorCalcParam<sym> const& power_sensor) {
593-
auto const w_p = diagonal_inverse(power_sensor.real_component.variance);
594-
auto const w_q = diagonal_inverse(power_sensor.imag_component.variance);
661+
DecomposedComplexRandVar<sym> const& measured_flow) {
662+
auto const w_p = diagonal_inverse(measured_flow.real_component.variance);
663+
auto const w_q = diagonal_inverse(measured_flow.imag_component.variance);
595664

596665
NRSEJacobian product{};
597666
product.dP_dt = dot(w_p, jac_block.dP_dt);
@@ -620,12 +689,12 @@ template <symmetry_tag sym_type> class NewtonRaphsonSESolver {
620689
///
621690
/// @param rhs_block RHS(row)
622691
/// @param f_T_k_w F_{k,1}^T . w_k
623-
/// @param power_sensor measurement
692+
/// @param measured_flow power or current sensor measurement
624693
/// @param f_x_complex calculated power
625694
static void multiply_add_jacobian_blocks_rhs(NRSERhs<sym>& rhs_block, NRSEJacobian const& f_T_k_w,
626-
PowerSensorCalcParam<sym> const& power_sensor,
695+
DecomposedComplexRandVar<sym> const& measured_flow,
627696
ComplexValue<sym> const& f_x_complex) {
628-
ComplexValue<sym> const delta_power = power_sensor.value() - f_x_complex;
697+
ComplexValue<sym> const delta_power = measured_flow.value() - f_x_complex;
629698
rhs_block.eta_theta() += dot(f_T_k_w.dP_dt, real(delta_power)) + dot(f_T_k_w.dP_dv, imag(delta_power));
630699
rhs_block.eta_v() += dot(f_T_k_w.dQ_dt, real(delta_power)) + dot(f_T_k_w.dQ_dv, imag(delta_power));
631700
}

tests/cpp_unit_tests/test_math_solver_common.hpp

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,13 @@ inline void assert_output(SolverOutput<sym> const& output, SolverOutput<sym> con
6161

6262
template <symmetry_tag sym_type> struct SteadyStateSolverTestGrid {
6363
/*
64-
network, v means voltage measured, p means power measured, pp means double measured
65-
variance always 1.0
66-
shunt0 (ys) (p)
67-
(pp) (y0, ys0) (y1) |
68-
source --yref-- bus0(vp) -p-branch0-pp- bus1 --branch1-p- bus2(vv)
69-
| | |
70-
load012 load345 (p) load6 (not connected) (p, rubbish value)
71-
for const z,
72-
rubbish value for load3/4
64+
network
65+
66+
shunt0 (ys)
67+
(y0, ys0) (y1) |
68+
source --yref-- bus0 --branch0-- bus1 --branch1-- bus2
69+
| | |
70+
load012 load345 load6 (not connected)
7371
7472
uref = 1.10
7573
u0 = 1.08 -1deg
@@ -95,13 +93,7 @@ template <symmetry_tag sym_type> struct SteadyStateSolverTestGrid {
9593
const_pq, const_i, const_y, const_pq, const_i, const_y,
9694
const_pq // not connected
9795
};
98-
result.voltage_sensors_per_bus = {from_sparse, {0, 1, 1, 3}};
99-
result.power_sensors_per_bus = {from_sparse, {0, 1, 1, 1}};
100-
result.power_sensors_per_source = {from_sparse, {0, 2}};
101-
result.power_sensors_per_load_gen = {from_sparse, {0, 0, 0, 0, 1, 2, 3, 4}};
102-
result.power_sensors_per_shunt = {from_sparse, {0, 1}};
103-
result.power_sensors_per_branch_from = {from_sparse, {0, 1, 1}};
104-
result.power_sensors_per_branch_to = {from_sparse, {0, 2, 3}};
96+
// sensors for se tests connected later only for se tests
10597
return result;
10698
};
10799

0 commit comments

Comments
 (0)