@@ -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
165166namespace 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
0 commit comments