#![cfg_attr(feature = "bench", feature(test))]
use ndarray::Array2;
use num_traits::{One, Zero};
use sqnc::traits::*;
use std::cmp::Ordering;
use std::fmt;
use std::iter::{self, FusedIterator};
use std::marker::PhantomData;
use std::ops;
pub type Power = u8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueMoreOrLess {
Value(usize),
More,
Less,
}
use ValueMoreOrLess::{Less, More};
impl From<usize> for ValueMoreOrLess {
#[inline]
fn from(value: usize) -> Self {
Self::Value(value)
}
}
impl fmt::Display for ValueMoreOrLess {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Value(value) => value.fmt(f),
Self::More => write!(f, "more"),
Self::Less => write!(f, "less"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
IncorrectNumberOfCoefficients {
expected: usize,
got: ValueMoreOrLess,
detail: Option<&'static str>,
},
TooManyVariables,
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::IncorrectNumberOfCoefficients {
expected,
got,
detail,
} => {
if let Some(detail) = detail {
write!(f, "{detail}: ")?;
}
if *expected == 1 {
write!(f, "Expected 1 coefficient but got {got}.")
} else {
write!(f, "Expected {expected} coefficients but got {got}.")
}
}
Self::TooManyVariables => {
write!(
f,
"The number of variables exceeds the maximum (`usize::MAX`)."
)
}
}
}
}
#[inline]
fn check_ncoeffs_sqnc<S: Sequence>(
coeffs: &S,
expected: usize,
detail: Option<&'static str>,
) -> Result<(), Error> {
let got = coeffs.len();
if got == expected {
Ok(())
} else {
Err(Error::IncorrectNumberOfCoefficients {
expected,
got: got.into(),
detail,
})
}
}
#[inline]
pub const fn ncoeffs(nvars: usize, degree: Power) -> usize {
match nvars {
0 => ncoeffs_impl(0, degree),
1 => ncoeffs_impl(1, degree),
2 => ncoeffs_impl(2, degree),
3 => ncoeffs_impl(3, degree),
_ => ncoeffs_impl(nvars, degree),
}
}
#[inline]
const fn ncoeffs_impl(nvars: usize, degree: Power) -> usize {
let mut n = 1;
let mut i = 0;
while i < nvars {
i += 1;
n = n * (degree as usize + i) / i;
}
n
}
#[inline]
pub fn ncoeffs_iter(nvars: usize) -> impl Iterator<Item = usize> {
degree_ncoeffs_iter(nvars).map(|(_, ncoeffs)| ncoeffs)
}
#[inline]
pub fn degree_ncoeffs_iter(nvars: usize) -> impl Iterator<Item = (Power, usize)> {
(0u8..).scan(1, move |ncoeffs, degree| {
let current = (degree, *ncoeffs);
*ncoeffs = *ncoeffs * (degree as usize + 1 + nvars) / (degree as usize + 1);
Some(current)
})
}
#[inline]
const fn ncoeffs_sum(nvars: usize, degree: Power) -> usize {
match nvars {
0 => ncoeffs_sum_impl(0, degree),
1 => ncoeffs_sum_impl(1, degree),
2 => ncoeffs_sum_impl(2, degree),
3 => ncoeffs_sum_impl(3, degree),
_ => ncoeffs_sum_impl(nvars, degree),
}
}
#[inline]
const fn ncoeffs_sum_impl(nvars: usize, degree: Power) -> usize {
let mut n = 1;
let mut i = 0;
while i <= nvars {
n = n * (degree as usize + i) / (i + 1);
i += 1;
}
n
}
pub fn degree(nvars: usize, ncoeffs: usize) -> Option<Power> {
match nvars {
0 => (ncoeffs == 1).then_some(0),
1 => ncoeffs
.checked_sub(1)
.and_then(|degree| degree.try_into().ok()),
_ => {
for (tdegree, tncoeffs) in degree_ncoeffs_iter(nvars) {
match tncoeffs.cmp(&ncoeffs) {
Ordering::Equal => {
return Some(tdegree);
}
Ordering::Greater => {
return None;
}
_ => {}
}
}
unreachable! {}
}
}
}
fn index_to_powers_rev_iter(
mut index: usize,
nvars: usize,
mut degree: Power,
) -> Option<impl Iterator<Item = Power> + ExactSizeIterator> {
(index < ncoeffs(nvars, degree)).then(|| {
Iterator::rev(0..nvars).map(move |var| {
if var == 0 {
degree - index as Power
} else {
for (i, n) in degree_ncoeffs_iter(var) {
if index < n {
let power = degree - i;
degree = i;
return power;
}
index -= n;
}
unreachable! {}
}
})
})
}
fn powers_rev_iter_to_index<PowersRevIter>(
rev_powers: PowersRevIter,
nvars: usize,
mut degree: Power,
) -> Option<usize>
where
PowersRevIter: Iterator<Item = Power>,
{
let mut index = 0;
for (var, power) in iter::zip(Iterator::rev(0..nvars), rev_powers) {
degree = degree.checked_sub(power)?;
index += ncoeffs_sum(var, degree);
}
Some(index)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MapDegree {
nvars: usize,
from_degree: Power,
to_degree: Power,
}
impl MapDegree {
pub fn new(nvars: usize, from_degree: Power, to_degree: Power) -> Option<Self> {
(to_degree >= from_degree).then_some(Self {
nvars,
from_degree,
to_degree,
})
}
}
impl<'this> SequenceTypes<'this> for MapDegree {
type Item = usize;
type Iter = sqnc::derive::Iter<'this, Self>;
}
impl Sequence for MapDegree {
#[inline]
fn len(&self) -> usize {
ncoeffs(self.nvars, self.from_degree)
}
#[inline]
fn get(&self, index: usize) -> Option<usize> {
let powers = index_to_powers_rev_iter(index, self.nvars, self.from_degree)?;
powers_rev_iter_to_index(powers, self.nvars, self.to_degree)
}
#[inline]
fn iter(&self) -> sqnc::derive::Iter<'_, Self> {
self.into()
}
}
impl IntoIterator for MapDegree {
type Item = usize;
type IntoIter = sqnc::derive::IntoIter<Self>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.into()
}
}
unsafe impl UniqueSequence for MapDegree {}
pub trait EvalOps<Coeff, Value: ?Sized> {
type Output;
fn init_acc_zero() -> Self::Output;
fn init_acc_coeff(coeff: Coeff) -> Self::Output;
fn update_acc_coeff(acc: &mut Self::Output, coeff: Coeff, value: &Value);
fn update_acc_inner(acc: &mut Self::Output, inner: Self::Output, value: &Value);
#[inline]
fn eval<Coeffs, Values>(
coeffs: &mut Coeffs,
values: &Values,
degree: Power,
) -> Result<Self::Output, Error>
where
Coeffs: Iterator<Item = Coeff>,
Values: SequenceRef<OwnedItem = Value> + ?Sized,
{
<EvalImpl<Self, Coeffs, Values>>::eval_nv(coeffs, values, degree, values.len()).ok_or_else(
|| Error::IncorrectNumberOfCoefficients {
expected: ncoeffs(values.len(), degree),
got: Less,
detail: None,
},
)
}
}
struct EvalImpl<Ops: ?Sized, Coeffs, Values: ?Sized>(
PhantomData<Ops>,
PhantomData<Coeffs>,
PhantomData<Values>,
);
impl<Ops, Coeffs, Values> EvalImpl<Ops, Coeffs, Values>
where
Ops: EvalOps<Coeffs::Item, Values::OwnedItem> + ?Sized,
Coeffs: Iterator,
Values: SequenceRef + ?Sized,
{
#[inline]
fn eval_0v(coeffs: &mut Coeffs) -> Option<Ops::Output> {
coeffs.next().map(Ops::init_acc_coeff)
}
#[inline]
fn eval_1v(coeffs: &mut Coeffs, values: &Values, degree: Power) -> Option<Ops::Output> {
let mut acc = Ops::init_acc_zero();
if let Some(value) = values.get(0) {
if coeffs
.take(degree as usize + 1)
.map(|coeff| Ops::update_acc_coeff(&mut acc, coeff, value))
.count()
!= degree as usize + 1
{
return None;
}
}
Some(acc)
}
#[inline]
fn eval_2v(coeffs: &mut Coeffs, values: &Values, degree: Power) -> Option<Ops::Output> {
let mut acc = Ops::init_acc_zero();
if let Some(value) = values.get(1) {
for p in 0..=degree {
let inner = Self::eval_1v(coeffs, values, p)?;
Ops::update_acc_inner(&mut acc, inner, value);
}
}
Some(acc)
}
#[inline]
fn eval_3v(coeffs: &mut Coeffs, values: &Values, degree: Power) -> Option<Ops::Output> {
let mut acc = Ops::init_acc_zero();
if let Some(value) = values.get(2) {
for p in 0..=degree {
let inner = Self::eval_2v(coeffs, values, p)?;
Ops::update_acc_inner(&mut acc, inner, value);
}
}
Some(acc)
}
#[inline]
fn eval_nv(
coeffs: &mut Coeffs,
values: &Values,
degree: Power,
nvars: usize,
) -> Option<Ops::Output> {
match nvars {
0 => Self::eval_0v(coeffs),
1 => Self::eval_1v(coeffs, values, degree),
2 => Self::eval_2v(coeffs, values, degree),
3 => Self::eval_3v(coeffs, values, degree),
_ => {
let mut acc = Ops::init_acc_zero();
if let Some(value) = values.get(nvars - 1) {
for p in 0..=degree {
let inner = Self::eval_nv(coeffs, values, p, nvars - 1)?;
Ops::update_acc_inner(&mut acc, inner, value);
}
}
Some(acc)
}
}
}
}
enum DefaultEvalOps {}
impl<Coeff, Value> EvalOps<Coeff, Value> for DefaultEvalOps
where
Value: Zero + ops::AddAssign + ops::AddAssign<Coeff> + for<'v> ops::MulAssign<&'v Value>,
{
type Output = Value;
#[inline]
fn init_acc_coeff(coeff: Coeff) -> Value {
let mut acc = Value::zero();
acc += coeff;
acc
}
#[inline]
fn init_acc_zero() -> Value {
Value::zero()
}
#[inline]
fn update_acc_coeff(acc: &mut Value, coeff: Coeff, value: &Value) {
*acc *= value;
*acc += coeff;
}
#[inline]
fn update_acc_inner(acc: &mut Value, inner: Value, value: &Value) {
*acc *= value;
*acc += inner;
}
}
#[inline]
pub fn eval<Coeff, Value, Coeffs, Values>(
coeffs: Coeffs,
values: &Values,
degree: Power,
) -> Result<Value, Error>
where
Value: Zero + ops::AddAssign + ops::AddAssign<Coeff> + for<'v> ops::MulAssign<&'v Value>,
Coeffs: IntoIterator<Item = Coeff>,
Values: SequenceRef<OwnedItem = Value> + ?Sized,
{
DefaultEvalOps::eval(&mut coeffs.into_iter(), values, degree)
}
pub trait Multiple {
type Output;
fn multiple(self, n: usize) -> Self::Output;
}
macro_rules! impl_int_mul {
($T:ty $(,)?) => {
impl Multiple for $T {
type Output = $T;
#[inline]
fn multiple(self, n: usize) -> Self::Output {
self * n as $T
}
}
impl Multiple for &$T {
type Output = $T;
#[inline]
fn multiple(self, n: usize) -> Self::Output {
self * n as $T
}
}
};
($T:ty, $($tail:tt)*) => {
impl_int_mul!{$T}
impl_int_mul!{$($tail)*}
};
}
impl_int_mul! {u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64}
#[derive(Debug, Clone)]
pub struct PartialDerivPlan {
indices_powers: Box<[(usize, usize)]>,
ncoeffs_input: usize,
degree_output: Power,
nvars: usize,
}
impl PartialDerivPlan {
pub fn new(nvars: usize, degree: Power, var: usize) -> Option<Self> {
let n = nvars.checked_sub(var + 1)?;
let ncoeffs_input = ncoeffs(nvars, degree);
let indices_powers = if degree == 0 {
[(0, 0)].into()
} else {
(0..ncoeffs_input)
.filter_map(move |index| {
let power = index_to_powers_rev_iter(index, nvars, degree)?.nth(n)?;
(power > 0).then_some((index, power as usize))
})
.collect()
};
Some(Self {
indices_powers,
ncoeffs_input,
degree_output: degree.saturating_sub(1),
nvars,
})
}
#[inline]
pub fn apply<Coeffs>(&self, coeffs: Coeffs) -> Result<PartialDeriv<'_, Coeffs>, Error>
where
Coeffs: Sequence,
{
check_ncoeffs_sqnc(&coeffs, self.ncoeffs_input, None)?;
Ok(PartialDeriv { plan: self, coeffs })
}
#[inline]
pub fn ncoeffs_input(&self) -> usize {
self.ncoeffs_input
}
#[inline]
pub fn ncoeffs_output(&self) -> usize {
self.indices_powers.len()
}
#[inline]
pub fn degree_output(&self) -> Power {
self.degree_output
}
#[inline]
pub fn nvars(&self) -> usize {
self.nvars
}
}
#[derive(Debug, Clone)]
pub struct PartialDeriv<'plan, Coeffs> {
plan: &'plan PartialDerivPlan,
coeffs: Coeffs,
}
impl<'this, 'plan, Coeffs, OCoeff> SequenceTypes<'this> for PartialDeriv<'plan, Coeffs>
where
for<'a> <Coeffs as SequenceTypes<'a>>::Item: Multiple<Output = OCoeff>,
Coeffs: Sequence,
{
type Item = OCoeff;
type Iter = PartialDerivIter<'plan, sqnc::Wrapper<&'this Coeffs, ((),)>>;
}
impl<'plan, Coeffs, OCoeff> Sequence for PartialDeriv<'plan, Coeffs>
where
for<'a> <Coeffs as SequenceTypes<'a>>::Item: Multiple<Output = OCoeff>,
Coeffs: Sequence,
{
#[inline]
fn len(&self) -> usize {
self.plan.indices_powers.len()
}
#[inline]
fn get(&self, index: usize) -> Option<OCoeff> {
let (index, power) = self.plan.indices_powers.get(index)?;
Some(self.coeffs.get(*index)?.multiple(*power))
}
#[inline]
fn iter(&self) -> PartialDerivIter<'plan, sqnc::Wrapper<&'_ Coeffs, ((),)>> {
PartialDerivIter {
indices_powers: self.plan.indices_powers.iter(),
coeffs: self.coeffs.as_sqnc(),
}
}
}
impl<'plan, Coeffs, OCoeff> IntoIterator for PartialDeriv<'plan, Coeffs>
where
Coeffs: Sequence,
for<'a> <Coeffs as SequenceTypes<'a>>::Item: Multiple<Output = OCoeff>,
{
type Item = OCoeff;
type IntoIter = PartialDerivIter<'plan, Coeffs>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
PartialDerivIter {
indices_powers: self.plan.indices_powers.iter(),
coeffs: self.coeffs,
}
}
}
#[derive(Debug, Clone)]
pub struct PartialDerivIter<'plan, Coeffs> {
indices_powers: std::slice::Iter<'plan, (usize, usize)>,
coeffs: Coeffs,
}
impl<'plan, Coeffs, OCoeff> Iterator for PartialDerivIter<'plan, Coeffs>
where
Coeffs: Sequence,
for<'a> <Coeffs as SequenceTypes<'a>>::Item: Multiple<Output = OCoeff>,
{
type Item = OCoeff;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let (index, power) = self.indices_powers.next()?;
Some(self.coeffs.get(*index)?.multiple(*power))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.indices_powers.size_hint()
}
}
impl<'plan, Coeffs, OCoeff> DoubleEndedIterator for PartialDerivIter<'plan, Coeffs>
where
Coeffs: Sequence,
for<'a> <Coeffs as SequenceTypes<'a>>::Item: Multiple<Output = OCoeff>,
{
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
let (index, power) = self.indices_powers.next_back()?;
Some(self.coeffs.get(*index)?.multiple(*power))
}
}
impl<'plan, Coeffs, OCoeff> ExactSizeIterator for PartialDerivIter<'plan, Coeffs>
where
Coeffs: Sequence,
for<'a> <Coeffs as SequenceTypes<'a>>::Item: Multiple<Output = OCoeff>,
{
}
impl<'plan, Coeffs, OCoeff> FusedIterator for PartialDerivIter<'plan, Coeffs>
where
Coeffs: Sequence,
for<'a> <Coeffs as SequenceTypes<'a>>::Item: Multiple<Output = OCoeff>,
{
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MulVar {
Left,
Right,
Both,
}
#[derive(Debug, Clone)]
pub struct MulPlan {
count: Box<[usize]>,
offsets: Box<[usize]>,
indices: Box<[[usize; 2]]>,
ncoeffs_left: usize,
ncoeffs_right: usize,
nvars_output: usize,
degree_output: Power,
}
impl MulPlan {
#[inline]
pub fn new<'vars, Vars>(vars: &'vars Vars, degree_left: Power, degree_right: Power) -> Self
where
Vars: SequenceOwned<OwnedItem = MulVar>,
<Vars as SequenceTypes<'vars>>::Iter: DoubleEndedIterator,
{
Self::for_output_degree(vars, degree_left, degree_right, degree_left + degree_right)
}
pub fn for_output_degree<'vars, Vars>(
vars: &'vars Vars,
degree_left: Power,
degree_right: Power,
degree_output: Power,
) -> Self
where
Vars: SequenceOwned<OwnedItem = MulVar>,
<Vars as SequenceTypes<'vars>>::Iter: DoubleEndedIterator,
{
let nvars_left = vars.iter().filter(|var| *var != MulVar::Right).count();
let nvars_right = vars.iter().filter(|var| *var != MulVar::Left).count();
let nvars_output = vars.len();
let ncoeffs_left = ncoeffs(nvars_left, degree_left);
let ncoeffs_right = ncoeffs(nvars_right, degree_right);
let ncoeffs_out = ncoeffs(nvars_output, degree_output);
let mut indices: Vec<[usize; 3]> = Vec::with_capacity(ncoeffs_left * ncoeffs_right);
for ileft in 0..ncoeffs_left {
for iright in 0..ncoeffs_right {
let mut lpowers = index_to_powers_rev_iter(ileft, nvars_left, degree_left).unwrap();
let mut rpowers =
index_to_powers_rev_iter(iright, nvars_right, degree_right).unwrap();
let opowers = vars.iter().rev().map(|var| match var {
MulVar::Left => lpowers.next().unwrap(),
MulVar::Right => rpowers.next().unwrap(),
MulVar::Both => lpowers.next().unwrap() + rpowers.next().unwrap(),
});
if let Some(iout) = powers_rev_iter_to_index(opowers, vars.len(), degree_output) {
indices.push([iout, ileft, iright]);
}
}
}
indices.sort_unstable();
let mut oiter = indices.iter().map(|[iout, _, _]| *iout).peekable();
let count: Box<[usize]> = Iterator::map(0..ncoeffs_out, |i| {
let mut n = 0;
while oiter.next_if(|j| i == *j).is_some() {
n += 1;
}
n
})
.collect();
let offsets = iter::once(0)
.chain(count.iter().copied())
.scan(0, |acc, n| {
*acc += n;
Some(*acc)
})
.collect();
let indices = indices
.into_iter()
.map(|[_, ileft, iright]| [ileft, iright])
.collect();
Self {
count,
offsets,
indices,
ncoeffs_left,
ncoeffs_right,
nvars_output,
degree_output,
}
}
#[inline]
pub fn same_vars(nvars: usize, degree_left: Power, degree_right: Power) -> Self {
Self::same_vars_for_output_degree(
nvars,
degree_left,
degree_right,
degree_left + degree_right,
)
}
#[inline]
pub fn same_vars_for_output_degree(
nvars: usize,
degree_left: Power,
degree_right: Power,
degree_output: Power,
) -> Self {
Self::for_output_degree(
&[MulVar::Both].copied().repeat(nvars),
degree_left,
degree_right,
degree_output,
)
}
#[inline]
pub fn different_vars(
nvars_left: usize,
nvars_right: usize,
degree_left: Power,
degree_right: Power,
) -> Result<Self, Error> {
Self::different_vars_for_output_degree(
nvars_left,
nvars_right,
degree_left,
degree_right,
degree_left + degree_right,
)
}
#[inline]
pub fn different_vars_for_output_degree(
nvars_left: usize,
nvars_right: usize,
degree_left: Power,
degree_right: Power,
degree_output: Power,
) -> Result<Self, Error> {
let vars = [MulVar::Left]
.copied()
.repeat(nvars_left)
.concat([MulVar::Right].copied().repeat(nvars_right))
.ok_or(Error::TooManyVariables)?;
Ok(Self::for_output_degree(
&vars,
degree_left,
degree_right,
degree_output,
))
}
#[inline]
pub fn apply<LCoeffs, RCoeffs>(
&self,
coeffs_left: LCoeffs,
coeffs_right: RCoeffs,
) -> Result<Mul<'_, LCoeffs, RCoeffs>, Error>
where
LCoeffs: Sequence,
RCoeffs: Sequence,
{
check_ncoeffs_sqnc(&coeffs_left, self.ncoeffs_left(), Some("left polynomial"))?;
check_ncoeffs_sqnc(
&coeffs_right,
self.ncoeffs_right(),
Some("right polynomial"),
)?;
Ok(Mul {
plan: self,
coeffs_left,
coeffs_right,
})
}
#[inline]
pub fn ncoeffs_left(&self) -> usize {
self.ncoeffs_left
}
#[inline]
pub fn ncoeffs_right(&self) -> usize {
self.ncoeffs_right
}
#[inline]
pub fn ncoeffs_output(&self) -> usize {
self.count.len()
}
#[inline]
pub fn nvars_output(&self) -> usize {
self.nvars_output
}
#[inline]
pub fn degree_output(&self) -> Power {
self.degree_output
}
}
#[derive(Debug, Clone)]
pub struct Mul<'plan, LCoeffs, RCoeffs> {
plan: &'plan MulPlan,
coeffs_left: LCoeffs,
coeffs_right: RCoeffs,
}
impl<'this, 'plan, LCoeffs, RCoeffs, OCoeff> SequenceTypes<'this> for Mul<'plan, LCoeffs, RCoeffs>
where
for<'a> <LCoeffs as SequenceTypes<'a>>::Item:
ops::Mul<<RCoeffs as SequenceTypes<'a>>::Item, Output = OCoeff>,
OCoeff: ops::AddAssign + Zero,
LCoeffs: Sequence,
RCoeffs: Sequence,
{
type Item = OCoeff;
type Iter = sqnc::derive::Iter<'this, Self>;
}
impl<'plan, LCoeffs, RCoeffs, OCoeff> Sequence for Mul<'plan, LCoeffs, RCoeffs>
where
for<'a> <LCoeffs as SequenceTypes<'a>>::Item:
ops::Mul<<RCoeffs as SequenceTypes<'a>>::Item, Output = OCoeff>,
OCoeff: ops::AddAssign + Zero,
LCoeffs: Sequence,
RCoeffs: Sequence,
{
#[inline]
fn len(&self) -> usize {
self.plan.ncoeffs_output()
}
#[inline]
fn get(&self, index: usize) -> Option<OCoeff> {
let start = *self.plan.offsets.get(index)?;
let stop = *self.plan.offsets.get(index + 1)?;
let n = stop.saturating_sub(start);
let mut value = OCoeff::zero();
for [l, r] in self.plan.indices.iter().skip(start).take(n) {
value += self.coeffs_left.get(*l)? * self.coeffs_right.get(*r)?;
}
Some(value)
}
#[inline]
fn iter(&self) -> sqnc::derive::Iter<'_, Self> {
self.into()
}
}
impl<'plan, LCoeffs, RCoeffs, OCoeff> IntoIterator for Mul<'plan, LCoeffs, RCoeffs>
where
for<'a> <LCoeffs as SequenceTypes<'a>>::Item:
ops::Mul<<RCoeffs as SequenceTypes<'a>>::Item, Output = OCoeff>,
OCoeff: ops::AddAssign + Zero,
LCoeffs: Sequence,
RCoeffs: Sequence,
{
type Item = OCoeff;
type IntoIter = sqnc::derive::IntoIter<Self>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.into()
}
}
pub fn composition_with_inner_matrix<Coeff, InnerCoeffs>(
mut inner_coeffs: InnerCoeffs,
inner_nvars: usize,
inner_degree: Power,
outer_nvars: usize,
outer_degree: Power,
) -> Result<Array2<Coeff>, Error>
where
Coeff: One + Zero + Clone + ops::AddAssign,
for<'a> &'a Coeff: ops::Mul<Output = Coeff>,
InnerCoeffs: Iterator<Item = Coeff>,
{
let outer_ncoeffs = ncoeffs(outer_nvars, outer_degree);
let result_degree = inner_degree * outer_degree;
let result_ncoeffs = ncoeffs(inner_nvars, result_degree);
let mut matrix: Array2<Coeff> = Array2::zeros((result_ncoeffs, outer_ncoeffs));
matrix[(result_ncoeffs - 1, outer_ncoeffs - 1)] = Coeff::one();
if outer_ncoeffs == 1 {
return Ok(matrix);
}
let mapped_indices = MapDegree::new(inner_nvars, inner_degree, result_degree).unwrap();
for ivar in 0..outer_nvars {
let icolumn = outer_ncoeffs - 1 - ncoeffs(ivar, outer_degree);
for irow in mapped_indices.iter() {
matrix[(irow, icolumn)] =
inner_coeffs
.next()
.ok_or_else(|| Error::IncorrectNumberOfCoefficients {
expected: ncoeffs(inner_nvars, inner_degree) * outer_nvars,
got: Less,
detail: None,
})?;
}
}
if inner_coeffs.next().is_some() {
return Err(Error::IncorrectNumberOfCoefficients {
expected: ncoeffs(inner_nvars, inner_degree) * outer_nvars,
got: More,
detail: None,
});
}
let mul = MulPlan::for_output_degree(
&[MulVar::Both].copied().repeat(inner_nvars),
result_degree,
result_degree,
result_degree,
);
let mut rev_powers: Vec<Power> = iter::repeat(0).take(outer_nvars).collect();
for icol in Iterator::rev(0..outer_ncoeffs) {
iter::zip(
rev_powers.iter_mut(),
index_to_powers_rev_iter(icol, outer_nvars, outer_degree).unwrap(),
)
.for_each(|(d, s)| *d = s);
if rev_powers.iter().sum::<Power>() <= 1 {
continue;
}
let rev_ivar = rev_powers.iter().position(|i| *i > 0).unwrap();
let icol1 = outer_ncoeffs - 1 - ncoeffs(outer_nvars - rev_ivar - 1, outer_degree);
rev_powers[rev_ivar] -= 1;
let icol2 = powers_rev_iter_to_index(rev_powers.iter().copied(), outer_nvars, outer_degree)
.unwrap();
let (icol1, icol2) = if icol1 <= icol2 {
(icol1, icol2)
} else {
(icol2, icol1)
};
let mut cols = matrix.columns_mut().into_iter();
let cols = cols.by_ref();
let mut col = cols.nth(icol).unwrap();
let col1 = cols.nth(icol1 - icol - 1).unwrap();
let col1 = col1.as_sqnc();
let col2;
let col2 = if icol2 == icol1 {
col1
} else {
col2 = cols.nth(icol2 - icol1 - 1).unwrap();
col2.as_sqnc()
};
for (dst, src) in iter::zip(col.iter_mut(), mul.apply(col1, col2).unwrap().iter()) {
*dst = src;
}
}
Ok(matrix)
}
#[cfg(test)]
mod tests {
use super::{Error, Less, MapDegree, More, MulPlan, MulVar, Multiple, PartialDerivPlan, Power};
use core::iter;
use ndarray::array;
use sqnc::traits::*;
#[test]
fn error() {
assert_eq!(
Error::IncorrectNumberOfCoefficients {
expected: 2,
got: 3.into(),
detail: None,
}
.to_string(),
"Expected 2 coefficients but got 3.",
);
assert_eq!(
Error::IncorrectNumberOfCoefficients {
expected: 1,
got: More,
detail: None,
}
.to_string(),
"Expected 1 coefficient but got more.",
);
assert_eq!(
Error::IncorrectNumberOfCoefficients {
expected: 2,
got: Less,
detail: Some("left polynomial"),
}
.to_string(),
"left polynomial: Expected 2 coefficients but got less.",
);
assert_eq!(
Error::TooManyVariables.to_string(),
"The number of variables exceeds the maximum (`usize::MAX`).",
);
}
#[test]
fn ncoeffs_degree() {
macro_rules! t {
($nvars:literal, $ncoeffs_array:expr) => {
let mut ncoeffs_sum = 0;
for (degree, ncoeffs) in $ncoeffs_array.iter().copied().enumerate() {
let degree = degree as Power;
assert_eq!(
super::ncoeffs($nvars, degree),
ncoeffs,
"ncoeffs({}, {degree}) != {ncoeffs}",
$nvars
);
assert_eq!(
super::ncoeffs_sum($nvars, degree),
ncoeffs_sum,
"ncoeffs_sum({}, {degree}) != {ncoeffs_sum}",
$nvars
);
if $nvars > 0 || degree == 0 {
assert_eq!(
super::degree($nvars, ncoeffs),
Some(degree),
"degree({}, {ncoeffs}) != Some({degree})",
$nvars
);
}
ncoeffs_sum += ncoeffs;
}
assert!(
super::ncoeffs_iter($nvars)
.take($ncoeffs_array.len())
.eq($ncoeffs_array),
"ncoeffs_iter({}).take({}) != {:?}",
$nvars,
$ncoeffs_array.len(),
$ncoeffs_array
);
assert!(
super::degree_ncoeffs_iter($nvars)
.take($ncoeffs_array.len())
.eq(iter::zip(0.., $ncoeffs_array)),
"degree_ncoeffs_iter({}).take({}) != {:?}",
$nvars,
$ncoeffs_array.len(),
$ncoeffs_array
);
};
}
t! {0, [1, 1, 1, 1, 1]}
t! {1, [1, 2, 3, 4, 5]}
t! {2, [1, 3, 6, 10, 15]}
t! {3, [1, 4, 10, 20, 35]}
t! {4, [1, 5, 15]}
assert_eq!(super::degree(0, 0), None);
assert_eq!(super::degree(0, 2), None);
assert_eq!(super::degree(1, 0), None);
assert_eq!(super::degree(2, 0), None);
assert_eq!(super::degree(2, 2), None);
assert_eq!(super::degree(2, 4), None);
assert_eq!(super::degree(2, 9), None);
assert_eq!(super::degree(2, 11), None);
assert_eq!(super::degree(3, 0), None);
assert_eq!(super::degree(3, 2), None);
assert_eq!(super::degree(3, 3), None);
assert_eq!(super::degree(4, 3), None);
}
#[test]
fn powers_index_maps() {
macro_rules! assert_index_powers {
($degree:literal, $desired_powers:tt) => {
let nvars = $desired_powers[0].len();
for (desired_index, desired_powers) in $desired_powers.into_iter().enumerate() {
assert_eq!(
super::powers_rev_iter_to_index(
desired_powers.iter().rev().copied(),
nvars,
$degree
),
Some(desired_index)
);
assert!(
super::index_to_powers_rev_iter(desired_index, nvars, $degree)
.unwrap()
.eq(desired_powers.iter().rev().copied())
)
}
};
}
assert_index_powers!(2, [[2], [1], [0]]);
assert_index_powers!(2, [[0, 2], [1, 1], [0, 1], [2, 0], [1, 0], [0, 0]]);
assert_index_powers!(
2,
[
[0, 0, 2],
[0, 1, 1],
[1, 0, 1],
[0, 0, 1],
[0, 2, 0],
[1, 1, 0],
[0, 1, 0],
[2, 0, 0],
[1, 0, 0],
[0, 0, 0]
]
);
}
#[test]
fn map_degree() {
assert_eq!(MapDegree::new(1, 3, 2), None);
assert!(MapDegree::new(0, 0, 0).unwrap().iter().eq([0]));
assert!(MapDegree::new(1, 2, 2).unwrap().iter().eq([0, 1, 2]));
assert!(MapDegree::new(1, 2, 3).unwrap().iter().eq([1, 2, 3]));
assert!(MapDegree::new(1, 2, 4).unwrap().iter().eq([2, 3, 4]));
assert!(MapDegree::new(2, 1, 2).unwrap().iter().eq([2, 4, 5]));
assert!(MapDegree::new(0, 0, 0).unwrap().into_iter().eq([0]));
assert!(MapDegree::new(1, 2, 2).unwrap().into_iter().eq([0, 1, 2]));
}
#[test]
fn eval_0d() {
let values: [usize; 0] = [];
assert_eq!(super::eval([1], &values, 0), Ok(1));
assert!(super::eval(0..0, &values, 0).is_err());
}
#[test]
fn eval_1d() {
let values = [5];
assert_eq!(super::eval([1], &values, 0), Ok(1));
assert_eq!(super::eval([2, 1], &values, 1), Ok(11));
assert_eq!(super::eval([3, 2, 1], &values, 2), Ok(86));
assert!(super::eval(0..1, &values, 1).is_err());
}
#[test]
fn eval_2d() {
let values = [5, 3];
assert_eq!(super::eval([1], &values, 0), Ok(1));
assert_eq!(super::eval([0, 0, 1], &values, 1), Ok(1));
assert_eq!(super::eval([0, 1, 0], &values, 1), Ok(5));
assert_eq!(super::eval([1, 0, 0], &values, 1), Ok(3));
assert_eq!(super::eval([3, 2, 1], &values, 1), Ok(20));
assert_eq!(super::eval(Iterator::rev(1..7), &values, 2), Ok(227));
assert!(super::eval(0..2, &values, 1).is_err());
}
#[test]
fn eval_3d() {
let values = [5, 3, 2];
assert_eq!(super::eval([1], &values, 0), Ok(1));
assert_eq!(super::eval([0, 0, 0, 1], &values, 1), Ok(1));
assert_eq!(super::eval([0, 0, 1, 0], &values, 1), Ok(5));
assert_eq!(super::eval([0, 1, 0, 0], &values, 1), Ok(3));
assert_eq!(super::eval([1, 0, 0, 0], &values, 1), Ok(2));
assert_eq!(super::eval([4, 3, 2, 1], &values, 1), Ok(28));
assert_eq!(super::eval(Iterator::rev(1..11), &values, 2), Ok(415));
assert!(super::eval(0..3, &values, 1).is_err());
}
#[test]
fn eval_4d() {
let values = [5, 3, 2, 1];
assert_eq!(super::eval([1], &values, 0), Ok(1));
assert_eq!(super::eval([0, 0, 0, 0, 1], &values, 1), Ok(1));
assert_eq!(super::eval([0, 0, 0, 1, 0], &values, 1), Ok(5));
assert_eq!(super::eval([0, 0, 1, 0, 0], &values, 1), Ok(3));
assert_eq!(super::eval([0, 1, 0, 0, 0], &values, 1), Ok(2));
assert_eq!(super::eval([1, 0, 0, 0, 0], &values, 1), Ok(1));
assert!(super::eval(0..4, &values, 1).is_err());
}
#[test]
fn multiple() {
assert_eq!(2.multiple(3), 6);
assert_eq!((&2).multiple(3), 6);
}
#[test]
fn partial_deriv_x0_x() {
let plan = PartialDerivPlan::new(1, 0, 0).unwrap();
assert_eq!(plan.ncoeffs_input(), 1);
assert_eq!(plan.ncoeffs_output(), 1);
assert_eq!(plan.degree_output(), 0);
assert_eq!(plan.nvars(), 1);
let pd = plan.apply([1]).unwrap();
assert!(pd.iter().eq([0]));
assert_eq!(pd.iter().size_hint(), (1, Some(1)));
assert_eq!(pd.get(0), Some(0));
assert_eq!(pd.len(), 1);
let iter = pd.into_iter();
assert_eq!(iter.size_hint(), (1, Some(1)));
assert!(iter.eq([0]));
}
#[test]
fn partial_deriv_x1_x() {
let plan = PartialDerivPlan::new(1, 1, 0).unwrap();
assert_eq!(plan.ncoeffs_input(), 2);
assert_eq!(plan.ncoeffs_output(), 1);
assert_eq!(plan.degree_output(), 0);
assert_eq!(plan.nvars(), 1);
let pd = plan.apply([2, 1]).unwrap();
assert!(pd.iter().eq([2]));
assert_eq!(pd.get(1), None);
assert_eq!(pd.len(), 1);
}
#[test]
fn partial_deriv_x3_x() {
let plan = PartialDerivPlan::new(1, 3, 0).unwrap();
assert_eq!(plan.ncoeffs_input(), 4);
assert_eq!(plan.ncoeffs_output(), 3);
assert_eq!(plan.degree_output(), 2);
assert_eq!(plan.nvars(), 1);
let pd = plan.apply([4, 3, 2, 1]).unwrap();
assert!(pd.iter().eq([12, 6, 2]));
assert_eq!(pd.get(2), Some(2));
assert_eq!(pd.len(), 3);
}
#[test]
fn partial_deriv_xy2_x() {
let plan = PartialDerivPlan::new(2, 2, 0).unwrap();
assert_eq!(plan.ncoeffs_input(), 6);
assert_eq!(plan.ncoeffs_output(), 3);
assert_eq!(plan.degree_output(), 1);
assert_eq!(plan.nvars(), 2);
let pd = plan.apply([6, 5, 4, 3, 2, 1]).unwrap();
assert!(pd.iter().eq([5, 6, 2]));
assert_eq!(pd.get(1), Some(6));
assert_eq!(pd.len(), 3);
}
#[test]
fn partial_deriv_xy2_y() {
let plan = PartialDerivPlan::new(2, 2, 1).unwrap();
assert_eq!(plan.ncoeffs_input(), 6);
assert_eq!(plan.ncoeffs_output(), 3);
assert_eq!(plan.degree_output(), 1);
assert_eq!(plan.nvars(), 2);
let pd = plan.apply([6, 5, 4, 3, 2, 1]).unwrap();
assert!(pd.iter().eq([12, 5, 4]));
assert!(pd.iter().rev().eq([4, 5, 12]));
assert_eq!(pd.get(2), Some(4));
assert_eq!(pd.len(), 3);
}
#[test]
fn partial_deriv_out_of_range() {
assert!(PartialDerivPlan::new(1, 2, 2).is_none());
}
#[test]
fn mul_x1_x1() {
let plan = MulPlan::same_vars(1, 1, 1);
assert_eq!(plan.ncoeffs_left(), 2);
assert_eq!(plan.ncoeffs_right(), 2);
assert_eq!(plan.ncoeffs_output(), 3);
assert_eq!(plan.nvars_output(), 1);
assert_eq!(plan.degree_output(), 2);
let mul = plan.apply([2, 1], [4, 3]).unwrap();
assert!(mul.iter().eq([8, 10, 3]));
assert!(mul.into_iter().eq([8, 10, 3]));
}
#[test]
fn mul_x1_y1() {
let plan = MulPlan::different_vars(1, 1, 1, 1).unwrap();
assert_eq!(plan.ncoeffs_left(), 2);
assert_eq!(plan.ncoeffs_right(), 2);
assert_eq!(plan.ncoeffs_output(), 6);
assert_eq!(plan.nvars_output(), 2);
assert_eq!(plan.degree_output(), 2);
let mul = plan.apply([2, 1], [4, 3]).unwrap();
assert!(mul.iter().eq([0, 8, 4, 0, 6, 3]));
assert!(mul.into_iter().eq([0, 8, 4, 0, 6, 3]));
}
#[test]
fn mul_xy1_y2() {
let plan = MulPlan::new(&[MulVar::Left, MulVar::Both].copied(), 1, 2);
assert_eq!(plan.ncoeffs_left(), 3);
assert_eq!(plan.ncoeffs_right(), 3);
assert_eq!(plan.ncoeffs_output(), 10);
assert_eq!(plan.nvars_output(), 2);
assert_eq!(plan.degree_output(), 3);
let mul = plan.apply([3, 2, 1], [6, 5, 4]).unwrap();
assert!(mul.iter().eq([18, 12, 21, 0, 10, 17, 0, 0, 8, 4]));
assert!(mul.into_iter().eq([18, 12, 21, 0, 10, 17, 0, 0, 8, 4]));
}
#[test]
fn mul_errors() {
let plan = MulPlan::same_vars(1, 1, 1);
assert_eq!(
plan.apply([2, 1, 3], [4, 3]).unwrap_err(),
Error::IncorrectNumberOfCoefficients {
expected: 2,
got: 3.into(),
detail: Some("left polynomial"),
}
);
assert_eq!(
plan.apply([2, 1], [4, 3, 2]).unwrap_err(),
Error::IncorrectNumberOfCoefficients {
expected: 2,
got: 3.into(),
detail: Some("right polynomial"),
}
);
}
#[test]
fn composition() {
assert_eq!(
super::composition_with_inner_matrix(0..0, 0, 0, 0, 0),
Ok(array![[1]])
);
let f = array![1, 2, 0, 0, 0, 0];
let g = array![-1, 1, 0, 0, 0, 0, 3, 2];
let m = super::composition_with_inner_matrix(g.iter().copied(), 3, 1, 2, 2).unwrap();
assert_eq!(m.dot(&f), array![0, 0, -6, -4, 0, 6, 4, 9, 12, 4]);
}
#[test]
fn composition_errors() {
assert_eq!(
super::composition_with_inner_matrix(0..1, 1, 3, 1, 2),
Err(Error::IncorrectNumberOfCoefficients {
expected: 4,
got: Less,
detail: None,
})
);
assert_eq!(
super::composition_with_inner_matrix(0..9, 1, 3, 1, 2),
Err(Error::IncorrectNumberOfCoefficients {
expected: 4,
got: More,
detail: None,
})
);
}
}
#[cfg(all(feature = "bench", test))]
mod benches {
extern crate test;
use self::test::Bencher;
use super::MulPlan;
use sqnc::traits::*;
use std::iter;
macro_rules! mk_bench_eval {
($name:ident, $nvars:literal, $degree:literal) => {
#[bench]
fn $name(b: &mut Bencher) {
let coeffs =
Vec::from_iter(Iterator::map(1..=super::ncoeffs($nvars, $degree), |i| {
i as f64
}));
let values: Vec<_> = (1..=$nvars).map(|x| x as f64).collect();
b.iter(|| {
super::eval(
test::black_box(coeffs.as_slice()),
test::black_box(&values[..]),
$degree,
)
.unwrap()
})
}
};
}
mk_bench_eval! {eval_1d_degree1, 1, 1}
mk_bench_eval! {eval_1d_degree2, 1, 2}
mk_bench_eval! {eval_1d_degree3, 1, 3}
mk_bench_eval! {eval_1d_degree4, 1, 4}
mk_bench_eval! {eval_2d_degree1, 2, 1}
mk_bench_eval! {eval_2d_degree2, 2, 2}
mk_bench_eval! {eval_2d_degree3, 2, 3}
mk_bench_eval! {eval_2d_degree4, 2, 4}
mk_bench_eval! {eval_3d_degree1, 3, 1}
mk_bench_eval! {eval_3d_degree2, 3, 2}
mk_bench_eval! {eval_3d_degree3, 3, 3}
mk_bench_eval! {eval_3d_degree4, 3, 4}
mk_bench_eval! {eval_4d_degree1, 4, 1}
mk_bench_eval! {eval_4d_degree2, 4, 2}
mk_bench_eval! {eval_4d_degree3, 4, 3}
mk_bench_eval! {eval_4d_degree4, 4, 4}
#[bench]
fn ncoeffs_3d_degree4(b: &mut Bencher) {
b.iter(|| super::ncoeffs(test::black_box(3), test::black_box(4)));
}
#[bench]
fn mul_xyz4_xyz2(b: &mut Bencher) {
let plan = MulPlan::same_vars(3, 4, 2);
let lcoeffs = Vec::from_iter(Iterator::map(0..super::ncoeffs(3, 4), |i| i as f64));
let rcoeffs = Vec::from_iter(Iterator::map(0..super::ncoeffs(3, 2), |i| i as f64));
let mut ocoeffs = Vec::from_iter(iter::repeat(0f64).take(plan.ncoeffs_output()));
b.iter(|| {
ocoeffs
.as_mut_sqnc()
.assign(
plan.apply(
test::black_box(lcoeffs.as_sqnc()),
test::black_box(rcoeffs.as_sqnc()),
)
.unwrap(),
)
.unwrap()
})
}
#[bench]
fn mul_x4_yz2(b: &mut Bencher) {
let plan = MulPlan::different_vars(1, 2, 4, 2).unwrap();
let lcoeffs: Vec<f64> = Iterator::map(0..super::ncoeffs(1, 4), |i| i as f64).collect();
let rcoeffs: Vec<f64> = Iterator::map(0..super::ncoeffs(2, 2), |i| i as f64).collect();
let mut ocoeffs: Vec<f64> = iter::repeat(0f64).take(plan.ncoeffs_output()).collect();
b.iter(|| {
ocoeffs
.as_mut_sqnc()
.assign(
MulPlan::apply(
test::black_box(&plan),
test::black_box(lcoeffs.as_sqnc()),
test::black_box(rcoeffs.as_sqnc()),
)
.unwrap(),
)
.unwrap()
})
}
}