use super::FixedPoint;
use crate::fixed_point::universal::fasc::stack_evaluator::BinaryStorage;
#[cfg(table_format = "q16_16")]
use crate::fixed_point::frac_config;
#[cfg(table_format = "q64_64")]
use crate::fixed_point::I256;
#[cfg(table_format = "q128_128")]
use crate::fixed_point::{I256, I512};
#[cfg(table_format = "q256_256")]
use crate::fixed_point::{I512, I1024};
pub(crate) use crate::fixed_point::universal::fasc::stack_evaluator::ComputeStorage;
pub(crate) use crate::fixed_point::universal::fasc::stack_evaluator::compute::downscale_to_storage;
pub(crate) use crate::fixed_point::universal::fasc::stack_evaluator::compute::sincos_at_compute_tier;
#[inline]
pub(crate) fn round_to_storage(acc: ComputeStorage) -> BinaryStorage {
downscale_to_storage(acc)
.expect("round_to_storage: result exceeds storage tier range")
}
#[inline]
pub(crate) fn upscale_to_compute(val: BinaryStorage) -> ComputeStorage {
#[cfg(table_format = "q64_64")]
{ I256::from_i128(val) << 64usize }
#[cfg(table_format = "q32_32")]
{ (val as i128) << 32 }
#[cfg(table_format = "q16_16")]
{ (val as i64) << frac_config::FRAC_BITS }
#[cfg(table_format = "q128_128")]
{ I512::from_i256(val) << 128usize }
#[cfg(table_format = "q256_256")]
{ I1024::from_i512(val) << 256usize }
}
pub fn compute_tier_dot(a: &[FixedPoint], b: &[FixedPoint]) -> FixedPoint {
assert_eq!(a.len(), b.len(), "compute_tier_dot: length mismatch");
#[cfg(all(table_format = "q16_16", target_arch = "x86_64"))]
{
if a.len() >= BOUNDED_DOT_MIN_LEN {
return FixedPoint::from_raw(round_to_storage(long_dot_acc(FixedPoint::raw_slice(a), FixedPoint::raw_slice(b))));
}
}
FixedPoint::from_raw(round_to_storage(compute_tier_dot_acc_pairs(
a.iter().zip(b).map(|(x, y)| (x.raw(), y.raw())),
)))
}
#[inline]
pub(crate) fn compute_tier_dot_raw(a: &[BinaryStorage], b: &[BinaryStorage]) -> BinaryStorage {
round_to_storage(compute_tier_dot_acc(a, b))
}
pub(crate) fn compute_tier_sqrt_dot(a: &[BinaryStorage], b: &[BinaryStorage]) -> Result<BinaryStorage, OverflowDetected> {
use crate::fixed_point::universal::fasc::stack_evaluator::compute::{compute_is_negative, sqrt_at_compute_tier};
let acc = compute_tier_dot_acc(a, b);
if compute_is_negative(&acc) { return Err(OverflowDetected::DomainError); }
downscale_to_storage(sqrt_at_compute_tier(acc))
}
#[inline]
pub(crate) fn compute_tier_dot_acc(a: &[BinaryStorage], b: &[BinaryStorage]) -> ComputeStorage {
assert_eq!(a.len(), b.len(), "compute_tier_dot_raw: length mismatch");
#[cfg(all(table_format = "q16_16", target_arch = "x86_64"))]
{
if a.len() >= BOUNDED_DOT_MIN_LEN {
return long_dot_acc(a, b);
}
}
compute_tier_dot_acc_pairs(a.iter().copied().zip(b.iter().copied()))
}
#[cfg(all(table_format = "q16_16", target_arch = "x86_64"))]
const BOUNDED_DOT_MIN_LEN: usize = 32;
#[cfg(all(table_format = "q16_16", target_arch = "x86_64"))]
#[inline(never)]
fn long_dot_acc(a: &[i32], b: &[i32]) -> i64 {
if std::is_x86_feature_detected!("avx2") {
if let Some(acc) = unsafe { bounded_dot_avx2(a, b) } {
return acc;
}
}
compute_tier_dot_acc_pairs(a.iter().copied().zip(b.iter().copied()))
}
#[cfg(all(table_format = "q16_16", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
pub(crate) unsafe fn max_abs_avx2(x: &[i32]) -> u32 {
let mut m = 0u32;
for &v in x {
m = m.max(v.unsigned_abs());
}
m
}
#[cfg(all(table_format = "q16_16", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn bounded_dot_avx2(a: &[i32], b: &[i32]) -> Option<i64> {
let bound = (a.len() as u128) * (max_abs_avx2(a) as u128) * (max_abs_avx2(b) as u128);
if bound >= 1u128 << 63 {
return None;
}
Some(unchecked_dot_avx2(a, b))
}
#[cfg(all(table_format = "q16_16", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
pub(crate) unsafe fn unchecked_dot_avx2(a: &[i32], b: &[i32]) -> i64 {
let mut acc = 0i64;
for (&x, &y) in a.iter().zip(b) {
acc = acc.wrapping_add((x as i64) * (y as i64));
}
acc
}
#[inline]
fn compute_tier_dot_acc_pairs(pairs: impl Iterator<Item = (BinaryStorage, BinaryStorage)>) -> ComputeStorage {
const OVERFLOW: &str = "compute_tier_dot: sum exceeds the compute tier";
#[cfg(table_format = "q64_64")]
{
let mut acc = I256::zero();
for (x, y) in pairs {
acc = acc.checked_add(I256::from_i128(x) * I256::from_i128(y)).expect(OVERFLOW);
}
acc
}
#[cfg(table_format = "q32_32")]
{
let mut acc: i128 = 0;
for (x, y) in pairs {
acc = acc.checked_add((x as i128) * (y as i128)).expect(OVERFLOW);
}
acc
}
#[cfg(table_format = "q16_16")]
{
let mut acc: i64 = 0;
for (x, y) in pairs {
acc = acc.checked_add((x as i64) * (y as i64)).expect(OVERFLOW);
}
acc
}
#[cfg(table_format = "q128_128")]
{
let mut acc = I512::zero();
for (x, y) in pairs {
let a_neg = x.is_negative();
let b_neg = y.is_negative();
let result_neg = a_neg != b_neg;
let abs_a = if a_neg { -x } else { x };
let abs_b = if b_neg { -y } else { y };
let product = abs_a.mul_to_i512(abs_b);
acc = acc.checked_add(if result_neg { -product } else { product }).expect(OVERFLOW);
}
acc
}
#[cfg(table_format = "q256_256")]
{
let mut acc = I1024::zero();
for (x, y) in pairs {
let a_neg = x.is_negative();
let b_neg = y.is_negative();
let result_neg = a_neg != b_neg;
let abs_a = if a_neg { -x } else { x };
let abs_b = if b_neg { -y } else { y };
let product = abs_a.mul_to_i1024(abs_b);
acc = acc.checked_add(if result_neg { -product } else { product }).expect(OVERFLOW);
}
acc
}
}
pub(crate) fn compute_tier_sub_dot_compute(
init: BinaryStorage,
a: &[BinaryStorage],
b: &[BinaryStorage],
) -> ComputeStorage {
const OVERFLOW: &str = "compute_tier_sub_dot: sum exceeds the compute tier";
assert_eq!(a.len(), b.len(), "compute_tier_sub_dot_compute: length mismatch");
#[cfg(table_format = "q64_64")]
{
let mut acc = I256::from_i128(init) << 64usize;
for i in 0..a.len() {
acc = acc.checked_sub(I256::from_i128(a[i]) * I256::from_i128(b[i])).expect(OVERFLOW);
}
acc
}
#[cfg(table_format = "q32_32")]
{
let mut acc: i128 = (init as i128) << 32;
for i in 0..a.len() {
acc = acc.checked_sub((a[i] as i128) * (b[i] as i128)).expect(OVERFLOW);
}
acc
}
#[cfg(table_format = "q16_16")]
{
let mut acc: i64 = (init as i64) << frac_config::FRAC_BITS;
for i in 0..a.len() {
acc = acc.checked_sub((a[i] as i64) * (b[i] as i64)).expect(OVERFLOW);
}
acc
}
#[cfg(table_format = "q128_128")]
{
let mut acc = I512::from_i256(init) << 128usize;
for i in 0..a.len() {
let a_neg = a[i].is_negative();
let b_neg = b[i].is_negative();
let result_neg = a_neg != b_neg;
let abs_a = if a_neg { -a[i] } else { a[i] };
let abs_b = if b_neg { -b[i] } else { b[i] };
let product = abs_a.mul_to_i512(abs_b);
acc = acc.checked_add(if result_neg { product } else { -product }).expect(OVERFLOW);
}
acc
}
#[cfg(table_format = "q256_256")]
{
let mut acc = I1024::from_i512(init) << 256usize;
for i in 0..a.len() {
let a_neg = a[i].is_negative();
let b_neg = b[i].is_negative();
let result_neg = a_neg != b_neg;
let abs_a = if a_neg { -a[i] } else { a[i] };
let abs_b = if b_neg { -b[i] } else { b[i] };
let product = abs_a.mul_to_i1024(abs_b);
acc = acc.checked_add(if result_neg { product } else { -product }).expect(OVERFLOW);
}
acc
}
}
use super::interval::exact_product;
use super::wide_acc::{divide_to_compute_nearest, exact_dot_compute, narrow_product_to_compute, widen_product, Wide};
use crate::fixed_point::core_types::errors::OverflowDetected;
use crate::fixed_point::universal::fasc::stack_evaluator::compute::{
compute_checked_add, compute_divide, compute_is_negative,
compute_is_zero, compute_multiply, compute_negate, make_compute_int, sqrt_at_compute_tier,
};
pub(crate) const STAGNATION_SWEEPS: usize = 5;
#[inline]
pub(crate) fn storage_frac_bits() -> u32 {
#[cfg(table_format = "q16_16")]
{ frac_config::FRAC_BITS }
#[cfg(table_format = "q32_32")]
{ 32 }
#[cfg(table_format = "q64_64")]
{ 64 }
#[cfg(table_format = "q128_128")]
{ 128 }
#[cfg(table_format = "q256_256")]
{ 256 }
}
#[inline]
pub(crate) fn compute_abs_shr(v: ComputeStorage, shift: u32) -> ComputeStorage {
let v = compute_abs(v);
#[cfg(any(table_format = "q16_16", table_format = "q32_32", table_format = "q64_64"))]
{ v >> shift }
#[cfg(any(table_format = "q128_128", table_format = "q256_256"))]
{ v >> shift as usize }
}
#[inline]
fn compute_quanta(shift: u32) -> ComputeStorage {
#[cfg(table_format = "q16_16")]
{ 1i64 << shift }
#[cfg(table_format = "q32_32")]
{ 1i128 << shift }
#[cfg(table_format = "q64_64")]
{ I256::from_i128(1) << shift as usize }
#[cfg(table_format = "q128_128")]
{ I512::from_i128(1) << shift as usize }
#[cfg(table_format = "q256_256")]
{ I1024::from_i128(1) << shift as usize }
}
#[inline]
pub(crate) fn compute_deflation_threshold(magnitude: ComputeStorage) -> ComputeStorage {
let shift = storage_frac_bits() * 3 / 2;
compute_abs_shr(magnitude, shift).max(compute_quanta(storage_frac_bits() / 2))
}
#[inline]
pub(crate) fn compute_noise_floor() -> ComputeStorage {
compute_quanta(storage_frac_bits() / 2)
}
#[inline]
pub(crate) fn compute_stagnation_threshold(magnitude: ComputeStorage) -> ComputeStorage {
compute_abs_shr(magnitude, storage_frac_bits()).max(compute_quanta(storage_frac_bits() / 2))
}
#[inline]
pub(crate) fn compute_abs(v: ComputeStorage) -> ComputeStorage {
if compute_is_negative(&v) { compute_negate(v) } else { v }
}
#[derive(Clone, Copy)]
pub(crate) struct Rotation {
cs: ComputeStorage,
sn: ComputeStorage,
}
impl Rotation {
#[inline]
pub(crate) fn from_parts(cs: ComputeStorage, sn: ComputeStorage) -> Self {
Rotation { cs, sn }
}
pub(crate) fn zeroing_compute(a: ComputeStorage, b: ComputeStorage) -> Result<Self, OverflowDetected> {
let one = make_compute_int(1);
let zero = make_compute_int(0);
if compute_is_zero(&b) {
return Ok(Rotation { cs: one, sn: zero });
}
if compute_is_zero(&a) {
let sn = if compute_is_negative(&b) { compute_negate(one) } else { one };
return Ok(Rotation { cs: zero, sn });
}
if compute_abs(b) > compute_abs(a) {
let tau = compute_divide(a, b)?;
let inv = compute_divide(one, sqrt_at_compute_tier(compute_add(one, compute_multiply(tau, tau))))?;
let sn = if compute_is_negative(&b) { compute_negate(inv) } else { inv };
Ok(Rotation { cs: compute_multiply(sn, tau), sn })
} else {
let tau = compute_divide(b, a)?;
let inv = compute_divide(one, sqrt_at_compute_tier(compute_add(one, compute_multiply(tau, tau))))?;
let cs = if compute_is_negative(&a) { compute_negate(inv) } else { inv };
Ok(Rotation { cs, sn: compute_multiply(cs, tau) })
}
}
pub(crate) fn apply_compute(
&self, x: ComputeStorage, y: ComputeStorage,
) -> Result<(ComputeStorage, ComputeStorage), OverflowDetected> {
let first = widen_product(self.cs, x).add_exact(widen_product(self.sn, y))?;
let second = widen_product(compute_negate(self.sn), x).add_exact(widen_product(self.cs, y))?;
Ok((narrow_product_to_compute(first)?, narrow_product_to_compute(second)?))
}
pub(crate) fn combine_compute(&self, x: ComputeStorage, y: ComputeStorage) -> Result<ComputeStorage, OverflowDetected> {
narrow_product_to_compute(widen_product(self.cs, x).add_exact(widen_product(self.sn, y))?)
}
#[inline]
pub(crate) fn cos_times_compute(&self, x: ComputeStorage) -> Result<ComputeStorage, OverflowDetected> {
compute_product(self.cs, x)
}
#[inline]
pub(crate) fn sin_times_compute(&self, x: ComputeStorage) -> Result<ComputeStorage, OverflowDetected> {
compute_product(self.sn, x)
}
#[inline]
pub(crate) fn neg_sin_times_compute(&self, x: ComputeStorage) -> Result<ComputeStorage, OverflowDetected> {
compute_product(compute_negate(self.sn), x)
}
}
#[inline]
pub(crate) fn compute_product(a: ComputeStorage, b: ComputeStorage) -> Result<ComputeStorage, OverflowDetected> {
narrow_product_to_compute(widen_product(a, b))
}
pub(crate) fn exact_dot(a: &[BinaryStorage], b: &[BinaryStorage]) -> Result<ComputeStorage, OverflowDetected> {
assert_eq!(a.len(), b.len(), "exact_dot: length mismatch");
let mut acc = make_compute_int(0);
for i in 0..a.len() {
acc = compute_checked_add(acc, exact_product(a[i], b[i]))?;
}
Ok(acc)
}
pub(crate) fn downscale_shifted_to_storage(v: ComputeStorage, shift: u32) -> Result<BinaryStorage, OverflowDetected> {
if shift == 0 {
return downscale_to_storage(v);
}
let f = storage_frac_bits();
let half = compute_quanta(f + shift - 1);
let q = compute_shr(compute_checked_add(v, half)?, f + shift);
downscale_to_storage(compute_shl(q, f))
}
#[inline]
pub(crate) fn compute_scale_up(v: ComputeStorage, shift: u32) -> ComputeStorage {
compute_shl(v, shift)
}
pub(crate) fn scale_up_exponent(values: &[ComputeStorage]) -> u32 {
let bits = values.iter().map(|&v| compute_bit_length(v)).max().unwrap_or(0);
let unit_bits = 2 * storage_frac_bits();
if bits == 0 || bits >= unit_bits { 0 } else { unit_bits - bits }
}
#[inline]
pub(crate) fn compute_bit_length(v: ComputeStorage) -> u32 {
#[cfg(table_format = "q16_16")]
{ 64 - v.unsigned_abs().leading_zeros() }
#[cfg(table_format = "q32_32")]
{ 128 - v.unsigned_abs().leading_zeros() }
#[cfg(any(table_format = "q64_64", table_format = "q128_128", table_format = "q256_256"))]
{
let words = compute_abs(v).words;
(0..words.len()).rev().find(|&i| words[i] != 0).map_or(0, |i| i as u32 * 64 + (64 - words[i].leading_zeros()))
}
}
#[inline]
pub(crate) fn compute_shl(v: ComputeStorage, shift: u32) -> ComputeStorage {
#[cfg(any(table_format = "q16_16", table_format = "q32_32"))]
{ v << shift }
#[cfg(any(table_format = "q64_64", table_format = "q128_128", table_format = "q256_256"))]
{ v << shift as usize }
}
#[inline]
pub(crate) fn compute_shr(v: ComputeStorage, shift: u32) -> ComputeStorage {
#[cfg(any(table_format = "q16_16", table_format = "q32_32", table_format = "q64_64"))]
{ v >> shift }
#[cfg(any(table_format = "q128_128", table_format = "q256_256"))]
{ v >> shift as usize }
}
pub(crate) fn householder_vector_compute(
x: &[ComputeStorage],
) -> Result<Option<(Vec<ComputeStorage>, ComputeStorage)>, OverflowDetected> {
let bits = x.iter().map(|&xi| compute_bit_length(xi)).max().unwrap_or(0);
if bits == 0 {
return Ok(None);
}
let unit_bits = 2 * storage_frac_bits();
let scaled: Vec<ComputeStorage>;
let x = if bits < unit_bits {
scaled = x.iter().map(|&xi| compute_shl(xi, unit_bits - bits)).collect();
&scaled[..]
} else if bits > unit_bits {
scaled = x.iter().map(|&xi| compute_shr(xi, bits - unit_bits)).collect();
&scaled[..]
} else {
x
};
let xx = narrow_product_to_compute(exact_dot_compute(x, x)?)?;
if compute_is_zero(&xx) {
return Ok(None);
}
let norm = sqrt_at_compute_tier(xx);
let alpha = if compute_is_negative(&x[0]) { norm } else { compute_negate(norm) };
let mut v = x.to_vec();
v[0] = compute_checked_add(x[0], compute_negate(alpha))?;
let vv = narrow_product_to_compute(exact_dot_compute(&v, &v)?)?;
if compute_is_zero(&vv) {
return Ok(None);
}
Ok(Some((v, vv)))
}
pub(crate) fn reflect_compute(
w: &mut [ComputeStorage], v: &[ComputeStorage], v_dot_v: ComputeStorage,
) -> Result<(), OverflowDetected> {
let vw = narrow_product_to_compute(exact_dot_compute(v, w)?)?;
if compute_is_zero(&vw) {
return Ok(());
}
let two_vw = compute_checked_add(vw, vw)?;
for (wk, vk) in w.iter_mut().zip(v) {
let update = divide_to_compute_nearest(widen_product(two_vw, *vk), v_dot_v)?;
*wk = compute_checked_add(*wk, compute_negate(update))?;
}
Ok(())
}
pub(crate) fn convergence_threshold(magnitude: FixedPoint) -> FixedPoint {
let quantum = FixedPoint::from_raw(quantum_raw());
let shifted = magnitude.abs().raw() >> half_frac_bits();
let result = FixedPoint::from_raw(shifted);
if result.is_zero() { quantum } else { result }
}
#[cfg(table_format = "q64_64")]
fn half_frac_bits() -> u32 { 32 }
#[cfg(table_format = "q32_32")]
fn half_frac_bits() -> u32 { 16 }
#[cfg(table_format = "q16_16")]
fn half_frac_bits() -> u32 { 8 }
#[cfg(table_format = "q128_128")]
fn half_frac_bits() -> u32 { 64 }
#[cfg(table_format = "q256_256")]
fn half_frac_bits() -> usize { 128 }
#[cfg(table_format = "q64_64")]
fn quantum_raw() -> BinaryStorage { 1i128 }
#[cfg(table_format = "q32_32")]
fn quantum_raw() -> BinaryStorage { 1i64 }
#[cfg(table_format = "q16_16")]
fn quantum_raw() -> BinaryStorage { 1i32 }
#[cfg(table_format = "q128_128")]
fn quantum_raw() -> BinaryStorage { I256::from_i128(1) }
#[cfg(table_format = "q256_256")]
fn quantum_raw() -> BinaryStorage { I512::from_i128(1) }
#[allow(unused_imports)]
use crate::fixed_point::domains::balanced_ternary::trit_packing::Trit;
#[allow(dead_code)]
pub fn compute_tier_trit_dot_raw(
packed_trits: &[u8],
num_elements: usize,
values: &[BinaryStorage],
scale: BinaryStorage,
) -> BinaryStorage {
assert!(values.len() >= num_elements, "compute_tier_trit_dot_raw: values shorter than num_elements");
let mut acc = compute_zero();
let mut trit_idx = 0;
for &byte in packed_trits {
if trit_idx >= num_elements {
break;
}
let mut remaining = byte;
let mut chunk_trits = [1u8; 5]; for j in (0..5).rev() {
chunk_trits[j] = remaining % 3;
remaining /= 3;
}
for j in 0..5 {
if trit_idx >= num_elements {
break;
}
let trit = chunk_trits[j];
if trit == 2 {
let widened = upscale_to_compute(values[trit_idx]);
acc = compute_add(acc, widened);
} else if trit == 0 {
let widened = upscale_to_compute(values[trit_idx]);
acc = compute_sub(acc, widened);
}
trit_idx += 1;
}
}
let dot_storage = round_to_storage(acc);
compute_tier_mul_pair(dot_storage, scale)
}
#[allow(dead_code)]
pub fn compute_tier_trit_matvec_raw(
packed_trits: &[u8],
rows: usize,
cols: usize,
values: &[BinaryStorage],
scales: &[BinaryStorage],
) -> Vec<BinaryStorage> {
assert!(values.len() >= cols, "compute_tier_trit_matvec_raw: values shorter than cols");
assert!(scales.len() >= rows, "compute_tier_trit_matvec_raw: scales shorter than rows");
let bytes_per_row = (cols + 4) / 5;
let mut result = Vec::with_capacity(rows);
for row in 0..rows {
let row_start = row * bytes_per_row;
let row_end = row_start + bytes_per_row;
let row_trits = &packed_trits[row_start..row_end];
let dot = compute_tier_trit_dot_raw(row_trits, cols, values, scales[row]);
result.push(dot);
}
result
}
#[allow(dead_code)]
#[inline]
fn compute_zero() -> ComputeStorage {
#[cfg(table_format = "q64_64")]
{ I256::zero() }
#[cfg(table_format = "q32_32")]
{ 0i128 }
#[cfg(table_format = "q16_16")]
{ 0i64 }
#[cfg(table_format = "q128_128")]
{ I512::zero() }
#[cfg(table_format = "q256_256")]
{ I1024::zero() }
}
#[allow(dead_code)]
#[inline]
fn compute_add(a: ComputeStorage, b: ComputeStorage) -> ComputeStorage {
a + b
}
#[allow(dead_code)]
#[inline]
fn compute_sub(a: ComputeStorage, b: ComputeStorage) -> ComputeStorage {
a - b
}
#[allow(dead_code)]
#[inline]
fn compute_tier_mul_pair(a: BinaryStorage, b: BinaryStorage) -> BinaryStorage {
#[cfg(table_format = "q64_64")]
{
let a_wide = I256::from_i128(a);
let b_wide = I256::from_i128(b);
let product = a_wide * b_wide;
round_to_storage(product)
}
#[cfg(table_format = "q32_32")]
{
let product = (a as i128) * (b as i128);
round_to_storage(product)
}
#[cfg(table_format = "q16_16")]
{
let product = (a as i64) * (b as i64);
round_to_storage(product)
}
#[cfg(table_format = "q128_128")]
{
let a_neg = a.is_negative();
let b_neg = b.is_negative();
let result_neg = a_neg != b_neg;
let abs_a = if a_neg { -a } else { a };
let abs_b = if b_neg { -b } else { b };
let product = abs_a.mul_to_i512(abs_b);
let product = if result_neg { -product } else { product };
round_to_storage(product)
}
#[cfg(table_format = "q256_256")]
{
let a_neg = a.is_negative();
let b_neg = b.is_negative();
let result_neg = a_neg != b_neg;
let abs_a = if a_neg { -a } else { a };
let abs_b = if b_neg { -b } else { b };
let product = abs_a.mul_to_i1024(abs_b);
let product = if result_neg { -product } else { product };
round_to_storage(product)
}
}