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

Using pivot perturbation for ill-conditioned state estimation #862

Merged
merged 62 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
476004d
start in-house factor
TonyXiang8787 Dec 27, 2024
779c307
use in-house factor
TonyXiang8787 Dec 27, 2024
c1db915
some unit test fail
TonyXiang8787 Dec 27, 2024
dbd89dd
fix is normal
TonyXiang8787 Dec 27, 2024
0b356cb
threshold to machine precision
TonyXiang8787 Dec 27, 2024
b6ab388
start perturbation
TonyXiang8787 Dec 27, 2024
16b5068
separate perturb function
TonyXiang8787 Dec 28, 2024
4773781
perturbation ready for matrix
TonyXiang8787 Dec 28, 2024
9e67119
iterative refinement does not compile yet
TonyXiang8787 Dec 28, 2024
58908bf
iterative refinement compiles
TonyXiang8787 Dec 28, 2024
272effd
debug with always enable iterative refinement
TonyXiang8787 Dec 28, 2024
839f852
try machine precision perturbation
TonyXiang8787 Dec 28, 2024
f71760e
works but converge very slow
TonyXiang8787 Dec 28, 2024
c888407
try to fix
TonyXiang8787 Dec 29, 2024
5520774
start observability
TonyXiang8787 Dec 29, 2024
fbee2f0
return observability
TonyXiang8787 Dec 29, 2024
22e37b0
try to consider observability
TonyXiang8787 Dec 29, 2024
cdf5e9c
fix a bug
TonyXiang8787 Dec 29, 2024
b146fb0
use cabs
TonyXiang8787 Dec 29, 2024
3bf4896
move to folder
TonyXiang8787 Dec 30, 2024
66d3e7a
unit test on scalar
TonyXiang8787 Dec 30, 2024
2c71b44
unit test
TonyXiang8787 Dec 30, 2024
42d6ebb
fix NRSE angle bug
TonyXiang8787 Dec 30, 2024
6e396c5
typo
TonyXiang8787 Dec 30, 2024
062a07a
fix voltage phasor check
TonyXiang8787 Dec 30, 2024
2bfd16d
add validation cases
TonyXiang8787 Dec 30, 2024
4cbd053
clang tidy
TonyXiang8787 Dec 30, 2024
abf6392
ignore warning
TonyXiang8787 Dec 30, 2024
5908cdb
cast on the whole expression
TonyXiang8787 Dec 30, 2024
b9bebe0
clang tidy
TonyXiang8787 Dec 30, 2024
5eb52b2
Merge branch 'main' into experimental/pivot-perturbation
TonyXiang8787 Dec 30, 2024
a0140b7
add meshed validation case which is failing. adjust error message.
TonyXiang8787 Dec 31, 2024
f822818
Merge branch 'main' into experimental/pivot-perturbation
TonyXiang8787 Jan 3, 2025
3afbaab
Merge remote-tracking branch 'origin/main' into experimental/pivot-pe…
TonyXiang8787 Jan 13, 2025
659db5a
add license notice
TonyXiang8787 Jan 13, 2025
6ba4c58
typo
TonyXiang8787 Jan 13, 2025
c06ba0d
license notice
TonyXiang8787 Jan 14, 2025
8a2b30b
apply some review suggestions
Jerry-Jinfeng-Guo Jan 16, 2025
71ab9c4
revert back set has pivot perturbation
TonyXiang8787 Jan 17, 2025
dfa6437
Update power_grid_model_c/power_grid_model/include/power_grid_model/m…
Jerry-Jinfeng-Guo Jan 21, 2025
1d15f26
Update power_grid_model_c/power_grid_model/include/power_grid_model/m…
TonyXiang8787 Jan 21, 2025
5537751
Update tests/cpp_unit_tests/test_sparse_lu_solver.cpp
TonyXiang8787 Jan 21, 2025
0e3cbd0
Update tests/cpp_unit_tests/test_sparse_lu_solver.cpp
TonyXiang8787 Jan 21, 2025
b1c3164
Update power_grid_model_c/power_grid_model/include/power_grid_model/m…
TonyXiang8787 Jan 21, 2025
38232f9
Merge branch 'main' into experimental/pivot-perturbation
TonyXiang8787 Jan 21, 2025
cc270c0
new back error
TonyXiang8787 Jan 21, 2025
ec55242
Merge branch 'main' into experimental/pivot-perturbation
TonyXiang8787 Jan 21, 2025
e82fae0
assert values
TonyXiang8787 Jan 21, 2025
25eda9f
change comment
TonyXiang8787 Jan 21, 2025
919b566
add more comment
TonyXiang8787 Jan 21, 2025
081005f
avoid bool chaining
TonyXiang8787 Jan 21, 2025
3234d4b
remove ill-conditioned reference
TonyXiang8787 Jan 21, 2025
5445c00
perturb only use references
TonyXiang8787 Jan 21, 2025
f59f8e4
relax perturbation
TonyXiang8787 Jan 21, 2025
2afd652
Update power_grid_model_c/power_grid_model/include/power_grid_model/m…
TonyXiang8787 Jan 21, 2025
df15f73
optimize loop
TonyXiang8787 Jan 21, 2025
df941a7
cmake policy based on version
TonyXiang8787 Jan 21, 2025
6434011
fix eigen initialization bug
TonyXiang8787 Jan 21, 2025
d58735e
Merge branch 'main' into experimental/pivot-perturbation
TonyXiang8787 Jan 22, 2025
7b67212
resolve some sonar-cloud issues
mgovers Jan 23, 2025
9f31867
Merge branch 'main' into experimental/pivot-perturbation
TonyXiang8787 Jan 24, 2025
5fb5aa0
set new cap
TonyXiang8787 Jan 24, 2025
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
1 change: 1 addition & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ readability-*,
-readability-function-cognitive-complexity,
-readability-identifier-length,
-readability-magic-numbers,
-bugprone-unchecked-optional-access,
'

WarningsAsErrors: '*'
Expand Down
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

include(GNUInstallDirs)

cmake_policy(SET CMP0167 OLD) # libboost-headers packaged by conda does not come with BoostConfig.cmake
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.30.0")
# libboost-headers packaged by conda does not come with BoostConfig.cmake
cmake_policy(SET CMP0167 OLD)
endif()
mgovers marked this conversation as resolved.
Show resolved Hide resolved

TonyXiang8787 marked this conversation as resolved.
Show resolved Hide resolved
find_package(Boost REQUIRED)
TonyXiang8787 marked this conversation as resolved.
Show resolved Hide resolved
find_package(Eigen3 CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class SparseMatrixError : public PowerGridError {
std::string("If you get this error from state estimation, ") +
"it might mean the system is not fully observable, i.e. not enough measurements.\n" +
"It might also mean that you are running into a corner case where PGM cannot resolve yet." +
"See https://github.com/PowerGridModel/power-grid-model/issues/853.");
"See https://github.com/PowerGridModel/power-grid-model/issues/864.");
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,18 @@ inline ComplexValue<asymmetric_t> piecewise_complex_value(ComplexValue<asymmetri
inline double cabs(double x) { return std::abs(x); }
inline double cabs(DoubleComplex const& x) { return std::sqrt(std::norm(x)); }
inline double abs2(DoubleComplex const& x) { return std::norm(x); }
template <column_vector_or_tensor DerivedA> inline auto cabs(Eigen::ArrayBase<DerivedA> const& m) {
template <column_vector_or_tensor DerivedA>
inline auto cabs(Eigen::ArrayBase<DerivedA> const& m)
requires(std::same_as<typename DerivedA::Scalar, DoubleComplex>)
{
return sqrt(abs2(m));
}
template <column_vector_or_tensor DerivedA>
inline auto cabs(Eigen::ArrayBase<DerivedA> const& m)
requires(std::same_as<typename DerivedA::Scalar, double>)
{
return m.abs();
}

// phase_shift(x) = e^{i arg(x)} = x / |x|
inline DoubleComplex phase_shift(DoubleComplex const x) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,14 @@ template <symmetry_tag sym_type> class IterativeLinearSESolver {
// preprocess measured value
sub_timer = Timer(calculation_info, 2221, "Pre-process measured value");
MeasuredValues<sym> const measured_values{y_bus.shared_topology(), input};
necessary_observability_check(measured_values, y_bus.math_topology(), y_bus.y_bus_structure());
auto const observability_result =
necessary_observability_check(measured_values, y_bus.math_topology(), y_bus.y_bus_structure());

// prepare matrix, including pre-factorization
// prepare matrix
sub_timer = Timer(calculation_info, 2222, "Prepare matrix, including pre-factorization");
prepare_matrix(y_bus, measured_values);
// prefactorize
sparse_solver_.prefactorize(data_gain_, perm_, observability_result.use_perturbation());

// initialize voltage with initial angle
sub_timer = Timer(calculation_info, 2223, "Initialize voltages");
Expand Down Expand Up @@ -252,8 +255,6 @@ template <symmetry_tag sym_type> class IterativeLinearSESolver {
Idx const data_idx_tranpose = y_bus.lu_transpose_entry()[data_idx_lu];
data_gain_[data_idx_lu].qh() = hermitian_transpose(data_gain_[data_idx_tranpose].q());
}
// prefactorize
sparse_solver_.prefactorize(data_gain_, perm_);
}

void prepare_rhs(YBus<sym> const& y_bus, MeasuredValues<sym> const& measured_value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ template <symmetry_tag sym_type> class NewtonRaphsonSESolver {
// preprocess measured value
sub_timer = Timer(calculation_info, 2221, "Pre-process measured value");
MeasuredValues<sym> const measured_values{y_bus.shared_topology(), input};
necessary_observability_check(measured_values, y_bus.math_topology(), y_bus.y_bus_structure());
auto const observability_result =
necessary_observability_check(measured_values, y_bus.math_topology(), y_bus.y_bus_structure());

// initialize voltage with initial angle
sub_timer = Timer(calculation_info, 2223, "Initialize voltages");
Expand All @@ -175,7 +176,8 @@ template <symmetry_tag sym_type> class NewtonRaphsonSESolver {
prepare_matrix_and_rhs(y_bus, measured_values, output.u);
// solve with prefactorization
sub_timer = Timer(calculation_info, 2225, "Solve sparse linear equation");
sparse_solver_.prefactorize_and_solve(data_gain_, perm_, delta_x_rhs_, delta_x_rhs_);
sparse_solver_.prefactorize_and_solve(data_gain_, perm_, delta_x_rhs_, delta_x_rhs_,
observability_result.use_perturbation());
sub_timer = Timer(calculation_info, 2226, "Iterate unknown");
max_dev = iterate_unknown(output.u, measured_values);
};
Expand Down Expand Up @@ -519,7 +521,8 @@ template <symmetry_tag sym_type> class NewtonRaphsonSESolver {
/// eta(row) += w_k . (z_k - f_k(x))
///
/// In case there is no angle measurement, the slack bus or arbitray bus measurement is considered to have a virtual
/// angle measurement of zero. w_theta = 1.0 by default for all measurements
/// angle measurement of zero. w_theta = w_k by default for all measurements
/// angle_error = u_error / u_rated (1.0) = w_k
///
/// @param block LHS(row, col), ie. LHS(row, row)
/// @param rhs_block RHS(row)
Expand All @@ -545,10 +548,10 @@ template <symmetry_tag sym_type> class NewtonRaphsonSESolver {
RealValue<sym> delta_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};
w_theta = RealTensor<sym>{w_v};
TonyXiang8787 marked this conversation as resolved.
Show resolved Hide resolved
} 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};
w_theta = RealTensor<sym>{w_v};
}

block.g_P_theta() += w_theta;
Expand Down
TonyXiang8787 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,25 @@ std::tuple<Idx, Idx> count_voltage_sensors(const Idx n_bus, const MeasuredValues
// lower triangle part is always zero
// for diagonal part, it will be one if there is bus injection
// for upper triangle part, it will be one if there is branch flow sensor and the branch is fully connected
// return a pair of
// a vector of flow sensor count
// a boolean indicating if the system is possibly ill-conditioned
template <symmetry_tag sym>
std::vector<int8_t> count_flow_sensors(MeasuredValues<sym> const& measured_values, MathModelTopology const& topo,
YBusStructure const& y_bus_structure) {
std::pair<std::vector<int8_t>, bool> count_flow_sensors(MeasuredValues<sym> const& measured_values,
MathModelTopology const& topo,
YBusStructure const& y_bus_structure) {
Idx const n_bus{topo.n_bus()};
std::vector<int8_t> flow_sensors(y_bus_structure.row_indptr.back(), 0); // initialize all to zero
std::pair<std::vector<int8_t>, bool> result{};
auto& [flow_sensors, possibly_ill_conditioned] = result;
flow_sensors.resize(y_bus_structure.row_indptr.back(), 0); // initialize all to zero
possibly_ill_conditioned = false;
for (Idx row = 0; row != n_bus; ++row) {
bool has_at_least_one_sensor{false};
// lower triangle is ignored and kept as zero
// diagonal for bus injection measurement
if (measured_values.has_bus_injection(row)) {
flow_sensors[y_bus_structure.bus_entry[row]] = 1;
has_at_least_one_sensor = true;
}
// upper triangle for branch flow measurement
for (Idx ybus_index = y_bus_structure.bus_entry[row] + 1; ybus_index != y_bus_structure.row_indptr[row + 1];
Expand All @@ -57,13 +66,22 @@ std::vector<int8_t> count_flow_sensors(MeasuredValues<sym> const& measured_value
if ((measured_values.has_branch_from(branch) || measured_values.has_branch_to(branch)) &&
topo.branch_bus_idx[branch][0] != -1 && topo.branch_bus_idx[branch][1] != -1) {
flow_sensors[ybus_index] = 1;
has_at_least_one_sensor = true;
break;
}
}
}
}
// check voltage sensor
if (measured_values.has_voltage(row) && measured_values.has_angle_measurement(row)) {
has_at_least_one_sensor = true;
}
// the system could be ill-conditioned if there is no flow sensor for one bus, except the last bus
if (!has_at_least_one_sensor && row != n_bus - 1) {
possibly_ill_conditioned = true;
}
}
return flow_sensors;
return result;
}

// re-organize the flow sensor for radial grid without phasor measurement
Expand Down Expand Up @@ -103,17 +121,27 @@ inline void assign_injection_sensor_radial(YBusStructure const& y_bus_structure,

} // namespace detail

struct ObservabilityResult {
bool is_sufficiently_observable{false};
bool is_possibly_ill_conditioned{false};
constexpr bool use_perturbation() const { return is_possibly_ill_conditioned && is_sufficiently_observable; }
};

template <symmetry_tag sym>
inline void necessary_observability_check(MeasuredValues<sym> const& measured_values, MathModelTopology const& topo,
YBusStructure const& y_bus_structure) {
inline ObservabilityResult necessary_observability_check(MeasuredValues<sym> const& measured_values,
MathModelTopology const& topo,
YBusStructure const& y_bus_structure) {
Idx const n_bus{topo.n_bus()};
ObservabilityResult result{};
figueroa1395 marked this conversation as resolved.
Show resolved Hide resolved

auto const [n_voltage_sensor, n_voltage_phasor_sensor] = detail::count_voltage_sensors(n_bus, measured_values);
if (n_voltage_sensor < 1) {
throw NotObservableError{"No voltage sensor found!\n"};
}

std::vector<int8_t> flow_sensors = detail::count_flow_sensors(measured_values, topo, y_bus_structure);
std::vector<int8_t> flow_sensors;
std::tie(flow_sensors, result.is_possibly_ill_conditioned) =
detail::count_flow_sensors(measured_values, topo, y_bus_structure);
// count flow sensors, note we manually specify the intial value type to avoid overflow
Idx const n_flow_sensor = std::reduce(flow_sensors.cbegin(), flow_sensors.cend(), Idx{}, std::plus<Idx>{});

Expand All @@ -130,12 +158,16 @@ inline void necessary_observability_check(MeasuredValues<sym> const& measured_va
if (topo.is_radial && n_voltage_phasor_sensor == 0) {
detail::assign_injection_sensor_radial(y_bus_structure, flow_sensors);
// count flow sensors again
Idx const n_flow_sensor_new = std::reduce(flow_sensors.cbegin(), flow_sensors.cend(), Idx{}, std::plus<Idx>{});
if (n_flow_sensor_new < n_bus - 1) {
if (Idx const n_flow_sensor_new =
std::reduce(flow_sensors.cbegin(), flow_sensors.cend(), Idx{}, std::plus<Idx>{});
n_flow_sensor_new < n_bus - 1) {
throw NotObservableError{"The number of power sensors appears sufficient, but they are not independent "
"enough. The system is still not observable.\n"};
}
result.is_sufficiently_observable = true;
}

return result;
}

} // namespace power_grid_model::math_solver
Loading
Loading