use crate::equations::Equations;
use crate::periodic::{sin_cos_pi_od, Parity};
use crate::shooting::Shooting;
use multidouble::dd::DD;
use multidouble::od::OD;
use multidouble::precision::{Double, Precision};
use multidouble::simd::Native;
#[derive(Clone, Debug)]
pub struct ProjectedRows {
pub block: usize,
pub field: usize,
pub pin: usize,
pub parity: Parity,
pub kmax: usize,
low: Vec<OD>,
high: Vec<OD>,
}
fn harmonics(parity: Parity, m: usize, kmax: usize) -> Vec<(usize, f64)> {
let (top, step, start) = match parity {
Parity::Full => (m / 2, 1, 0),
Parity::Odd => (m - 1, 2, 1),
Parity::Even => (m, 2, 0),
};
(start..=top.min(kmax))
.step_by(step)
.map(|k| {
(
k,
if k == 0 || (k == top && parity != Parity::Odd) {
1.0
} else {
2.0
},
)
})
.collect()
}
impl ProjectedRows {
pub fn new(m: usize, block: usize, field: usize, pin: usize, parity: Parity, kmax: usize) -> Self {
let cosines: Vec<OD> = (0..2 * m).map(|t| sin_cos_pi_od(t as i64, m as i64).1).collect();
let mult = if parity == Parity::Full { 2 } else { 1 };
let hs = harmonics(parity, m, kmax);
let inv_m = OD::from_f64(1.0) / OD::from_f64(m as f64);
let low: Vec<OD> = (0..m)
.map(|l| {
let s = hs.iter().fold(OD::from_f64(0.0), |a, &(k, w)| {
a + cosines[(mult * k * l) % (2 * m)].mul_f(w)
});
s * inv_m
})
.collect();
let high = low
.iter()
.enumerate()
.map(|(l, &p)| if l == 0 { OD::from_f64(1.0) - p } else { -p })
.collect();
ProjectedRows {
block,
field,
pin,
parity,
kmax,
low,
high,
}
}
pub fn entry<P: Precision<Native>>(&self, low: bool, i: usize, j: usize, dd: bool) -> P::Scalar {
let v = if low { &self.low } else { &self.high }[i.abs_diff(j)];
P::new(&v.x[..if dd { 2 } else { P::PARTS }])
}
pub fn apply<P: Precision<Native>>(&self, x: &[P::Scalar], y: Option<&[P::Scalar]>, dd: bool) -> Vec<P::Scalar> {
let m = self.low.len();
assert!(
x.len() == m && y.is_none_or(|y| y.len() == m),
"ProjectedRows::apply: values for {m} points"
);
(0..m)
.map(|i| {
let lo = (0..m).map(|j| (self.entry::<P>(true, i, j, dd), x[j]));
match y {
Some(y) => P::dot(lo.chain((0..m).map(|j| (self.entry::<P>(false, i, j, dd), y[j])))),
None => P::dot(lo),
}
})
.collect()
}
}
impl<E: Equations<B>, const B: usize> Shooting<E, B> {
pub(crate) fn projector_dd(&self) -> bool {
#[cfg(feature = "mutate")]
if self.control == crate::control::Control::ProjectorDD {
return true;
}
false
}
pub fn project_rows(
&mut self,
block: usize,
field: usize,
pin: usize,
parity: Parity,
kmax: usize,
) -> Result<(), String> {
let m = Self::M;
if block > self.k() || field >= E::NF {
return Err(format!("project_rows: no field {field} of block {block}"));
}
if pin.checked_add(m).is_none_or(|end| end > self.off_global()) {
return Err(format!(
"project_rows: the pinned unknowns from {pin} pass the fields (they end at {})",
self.off_global()
));
}
if self.projected.iter().any(|p| p.block == block && p.field == field) {
return Err(format!(
"project_rows: field {field} of block {block} is projected already"
));
}
if let Some(p) = self.projected.iter().find(|p| p.pin < pin + m && pin < p.pin + m) {
return Err(format!(
"project_rows: the pinned unknowns {pin}..{} overlap those of field {} of block {}",
pin + m,
p.field,
p.block
));
}
self.projected
.push(ProjectedRows::new(m, block, field, pin, parity, kmax));
Ok(())
}
pub(crate) fn project_residual<P: Precision<Native>>(
&self,
f: &mut [P::Scalar],
pinned: impl Fn(&ProjectedRows) -> Vec<P::Scalar>,
) {
let m = Self::M;
for p in &self.projected {
let r0 = self.row_block(p.block) + p.field * m;
let rows = p.apply::<P>(&f[r0..r0 + m], Some(&pinned(p)), self.projector_dd());
f[r0..r0 + m].copy_from_slice(&rows);
}
}
pub(crate) fn project_jacobian<P: Precision<Native>>(&self, c: &mut [Vec<f64>], ld: usize) {
self.project_columns::<P>(c, ld, self.len(), true);
}
pub(crate) fn project_f64_block(
&self,
b: usize,
w: &mut [f64],
ld: usize,
rel: usize,
cols: usize,
pos: impl Fn(usize) -> Option<usize>,
) -> Result<(), String> {
let m = Self::M;
let dd = self.projector_dd();
for p in self.projected.iter().filter(|p| p.block == b) {
let r0 = rel + p.field * m;
for j in 0..cols {
if (0..m).all(|i| w[j * ld + r0 + i] == 0.0) {
continue;
}
let x: Vec<DD> = (0..m).map(|i| DD::from_f64(w[j * ld + r0 + i])).collect();
for (i, v) in p.apply::<Double>(&x, None, dd).into_iter().enumerate() {
w[j * ld + r0 + i] = v.hi;
}
}
for jj in 0..m {
let c = pos(p.pin + jj).ok_or_else(|| {
format!(
"projected rows of block {b}: the pinned unknown {} has no column here",
p.pin + jj
)
})?;
for i in 0..m {
let k = c * ld + r0 + i;
w[k] = (DD::from_f64(w[k]) + p.entry::<Double>(false, i, jj, dd)).hi;
}
}
}
Ok(())
}
pub(crate) fn project_columns<P: Precision<Native>>(
&self,
c: &mut [Vec<f64>],
ld: usize,
ncols: usize,
pinned: bool,
) {
let m = Self::M;
let dd = self.projector_dd();
let at = |c: &[Vec<f64>], k: usize| P::from_raw(&c.iter().map(|cq| cq[k]).collect::<Vec<_>>());
let put = |c: &mut [Vec<f64>], k: usize, v: P::Scalar| {
for (q, cq) in c.iter_mut().enumerate() {
cq[k] = P::part(v, q);
}
};
for p in &self.projected {
let r0 = self.row_block(p.block) + p.field * m;
for j in 0..ncols {
if (0..m).all(|i| c[0][j * ld + r0 + i] == 0.0) {
continue;
}
let x: Vec<P::Scalar> = (0..m).map(|i| at(c, j * ld + r0 + i)).collect();
for (i, v) in p.apply::<P>(&x, None, dd).into_iter().enumerate() {
put(c, j * ld + r0 + i, v);
}
}
for jj in 0..if pinned { m } else { 0 } {
for i in 0..m {
let k = (p.pin + jj) * ld + r0 + i;
put(c, k, at(c, k) + p.entry::<P>(false, i, jj, dd));
}
}
}
}
}