use num_complex::Complex64 as c64;
use super::{Direction, Edges, Field2d, Polarization, PortMode, Solver2d, stretch};
use crate::{Error, Result};
impl Solver2d {
fn solve_transposed(&self, rhs: &[c64]) -> Vec<c64> {
use faer::linalg::solvers::Solve;
let n = rhs.len();
let b = faer::Mat::<c64>::from_fn(n, 1, |r, _| rhs[r]);
let u = self.lu.solve_transpose(&b);
let mut values: Vec<c64> = (0..n).map(|r| u[(r, 0)]).collect();
let mut residual = rhs.to_vec();
for t in &self.entries {
residual[t.col] -= t.val * values[t.row];
}
let r = faer::Mat::<c64>::from_fn(n, 1, |k, _| residual[k]);
let correction = self.lu.solve_transpose(&r);
for (k, v) in values.iter_mut().enumerate() {
*v += correction[(k, 0)];
}
values
}
pub fn mode_power_gradient(
&self,
field: &Field2d,
mode: &PortMode,
direction: Direction,
) -> Result<(f64, Vec<f64>)> {
let g = self.grid;
let (nx, ny) = (g.nx, g.ny);
if field.values.len() != nx * ny || mode.profile.len() != ny {
return Err(Error::invalid(
"fdfd gradient",
"the field and the mode must belong to this problem",
));
}
if self.polarization == Polarization::Hz && !self.cellwise {
return Err(Error::invalid(
"fdfd gradient",
"with H along z the gradient is for a problem given cell by cell (from_cells)",
));
}
let step = (c64::new(0.0, 1.0) * mode.beta * mode.dx).exp();
let d = step - 1.0 / step;
let (on_c, on_next) = match direction {
Direction::Forward => (-1.0 / (step * d), 1.0 / d),
Direction::Backward => (1.0 + 1.0 / (step * d), -1.0 / d),
};
let mut c = vec![c64::new(0.0, 0.0); nx * ny];
for j in 0..ny {
let wp = mode.weights[j] * mode.profile[j];
c[j * nx + mode.column] = wp * on_c;
c[j * nx + mode.column + 1] = wp * on_next;
}
let a: c64 = c.iter().zip(&field.values).map(|(c, u)| c * u).sum();
let adjoint_source: Vec<c64> = c.iter().map(|c| a.conj() * c).collect();
let lambda = self.solve_transposed(&adjoint_source);
let u = &field.values;
let mut gradient = vec![0.0; nx * ny];
match self.polarization {
Polarization::Ez => {
let k2 = self.k0 * self.k0;
for (k, g) in gradient.iter_mut().enumerate() {
*g = -2.0 * (lambda[k] * k2 * u[k]).re;
}
}
Polarization::Hz => self.hz_gradient(&lambda, u, &mut gradient),
}
Ok((a.norm_sqr(), gradient))
}
fn hz_gradient(&self, lambda: &[c64], u: &[c64], gradient: &mut [f64]) {
let g = self.grid;
let (nx, ny) = (g.nx, g.ny);
let b = &self.boundaries;
let span_x = (g.x0, g.x0 + nx as f64 * g.dx);
let span_y = (g.y0, g.y0 + ny as f64 * g.dy);
let sx = |x: f64| stretch(x, span_x, b.x.pml(), g.dx, self.k0, b);
let sy = |y: f64| stretch(y, span_y, b.y.pml(), g.dy, self.k0, b);
let beyond = |n: i64, len: usize, edges: Edges, step: f64| -> Option<(usize, c64)> {
if (0..len as i64).contains(&n) {
return Some((n as usize, c64::new(1.0, 0.0)));
}
match edges {
Edges::Bloch { k } => {
let turns = if n < 0 { -1.0 } else { 1.0 };
Some((
n.rem_euclid(len as i64) as usize,
c64::from_polar(1.0, turns * k * len as f64 * step),
))
}
Edges::Pml { .. } => None,
}
};
let owners = |n: usize, len: usize, bloch: bool| -> [(usize, f64); 2] {
if n == 0 || n == len {
if bloch {
[(len - 1, 0.5), (0, 0.5)]
} else {
let only = n.min(len - 1);
[(only, 0.5), (only, 0.5)]
}
} else {
[(n - 1, 0.5), (n, 0.5)]
}
};
let bloch = |e: Edges| matches!(e, Edges::Bloch { .. });
for j in 0..ny {
for i in 0..nx {
let k = j * nx + i;
let (x, y) = (g.x(i), g.y(j));
let faces = [
(
i,
0,
self.eps_y[j * (nx + 1) + i],
sx(x) * sx(x - g.dx / 2.0),
g.dx,
-1i64,
),
(
i + 1,
0,
self.eps_y[j * (nx + 1) + i + 1],
sx(x) * sx(x + g.dx / 2.0),
g.dx,
1,
),
(
j,
1,
self.eps_x[j * nx + i],
sy(y) * sy(y - g.dy / 2.0),
g.dy,
-1,
),
(
j + 1,
1,
self.eps_x[(j + 1) * nx + i],
sy(y) * sy(y + g.dy / 2.0),
g.dy,
1,
),
];
for (face, axis, eps, s, h, offset) in faces {
let coupling = 1.0 / (eps * s * h * h);
let neighbour = if axis == 0 {
beyond(i as i64 + offset, nx, b.x, g.dx).map(|(n, p)| u[j * nx + n] * p)
} else {
beyond(j as i64 + offset, ny, b.y, g.dy).map(|(n, p)| u[n * nx + i] * p)
};
let difference = neighbour.unwrap_or(c64::new(0.0, 0.0)) - u[k];
let term = -2.0 * (lambda[k] * (-coupling / eps) * difference).re;
let shares = if axis == 0 {
owners(face, nx, bloch(b.x)).map(|(n, w)| (j * nx + n, w))
} else {
owners(face, ny, bloch(b.y)).map(|(n, w)| (n * nx + i, w))
};
for (cell, w) in shares {
gradient[cell] += w * term;
}
}
}
}
}
}