Skip to content

Commit 4c24d4a

Browse files
authored
Merge branch 'main' into dense-inversion
2 parents 1758f4d + 9955852 commit 4c24d4a

2 files changed

Lines changed: 107 additions & 24 deletions

File tree

power_grid_model_c/power_grid_model/include/power_grid_model/math_solver/newton_raphson_pf_solver.hpp

Lines changed: 107 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ J.L -= -dQ_cal_m/dV
160160
#include <algorithm>
161161
#include <complex>
162162
#include <functional>
163+
#include <ranges>
163164
#include <vector>
164165

165166
namespace power_grid_model::math_solver {
@@ -180,6 +181,12 @@ template <symmetry_tag sym> struct PolarPhasor : public Block<double, sym, false
180181

181182
GetterType<0, 0> p() { return this->template get_val<0, 0>(); }
182183
GetterType<1, 0> q() { return this->template get_val<1, 0>(); }
184+
185+
// linear solve path getters: x0 = Ur, x1 = Ui and Eq0 = Real, Eq1 = Imag
186+
GetterType<0, 0> i_real() { return this->template get_val<0, 0>(); }
187+
GetterType<1, 0> i_imag() { return this->template get_val<1, 0>(); }
188+
GetterType<0, 0> u_real() { return this->template get_val<0, 0>(); }
189+
GetterType<1, 0> u_imag() { return this->template get_val<1, 0>(); }
183190
};
184191

185192
// class for complex power
@@ -204,6 +211,13 @@ template <symmetry_tag sym> class PFJacBlock : public Block<double, sym, true, 2
204211
GetterType<0, 1> n() { return this->template get_val<0, 1>(); }
205212
GetterType<1, 0> m() { return this->template get_val<1, 0>(); }
206213
GetterType<1, 1> l() { return this->template get_val<1, 1>(); }
214+
215+
// linear solve path getters: x0 = Ur, x1 = Ui and Eq0 = Real, Eq1 = Imag
216+
// System: [[G, -B], [B, G]] * [Ur, Ui]^T = [Ir, Ii]^T
217+
GetterType<0, 0> real_real() { return this->template get_val<0, 0>(); }
218+
GetterType<0, 1> real_imag() { return this->template get_val<0, 1>(); }
219+
GetterType<1, 0> imag_real() { return this->template get_val<1, 0>(); }
220+
GetterType<1, 1> imag_imag() { return this->template get_val<1, 1>(); }
207221
};
208222

209223
// solver
@@ -228,19 +242,46 @@ class NewtonRaphsonPFSolver : public IterativePFSolver<sym_type, NewtonRaphsonPF
228242
bus_types_(y_bus.size(), BusType::pq),
229243
voltage_regulators_per_load_gen_{std::ref(topo.voltage_regulators_per_load_gen)} {}
230244

231-
// Initilize the unknown variable in polar form
245+
// Initialize the unknown variable in polar form using a linear voltage guess solved in the real domain.
246+
// This implementation reuses the class-level Jacobian matrix (data_jac_) and RHS vector (del_x_pq_)
247+
// to eliminate temporary memory allocations.
248+
// The complex system (G + jB)(Ur + jUi) = Ir + jIi is mapped to the real system:
249+
// [[G, -B], [B, G]] [Ur, Ui]^T = [Ir, Ii]^T
250+
// This mapping ensures that G aligns with the N/M sub-blocks as in the NR Jacobian.
232251
void initialize_derived_solver(YBus<sym> const& y_bus, PowerFlowInput<sym> const& input,
233252
SolverOutput<sym>& output) {
234-
using LinearSparseSolverType = SparseLUSolver<ComplexTensor<sym>, ComplexValue<sym>, ComplexValue<sym>>;
253+
// Reset reused buffers to zero
254+
std::ranges::fill(data_jac_, PFJacBlock<sym>{});
255+
std::ranges::fill(del_x_pq_, PolarPhasor<sym>{});
256+
257+
// Map network admittance to real-domain system
258+
IdxVector const& map_lu_y_bus = y_bus.map_lu_y_bus();
259+
ComplexTensorVector<sym> const& ydata = y_bus.admittance();
260+
for (Idx jac_idx = 0; jac_idx < std::ssize(data_jac_); ++jac_idx) {
261+
Idx const k_y_bus = map_lu_y_bus[jac_idx];
262+
if (k_y_bus != -1) {
263+
set_linear_block(data_jac_[jac_idx], ydata[k_y_bus]);
264+
}
265+
}
266+
267+
IdxVector const& bus_entry = y_bus.lu_diag();
268+
auto const& load_gens_per_bus = this->load_gens_per_bus_.get();
269+
auto const& sources_per_bus = this->sources_per_bus_.get();
270+
for (auto const& [bus_number, load_gens, sources] :
271+
enumerated_zip_sequence(load_gens_per_bus, sources_per_bus)) {
272+
Idx const diagonal_position = bus_entry[bus_number];
273+
add_linear_initial_guess_loads(load_gens, data_jac_[diagonal_position], input);
274+
add_linear_initial_guess_sources(sources, data_jac_[diagonal_position], del_x_pq_[bus_number], y_bus,
275+
input);
276+
}
235277

236-
ComplexTensorVector<sym> linear_mat_data(y_bus.nnz_lu());
237-
LinearSparseSolverType linear_sparse_solver{y_bus.row_indptr_lu(), y_bus.col_indices_lu(), y_bus.lu_diag()};
238-
typename LinearSparseSolverType::BlockPermArray linear_perm(y_bus.size());
278+
// Solve: [Ur, Ui] result in [p, q]
279+
sparse_solver_.prefactorize_and_solve(data_jac_, perm_, del_x_pq_, del_x_pq_);
239280

240-
detail::copy_y_bus<sym>(y_bus, linear_mat_data);
241-
detail::prepare_linear_matrix_and_rhs(y_bus, input, this->load_gens_per_bus_.get(),
242-
this->sources_per_bus_.get(), output, linear_mat_data);
243-
linear_sparse_solver.prefactorize_and_solve(linear_mat_data, linear_perm, output.u, output.u);
281+
for (Idx const i : std::views::iota(Idx{}, this->n_bus_)) {
282+
output.u[i] =
283+
ComplexValue<sym>{RealValue<sym>{del_x_pq_[i].u_real()}, RealValue<sym>{del_x_pq_[i].u_imag()}};
284+
}
244285

245286
set_u_ref_and_bus_types(input, output.u);
246287

@@ -302,7 +343,7 @@ class NewtonRaphsonPFSolver : public IterativePFSolver<sym_type, NewtonRaphsonPF
302343
// 1. negative power injection: - p/q_calculated
303344
// 2. power unbalance: p/q_specified - p/q_calculated
304345
// 3. unknown iterative
305-
std::vector<ComplexPower<sym>> del_x_pq_;
346+
std::vector<PolarPhasor<sym>> del_x_pq_;
306347

307348
SparseSolverType sparse_solver_;
308349
// permutation array
@@ -432,6 +473,44 @@ class NewtonRaphsonPFSolver : public IterativePFSolver<sym_type, NewtonRaphsonPF
432473
}
433474
}
434475

476+
void add_linear_initial_guess_loads(IdxRange const& load_gens, PFJacBlock<sym>& block,
477+
PowerFlowInput<sym> const& input) {
478+
for (Idx const load_number : load_gens) {
479+
auto const& s = input.s_injection[load_number];
480+
// Y_load = P - jQ -> G=P, B=-Q
481+
// System: [[G, -B], [B, G]] [Ur, Ui]^T = [Ir, Ii]^T
482+
// Diagonal mapping: coeff of Ur in Real Eq = G = real(Y_load) = P
483+
// coeff of Ui in Real Eq = -B = imag(Y_load) = Q
484+
// coeff of Ur in Imag Eq = B = -imag(Y_load) = -Q
485+
// coeff of Ui in Imag Eq = G = real(Y_load) = P
486+
ComplexValue<sym> const y_load = -conj(s);
487+
add_diag(block.real_imag(), -imag(y_load));
488+
add_diag(block.real_real(), real(y_load));
489+
add_diag(block.imag_imag(), real(y_load));
490+
add_diag(block.imag_real(), imag(y_load));
491+
}
492+
}
493+
494+
void add_linear_initial_guess_sources(IdxRange const& sources, PFJacBlock<sym>& block, PolarPhasor<sym>& rhs,
495+
YBus<sym> const& y_bus, PowerFlowInput<sym> const& input) {
496+
for (Idx const source_number : sources) {
497+
ComplexTensor<sym> const y_source =
498+
y_bus.math_model_param().source_param[source_number].template y_ref<sym>();
499+
ComplexValue<sym> const u_source{input.source[source_number]};
500+
501+
// coeff of Ui in Real Eq = -B
502+
block.real_imag() -= imag(y_source);
503+
// coeff of Ur in Real Eq = G
504+
block.real_real() += real(y_source);
505+
// coeff of Ui in Imag Eq = G
506+
block.imag_imag() += real(y_source);
507+
// coeff of Ur in Imag Eq = B
508+
block.imag_real() += imag(y_source);
509+
510+
add_linear_rhs(rhs, y_source, u_source);
511+
}
512+
}
513+
435514
void add_loads(IdxRange const& load_gens, Idx bus_number, Idx diagonal_position, PowerFlowInput<sym> const& input,
436515
std::vector<LoadGenType> const& load_gen_type) {
437516
using enum LoadGenType;
@@ -517,6 +596,24 @@ class NewtonRaphsonPFSolver : public IterativePFSolver<sym_type, NewtonRaphsonPF
517596
data_jac_[diagonal_position].l() += block_mm.l();
518597
}
519598
}
599+
600+
// Helper to map complex admittance to real-domain block matrix using PFJacBlock getters.
601+
// This mapping ensures G aligns with the N/M sub-blocks as in the NR Jacobian.
602+
static void set_linear_block(PFJacBlock<sym>& block, ComplexTensor<sym> const& y) {
603+
RealTensor<sym> const g = real(y);
604+
RealTensor<sym> const b = imag(y);
605+
block.real_imag() = -b;
606+
block.real_real() = g;
607+
block.imag_imag() = g;
608+
block.imag_real() = b;
609+
}
610+
611+
// Helper to map complex injection I = Y * U to real-domain RHS [Ir, Ii]^T using PolarPhasor getters.
612+
static void add_linear_rhs(PolarPhasor<sym>& rhs, ComplexTensor<sym> const& y, ComplexValue<sym> const& u) {
613+
ComplexValue<sym> const i_rhs = dot(y, u);
614+
rhs.i_real() += real(i_rhs);
615+
rhs.i_imag() += imag(i_rhs);
616+
}
520617
};
521618

522619
} // namespace newton_raphson_pf

src/power_grid_model/_core/power_grid_model.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -470,20 +470,6 @@ def _calculate_short_circuit( # noqa: PLR0913
470470
experimental_features=experimental_features,
471471
)
472472

473-
@overload
474-
def calculate_power_flow(
475-
self,
476-
*,
477-
symmetric: bool = ...,
478-
error_tolerance: float = ...,
479-
max_iterations: int = ...,
480-
calculation_method: CalculationMethod | str = ...,
481-
threading: int = ...,
482-
output_component_types: None | set[ComponentTypeVar] | list[ComponentTypeVar] = ...,
483-
continue_on_batch_error: bool = ...,
484-
decode_error: bool = ...,
485-
tap_changing_strategy: TapChangingStrategy | str = ...,
486-
) -> SingleRowBasedDataset: ...
487473
@overload
488474
def calculate_power_flow(
489475
self,

0 commit comments

Comments
 (0)