Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
07f87d1
Bump astral-sh/setup-uv in the all-dependencies group
dependabot[bot] Aug 25, 2026
e7bcce6
initial attempt; some tests are failing ofc
Jerry-Jinfeng-Guo Aug 25, 2026
58d7218
api all updates still failing
Jerry-Jinfeng-Guo Sep 1, 2026
42317ee
touched on the topological node output; validation tests are still fa…
Jerry-Jinfeng-Guo Sep 1, 2026
adf74ba
Merge branch 'main' into feature/link-registration-dual-code-path
Jerry-Jinfeng-Guo Sep 1, 2026
185cc17
remove redef
Jerry-Jinfeng-Guo Sep 1, 2026
9ad2ec5
[WIP] partial fix: power flow works
figueroa1395 Sep 2, 2026
6fdc169
SE working
figueroa1395 Sep 2, 2026
22c17af
address fault to node logic
nitbharambe Sep 2, 2026
f6ee4f7
fix tap regulator
figueroa1395 Sep 2, 2026
66876c0
update pf results and mark sc tests as fail
Jerry-Jinfeng-Guo Sep 4, 2026
953427d
format a source file in base branch
Jerry-Jinfeng-Guo Sep 4, 2026
74ddc1b
sc tests are not to be marked as fail; further follow up in #1566
Jerry-Jinfeng-Guo Sep 4, 2026
c942f48
super node test updated
Jerry-Jinfeng-Guo Sep 4, 2026
25934a4
Merge pull request #1569 from PowerGridModel/feature/link-tests-updat…
Jerry-Jinfeng-Guo Sep 4, 2026
d656dd6
Merge branch 'main' into feature/link-registration-dual-code-path
Jerry-Jinfeng-Guo Sep 4, 2026
0e496b2
fix the test fixture
Jerry-Jinfeng-Guo Sep 4, 2026
8404bcc
error tollerance for u angle dummy pf test
Jerry-Jinfeng-Guo Sep 4, 2026
bf78a89
Merge branch 'main' into feature/link-registration-dual-code-path
Jerry-Jinfeng-Guo Sep 4, 2026
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 @@ -506,7 +506,8 @@ struct ReducedComponentTopology {

struct TopologicalNode {
IdxVector user_nodes;
std::vector<BranchIdx> user_links;
std::vector<BranchIdx>
user_links; // TODO(figueroa1395): temporarily doesn't have user nodes, but internal nodes while exploring

constexpr auto is_supernode() const noexcept -> bool { return user_nodes.size() > 1 && !user_links.empty(); }
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,12 @@ inline void rebuild_topology(typename ModelType::MainModelState& state, SolverPr

// clear old solvers
reset_solvers(state, solver_context, solvers_cache_status);
ComponentConnections const comp_conn = main_core::construct_components_connections<ModelType>(state.components);
// Determine the path based on whether link_node_idx is populated (new path) or empty (old path)
// link_node_idx being non-empty means new path where links are separated (no node injection sensors)
// link_node_idx being empty means old path where all edges treated as branches (has node injection sensors)
bool const has_node_injection_sensors = state.comp_topo->link_node_idx.empty();
ComponentConnections const comp_conn =
main_core::construct_components_connections<ModelType>(state.components, has_node_injection_sensors);

// re build
assert((state.comp_topo->link_node_idx.empty() ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,10 @@ prepare_short_circuit_input(main_model_state_c auto const& state, ComponentToMat
for (Idx const fault_idx : IdxRange{state.components.template size<Fault>()}) {
auto const& fault = state.components.template get_item_by_seq<Fault>(fault_idx);
if (fault.status()) {
auto const node_idx = state.components.template get_seq<Node>(fault.get_fault_object());
auto const topo_bus_idx = state.topo_comp_coup->node[node_idx];
auto const user_node_idx = state.components.template get_seq<Node>(fault.get_fault_object());
auto const topo_node_idx =
state.reduced_topology->topo_node_coup.coupling.user_nodes_to_topo_nodes[user_node_idx].group;
auto const topo_bus_idx = state.topo_comp_coup->node[topo_node_idx];
Comment on lines +287 to +290

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the SC bug. Wrong mapping was used.


if (topo_bus_idx.group >= 0) { // Consider non-isolated objects only
topo_fault_indices[topo_bus_idx.group].push_back(fault_idx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ constexpr auto get_component_citer(ComponentContainer const& components) {

namespace detail {
template <typename Component> struct topology_base_type;
template <std::derived_from<Edge> Component> struct topology_base_type<Component> {
using type = Edge;
template <std::derived_from<Branch> Component> struct topology_base_type<Component> {
using type = Branch;
};
template <std::derived_from<Branch3> Component> struct topology_base_type<Component> {
using type = Branch3;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,11 @@ constexpr auto output_result(Component const& node, MainModelState<ComponentCont
// output link
template <std::same_as<Link> Component, class ComponentContainer, steady_state_solver_output_type SolverOutputType>
requires model_component_state_c<MainModelState, ComponentContainer, Component>
constexpr auto output_result(Component const& link, MainModelState<ComponentContainer> const& /* state */,
constexpr auto output_result(Component const& link, MainModelState<ComponentContainer> const& state,
MathOutput<std::vector<SolverOutputType>> const& math_output, Idx2D const& topo_id) {
using sym = decode_symmetry_v<SolverOutputType>;

if (topo_id.group == disconnected) {
if (topo_id.group == disconnected || state.topo_comp_coup->node[topo_id.group].group == disconnected) {
return link.template get_null_output<sym>();
}
if (!link.edge_status()) {
Expand All @@ -146,9 +146,9 @@ constexpr auto output_result(Component const& link, MainModelState<ComponentCont
}
template <std::same_as<Link> Component, class ComponentContainer, short_circuit_solver_output_type SolverOutputType>
requires model_component_state_c<MainModelState, ComponentContainer, Component>
inline auto output_result(Component const& link, MainModelState<ComponentContainer> const& /* state */,
inline auto output_result(Component const& link, MainModelState<ComponentContainer> const& state,
MathOutput<std::vector<SolverOutputType>> const& math_output, Idx2D const& topo_id) {
if (topo_id.group == disconnected) {
if (topo_id.group == disconnected || state.topo_comp_coup->node[topo_id.group].group == disconnected) {
Comment on lines 139 to +151

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was another bug. The output didn't understand that a link was not energized if it's statuses where on but it wasn't connected to a source.

return link.get_null_sc_output();
}
if (!link.edge_status()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
#include <concepts>

namespace power_grid_model::main_core {
template <std::derived_from<Edge> ComponentType, class ComponentContainer>
template <std::derived_from<Branch> ComponentType, class ComponentContainer>
requires model_component_state_c<MainModelState, ComponentContainer, ComponentType>
constexpr auto get_branch_nodes(MainModelState<ComponentContainer> const& state, Idx topology_sequence_idx) {
return state.comp_topo->branch_node_idx[topology_sequence_idx];
Expand Down Expand Up @@ -56,7 +56,7 @@ constexpr auto get_math_id(MainModelState<ComponentContainer> const& state, Idx
return state.topo_comp_coup->node[topology_sequence_idx];
}

template <std::derived_from<Edge> ComponentType, class ComponentContainer>
template <std::derived_from<Branch> ComponentType, class ComponentContainer>
requires model_component_state_c<MainModelState, ComponentContainer, ComponentType>
constexpr auto get_math_id(MainModelState<ComponentContainer> const& state, Idx topology_sequence_idx) {
return state.topo_comp_coup->branch[topology_sequence_idx];
Expand Down Expand Up @@ -92,10 +92,24 @@ constexpr auto comp_base_sequence_cbegin(MainModelState<ComponentContainer> cons
return state.reduced_topology->topo_node_coup.coupling.user_nodes_to_topo_nodes.cbegin();
}

template <std::derived_from<Edge> Component, class ComponentContainer>
template <std::derived_from<Branch> Component, class ComponentContainer>
requires model_component_state_c<MainModelState, ComponentContainer, Component>
constexpr auto comp_base_sequence_cbegin(MainModelState<ComponentContainer> const& state) {
return state.topo_comp_coup->branch.cbegin() + get_component_sequence_offset<Edge, Component>(state.components);
return state.topo_comp_coup->branch.cbegin() + get_component_sequence_offset<Branch, Component>(state.components);
}

template <std::same_as<Link> Component, class ComponentContainer>
requires model_component_state_c<MainModelState, ComponentContainer, Component>
constexpr auto comp_base_sequence_cbegin(MainModelState<ComponentContainer> const& state) {
auto const& link_topo_ids = state.reduced_topology->topo_node_coup.coupling.user_links_to_topo_nodes;

if (std::ranges::ssize(link_topo_ids) == get_component_size<Link>(state.components)) {
// new path: links are not branches
return link_topo_ids.cbegin();
} else {
// legacy path: links are branches
return state.topo_comp_coup->branch.cbegin() + get_component_sequence_offset<Edge, Link>(state.components);
}
Comment on lines +101 to +112

@figueroa1395 figueroa1395 Sep 3, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest of the issues so far, were related to the "two-path dispatch", similar to this. Where in the old path Branches included links, so the offset was different to the new path (or the container to use was different), where branches and links are separated.

I think it's good that we spam TODOs in every such two-code-path place introduced here, so we can easily pinpoint for the cleanup after v2.

}

template <std::derived_from<Branch3> Component, class ComponentContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ constexpr void register_topology_components(ComponentContainer const& components
}

template <std::same_as<Edge> Component, class ComponentContainer>
requires common::component_container_c<ComponentContainer, Component, Edge, Node>
requires common::component_container_c<ComponentContainer, Component, Node>
constexpr void register_topology_components(ComponentContainer const& components, ComponentTopology& comp_topo) {
apply_registration<Edge>(components, comp_topo.branch_node_idx, [&components](Edge const& edge) {
return BranchIdx{get_component_sequence_idx<Node>(components, edge.from_node()),
Expand Down Expand Up @@ -201,8 +201,28 @@ constexpr void register_topology_components(ComponentContainer const& components
[](Regulator const& regulator) { return regulator.regulated_object_type(); });
}

// new path: only Branch-derived types (not Link) in branch_node_idx
template <std::same_as<Branch> Component, class ComponentContainer>
requires common::component_container_c<ComponentContainer, Component, Node>
constexpr void register_topology_components(ComponentContainer const& components, ComponentTopology& comp_topo) {
apply_registration<Component>(components, comp_topo.branch_node_idx, [&components](Component const& branch) {
return BranchIdx{get_component_sequence_idx<Node>(components, branch.from_node()),
get_component_sequence_idx<Node>(components, branch.to_node())};
});
}

// new path: Link in link_node_idx so topology builder can create super-nodes
template <std::same_as<Link> Component, class ComponentContainer>
requires common::component_container_c<ComponentContainer, Component, Node>
constexpr void register_topology_components(ComponentContainer const& components, ComponentTopology& comp_topo) {
apply_registration<Component>(components, comp_topo.link_node_idx, [&components](Component const& link) {
return BranchIdx{get_component_sequence_idx<Node>(components, link.from_node()),
get_component_sequence_idx<Node>(components, link.to_node())};
});
}

template <std::same_as<Edge> Component, class ComponentContainer>
requires common::component_container_c<ComponentContainer, Component, Edge>
requires common::component_container_c<ComponentContainer, Component>
constexpr void register_connections_components(ComponentContainer const& components, ComponentConnections& comp_conn) {
apply_registration<Edge>(components, comp_conn.branch_connected, [](Edge const& edge) {
return BranchConnected{status_to_int(edge.from_status()), status_to_int(edge.to_status())};
Expand All @@ -229,31 +249,75 @@ constexpr void register_connections_components(ComponentContainer const& compone
[](Source const& source) { return source.status(); });
}

// new path: only Branch-derived types (not Link) in branch_connected
template <std::same_as<Branch> Component, class ComponentContainer>
requires common::component_container_c<ComponentContainer, Component>
constexpr void register_connections_components(ComponentContainer const& components, ComponentConnections& comp_conn) {
apply_registration<Component>(components, comp_conn.branch_connected, [](Component const& branch) {
return BranchConnected{status_to_int(branch.from_status()), status_to_int(branch.to_status())};
});
apply_registration<Component>(components, comp_conn.branch_phase_shift,
[](Branch const& branch) { return branch.phase_shift(); });
}

// new path: Link in link_connected so topology builder can handle super-nodes
template <std::same_as<Link> Component, class ComponentContainer>
requires common::component_container_c<ComponentContainer, Component>
constexpr void register_connections_components(ComponentContainer const& components, ComponentConnections& comp_conn) {
apply_registration<Component>(components, comp_conn.link_connected, [](Component const& link) {
return BranchConnected{status_to_int(link.from_status()), status_to_int(link.to_status())};
});
// Note: Links always have zero phase shift, so no phase_shift registration needed
}

} // namespace detail

// entry point -> how to differentiate when a component is a sensor on a node? can i do it here?
template <typename ModelType>
requires common::component_container_c<typename ModelType::ComponentContainer, Node, Edge, Branch3, Source, Shunt,
GenericLoadGen, GenericVoltageSensor, GenericPowerSensor,
GenericCurrentSensor, Regulator>
ComponentTopology construct_topology(typename ModelType::ComponentContainer const& components) {
GenericCurrentSensor, Regulator, Branch, Link>
ComponentTopology construct_topology(typename ModelType::ComponentContainer const& components,
bool has_node_injection_sensors = false) {
ComponentTopology comp_topo;
using TopologyTypesTuple = ModelType::TopologyTypesTuple;
main_core::utils::run_functor_with_tuple_return_void<TopologyTypesTuple>(
[&components, &comp_topo]<typename CompType>() {
detail::register_topology_components<CompType>(components, comp_topo);
if constexpr (!std::same_as<CompType, Edge>) {
detail::register_topology_components<CompType>(components, comp_topo);
}
});
if (has_node_injection_sensors) {
// old path: all edges (branches + links) in branch_node_idx
detail::register_topology_components<Edge>(components, comp_topo);
} else {
// new path: branches only in branch_node_idx, links in link_node_idx
detail::register_topology_components<Branch>(components, comp_topo);
detail::register_topology_components<Link>(components, comp_topo);
}
return comp_topo;
}

template <typename ModelType>
requires common::component_container_c<typename ModelType::ComponentContainer, Edge, Branch3, Source>
ComponentConnections construct_components_connections(typename ModelType::ComponentContainer const& components) {
requires common::component_container_c<typename ModelType::ComponentContainer, Edge, Branch, Branch3, Source, Link>
ComponentConnections construct_components_connections(typename ModelType::ComponentContainer const& components,
bool has_node_injection_sensors = false) {
ComponentConnections comp_conn;
using TopologyConnectionTypesTuple = ModelType::TopologyConnectionTypesTuple;
main_core::utils::run_functor_with_tuple_return_void<TopologyConnectionTypesTuple>(
[&components, &comp_conn]<typename CompType>() {
detail::register_connections_components<CompType>(components, comp_conn);
if constexpr (!std::same_as<CompType, Edge>) {
detail::register_connections_components<CompType>(components, comp_conn);
}
});
if (has_node_injection_sensors) {
// old path: all edges (branches + links) in branch_connected
detail::register_connections_components<Edge>(components, comp_conn);
} else {
// new path: branches only in branch_connected, links in link_connected
detail::register_connections_components<Branch>(components, comp_conn);
detail::register_connections_components<Link>(components, comp_conn);
}
return comp_conn;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "../component/branch.hpp"
#include "../component/branch3.hpp"
#include "../component/component.hpp"
#include "../component/edge.hpp"
#include "../math_solver/y_bus.hpp"
#include "container_queries.hpp"
#include "math_state.hpp"
Expand All @@ -24,17 +25,18 @@ constexpr Idx isolated_component{-1};

namespace detail {

template <std::derived_from<Edge> ComponentType, math_model_param_c MathModelParamType, typename ComponentContainer>
template <typename ComponentType, math_model_param_c MathModelParamType, typename ComponentContainer>
requires std::same_as<ComponentType, Edge> || std::same_as<ComponentType, Branch>
constexpr void add_to_math_model_params(std::vector<MathModelParamType>& math_model_param,
MainModelState<ComponentContainer> const& state,
Idx const topology_sequence_idx) {
Idx2D const math_idx = get_math_id<Edge>(state, topology_sequence_idx);
Idx2D const math_idx = get_math_id<Branch>(state, topology_sequence_idx);
if (math_idx.group == isolated_component) {
return;
}
// assign parameters
auto& model_params = math_model_param[math_idx.group];
auto branch_params = state.components.template get_item_by_seq<Edge>(topology_sequence_idx)
auto branch_params = state.components.template get_item_by_seq<ComponentType>(topology_sequence_idx)
.template calc_param<typename MathModelParamType::sym>();

if constexpr (std::derived_from<MathModelParamType, MathModelParamIncrement<typename MathModelParamType::sym>>) {
Expand Down Expand Up @@ -123,12 +125,27 @@ constexpr void add_to_math_model_params(std::vector<MathModelParamType> const& /
}

template <typename ComponentType, symmetry_tag sym, typename ComponentContainer>
requires std::derived_from<ComponentType, Edge> || std::derived_from<ComponentType, Branch3> ||
requires std::derived_from<ComponentType, Branch> || std::derived_from<ComponentType, Branch3> ||
std::derived_from<ComponentType, Shunt> || std::derived_from<ComponentType, Source>
constexpr void add_to_increment(std::vector<MathModelParamIncrement<sym>>& increments,
MainModelState<ComponentContainer> const& state, Idx2D const& changed_component_idx) {
Idx const topo_sequence_idx = main_core::get_topology_index<ComponentType>(state.components, changed_component_idx);
add_to_math_model_params<ComponentType>(increments, state, topo_sequence_idx);
if constexpr (std::derived_from<ComponentType, Branch>) {
if (state.comp_topo->link_node_idx.empty()) {
// legacy branch_node_idx is registered from Edge
Idx const topology_idx = get_component_sequence_idx<Edge>(state.components, changed_component_idx);

add_to_math_model_params<Edge>(increments, state, topology_idx);
} else {
// new branch_node_idx is registered from Branch
Idx const topology_idx = get_topology_index<ComponentType>(state.components, changed_component_idx);

add_to_math_model_params<Branch>(increments, state, topology_idx);
}
} else {
Idx const topology_idx = get_topology_index<ComponentType>(state.components, changed_component_idx);

add_to_math_model_params<ComponentType>(increments, state, topology_idx);
}
}
// default implementation for other components, does nothing
template <typename ComponentType, symmetry_tag sym, typename ComponentContainer>
Expand Down Expand Up @@ -192,8 +209,16 @@ inline std::vector<MathModelParam<sym>> get_math_param(main_model_state_c auto c
math_param[i].source_param.resize(state.math_topology[i]->n_source());
}
// loop all branch
for (Idx const i : IdxRange{std::ssize(state.comp_topo->branch_node_idx)}) {
detail::add_to_math_model_params<Edge>(math_param, state, i);
if (state.comp_topo->link_node_idx.empty()) {
// legacy topology: branch_node_idx contains every Edge, including Link
for (Idx const i : IdxRange{std::ssize(state.comp_topo->branch_node_idx)}) {
detail::add_to_math_model_params<Edge>(math_param, state, i);
}
} else {
// reduced topology: Links are separate and branch_node_idx contains only Branch
for (Idx const i : IdxRange{std::ssize(state.comp_topo->branch_node_idx)}) {
detail::add_to_math_model_params<Branch>(math_param, state, i);
}
}
// loop all branch3
for (Idx const i : IdxRange{std::ssize(state.comp_topo->branch3_node_idx)}) {
Expand Down
Loading
Loading