Skip to content

Commit 6af69ec

Browse files
Merge branch 'feature/full-observability-check-meshed-network-without-voltage-phasor' into feature/unit-test-observability-check
2 parents c463ec1 + 67950e7 commit 6af69ec

File tree

1 file changed

+16
-29
lines changed
  • power_grid_model_c/power_grid_model/include/power_grid_model/math_solver

1 file changed

+16
-29
lines changed

power_grid_model_c/power_grid_model/include/power_grid_model/math_solver/observability.hpp

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ struct ObservabilitySensorsResult {
1616
std::vector<int8_t> flow_sensors; // power sensor and current sensor
1717
std::vector<int8_t> voltage_phasor_sensors; // voltage phasor sensors
1818
std::vector<int8_t> bus_injections; // bus injections, zero injection and power sensors at buses
19+
int8_t total_injections{0}; // total number of injections at buses
1920
bool is_possibly_ill_conditioned{false};
2021
};
2122
enum class ConnectivityStatus : std::int8_t {
@@ -75,7 +76,7 @@ ObservabilitySensorsResult scan_network_sensors(MeasuredValues<sym> const& measu
7576
// diagonal for bus injection measurement
7677
if (measured_values.has_bus_injection(bus)) {
7778
result.bus_injections[bus] = 1;
78-
result.bus_injections.back() += 1;
79+
result.total_injections += 1;
7980
result.flow_sensors[current_bus_entry] = 1;
8081
has_at_least_one_sensor = true;
8182
bus_neighbourhood_info[bus].status = ConnectivityStatus::node_measured; // only treat power/0 injection
@@ -351,13 +352,12 @@ inline bool find_spanning_tree_from_node(Idx start_bus, Idx n_bus,
351352
return false;
352353
};
353354

354-
auto try_general_connection_rules = [&local_neighbour_list, &visited, &discovered_edges, &edge_track, &current_bus,
355-
&downwind](bool& step_success, bool current_bus_no_measurement) {
355+
auto try_general_connection_rules = [&local_neighbour_list, &visited, &discovered_edges, &edge_track,
356+
&current_bus](bool& step_success, bool current_bus_no_measurement) {
356357
// Helper lambda to handle common edge processing logic
357-
auto process_edge = [&local_neighbour_list, &visited, &discovered_edges, &edge_track, &current_bus, &downwind,
358+
auto process_edge = [&local_neighbour_list, &visited, &discovered_edges, &edge_track, &current_bus,
358359
&step_success](auto& neighbour, ConnectivityStatus neighbour_status,
359-
ConnectivityStatus reverse_status, bool use_current_node,
360-
bool set_upwind = false) {
360+
ConnectivityStatus reverse_status, bool use_current_node) {
361361
discovered_edges.emplace_back(current_bus, neighbour.bus);
362362
edge_track.emplace_back(current_bus, neighbour.bus);
363363
visited[current_bus] = BusVisited::Visited;
@@ -380,11 +380,6 @@ inline bool find_spanning_tree_from_node(Idx start_bus, Idx n_bus,
380380
local_neighbour_list[neighbour.bus].status = ConnectivityStatus::has_no_measurement;
381381
}
382382

383-
// Set direction flag if needed
384-
if (set_upwind) {
385-
downwind = false; // upwind
386-
}
387-
388383
current_bus = neighbour.bus;
389384
step_success = true;
390385
return true;
@@ -398,20 +393,15 @@ inline bool find_spanning_tree_from_node(Idx start_bus, Idx n_bus,
398393
bool const neighbour_bus_has_no_measurement =
399394
local_neighbour_list[neighbour.bus].status == ConnectivityStatus::has_no_measurement;
400395

401-
if (!current_bus_no_measurement && (downwind && neighbour_bus_has_no_measurement)) {
402-
// Case: current has measurement, downwind, neighbour empty
403-
return process_edge(neighbour, ConnectivityStatus::branch_discovered_with_to_node_sensor,
404-
ConnectivityStatus::branch_discovered_with_from_node_sensor, true);
405-
}
406-
if (!current_bus_no_measurement && (downwind || neighbour_bus_has_no_measurement)) {
407-
// Case: current has measurement, (downwind OR neighbour empty)
396+
if (!current_bus_no_measurement && neighbour_bus_has_no_measurement) {
397+
// Case: current has measurement, neighbour empty (not in downwind mode)
408398
return process_edge(neighbour, ConnectivityStatus::branch_discovered_with_from_node_sensor,
409399
ConnectivityStatus::branch_discovered_with_to_node_sensor, true);
410400
}
411401
if (!neighbour_bus_has_no_measurement) {
412402
// Case: neighbour has measurement
413403
return process_edge(neighbour, ConnectivityStatus::branch_discovered_with_from_node_sensor,
414-
ConnectivityStatus::branch_discovered_with_to_node_sensor, false, true);
404+
ConnectivityStatus::branch_discovered_with_to_node_sensor, false);
415405
}
416406
}
417407
return false;
@@ -420,10 +410,10 @@ inline bool find_spanning_tree_from_node(Idx start_bus, Idx n_bus,
420410
// Helper function to reassign nodal measurement between two connected nodes
421411
auto reassign_nodal_measurement = [&local_neighbour_list](Idx from_node, Idx to_node) {
422412
// no reassignment possible if reached via edge measurement
423-
auto const branch_it =
424-
std::ranges::find_if(local_neighbour_list[from_node].direct_neighbours,
425-
[to_node](auto const& neighbour) { return neighbour.bus == to_node; });
426-
if (branch_it != local_neighbour_list[from_node].direct_neighbours.end() &&
413+
if (auto const branch_it =
414+
std::ranges::find_if(local_neighbour_list[from_node].direct_neighbours,
415+
[to_node](auto const& neighbour) { return neighbour.bus == to_node; });
416+
branch_it != local_neighbour_list[from_node].direct_neighbours.end() &&
427417
branch_it->status == ConnectivityStatus::branch_native_measurement_consumed) {
428418
return;
429419
}
@@ -447,7 +437,7 @@ inline bool find_spanning_tree_from_node(Idx start_bus, Idx n_bus,
447437
// Find and update the reverse connection from to_node to from_node
448438
for (auto& neighbour : local_neighbour_list[to_node].direct_neighbours) {
449439
if (neighbour.bus == from_node) {
450-
// Change to downstream connection (from from_node to to_node perspective)
440+
// Change from from_node to to_node perspective
451441
neighbour.status = ConnectivityStatus::branch_discovered_with_from_node_sensor;
452442
break;
453443
}
@@ -509,10 +499,7 @@ inline bool find_spanning_tree_from_node(Idx start_bus, Idx n_bus,
509499
}
510500

511501
inline bool
512-
sufficient_condition_meshed_without_voltage_phasor(std::vector<detail::BusNeighbourhoodInfo> const& _neighbour_list) {
513-
// make a copy of the neighbour list
514-
std::vector<detail::BusNeighbourhoodInfo> const neighbour_list = _neighbour_list;
515-
502+
sufficient_condition_meshed_without_voltage_phasor(std::vector<detail::BusNeighbourhoodInfo> const& neighbour_list) {
516503
auto const n_bus = static_cast<Idx>(neighbour_list.size());
517504
std::vector<Idx> starting_candidates;
518505
prepare_starting_nodes(neighbour_list, n_bus, starting_candidates);
@@ -563,7 +550,7 @@ inline ObservabilityResult observability_check(MeasuredValues<sym> const& measur
563550
}
564551

565552
// Sufficient early out, enough nodal measurement equals observable
566-
if (observability_sensors.bus_injections.back() > n_bus - 2) {
553+
if (observability_sensors.total_injections > n_bus - 2) {
567554
return ObservabilityResult{.is_observable = true,
568555
.is_possibly_ill_conditioned = observability_sensors.is_possibly_ill_conditioned};
569556
}

0 commit comments

Comments
 (0)