use std::f64::consts::PI;
use nalgebra::{DVector, ComplexField, SimdComplexField};
use nalgebra_sparse::CscMatrix;
use num_complex::Complex64;
use num_traits::Zero;
use super::new_dsdvbus2::{fill_jacobian_v2, JacobianPattern2};
use super::newtonpf::assemble_f_v2;
use super::solver::Solve;
#[allow(non_snake_case, clippy::too_many_arguments)]
pub fn newton_pf_iwamoto<Solver: Solve>(
Ybus: &CscMatrix<Complex64>,
Sbus: &DVector<Complex64>,
v_init: &DVector<Complex64>,
npv: usize,
npq: usize,
tolerance: Option<f64>,
max_iter: Option<usize>,
solver: &mut Solver,
) -> Result<(DVector<Complex64>, usize), (String, DVector<Complex64>, usize)> {
let mut v = v_init.clone();
let max_iter = max_iter.unwrap_or(100);
let tol = tolerance.unwrap_or(1e-6);
let j_pattern = JacobianPattern2::build_from_permuted(
Ybus.col_offsets(),
Ybus.row_indices(),
npv,
npq,
);
let n_state = npv + 2 * npq;
let mut j_values = vec![0.0; j_pattern.nnz_j];
let n_bus = npv + npq;
let mut mis = &v.component_mul(&(Ybus * &v).conjugate()) - Sbus;
let mut F = DVector::zeros(n_state);
assemble_f_v2(&mut F, n_bus, &mis, n_state, npq);
if F.norm() < tol {
return Ok((v, 0));
}
let mut v_m = v.map(|e| e.simd_modulus());
let mut v_a = v.map(|e| e.simd_argument());
let mut v_norm = v.map(|e| e.simd_signum());
let Ap = unsafe {
std::slice::from_raw_parts_mut(
j_pattern.j_col_ptrs.as_ptr() as *mut usize,
j_pattern.j_col_ptrs.len(),
)
};
let Ai = unsafe {
std::slice::from_raw_parts_mut(
j_pattern.j_row_indices.as_ptr() as *mut usize,
j_pattern.j_row_indices.len(),
)
};
for it in 0..max_iter {
let ibus = Ybus * &v;
fill_jacobian_v2(
Ybus,
v.as_slice(),
v_norm.as_slice(),
ibus.as_slice(),
&j_pattern,
npv,
npq,
&mut j_values,
);
let a = F.clone();
let _ = solver.solve(
Ap,
Ai,
j_values.as_mut_slice(),
F.data.as_mut_slice(),
n_state,
);
let dx = &F;
let mut dv = DVector::zeros(v.len());
for i in 0..v.len() {
if i < npq {
let dx_theta = dx[i];
let dx_v = dx[n_bus + i];
let angle = v_a[i];
let mag = v_m[i];
dv[i] = Complex64::from_polar(1.0, angle) * Complex64::new(-dx_v, -mag * dx_theta);
} else if i < n_bus {
let dx_theta = dx[i];
let angle = v_a[i];
let mag = v_m[i];
dv[i] = Complex64::from_polar(1.0, angle) * Complex64::new(0.0, -mag * dx_theta);
} else {
dv[i] = Complex64::zero();
}
}
let ybus_dv = Ybus * &dv;
let c_complex = dv.component_mul(&ybus_dv.map(|e| e.conjugate()));
let mut c = DVector::zeros(n_state);
assemble_f_v2(&mut c, n_bus, &c_complex, n_state, npq);
let mu = solve_iwamoto_multiplier(&a, &c);
v_a.rows_range_mut(0..n_bus)
.zip_apply(&dx.rows_range(0..n_bus), |a, b| {
*a -= mu * b;
*a = a.rem_euclid(2.0 * PI);
});
let mut vm_pq = v_m.rows_range_mut(0..npq);
vm_pq.zip_apply(&dx.rows_range(n_bus..n_state), |a, b| *a -= mu * b);
v_norm.zip_apply(&v_a, |a, va| *a = Complex64::from_polar(1.0, va));
v.zip_zip_apply(&v_norm, &v_m, |a, e, vm| *a = vm * e);
v.component_mul(&(Ybus * &v).conjugate())
.sub_to(Sbus, &mut mis);
assemble_f_v2(&mut F, n_bus, &mis, n_state, npq);
if F.norm() < tol {
return Ok((v, it + 1));
}
}
Err((String::from("Did not converge!"), v, max_iter))
}
#[allow(non_snake_case)]
fn solve_iwamoto_multiplier(a: &DVector<f64>, c: &DVector<f64>) -> f64 {
let a_dot_a = a.dot(a);
let a_dot_c = a.dot(c);
let c_dot_c = c.dot(c);
if c_dot_c < 1e-12 {
return 1.0;
}
let g0 = -a_dot_a;
let g1 = a_dot_a + 2.0 * a_dot_c;
let g2 = -3.0 * a_dot_c;
let g3 = 2.0 * c_dot_c;
let A = g2 / g3;
let B = g1 / g3;
let C = g0 / g3;
let p = B - A * A / 3.0;
let q = C - A * B / 3.0 + 2.0 * A * A * A / 27.0;
let D = (q / 2.0) * (q / 2.0) + (p / 3.0) * (p / 3.0) * (p / 3.0);
let mut roots = Vec::new();
roots.push(1.0);
if D > 0.0 {
let sqrt_d = D.sqrt();
let u = -q / 2.0 + sqrt_d;
let v = -q / 2.0 - sqrt_d;
let u_cbrt = u.signum() * u.abs().powf(1.0 / 3.0);
let v_cbrt = v.signum() * v.abs().powf(1.0 / 3.0);
let x = u_cbrt + v_cbrt;
let mu = x - A / 3.0;
roots.push(mu);
} else {
let r = (-p * p * p / 27.0).sqrt();
if r > 1e-12 {
let cos_val = (-q / (2.0 * r)).clamp(-1.0, 1.0);
let phi = cos_val.acos();
let term = 2.0 * (-p / 3.0).sqrt();
for k in 0..3 {
let x = term * ((phi + 2.0 * PI * (k as f64)) / 3.0).cos();
let mu = x - A / 3.0;
roots.push(mu);
}
}
}
let mut best_mu = 1.0;
let mut min_val = f64::MAX;
for &r in &roots {
let mu = r.clamp(0.05, 1.0);
let mut f_mu = DVector::zeros(a.len());
for i in 0..a.len() {
f_mu[i] = (1.0 - mu) * a[i] + mu * mu * c[i];
}
let val = f_mu.dot(&f_mu);
if val < min_val {
min_val = val;
best_mu = mu;
}
}
best_mu
}