use super::{Op, Side, Uplo, gemm, with_scratch};
use crate::math::matrix::Scalar;
use core::mem::MaybeUninit;
#[cfg(all(target_arch = "aarch64", target_endian = "little", not(miri)))]
mod aarch64;
pub(super) const LEAF: usize = 16;
const TINY: usize = 4;
const ROWS: usize = 16;
#[allow(clippy::too_many_arguments)]
pub(super) fn solve<T: Scalar>(
side: Side,
uplo: Uplo,
op: Op,
unit: bool,
m: usize,
n: usize,
a: &[T],
lda: usize,
b: &mut [T],
ldb: usize,
) {
if m == 0 || n == 0 {
return;
}
let t = Tri::new(a, lda, uplo, op, unit);
match side {
Side::Left if m <= LEAF => left_leaf(&t, m, n, b, ldb, None),
Side::Left => with_scratch(m * n, |x| left(&t, m, n, b, ldb, x, m)),
Side::Right => right(&t, m, n, b, ldb),
}
}
#[allow(clippy::too_many_arguments)]
pub(super) fn solve_into<T: Scalar>(
uplo: Uplo,
op: Op,
unit: bool,
m: usize,
n: usize,
a: &[T],
lda: usize,
b: &mut [T],
ldb: usize,
x: &mut [T],
ldx: usize,
) {
if m == 0 || n == 0 {
return;
}
let t = Tri::new(a, lda, uplo, op, unit);
left(&t, m, n, b, ldb, x, ldx);
}
struct Tri<'a, T> {
a: &'a [T],
lda: usize,
op: Op,
lower: bool,
unit: bool,
}
impl<'a, T: Scalar> Tri<'a, T> {
fn new(a: &'a [T], lda: usize, uplo: Uplo, op: Op, unit: bool) -> Self {
Tri {
a,
lda,
op,
lower: (uplo == Uplo::Lower) == (op == Op::N),
unit,
}
}
#[inline(always)]
fn at(&self, i: usize, j: usize) -> T {
self.op.at(self.a, self.lda, i, j)
}
fn block(&self, i: usize, j: usize) -> &'a [T] {
if self.op == Op::N {
&self.a[i + j * self.lda..]
} else {
&self.a[j + i * self.lda..]
}
}
fn sub(&self, k: usize) -> Self {
Tri {
a: &self.a[k + k * self.lda..],
..*self
}
}
}
impl<T> Clone for Tri<'_, T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Tri<'_, T> {}
fn split(k: usize) -> usize {
(k / 2).next_multiple_of(LEAF).min(k - 1)
}
fn left<T: Scalar>(
t: &Tri<T>,
m: usize,
n: usize,
b: &mut [T],
ldb: usize,
x: &mut [T],
ldx: usize,
) {
if m <= LEAF {
left_leaf(t, m, n, b, ldb, Some((x, ldx)));
return;
}
let k1 = split(m);
let k2 = m - k1;
let (a11, a22) = (t, &t.sub(k1));
let minus_one = -T::one();
if t.lower {
left(a11, k1, n, b, ldb, x, ldx);
gemm(
k2,
n,
k1,
minus_one,
t.block(k1, 0),
t.lda,
t.op,
x,
ldx,
Op::N,
T::one(),
&mut b[k1..],
ldb,
);
left(a22, k2, n, &mut b[k1..], ldb, &mut x[k1..], ldx);
} else {
left(a22, k2, n, &mut b[k1..], ldb, &mut x[k1..], ldx);
gemm(
k1,
n,
k2,
minus_one,
t.block(0, k1),
t.lda,
t.op,
&x[k1..],
ldx,
Op::N,
T::one(),
b,
ldb,
);
left(a11, k1, n, b, ldb, x, ldx);
}
}
fn right<T: Scalar>(t: &Tri<T>, m: usize, n: usize, b: &mut [T], ldb: usize) {
if n <= LEAF {
right_leaf(t, m, n, b, ldb);
return;
}
let k1 = split(n);
let k2 = n - k1;
let (a11, a22) = (t, &t.sub(k1));
let minus_one = -T::one();
let (b1, b2) = b.split_at_mut(k1 * ldb);
if t.lower {
right(a22, m, k2, b2, ldb);
gemm(
m,
k1,
k2,
minus_one,
b2,
ldb,
Op::N,
t.block(k1, 0),
t.lda,
t.op,
T::one(),
b1,
ldb,
);
right(a11, m, k1, b1, ldb);
} else {
right(a11, m, k1, b1, ldb);
gemm(
m,
k2,
k1,
minus_one,
b1,
ldb,
Op::N,
t.block(0, k1),
t.lda,
t.op,
T::one(),
b2,
ldb,
);
right(a22, m, k2, b2, ldb);
}
}
struct Packed<T> {
l: [MaybeUninit<T>; LEAF * LEAF],
columns: usize,
r: [T; LEAF],
forward: bool,
}
impl<T: Scalar> Packed<T> {
fn new() -> Self {
Packed {
l: [const { MaybeUninit::uninit() }; LEAF * LEAF],
columns: 0,
r: [T::one(); LEAF],
forward: false,
}
}
fn fill(&mut self, t: &Tri<T>, k: usize, transpose: bool) -> bool {
self.columns = k.next_power_of_two().clamp(4, LEAF);
for v in &mut self.l[..self.columns * LEAF] {
v.write(T::zero());
}
self.forward = t.lower != transpose;
let (a, lda, op) = (t.a, t.lda, t.op);
let conj = op == Op::C;
let column = (op == Op::N) != transpose;
for p in 0..k {
if !t.unit {
let d = a[p + p * lda];
let d = if conj { d.conj() } else { d };
let q = T::one() / d;
if !(d.is_finite() && q.is_finite()) || q == T::zero() {
return false;
}
self.r[p] = q;
}
let rp = self.r[p];
let rows = if self.forward { p + 1..k } else { 0..p };
let dst = &mut self.l[p * LEAF..p * LEAF + LEAF];
if column {
let src = &a[p * lda..];
for i in rows {
let v = if conj { src[i].conj() } else { src[i] };
dst[i].write(v * rp);
}
} else {
for i in rows {
let v = a[p + i * lda];
let v = if conj { v.conj() } else { v };
dst[i].write(v * rp);
}
}
}
true
}
fn l(&self) -> &[T] {
let l = &self.l[..self.columns * LEAF];
unsafe { &*(l as *const [MaybeUninit<T>] as *const [T]) }
}
}
fn left_leaf<T: Scalar>(
t: &Tri<T>,
m: usize,
n: usize,
b: &mut [T],
ldb: usize,
mut x: Option<(&mut [T], usize)>,
) {
if m <= TINY && (n <= 2 || (t.unit && n <= 8)) {
substitute(Side::Left, t, m, n, b, ldb);
if let Some((x, ldx)) = x {
for j in 0..n {
x[j * ldx..j * ldx + m].copy_from_slice(&b[j * ldb..j * ldb + m]);
}
}
return;
}
let mut packed = Packed::new();
let p = packed.fill(t, m, false).then_some(&packed);
let mut j = 0;
while j < n {
let c = if n - j >= 2 { 2 } else { 1 };
let done = p.as_ref().is_some_and(|p| {
let x = x.as_mut().map(|(x, l)| (&mut **x, *l));
#[cfg(all(target_arch = "aarch64", target_endian = "little", not(miri)))]
let x = match left_neon(p, t.unit, m, j, c, b, ldb, x) {
Ok(done) => return done,
Err(x) => x,
};
match (m.div_ceil(4), c) {
(1, 2) => left_columns::<T, 1, 2>(p, t.unit, m, j, b, ldb, x),
(1, _) => left_columns::<T, 1, 1>(p, t.unit, m, j, b, ldb, x),
(2, 2) => left_columns::<T, 2, 2>(p, t.unit, m, j, b, ldb, x),
(2, _) => left_columns::<T, 2, 1>(p, t.unit, m, j, b, ldb, x),
(3, 2) => left_columns::<T, 3, 2>(p, t.unit, m, j, b, ldb, x),
(3, _) => left_columns::<T, 3, 1>(p, t.unit, m, j, b, ldb, x),
(_, 2) => left_columns::<T, 4, 2>(p, t.unit, m, j, b, ldb, x),
(_, _) => left_columns::<T, 4, 1>(p, t.unit, m, j, b, ldb, x),
}
});
if !done {
let b = &mut b[j * ldb..];
substitute(Side::Left, t, m, c, b, ldb);
if let Some((x, ldx)) = x.as_mut() {
for q in 0..c {
let x = &mut x[(j + q) * *ldx..];
x[..m].copy_from_slice(&b[q * ldb..q * ldb + m]);
}
}
}
j += c;
}
}
#[cfg(all(target_arch = "aarch64", target_endian = "little", not(miri)))]
#[allow(clippy::too_many_arguments)]
fn left_neon<'x, T: Scalar>(
p: &Packed<T>,
unit: bool,
m: usize,
j: usize,
c: usize,
b: &mut [T],
ldb: usize,
x: Option<(&'x mut [T], usize)>,
) -> Result<bool, Option<(&'x mut [T], usize)>> {
use core::any::TypeId;
let (is_f64, is_f32) = (
TypeId::of::<T>() == TypeId::of::<f64>(),
TypeId::of::<T>() == TypeId::of::<f32>(),
);
if !(is_f64 || is_f32) {
return Err(x);
}
assert!((1..=LEAF).contains(&m) && (1..=2).contains(&c));
assert!((j + c - 1) * ldb + m <= b.len());
assert!(p.l().len() >= LEAF * m.next_power_of_two().clamp(4, LEAF));
let bp = b[j * ldb..].as_mut_ptr();
let (xp, ldx) = match x {
Some((x, ldx)) => {
assert!((j + c - 1) * ldx + m <= x.len());
(x[j * ldx..].as_mut_ptr(), ldx)
}
None => (core::ptr::null_mut(), 0),
};
let (l, r, f) = (p.l(), &p.r, p.forward);
unsafe {
if is_f64 {
let (l, r) = (
&*(l as *const [T] as *const [f64]),
&*(r as *const [T; LEAF]).cast(),
);
let (b, x) = (bp.cast::<f64>(), xp.cast::<f64>());
Ok(match (m, c) {
(..=2, 1) => aarch64::left_f64::<1, 1>(l, r, f, unit, m, b, ldb, x, ldx),
(..=2, _) => aarch64::left_f64::<1, 2>(l, r, f, unit, m, b, ldb, x, ldx),
(..=4, 1) => aarch64::left_f64::<2, 1>(l, r, f, unit, m, b, ldb, x, ldx),
(..=4, _) => aarch64::left_f64::<2, 2>(l, r, f, unit, m, b, ldb, x, ldx),
(..=8, 1) => aarch64::left_f64::<4, 1>(l, r, f, unit, m, b, ldb, x, ldx),
(..=8, _) => aarch64::left_f64::<4, 2>(l, r, f, unit, m, b, ldb, x, ldx),
(_, 1) => aarch64::left_f64::<8, 1>(l, r, f, unit, m, b, ldb, x, ldx),
(_, _) => aarch64::left_f64::<8, 2>(l, r, f, unit, m, b, ldb, x, ldx),
})
} else {
let (l, r) = (
&*(l as *const [T] as *const [f32]),
&*(r as *const [T; LEAF]).cast(),
);
let (b, x) = (bp.cast::<f32>(), xp.cast::<f32>());
Ok(match (m, c) {
(..=4, 1) => aarch64::left_f32::<1, 1>(l, r, f, unit, m, b, ldb, x, ldx),
(..=4, _) => aarch64::left_f32::<1, 2>(l, r, f, unit, m, b, ldb, x, ldx),
(..=8, 1) => aarch64::left_f32::<2, 1>(l, r, f, unit, m, b, ldb, x, ldx),
(..=8, _) => aarch64::left_f32::<2, 2>(l, r, f, unit, m, b, ldb, x, ldx),
(_, 1) => aarch64::left_f32::<4, 1>(l, r, f, unit, m, b, ldb, x, ldx),
(_, _) => aarch64::left_f32::<4, 2>(l, r, f, unit, m, b, ldb, x, ldx),
})
}
}
}
#[inline(always)]
fn left_columns<T: Scalar, const G: usize, const C: usize>(
p: &Packed<T>,
unit: bool,
m: usize,
j: usize,
b: &mut [T],
ldb: usize,
x: Option<(&mut [T], usize)>,
) -> bool {
assert!(m <= 4 * G && G * 4 <= LEAF);
let mut y = [[[T::zero(); 4]; G]; C];
for (c, y) in y.iter_mut().enumerate() {
let col = &b[(j + c) * ldb..][..m];
for (i, v) in col.iter().enumerate() {
y[i / 4][i % 4] = *v;
}
}
let pl = p.l();
let l = |i: usize, q: usize| pl[i + q * LEAF];
if p.forward {
for g in 0..G {
for q in 0..4 {
for k in q + 1..4 {
let lk = l(4 * g + k, 4 * g + q);
for y in &mut y {
y[g][k] = y[g][k].mul_sub(lk, y[g][q]);
}
}
}
for h in g + 1..G {
for q in 0..4 {
for k in 0..4 {
let lk = l(4 * h + k, 4 * g + q);
for y in &mut y {
y[h][k] = y[h][k].mul_sub(lk, y[g][q]);
}
}
}
}
}
} else {
for g in (0..G).rev() {
for q in (0..4).rev() {
for k in 0..q {
let lk = l(4 * g + k, 4 * g + q);
for y in &mut y {
y[g][k] = y[g][k].mul_sub(lk, y[g][q]);
}
}
}
for h in 0..g {
for q in 0..4 {
for k in 0..4 {
let lk = l(4 * h + k, 4 * g + q);
for y in &mut y {
y[h][k] = y[h][k].mul_sub(lk, y[g][q]);
}
}
}
}
}
}
if !y.iter().flatten().flatten().all(|v| v.is_finite()) {
return false;
}
if !unit {
for y in &mut y {
for (g, y) in y.iter_mut().enumerate() {
for (k, y) in y.iter_mut().enumerate() {
*y *= p.r[4 * g + k];
}
}
}
}
for (c, y) in y.iter().enumerate() {
let col = &mut b[(j + c) * ldb..][..m];
for (i, v) in col.iter_mut().enumerate() {
*v = y[i / 4][i % 4];
}
}
if let Some((x, ldx)) = x {
for (c, y) in y.iter().enumerate() {
let col = &mut x[(j + c) * ldx..][..m];
for (i, v) in col.iter_mut().enumerate() {
*v = y[i / 4][i % 4];
}
}
}
true
}
fn right_leaf<T: Scalar>(t: &Tri<T>, m: usize, n: usize, b: &mut [T], ldb: usize) {
if n <= TINY && t.unit {
substitute(Side::Right, t, m, n, b, ldb);
return;
}
let mut p = Packed::new();
if !p.fill(t, n, true) {
substitute(Side::Right, t, m, n, b, ldb);
return;
}
let mut i = 0;
while i < m {
#[cfg(all(target_arch = "aarch64", target_endian = "little", not(miri)))]
if let Some((rows, done)) = right_neon(&p, t.unit, m - i, n, &mut b[i..], ldb) {
if !done {
substitute(Side::Right, t, rows, n, &mut b[i..], ldb);
}
i += rows;
continue;
}
let (rows, done) = if m - i >= ROWS {
(ROWS, right_rows::<T, ROWS>(&p, t.unit, n, i, b, ldb))
} else if m - i >= 4 {
(4, right_rows::<T, 4>(&p, t.unit, n, i, b, ldb))
} else {
(1, right_rows::<T, 1>(&p, t.unit, n, i, b, ldb))
};
if !done {
substitute(Side::Right, t, rows, n, &mut b[i..], ldb);
}
i += rows;
}
}
#[cfg(all(target_arch = "aarch64", target_endian = "little", not(miri)))]
fn right_neon<T: Scalar>(
p: &Packed<T>,
unit: bool,
m: usize,
n: usize,
b: &mut [T],
ldb: usize,
) -> Option<(usize, bool)> {
use core::any::TypeId;
let is_f64 = TypeId::of::<T>() == TypeId::of::<f64>();
let is_f32 = TypeId::of::<T>() == TypeId::of::<f32>();
let lanes = if is_f64 { 2 } else { 4 };
if !(is_f64 || is_f32) || m < lanes {
return None;
}
let rows = if m >= 16 { 16 } else { lanes };
assert!((1..=LEAF).contains(&n) && (n - 1) * ldb + rows <= b.len());
assert!(p.l().len() >= LEAF * n.next_power_of_two().clamp(4, LEAF));
let (l, r, f) = (p.l(), &p.r, p.forward);
let bp = b.as_mut_ptr();
let done = unsafe {
let r = r as *const [T; LEAF];
if is_f64 {
let (l, r, b) = (
&*(l as *const [T] as *const [f64]),
&*r.cast(),
bp.cast::<f64>(),
);
if rows == 16 {
aarch64::right_f64::<8>(l, r, f, unit, n, b, ldb)
} else {
aarch64::right_f64::<1>(l, r, f, unit, n, b, ldb)
}
} else {
let (l, r, b) = (
&*(l as *const [T] as *const [f32]),
&*r.cast(),
bp.cast::<f32>(),
);
if rows == 16 {
aarch64::right_f32::<4>(l, r, f, unit, n, b, ldb)
} else {
aarch64::right_f32::<1>(l, r, f, unit, n, b, ldb)
}
}
};
Some((rows, done))
}
#[inline(always)]
fn right_rows<T: Scalar, const R: usize>(
p: &Packed<T>,
unit: bool,
n: usize,
i: usize,
b: &mut [T],
ldb: usize,
) -> bool {
assert!(n <= LEAF);
let pl = p.l();
let mut y = [[T::zero(); R]; LEAF];
let solve = |j: usize, y: &mut [[T; R]; LEAF], before: core::ops::Range<usize>| {
let s = i + j * ldb;
let mut acc: [T; R] = b[s..s + R].try_into().unwrap();
for q in before {
let l = pl[j + q * LEAF];
for (acc, y) in acc.iter_mut().zip(&y[q]) {
*acc = acc.mul_sub(*y, l);
}
}
y[j] = acc;
};
if p.forward {
for j in 0..n {
solve(j, &mut y, 0..j);
}
} else {
for j in (0..n).rev() {
solve(j, &mut y, j + 1..n);
}
}
if !y[..n].iter().flatten().all(|v| v.is_finite()) {
return false;
}
for (j, y) in y.iter().enumerate().take(n) {
let s = i + j * ldb;
let col = &mut b[s..s + R];
if unit {
col.copy_from_slice(y);
} else {
for (b, y) in col.iter_mut().zip(y) {
*b = *y * p.r[j];
}
}
}
true
}
fn substitute<T: Scalar>(side: Side, t: &Tri<T>, m: usize, n: usize, b: &mut [T], ldb: usize) {
let (lower, unit) = (t.lower, t.unit);
let at = |i: usize, j: usize| t.at(i, j);
match side {
Side::Left => {
for j in 0..n {
let col = &mut b[j * ldb..j * ldb + m];
if lower {
for i in 0..m {
let mut s = col[i];
for (p, x) in col[..i].iter().enumerate() {
s -= at(i, p) * *x;
}
col[i] = if unit { s } else { s / at(i, i) };
}
} else {
for i in (0..m).rev() {
let mut s = col[i];
for (p, x) in col.iter().enumerate().skip(i + 1) {
s -= at(i, p) * *x;
}
col[i] = if unit { s } else { s / at(i, i) };
}
}
}
}
Side::Right => {
let order: Vec<usize> = if lower {
(0..n).rev().collect()
} else {
(0..n).collect()
};
for &j in &order {
for &p in &order {
if p == j {
break;
}
let x = at(p, j);
for i in 0..m {
let v = b[i + p * ldb];
b[i + j * ldb] -= v * x;
}
}
if !unit {
let d = at(j, j);
for i in 0..m {
b[i + j * ldb] /= d;
}
}
}
}
}
}