From fd7ecde1d162920065bc5f2e1a53741aadecea82 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Tue, 20 Jan 2026 13:15:24 +0100 Subject: [PATCH 1/9] split calculation typing out of main model impl Signed-off-by: Martijn Govers --- .../include/power_grid_model/calculation.hpp | 82 +++++++++++++++++++ .../power_grid_model/main_model_impl.hpp | 81 ++---------------- 2 files changed, 88 insertions(+), 75 deletions(-) create mode 100644 power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp new file mode 100644 index 000000000..61654e067 --- /dev/null +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Contributors to the Power Grid Model project +// +// SPDX-License-Identifier: MPL-2.0 + +#pragma once + +#include "common/common.hpp" +#include "common/enum.hpp" + +namespace power_grid_model { +// solver output type to output type getter meta function +template struct output_type_getter; +template struct output_type_getter { + using type = meta_data::sc_output_getter_s; +}; +template <> struct output_type_getter> { + using type = meta_data::sym_output_getter_s; +}; +template <> struct output_type_getter> { + using type = meta_data::asym_output_getter_s; +}; + +struct calculation_type_t {}; +struct power_flow_t : calculation_type_t {}; +struct state_estimation_t : calculation_type_t {}; +struct short_circuit_t : calculation_type_t {}; + +template +concept calculation_type_tag = std::derived_from; + +template +decltype(auto) calculation_symmetry_func_selector(CalculationSymmetry calculation_symmetry, Functor&& f, + Args&&... args) { + using enum CalculationSymmetry; + + switch (calculation_symmetry) { + case symmetric: + return std::forward(f).template operator()(std::forward(args)...); + case asymmetric: + return std::forward(f).template operator()(std::forward(args)...); + default: + throw MissingCaseForEnumError{"Calculation symmetry selector", calculation_symmetry}; + } +} + +template +decltype(auto) calculation_type_func_selector(CalculationType calculation_type, Functor&& f, Args&&... args) { + using enum CalculationType; + + switch (calculation_type) { + case CalculationType::power_flow: + return std::forward(f).template operator()(std::forward(args)...); + case CalculationType::state_estimation: + return std::forward(f).template operator()(std::forward(args)...); + case CalculationType::short_circuit: + return std::forward(f).template operator()(std::forward(args)...); + default: + throw MissingCaseForEnumError{"CalculationType", calculation_type}; + } +} + +template +decltype(auto) calculation_type_symmetry_func_selector(CalculationType calculation_type, + CalculationSymmetry calculation_symmetry, Functor&& f, + Args&&... args) { + calculation_type_func_selector( + calculation_type, + []( + CalculationSymmetry calculation_symmetry_, Functor_&& f_, Args_&&... args_) { + calculation_symmetry_func_selector( + calculation_symmetry_, + [](SubFunctor&& sub_f, + SubArgs&&... sub_args) { + std::forward(sub_f).template operator()( + std::forward(sub_args)...); + }, + std::forward(f_), std::forward(args_)...); + }, + calculation_symmetry, std::forward(f), std::forward(args)...); +} + +} // namespace power_grid_model diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp index d376683be..1ed082d5f 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp @@ -8,6 +8,7 @@ // main include #include "batch_parameter.hpp" +#include "calculation.hpp" #include "calculation_parameters.hpp" #include "calculation_preparation.hpp" #include "container.hpp" @@ -43,77 +44,6 @@ #include namespace power_grid_model { -// solver output type to output type getter meta function - -template struct output_type_getter; -template struct output_type_getter { - using type = meta_data::sc_output_getter_s; -}; -template <> struct output_type_getter> { - using type = meta_data::sym_output_getter_s; -}; -template <> struct output_type_getter> { - using type = meta_data::asym_output_getter_s; -}; - -struct power_flow_t {}; -struct state_estimation_t {}; -struct short_circuit_t {}; - -template -concept calculation_type_tag = std::derived_from || std::derived_from || - std::derived_from; - -template -decltype(auto) calculation_symmetry_func_selector(CalculationSymmetry calculation_symmetry, Functor&& f, - Args&&... args) { - using enum CalculationSymmetry; - - switch (calculation_symmetry) { - case symmetric: - return std::forward(f).template operator()(std::forward(args)...); - case asymmetric: - return std::forward(f).template operator()(std::forward(args)...); - default: - throw MissingCaseForEnumError{"Calculation symmetry selector", calculation_symmetry}; - } -} - -template -decltype(auto) calculation_type_func_selector(CalculationType calculation_type, Functor&& f, Args&&... args) { - using enum CalculationType; - - switch (calculation_type) { - case CalculationType::power_flow: - return std::forward(f).template operator()(std::forward(args)...); - case CalculationType::state_estimation: - return std::forward(f).template operator()(std::forward(args)...); - case CalculationType::short_circuit: - return std::forward(f).template operator()(std::forward(args)...); - default: - throw MissingCaseForEnumError{"CalculationType", calculation_type}; - } -} - -template -decltype(auto) calculation_type_symmetry_func_selector(CalculationType calculation_type, - CalculationSymmetry calculation_symmetry, Functor&& f, - Args&&... args) { - calculation_type_func_selector( - calculation_type, - []( - CalculationSymmetry calculation_symmetry_, Functor_&& f_, Args_&&... args_) { - calculation_symmetry_func_selector( - calculation_symmetry_, - [](SubFunctor&& sub_f, - SubArgs&&... sub_args) { - std::forward(sub_f).template operator()( - std::forward(sub_args)...); - }, - std::forward(f_), std::forward(args_)...); - }, - calculation_symmetry, std::forward(f), std::forward(args)...); -} template requires(main_core::is_main_model_type_v) @@ -399,10 +329,11 @@ class MainModelImpl { assert(&state == &state_); return calculate_, MathSolverProxy, YBus, ShortCircuitInput>( - [this, voltage_scaling](Idx n_math_solvers) { - assert(solvers_cache_status_.is_topology_valid()); - assert(solvers_cache_status_.template is_parameter_valid()); - return main_core::prepare_short_circuit_input(state_, state_.comp_coup, n_math_solvers, + [&state, &comp_coup = state_.comp_coup, &solvers_cache_status = solvers_cache_status_, + voltage_scaling](Idx n_math_solvers) { + assert(solvers_cache_status.is_topology_valid()); + assert(solvers_cache_status.template is_parameter_valid()); + return main_core::prepare_short_circuit_input(state, comp_coup, n_math_solvers, voltage_scaling); }, [calculation_method, &logger](MathSolverProxy& solver, YBus const& y_bus, From 6908f46783ec341ecb6ee0f566a0c536c4377f2c Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Wed, 21 Jan 2026 15:18:19 +0100 Subject: [PATCH 2/9] move calculator implementation detail logic out of main model impl (wip) Signed-off-by: Martijn Govers --- CMakeLists.txt | 11 ++- .../include/power_grid_model/calculation.hpp | 68 +++++++++++--- .../calculation_input_preparation.hpp | 12 ++- .../main_core/state_queries.hpp | 1 - .../power_grid_model/main_core/y_bus.hpp | 2 + .../power_grid_model/main_model_impl.hpp | 88 +++++++++---------- 6 files changed, 119 insertions(+), 63 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bb14a5b4..711b87706 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,11 +13,18 @@ project(power_grid_model VERSION ${PGM_VERSION}) option(PGM_ENABLE_DEV_BUILD "Enable developer build (e.g.: tests)" OFF) -set(CMAKE_CXX_STANDARD 23) +set(PGM_C_STANDARD 11) +set(PGM_CXX_STANDARD 23) + +if(NOT CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS ${PGM_CXX_STANDARD}) + set(CMAKE_CXX_STANDARD ${PGM_CXX_STANDARD}) +endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_VISIBILITY_PRESET hidden) -set(CMAKE_C_STANDARD 11) +if(NOT CMAKE_C_STANDARD OR CMAKE_C_STANDARD LESS ${PGM_C_STANDARD}) + set(CMAKE_C_STANDARD ${PGM_C_STANDARD}) +endif() set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS OFF) set(CMAKE_C_VISIBILITY_PRESET hidden) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp index 61654e067..461693df0 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp @@ -7,18 +7,10 @@ #include "common/common.hpp" #include "common/enum.hpp" +#include "main_core/calculation_input_preparation.hpp" +#include "main_core/main_model_type.hpp" + namespace power_grid_model { -// solver output type to output type getter meta function -template struct output_type_getter; -template struct output_type_getter { - using type = meta_data::sc_output_getter_s; -}; -template <> struct output_type_getter> { - using type = meta_data::sym_output_getter_s; -}; -template <> struct output_type_getter> { - using type = meta_data::asym_output_getter_s; -}; struct calculation_type_t {}; struct power_flow_t : calculation_type_t {}; @@ -79,4 +71,58 @@ decltype(auto) calculation_type_symmetry_func_selector(CalculationType calculati calculation_symmetry, std::forward(f), std::forward(args)...); } +template struct Calculator; +template struct Calculator { + static auto scenario_couple_components(ComponentToMathCoupling& /*comp_coup*/) { + return [] { /*no-op*/ }; + } + template static auto preparer(State const& state) { + return [&state](Idx n_math_solvers) { return main_core::prepare_power_flow_input(state, n_math_solvers); }; + } + static auto solver(CalculationMethod calculation_method, double err_tol, Idx max_iter, Logger& logger) { + return [err_tol, max_iter, calculation_method, &logger](MathSolverProxy& solver, YBus const& y_bus, + PowerFlowInput const& input) { + return solver.get().run_power_flow(input, err_tol, max_iter, logger, calculation_method, y_bus); + }; + } +}; +template struct Calculator { + static auto scenario_couple_components(ComponentToMathCoupling& /*comp_coup*/) { + return [] { /*no-op*/ }; + } + template static auto preparer(State const& state) { + return [&state](Idx n_math_solvers) { + return main_core::prepare_state_estimation_input(state, n_math_solvers); + }; + } + static auto solver(CalculationMethod calculation_method, double err_tol, Idx max_iter, Logger& logger) { + return [err_tol, max_iter, calculation_method, &logger](MathSolverProxy& solver, YBus const& y_bus, + StateEstimationInput const& input) { + return solver.get().run_state_estimation(input, err_tol, max_iter, logger, calculation_method, y_bus); + }; + } +}; + +template struct Calculator { + + static auto scenario_couple_components(ComponentToMathCoupling& comp_coup) { + return [&comp_coup] { /*no-op*/ }; + } + template + static auto preparer(State const& state, ComponentToMathCoupling& comp_coup, + ShortCircuitVoltageScaling voltage_scaling) { + return [&state, &comp_coup, voltage_scaling](Idx n_math_solvers) { + // assert(solvers_cache_status.is_topology_valid()); + // assert(solvers_cache_status.template is_parameter_valid()); + return main_core::prepare_short_circuit_input(state, comp_coup, n_math_solvers, voltage_scaling); + }; + } + static auto solver(CalculationMethod calculation_method, Logger& logger) { + return [calculation_method, &logger](MathSolverProxy& solver, YBus const& y_bus, + ShortCircuitInput const& input) { + return solver.get().run_short_circuit(input, logger, calculation_method, y_bus); + }; + } +}; + } // namespace power_grid_model diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp index 4e6edd49d..38dc8422e 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp @@ -5,8 +5,10 @@ #pragma once #include "state_queries.hpp" +#include "y_bus.hpp" #include "../calculation_parameters.hpp" +#include "../index_mapping.hpp" #include #include @@ -238,6 +240,8 @@ std::vector> prepare_state_estimation_input(main_model return se_input; } +template void prepare_short_circuit_scenario_topo() {} + template std::vector prepare_short_circuit_input(main_model_state_c auto const& state, ComponentToMathCoupling& comp_coup, Idx n_math_solvers, @@ -248,7 +252,7 @@ std::vector prepare_short_circuit_input(main_model_state_c au std::vector topo_fault_indices(state.math_topology.size()); std::vector topo_bus_indices(state.math_topology.size()); - for (Idx fault_idx{0}; fault_idx < state.components.template size(); ++fault_idx) { + for (Idx fault_idx : IdxRange{state.components.template size()}) { auto const& fault = state.components.template get_item_by_seq(fault_idx); if (fault.status()) { auto const node_idx = state.components.template get_seq(fault.get_fault_object()); @@ -265,11 +269,11 @@ std::vector prepare_short_circuit_input(main_model_state_c au Idx2D{.group = isolated_component, .pos = not_connected}); std::vector sc_input(n_math_solvers); - for (Idx i = 0; i != n_math_solvers; ++i) { + for (Idx i : IdxRange{n_math_solvers}) { auto map = build_dense_mapping(topo_bus_indices[i], state.math_topology[i]->n_bus()); - for (Idx reordered_idx{0}; reordered_idx < static_cast(map.reorder.size()); ++reordered_idx) { - fault_coup[topo_fault_indices[i][map.reorder[reordered_idx]]] = Idx2D{.group = i, .pos = reordered_idx}; + for (auto&& [reordered_idx, original_idx] : map.reorder | std::views::enumerate) { + fault_coup[topo_fault_indices[i][original_idx]] = Idx2D{.group = i, .pos = reordered_idx}; } sc_input[i].fault_buses = {from_dense, std::move(map.indvector), state.math_topology[i]->n_bus()}; diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp index b3a5e64b7..b6a8f0075 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp @@ -9,7 +9,6 @@ #include "../all_components.hpp" namespace power_grid_model::main_core { - template ComponentType, class ComponentContainer> requires model_component_state_c constexpr auto get_branch_nodes(MainModelState const& state, Idx topology_sequence_idx) { diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp index 183abcfd9..9febd634c 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp @@ -4,6 +4,8 @@ #pragma once +#include "math_state.hpp" + #include "../common/common.hpp" namespace power_grid_model::main_core { diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp index 1ed082d5f..ae5975675 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp @@ -44,6 +44,17 @@ #include namespace power_grid_model { +// solver output type to output type getter meta function +template struct output_type_getter; +template struct output_type_getter { + using type = meta_data::sc_output_getter_s; +}; +template <> struct output_type_getter> { + using type = meta_data::sym_output_getter_s; +}; +template <> struct output_type_getter> { + using type = meta_data::asym_output_getter_s; +}; template requires(main_core::is_main_model_type_v) @@ -254,20 +265,24 @@ class MainModelImpl { } template - requires std::invocable, Idx /*n_math_solvers*/> && + typename CoupleComponentsFn, typename PrepareInputFn, typename SolveFn> + requires std::invocable> && + std::invocable, Idx /*n_math_solvers*/> && std::invocable, MathSolverType&, YBus const&, InputType const&> && std::same_as, std::vector> && std::same_as, SolverOutputType> - std::vector calculate_(PrepareInputFn&& prepare_input, SolveFn&& solve, Logger& logger) { + std::vector calculate_(CoupleComponentsFn&& couple_components, PrepareInputFn&& prepare_input, + SolveFn&& solve, Logger& logger) { using sym = typename SolverOutputType::sym; assert(construction_complete_); // prepare - auto const& input = [this, &logger, prepare_input_ = std::forward(prepare_input)] { + auto const& input = [this, &logger, couple_components_ = std::forward(couple_components), + prepare_input_ = std::forward(prepare_input)] { Timer const timer{logger, LogEvent::prepare}; assert(construction_complete_); + couple_components_(); prepare_solvers(state_, solver_preparation_context_, solvers_cache_status_); assert(solvers_cache_status_.is_topology_valid()); assert(solvers_cache_status_.template is_parameter_valid()); @@ -288,59 +303,42 @@ class MainModelImpl { } template auto calculate_power_flow_(double err_tol, Idx max_iter, Logger& logger) { - return - [this, err_tol, max_iter, &logger](MainModelState const& state, - CalculationMethod calculation_method) -> std::vector> { - return calculate_, MathSolverProxy, YBus, PowerFlowInput>( - [&state](Idx n_math_solvers) { - return main_core::prepare_power_flow_input(state, n_math_solvers); - }, - [err_tol, max_iter, calculation_method, - &logger](MathSolverProxy& solver, YBus const& y_bus, PowerFlowInput const& input) { - return solver.get().run_power_flow(input, err_tol, max_iter, logger, calculation_method, y_bus); - }, - logger); - }; + using Calc = Calculator; + + return [this, &comp_coup = state_.comp_coup, err_tol, max_iter, + &logger](MainModelState const& state, + CalculationMethod calculation_method) -> std::vector> { + return calculate_, MathSolverProxy, YBus, PowerFlowInput>( + Calc::scenario_couple_components(comp_coup), Calc::preparer(state), + Calc::solver(calculation_method, err_tol, max_iter, logger), logger); + }; } template auto calculate_state_estimation_(double err_tol, Idx max_iter, Logger& logger) { - return - [this, err_tol, max_iter, &logger](MainModelState const& state, - CalculationMethod calculation_method) -> std::vector> { - return calculate_, MathSolverProxy, YBus, StateEstimationInput>( - [&state](Idx n_math_solvers) { - return main_core::prepare_state_estimation_input(state, n_math_solvers); - }, - [err_tol, max_iter, calculation_method, &logger]( - MathSolverProxy& solver, YBus const& y_bus, StateEstimationInput const& input) { - return solver.get().run_state_estimation(input, err_tol, max_iter, logger, calculation_method, - y_bus); - }, - logger); - }; + using Calc = Calculator; + + return [this, &comp_coup = state_.comp_coup, err_tol, max_iter, + &logger](MainModelState const& state, + CalculationMethod calculation_method) -> std::vector> { + return calculate_, MathSolverProxy, YBus, StateEstimationInput>( + Calc::scenario_couple_components(comp_coup), Calc::preparer(state), + Calc::solver(calculation_method, err_tol, max_iter, logger), logger); + }; } template auto calculate_short_circuit_(ShortCircuitVoltageScaling voltage_scaling, Logger& logger) { - return [this, voltage_scaling, + using Calc = Calculator; + + return [this, &comp_coup = state_.comp_coup, voltage_scaling, &logger](MainModelState const& state, CalculationMethod calculation_method) -> std::vector> { (void)state; // to avoid unused-lambda-capture when in Release build - assert(&state == &state_); + // assert(&state == &state_); return calculate_, MathSolverProxy, YBus, ShortCircuitInput>( - [&state, &comp_coup = state_.comp_coup, &solvers_cache_status = solvers_cache_status_, - voltage_scaling](Idx n_math_solvers) { - assert(solvers_cache_status.is_topology_valid()); - assert(solvers_cache_status.template is_parameter_valid()); - return main_core::prepare_short_circuit_input(state, comp_coup, n_math_solvers, - voltage_scaling); - }, - [calculation_method, &logger](MathSolverProxy& solver, YBus const& y_bus, - ShortCircuitInput const& input) { - return solver.get().run_short_circuit(input, logger, calculation_method, y_bus); - }, - logger); + Calc::scenario_couple_components(comp_coup), Calc::preparer(state, comp_coup, voltage_scaling), + Calc::solver(calculation_method, logger), logger); }; } From d6ac715e1080d402230f65db5cdadff1a69df16e Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Tue, 27 Jan 2026 11:11:12 +0100 Subject: [PATCH 3/9] homogenize Signed-off-by: Martijn Govers --- .../include/power_grid_model/calculation.hpp | 14 ++------- .../power_grid_model/main_model_impl.hpp | 29 +++++++++---------- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp index 461693df0..06c345918 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp @@ -73,10 +73,7 @@ decltype(auto) calculation_type_symmetry_func_selector(CalculationType calculati template struct Calculator; template struct Calculator { - static auto scenario_couple_components(ComponentToMathCoupling& /*comp_coup*/) { - return [] { /*no-op*/ }; - } - template static auto preparer(State const& state) { + template static auto preparer(State const& state, ComponentToMathCoupling& /*comp_coup*/) { return [&state](Idx n_math_solvers) { return main_core::prepare_power_flow_input(state, n_math_solvers); }; } static auto solver(CalculationMethod calculation_method, double err_tol, Idx max_iter, Logger& logger) { @@ -87,10 +84,7 @@ template struct Calculator { } }; template struct Calculator { - static auto scenario_couple_components(ComponentToMathCoupling& /*comp_coup*/) { - return [] { /*no-op*/ }; - } - template static auto preparer(State const& state) { + template static auto preparer(State const& state, ComponentToMathCoupling& /*comp_coup*/) { return [&state](Idx n_math_solvers) { return main_core::prepare_state_estimation_input(state, n_math_solvers); }; @@ -104,10 +98,6 @@ template struct Calculator { }; template struct Calculator { - - static auto scenario_couple_components(ComponentToMathCoupling& comp_coup) { - return [&comp_coup] { /*no-op*/ }; - } template static auto preparer(State const& state, ComponentToMathCoupling& comp_coup, ShortCircuitVoltageScaling voltage_scaling) { diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp index be5f99bd0..bd3788c25 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp @@ -265,24 +265,20 @@ class MainModelImpl { } template - requires std::invocable> && - std::invocable, Idx /*n_math_solvers*/> && + typename PrepareInputFn, typename SolveFn> + requires std::invocable, Idx /*n_math_solvers*/> && std::invocable, MathSolverType&, YBus const&, InputType const&> && std::same_as, std::vector> && std::same_as, SolverOutputType> - std::vector calculate_(CoupleComponentsFn&& couple_components, PrepareInputFn&& prepare_input, - SolveFn&& solve, Logger& logger) { + std::vector calculate_(PrepareInputFn&& prepare_input, SolveFn&& solve, Logger& logger) { using sym = typename SolverOutputType::sym; assert(construction_complete_); // prepare - auto const& input = [this, &logger, couple_components_ = std::forward(couple_components), - prepare_input_ = std::forward(prepare_input)] { + auto const& input = [this, &logger, prepare_input_ = std::forward(prepare_input)] { Timer const timer{logger, LogEvent::prepare}; assert(construction_complete_); - couple_components_(); prepare_solvers(state_, solver_preparation_context_, solvers_cache_status_); assert(solvers_cache_status_.is_topology_valid()); assert(solvers_cache_status_.template is_parameter_valid()); @@ -308,9 +304,11 @@ class MainModelImpl { return [this, &comp_coup = state_.comp_coup, err_tol, max_iter, &logger](MainModelState const& state, CalculationMethod calculation_method) -> std::vector> { + (void)state; // to avoid unused-lambda-capture when in Release build + assert(&state == &state_); + return calculate_, MathSolverProxy, YBus, PowerFlowInput>( - Calc::scenario_couple_components(comp_coup), Calc::preparer(state), - Calc::solver(calculation_method, err_tol, max_iter, logger), logger); + Calc::preparer(state, comp_coup), Calc::solver(calculation_method, err_tol, max_iter, logger), logger); }; } @@ -320,9 +318,11 @@ class MainModelImpl { return [this, &comp_coup = state_.comp_coup, err_tol, max_iter, &logger](MainModelState const& state, CalculationMethod calculation_method) -> std::vector> { + (void)state; // to avoid unused-lambda-capture when in Release build + assert(&state == &state_); + return calculate_, MathSolverProxy, YBus, StateEstimationInput>( - Calc::scenario_couple_components(comp_coup), Calc::preparer(state), - Calc::solver(calculation_method, err_tol, max_iter, logger), logger); + Calc::preparer(state, comp_coup), Calc::solver(calculation_method, err_tol, max_iter, logger), logger); }; } @@ -334,11 +334,10 @@ class MainModelImpl { &logger](MainModelState const& state, CalculationMethod calculation_method) -> std::vector> { (void)state; // to avoid unused-lambda-capture when in Release build - // assert(&state == &state_); + assert(&state == &state_); return calculate_, MathSolverProxy, YBus, ShortCircuitInput>( - Calc::scenario_couple_components(comp_coup), Calc::preparer(state, comp_coup, voltage_scaling), - Calc::solver(calculation_method, logger), logger); + Calc::preparer(state, comp_coup, voltage_scaling), Calc::solver(calculation_method, logger), logger); }; } From 4f61d497627f48303dde9b1de7116b21ca42653d Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Tue, 27 Jan 2026 11:43:26 +0100 Subject: [PATCH 4/9] more homogenize Signed-off-by: Martijn Govers --- .../include/power_grid_model/calculation.hpp | 29 +++++++++++-------- .../power_grid_model/main_model_impl.hpp | 25 ++++++++-------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp index 06c345918..e5408ee66 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp @@ -4,6 +4,8 @@ #pragma once +#include "main_model_fwd.hpp" + #include "common/common.hpp" #include "common/enum.hpp" @@ -73,25 +75,29 @@ decltype(auto) calculation_type_symmetry_func_selector(CalculationType calculati template struct Calculator; template struct Calculator { - template static auto preparer(State const& state, ComponentToMathCoupling& /*comp_coup*/) { + template + static auto preparer(State const& state, ComponentToMathCoupling& /*comp_coup*/, + MainModelOptions const& /*options*/) { return [&state](Idx n_math_solvers) { return main_core::prepare_power_flow_input(state, n_math_solvers); }; } - static auto solver(CalculationMethod calculation_method, double err_tol, Idx max_iter, Logger& logger) { - return [err_tol, max_iter, calculation_method, &logger](MathSolverProxy& solver, YBus const& y_bus, - PowerFlowInput const& input) { + static auto solver(CalculationMethod calculation_method, MainModelOptions const& options, Logger& logger) { + return [calculation_method, err_tol = options.err_tol, max_iter = options.max_iter, + &logger](MathSolverProxy& solver, YBus const& y_bus, PowerFlowInput const& input) { return solver.get().run_power_flow(input, err_tol, max_iter, logger, calculation_method, y_bus); }; } }; template struct Calculator { - template static auto preparer(State const& state, ComponentToMathCoupling& /*comp_coup*/) { + template + static auto preparer(State const& state, ComponentToMathCoupling& /*comp_coup*/, + MainModelOptions const& /*options*/) { return [&state](Idx n_math_solvers) { return main_core::prepare_state_estimation_input(state, n_math_solvers); }; } - static auto solver(CalculationMethod calculation_method, double err_tol, Idx max_iter, Logger& logger) { - return [err_tol, max_iter, calculation_method, &logger](MathSolverProxy& solver, YBus const& y_bus, - StateEstimationInput const& input) { + static auto solver(CalculationMethod calculation_method, MainModelOptions const& options, Logger& logger) { + return [calculation_method, err_tol = options.err_tol, max_iter = options.max_iter, + &logger](MathSolverProxy& solver, YBus const& y_bus, StateEstimationInput const& input) { return solver.get().run_state_estimation(input, err_tol, max_iter, logger, calculation_method, y_bus); }; } @@ -99,15 +105,14 @@ template struct Calculator { template struct Calculator { template - static auto preparer(State const& state, ComponentToMathCoupling& comp_coup, - ShortCircuitVoltageScaling voltage_scaling) { - return [&state, &comp_coup, voltage_scaling](Idx n_math_solvers) { + static auto preparer(State const& state, ComponentToMathCoupling& comp_coup, MainModelOptions const& options) { + return [&state, &comp_coup, voltage_scaling = options.short_circuit_voltage_scaling](Idx n_math_solvers) { // assert(solvers_cache_status.is_topology_valid()); // assert(solvers_cache_status.template is_parameter_valid()); return main_core::prepare_short_circuit_input(state, comp_coup, n_math_solvers, voltage_scaling); }; } - static auto solver(CalculationMethod calculation_method, Logger& logger) { + static auto solver(CalculationMethod calculation_method, MainModelOptions const& /*options*/, Logger& logger) { return [calculation_method, &logger](MathSolverProxy& solver, YBus const& y_bus, ShortCircuitInput const& input) { return solver.get().run_short_circuit(input, logger, calculation_method, y_bus); diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp index bd3788c25..343cdba8b 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp @@ -298,46 +298,45 @@ class MainModelImpl { }(); } - template auto calculate_power_flow_(double err_tol, Idx max_iter, Logger& logger) { + template auto calculate_power_flow_(Options const& options, Logger& logger) { using Calc = Calculator; - return [this, &comp_coup = state_.comp_coup, err_tol, max_iter, + return [this, &comp_coup = state_.comp_coup, &options, &logger](MainModelState const& state, CalculationMethod calculation_method) -> std::vector> { (void)state; // to avoid unused-lambda-capture when in Release build assert(&state == &state_); return calculate_, MathSolverProxy, YBus, PowerFlowInput>( - Calc::preparer(state, comp_coup), Calc::solver(calculation_method, err_tol, max_iter, logger), logger); + Calc::preparer(state, comp_coup, options), Calc::solver(calculation_method, options, logger), logger); }; } - template auto calculate_state_estimation_(double err_tol, Idx max_iter, Logger& logger) { + template auto calculate_state_estimation_(Options const& options, Logger& logger) { using Calc = Calculator; - return [this, &comp_coup = state_.comp_coup, err_tol, max_iter, + return [this, &comp_coup = state_.comp_coup, &options, &logger](MainModelState const& state, CalculationMethod calculation_method) -> std::vector> { (void)state; // to avoid unused-lambda-capture when in Release build assert(&state == &state_); return calculate_, MathSolverProxy, YBus, StateEstimationInput>( - Calc::preparer(state, comp_coup), Calc::solver(calculation_method, err_tol, max_iter, logger), logger); + Calc::preparer(state, comp_coup, options), Calc::solver(calculation_method, options, logger), logger); }; } - template - auto calculate_short_circuit_(ShortCircuitVoltageScaling voltage_scaling, Logger& logger) { + template auto calculate_short_circuit_(Options const& options, Logger& logger) { using Calc = Calculator; - return [this, &comp_coup = state_.comp_coup, voltage_scaling, + return [this, &comp_coup = state_.comp_coup, &options, &logger](MainModelState const& state, CalculationMethod calculation_method) -> std::vector> { (void)state; // to avoid unused-lambda-capture when in Release build assert(&state == &state_); return calculate_, MathSolverProxy, YBus, ShortCircuitInput>( - Calc::preparer(state, comp_coup, voltage_scaling), Calc::solver(calculation_method, logger), logger); + Calc::preparer(state, comp_coup, options), Calc::solver(calculation_method, options, logger), logger); }; } @@ -348,11 +347,11 @@ class MainModelImpl { assert(options.optimizer_type == OptimizerType::no_optimization || (std::derived_from)); if constexpr (std::derived_from) { - return calculate_power_flow_(options.err_tol, options.max_iter, logger); + return calculate_power_flow_(options, logger); } else if constexpr (std::derived_from) { - return calculate_state_estimation_(options.err_tol, options.max_iter, logger); + return calculate_state_estimation_(options, logger); } else if constexpr (std::derived_from) { - return calculate_short_circuit_(options.short_circuit_voltage_scaling, logger); + return calculate_short_circuit_(options, logger); } else { throw UnreachableHit{"MainModelImpl::calculate", "Unknown calculation type"}; } From 6ca9d39cfcc8219d1467b985b47b4c3add862746 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Wed, 28 Jan 2026 11:24:55 +0100 Subject: [PATCH 5/9] cleanup Signed-off-by: Martijn Govers --- .../power_grid_model/main_model_impl.hpp | 87 ++++++------------- 1 file changed, 26 insertions(+), 61 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp index 343cdba8b..0dfdafb3a 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp @@ -264,14 +264,18 @@ class MainModelImpl { }); } - template + template requires std::invocable, Idx /*n_math_solvers*/> && - std::invocable, MathSolverType&, YBus const&, InputType const&> && - std::same_as, std::vector> && - std::same_as, - SolverOutputType> - std::vector calculate_(PrepareInputFn&& prepare_input, SolveFn&& solve, Logger& logger) { + std::ranges::range> && + std::invocable< + std::remove_cvref_t, MathSolverType&, YBus const&, + typename std::invoke_result_t::const_reference> && + solver_output_type::const_reference>> + auto calculate_(PrepareInputFn&& prepare_input, SolveFn&& solve, Logger& logger) { + using InputType = typename std::invoke_result_t::const_reference; + using SolverOutputType = typename std::invoke_result_t; using sym = typename SolverOutputType::sym; assert(construction_complete_); @@ -298,71 +302,32 @@ class MainModelImpl { }(); } - template auto calculate_power_flow_(Options const& options, Logger& logger) { - using Calc = Calculator; - - return [this, &comp_coup = state_.comp_coup, &options, - &logger](MainModelState const& state, - CalculationMethod calculation_method) -> std::vector> { - (void)state; // to avoid unused-lambda-capture when in Release build - assert(&state == &state_); - - return calculate_, MathSolverProxy, YBus, PowerFlowInput>( - Calc::preparer(state, comp_coup, options), Calc::solver(calculation_method, options, logger), logger); - }; - } - - template auto calculate_state_estimation_(Options const& options, Logger& logger) { - using Calc = Calculator; - - return [this, &comp_coup = state_.comp_coup, &options, - &logger](MainModelState const& state, - CalculationMethod calculation_method) -> std::vector> { - (void)state; // to avoid unused-lambda-capture when in Release build - assert(&state == &state_); - - return calculate_, MathSolverProxy, YBus, StateEstimationInput>( - Calc::preparer(state, comp_coup, options), Calc::solver(calculation_method, options, logger), logger); - }; - } - - template auto calculate_short_circuit_(Options const& options, Logger& logger) { - using Calc = Calculator; - - return [this, &comp_coup = state_.comp_coup, &options, - &logger](MainModelState const& state, - CalculationMethod calculation_method) -> std::vector> { - (void)state; // to avoid unused-lambda-capture when in Release build - assert(&state == &state_); - - return calculate_, MathSolverProxy, YBus, ShortCircuitInput>( - Calc::preparer(state, comp_coup, options), Calc::solver(calculation_method, options, logger), logger); - }; - } - // Calculate with optimization, e.g., automatic tap changer template auto calculate(Options const& options, Logger& logger) { - auto const calculator = [this, &options, &logger] { + auto const get_calculator = [this, &options, &logger] { + using Calc = Calculator; + assert(options.optimizer_type == OptimizerType::no_optimization || (std::derived_from)); - if constexpr (std::derived_from) { - return calculate_power_flow_(options, logger); - } else if constexpr (std::derived_from) { - return calculate_state_estimation_(options, logger); - } else if constexpr (std::derived_from) { - return calculate_short_circuit_(options, logger); - } else { - throw UnreachableHit{"MainModelImpl::calculate", "Unknown calculation type"}; - } - }(); + + return [this, &comp_coup = state_.comp_coup, &options, &logger](MainModelState const& state, + CalculationMethod calculation_method) { + (void)state; // to avoid unused-lambda-capture when in Release build + assert(&state == &state_); + + return calculate_, YBus>(Calc::preparer(state, comp_coup, options), + Calc::solver(calculation_method, options, logger), + logger); + }; + }; SearchMethod const& search_method = options.optimizer_strategy == OptimizerStrategy::any ? SearchMethod::linear_search : SearchMethod::binary_search; return optimizer::get_optimizer( - options.optimizer_type, options.optimizer_strategy, calculator, + options.optimizer_type, options.optimizer_strategy, get_calculator(), [this](ConstDataset const& update_data) { this->update_components(update_data); }, From 118fcaec91e156e1f332b24dc8612c456cdf4d01 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Wed, 28 Jan 2026 11:43:37 +0100 Subject: [PATCH 6/9] use custom enumerate because views::enumerate not available on all platforms Signed-off-by: Martijn Govers --- .../main_core/calculation_input_preparation.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp index ea8c9d798..188653ef8 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp @@ -282,7 +282,7 @@ std::vector prepare_short_circuit_input(main_model_state_c au for (Idx i : IdxRange{n_math_solvers}) { auto map = build_dense_mapping(topo_bus_indices[i], state.math_topology[i]->n_bus()); - for (auto&& [reordered_idx, original_idx] : map.reorder | std::views::enumerate) { + for (auto&& [reordered_idx, original_idx] : enumerated_zip_sequence(map.reorder)) { fault_coup[topo_fault_indices[i][original_idx]] = Idx2D{.group = i, .pos = reordered_idx}; } From 897fa999d1a0d9c980b898df4485a8ca55350f73 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Wed, 28 Jan 2026 13:00:05 +0100 Subject: [PATCH 7/9] use views enumerate overload when not provided Signed-off-by: Martijn Govers --- .../power_grid_model/common/counting_iterator.hpp | 8 ++++++++ .../main_core/calculation_input_preparation.hpp | 2 +- tests/cpp_unit_tests/test_counting_iterator.cpp | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/common/counting_iterator.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/common/counting_iterator.hpp index 6ecc3c7b2..4e5ba1432 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/common/counting_iterator.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/common/counting_iterator.hpp @@ -21,4 +21,12 @@ struct IdxRange : public std::ranges::iota_view { }; using IdxCount = typename IdxRange::iterator; +#if __cpp_lib_ranges_enumerate >= 20202L +using std::views::enumerate; +#else +constexpr auto enumerate(std::ranges::viewable_range auto&& rng) { + return std::views::zip(IdxRange{std::ssize(rng)}, rng); +} +#endif + } // namespace power_grid_model diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp index 188653ef8..3f48a96d5 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp @@ -282,7 +282,7 @@ std::vector prepare_short_circuit_input(main_model_state_c au for (Idx i : IdxRange{n_math_solvers}) { auto map = build_dense_mapping(topo_bus_indices[i], state.math_topology[i]->n_bus()); - for (auto&& [reordered_idx, original_idx] : enumerated_zip_sequence(map.reorder)) { + for (auto&& [reordered_idx, original_idx] : enumerate(map.reorder)) { fault_coup[topo_fault_indices[i][original_idx]] = Idx2D{.group = i, .pos = reordered_idx}; } diff --git a/tests/cpp_unit_tests/test_counting_iterator.cpp b/tests/cpp_unit_tests/test_counting_iterator.cpp index 60f117cb6..3d374c156 100644 --- a/tests/cpp_unit_tests/test_counting_iterator.cpp +++ b/tests/cpp_unit_tests/test_counting_iterator.cpp @@ -30,4 +30,18 @@ TEST_CASE("Counting Iterator") { CHECK(*IdxRange{IdxRange{1, 3}.begin(), IdxRange{1, 3}.end()}.begin() == 1); CHECK(*(IdxRange{IdxRange{1, 3}.begin(), IdxRange{1, 3}.end()}.end() - 1) == 2); } + +TEST_CASE("Enumerate") { + IdxVector vec{10, 20, 30}; + auto enumerated = enumerate(vec); + auto it = enumerated.begin(); + CHECK(std::get<0>(*it) == 0); + CHECK(std::get<1>(*it) == 10); + ++it; + CHECK(std::get<0>(*it) == 1); + CHECK(std::get<1>(*it) == 20); + ++it; + CHECK(std::get<0>(*it) == 2); + CHECK(std::get<1>(*it) == 30); +} } // namespace power_grid_model From 5cddc0d2ffd313b0ac68cca8e9fe32a4c4a479e8 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Wed, 28 Jan 2026 13:07:10 +0100 Subject: [PATCH 8/9] sonar Signed-off-by: Martijn Govers --- .../power_grid_model/include/power_grid_model/calculation.hpp | 2 -- .../main_core/calculation_input_preparation.hpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp index e5408ee66..6cef2efc2 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation.hpp @@ -107,8 +107,6 @@ template struct Calculator { template static auto preparer(State const& state, ComponentToMathCoupling& comp_coup, MainModelOptions const& options) { return [&state, &comp_coup, voltage_scaling = options.short_circuit_voltage_scaling](Idx n_math_solvers) { - // assert(solvers_cache_status.is_topology_valid()); - // assert(solvers_cache_status.template is_parameter_valid()); return main_core::prepare_short_circuit_input(state, comp_coup, n_math_solvers, voltage_scaling); }; } diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp index 3f48a96d5..71fed214d 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp @@ -250,8 +250,6 @@ std::vector> prepare_state_estimation_input(main_model return se_input; } -template void prepare_short_circuit_scenario_topo() {} - template std::vector prepare_short_circuit_input(main_model_state_c auto const& state, ComponentToMathCoupling& comp_coup, Idx n_math_solvers, From 356b2dd8bfd44cae0f8ffc80de2dab1014225e34 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Wed, 28 Jan 2026 14:45:26 +0100 Subject: [PATCH 9/9] clang-tidy Signed-off-by: Martijn Govers --- .../main_core/calculation_input_preparation.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp index 71fed214d..f3198164c 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp @@ -260,7 +260,7 @@ std::vector prepare_short_circuit_input(main_model_state_c au std::vector topo_fault_indices(state.math_topology.size()); std::vector topo_bus_indices(state.math_topology.size()); - for (Idx fault_idx : IdxRange{state.components.template size()}) { + for (Idx const fault_idx : IdxRange{state.components.template size()}) { auto const& fault = state.components.template get_item_by_seq(fault_idx); if (fault.status()) { auto const node_idx = state.components.template get_seq(fault.get_fault_object()); @@ -277,7 +277,7 @@ std::vector prepare_short_circuit_input(main_model_state_c au Idx2D{.group = isolated_component, .pos = not_connected}); std::vector sc_input(n_math_solvers); - for (Idx i : IdxRange{n_math_solvers}) { + for (Idx const i : IdxRange{n_math_solvers}) { auto map = build_dense_mapping(topo_bus_indices[i], state.math_topology[i]->n_bus()); for (auto&& [reordered_idx, original_idx] : enumerate(map.reorder)) {