use crate::prelude_dev::*;
use core::ops::{Add, Mul};
use num::complex::ComplexFloat;
use num::{FromPrimitive, One, Zero};
use rstsr_dtype_traits::ExtReal;

impl<T, D> OpSumAPI<T, D> for DeviceCpuSerial
where
    T: Zero + Add<Output = T> + Clone,
    D: DimAPI,
{
    type TOut = T;

    fn sum_all(&self, a: &Vec<T>, la: &Layout<D>) -> Result<T> {
        let f_init = T::zero;
        let f = |acc, x| acc + x;
        let f_sum = |acc1, acc2| acc1 + acc2;
        let f_out = |acc| acc;

        reduce_all_cpu_serial(a, la, f_init, f, f_sum, f_out)
    }

    fn sum_axes(
        &self,
        a: &Vec<T>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<T>>, T, Self>, Layout<IxD>)> {
        let f_init = T::zero;
        let f = |acc, x| acc + x;
        let f_sum = |acc1, acc2| acc1 + acc2;
        let f_out = |acc| acc;

        let (out, layout_out) = reduce_axes_cpu_serial(a, &la.to_dim()?, axes, f_init, f, f_sum, f_out)?;
        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }
}

impl<T, D> OpMinAPI<T, D> for DeviceCpuSerial
where
    T: ExtReal,
    D: DimAPI,
{
    type TOut = T;

    fn min_all(&self, a: &Vec<T>, la: &Layout<D>) -> Result<T> {
        if la.size() == 0 {
            rstsr_raise!(InvalidValue, "zero-size array is not supported for min")?;
        }

        let f_init = T::ext_max_value;
        let f = |acc: T, x: T| acc.ext_min(x);
        let f_sum = |acc1: T, acc2: T| acc1.ext_min(acc2);
        let f_out = |acc| acc;
        reduce_all_cpu_serial(a, la, f_init, f, f_sum, f_out)
    }

    fn min_axes(
        &self,
        a: &Vec<T>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<T>>, T, Self>, Layout<IxD>)> {
        if la.size() == 0 {
            rstsr_raise!(InvalidValue, "zero-size array is not supported for min")?;
        }

        let f_init = T::ext_max_value;
        let f = |acc: T, x: T| acc.ext_min(x);
        let f_sum = |acc1: T, acc2: T| acc1.ext_min(acc2);
        let f_out = |acc| acc;

        let (out, layout_out) = reduce_axes_cpu_serial(a, &la.to_dim()?, axes, f_init, f, f_sum, f_out)?;
        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }
}

impl<T, D> OpMaxAPI<T, D> for DeviceCpuSerial
where
    T: ExtReal,
    D: DimAPI,
{
    type TOut = T;

    fn max_all(&self, a: &Vec<T>, la: &Layout<D>) -> Result<T> {
        if la.size() == 0 {
            rstsr_raise!(InvalidValue, "zero-size array is not supported for max")?;
        }

        let f_init = T::ext_min_value;
        let f = |acc: T, x: T| acc.ext_max(x);
        let f_sum = |acc1: T, acc2: T| acc1.ext_max(acc2);
        let f_out = |acc| acc;

        reduce_all_cpu_serial(a, la, f_init, f, f_sum, f_out)
    }

    fn max_axes(
        &self,
        a: &Vec<T>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<T>>, T, Self>, Layout<IxD>)> {
        if la.size() == 0 {
            rstsr_raise!(InvalidValue, "zero-size array is not supported for max")?;
        }

        let f_init = T::ext_min_value;
        let f = |acc: T, x: T| acc.ext_max(x);
        let f_sum = |acc1: T, acc2: T| acc1.ext_max(acc2);
        let f_out = |acc| acc;

        let (out, layout_out) = reduce_axes_cpu_serial(a, &la.to_dim()?, axes, f_init, f, f_sum, f_out)?;
        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }
}

impl<T, D> OpProdAPI<T, D> for DeviceCpuSerial
where
    T: Clone + One + Mul<Output = T>,
    D: DimAPI,
{
    type TOut = T;

    fn prod_all(&self, a: &Vec<T>, la: &Layout<D>) -> Result<T> {
        let f_init = T::one;
        let f = |acc, x| acc * x;
        let f_sum = |acc1, acc2| acc1 * acc2;
        let f_out = |acc| acc;

        reduce_all_cpu_serial(a, la, f_init, f, f_sum, f_out)
    }

    fn prod_axes(
        &self,
        a: &Vec<T>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<T>>, T, Self>, Layout<IxD>)> {
        let f_init = T::one;
        let f = |acc, x| acc * x;
        let f_sum = |acc1, acc2| acc1 * acc2;
        let f_out = |acc| acc;

        let (out, layout_out) = reduce_axes_cpu_serial(a, &la.to_dim()?, axes, f_init, f, f_sum, f_out)?;
        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }
}

impl<T, D> OpMeanAPI<T, D> for DeviceCpuSerial
where
    T: Clone + ComplexFloat + FromPrimitive,
    D: DimAPI,
{
    type TOut = T;

    fn mean_all(&self, a: &Vec<T>, la: &Layout<D>) -> Result<T> {
        let size = la.size();
        let f_init = T::zero;
        let f = |acc, x| acc + x;
        let f_sum = |acc, x| acc + x;
        let f_out = |acc| acc / T::from_usize(size).unwrap();

        let sum = reduce_all_cpu_serial(a, la, f_init, f, f_sum, f_out)?;
        Ok(sum)
    }

    fn mean_axes(
        &self,
        a: &Vec<T>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<T>>, T, Self>, Layout<IxD>)> {
        let (layout_axes, _) = la.dim_split_axes(axes)?;
        let size = layout_axes.size();
        let f_init = T::zero;
        let f = |acc, x| acc + x;
        let f_sum = |acc, x| acc + x;
        let f_out = |acc| acc / T::from_usize(size).unwrap();

        let (out, layout_out) = reduce_axes_cpu_serial(a, &la.to_dim()?, axes, f_init, f, f_sum, f_out)?;
        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }
}

impl<T, D> OpVarAPI<T, D> for DeviceCpuSerial
where
    T: Clone + ComplexFloat + FromPrimitive,
    T::Real: Clone + ComplexFloat + FromPrimitive,
    D: DimAPI,
{
    type TOut = T::Real;

    fn var_all(&self, a: &Vec<T>, la: &Layout<D>) -> Result<T::Real> {
        let size = la.size();

        let f_init = || (T::zero(), T::Real::zero());
        let f = |(acc_1, acc_2): (T, T::Real), x: T| (acc_1 + x, acc_2 + (x * x.conj()).re());
        let f_sum = |(acc_1, acc_2): (T, T::Real), (x_1, x_2)| (acc_1 + x_1, acc_2 + x_2);
        let f_out = |(acc_1, acc_2): (T, T::Real)| {
            let size_1 = T::from_usize(size).unwrap();
            let size_2 = T::Real::from_usize(size).unwrap();
            let mean = acc_1 / size_1;
            acc_2 / size_2 - (mean * mean.conj()).re()
        };

        let result = reduce_all_cpu_serial(a, la, f_init, f, f_sum, f_out)?;
        Ok(result)
    }

    fn var_axes(
        &self,
        a: &Vec<T>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<T::Real>>, T::Real, Self>, Layout<IxD>)> {
        let (layout_axes, _) = la.dim_split_axes(axes)?;
        let size = layout_axes.size();

        let f_init = || (T::zero(), T::Real::zero());
        let f = |(acc_1, acc_2): (T, T::Real), x: T| (acc_1 + x, acc_2 + (x * x.conj()).re());
        let f_sum = |(acc_1, acc_2): (T, T::Real), (x_1, x_2)| (acc_1 + x_1, acc_2 + x_2);
        let f_out = |(acc_1, acc_2): (T, T::Real)| {
            let size_1 = T::from_usize(size).unwrap();
            let size_2 = T::Real::from_usize(size).unwrap();
            let mean = acc_1 / size_1;
            acc_2 / size_2 - (mean * mean.conj()).re()
        };

        let (out, layout_out) = reduce_axes_cpu_serial(a, &la.to_dim()?, axes, f_init, f, f_sum, f_out)?;

        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }
}

impl<T, D> OpStdAPI<T, D> for DeviceCpuSerial
where
    T: Clone + ComplexFloat + FromPrimitive,
    T::Real: Clone + ComplexFloat + FromPrimitive,
    D: DimAPI,
{
    type TOut = T::Real;

    fn std_all(&self, a: &Vec<T>, la: &Layout<D>) -> Result<T::Real> {
        let size = la.size();

        let f_init = || (T::zero(), T::Real::zero());
        let f = |(acc_1, acc_2): (T, T::Real), x: T| (acc_1 + x, acc_2 + (x * x.conj()).re());
        let f_sum = |(acc_1, acc_2): (T, T::Real), (x_1, x_2)| (acc_1 + x_1, acc_2 + x_2);
        let f_out = |(acc_1, acc_2): (T, T::Real)| {
            let size_1 = T::from_usize(size).unwrap();
            let size_2 = T::Real::from_usize(size).unwrap();
            let mean = acc_1 / size_1;
            let var = acc_2 / size_2 - (mean * mean.conj()).re();
            var.sqrt()
        };

        let result = reduce_all_cpu_serial(a, la, f_init, f, f_sum, f_out)?;
        Ok(result)
    }

    fn std_axes(
        &self,
        a: &Vec<T>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<T::Real>>, T::Real, Self>, Layout<IxD>)> {
        let (layout_axes, _) = la.dim_split_axes(axes)?;
        let size = layout_axes.size();

        let f_init = || (T::zero(), T::Real::zero());
        let f = |(acc_1, acc_2): (T, T::Real), x: T| (acc_1 + x, acc_2 + (x * x.conj()).re());
        let f_sum = |(acc_1, acc_2): (T, T::Real), (x_1, x_2)| (acc_1 + x_1, acc_2 + x_2);
        let f_out = |(acc_1, acc_2): (T, T::Real)| {
            let size_1 = T::from_usize(size).unwrap();
            let size_2 = T::Real::from_usize(size).unwrap();
            let mean = acc_1 / size_1;
            let var = acc_2 / size_2 - (mean * mean.conj()).re();
            var.sqrt()
        };

        let (out, layout_out) = reduce_axes_cpu_serial(a, &la.to_dim()?, axes, f_init, f, f_sum, f_out)?;

        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }
}

impl<T, D> OpL2NormAPI<T, D> for DeviceCpuSerial
where
    T: Clone + ComplexFloat + FromPrimitive,
    T::Real: Clone + ComplexFloat + FromPrimitive,
    D: DimAPI,
{
    type TOut = T::Real;

    fn l2_norm_all(&self, a: &Vec<T>, la: &Layout<D>) -> Result<T::Real> {
        let f_init = || T::Real::zero();
        let f = |acc: T::Real, x: T| acc + (x * x.conj()).re();
        let f_sum = |acc: T::Real, x: T::Real| acc + x;
        let f_out = |acc: T::Real| acc.sqrt();

        let result = reduce_all_cpu_serial(a, la, f_init, f, f_sum, f_out)?;
        Ok(result)
    }

    fn l2_norm_axes(
        &self,
        a: &Vec<T>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<T::Real>>, T::Real, Self>, Layout<IxD>)> {
        let f_init = || T::Real::zero();
        let f = |acc: T::Real, x: T| acc + (x * x.conj()).re();
        let f_sum = |acc: T::Real, x: T::Real| acc + x;
        let f_out = |acc: T::Real| acc.sqrt();

        let (out, layout_out) = reduce_axes_cpu_serial(a, &la.to_dim()?, axes, f_init, f, f_sum, f_out)?;

        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }
}

impl<T, D> OpArgMinAPI<T, D> for DeviceCpuSerial
where
    T: Clone + PartialOrd,
    D: DimAPI,
{
    type TOut = usize;

    fn argmin_axes(
        &self,
        a: &Vec<T>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<usize>>, Self::TOut, Self>, Layout<IxD>)> {
        let f_comp = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y < x)
            } else {
                Some(true)
            }
        };
        let f_eq = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y == x)
            } else {
                Some(false)
            }
        };
        let (out, layout_out) = reduce_axes_arg_cpu_serial(a, la, axes, f_comp, f_eq, RowMajor)?;
        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }

    fn argmin_all(&self, a: &Vec<T>, la: &Layout<D>) -> Result<Self::TOut> {
        let f_comp = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y < x)
            } else {
                Some(true)
            }
        };
        let f_eq = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y == x)
            } else {
                Some(false)
            }
        };
        let result = reduce_all_arg_cpu_serial(a, la, f_comp, f_eq, RowMajor)?;
        Ok(result)
    }
}

impl<T, D> OpArgMaxAPI<T, D> for DeviceCpuSerial
where
    T: Clone + PartialOrd,
    D: DimAPI,
{
    type TOut = usize;

    fn argmax_axes(
        &self,
        a: &Vec<T>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<usize>>, Self::TOut, Self>, Layout<IxD>)> {
        let f_comp = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y > x)
            } else {
                Some(true)
            }
        };
        let f_eq = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y == x)
            } else {
                Some(false)
            }
        };
        let (out, layout_out) = reduce_axes_arg_cpu_serial(a, la, axes, f_comp, f_eq, RowMajor)?;
        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }

    fn argmax_all(&self, a: &Vec<T>, la: &Layout<D>) -> Result<Self::TOut> {
        let f_comp = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y > x)
            } else {
                Some(true)
            }
        };
        let f_eq = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y == x)
            } else {
                Some(false)
            }
        };
        let result = reduce_all_arg_cpu_serial(a, la, f_comp, f_eq, RowMajor)?;
        Ok(result)
    }
}

impl<D> OpAllAPI<bool, D> for DeviceCpuSerial
where
    D: DimAPI,
{
    type TOut = bool;

    fn all_all(&self, a: &Vec<bool>, la: &Layout<D>) -> Result<bool> {
        let f_init = || true;
        let f = |acc, x| acc && x;
        let f_sum = |acc1, acc2| acc1 && acc2;
        let f_out = |acc| acc;

        reduce_all_cpu_serial(a, la, f_init, f, f_sum, f_out)
    }

    fn all_axes(
        &self,
        a: &Vec<bool>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<bool>>, bool, Self>, Layout<IxD>)> {
        let f_init = || true;
        let f = |acc, x| acc && x;
        let f_sum = |acc1, acc2| acc1 && acc2;
        let f_out = |acc| acc;

        let (out, layout_out) = reduce_axes_cpu_serial(a, &la.to_dim()?, axes, f_init, f, f_sum, f_out)?;
        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }
}

impl<D> OpAnyAPI<bool, D> for DeviceCpuSerial
where
    D: DimAPI,
{
    type TOut = bool;

    fn any_all(&self, a: &Vec<bool>, la: &Layout<D>) -> Result<bool> {
        let f_init = || false;
        let f = |acc, x| acc || x;
        let f_sum = |acc1, acc2| acc1 || acc2;
        let f_out = |acc| acc;

        reduce_all_cpu_serial(a, la, f_init, f, f_sum, f_out)
    }

    fn any_axes(
        &self,
        a: &Vec<bool>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<bool>>, bool, Self>, Layout<IxD>)> {
        let f_init = || false;
        let f = |acc, x| acc || x;
        let f_sum = |acc1, acc2| acc1 || acc2;
        let f_out = |acc| acc;

        let (out, layout_out) = reduce_axes_cpu_serial(a, &la.to_dim()?, axes, f_init, f, f_sum, f_out)?;
        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }
}

impl<T, D> OpCountNonZeroAPI<T, D> for DeviceCpuSerial
where
    T: Clone + PartialEq + Zero,
    D: DimAPI,
{
    type TOut = usize;

    fn count_nonzero_all(&self, a: &Vec<T>, la: &Layout<D>) -> Result<usize> {
        let f_init = || 0;
        let f = |acc, x| if x != T::zero() { acc + 1 } else { acc };
        let f_sum = |acc1, acc2| acc1 + acc2;
        let f_out = |acc| acc;

        reduce_all_cpu_serial(a, la, f_init, f, f_sum, f_out)
    }

    fn count_nonzero_axes(
        &self,
        a: &Vec<T>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<usize>>, usize, Self>, Layout<IxD>)> {
        let f_init = || 0;
        let f = |acc, x| if x != T::zero() { acc + 1 } else { acc };
        let f_sum = |acc1, acc2| acc1 + acc2;
        let f_out = |acc| acc;

        let (out, layout_out) = reduce_axes_cpu_serial(a, &la.to_dim()?, axes, f_init, f, f_sum, f_out)?;
        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }
}

impl<T, D> OpUnraveledArgMinAPI<T, D> for DeviceCpuSerial
where
    T: Clone + PartialOrd,
    D: DimAPI,
{
    fn unraveled_argmin_axes(
        &self,
        a: &Vec<T>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<IxD>>, IxD, Self>, Layout<IxD>)> {
        let f_comp = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y < x)
            } else {
                Some(true)
            }
        };
        let f_eq = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y == x)
            } else {
                Some(false)
            }
        };
        let (out, layout_out) = reduce_axes_unraveled_arg_cpu_serial(a, la, axes, f_comp, f_eq)?;
        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }

    fn unraveled_argmin_all(&self, a: &<Self as DeviceRawAPI<T>>::Raw, la: &Layout<D>) -> Result<D> {
        let f_comp = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y < x)
            } else {
                Some(true)
            }
        };
        let f_eq = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y == x)
            } else {
                Some(false)
            }
        };
        let result = reduce_all_unraveled_arg_cpu_serial(a, la, f_comp, f_eq)?;
        Ok(result)
    }
}

impl<T, D> OpUnraveledArgMaxAPI<T, D> for DeviceCpuSerial
where
    T: Clone + PartialOrd,
    D: DimAPI,
{
    fn unraveled_argmax_axes(
        &self,
        a: &Vec<T>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<IxD>>, IxD, Self>, Layout<IxD>)> {
        let f_comp = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y > x)
            } else {
                Some(true)
            }
        };
        let f_eq = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y == x)
            } else {
                Some(false)
            }
        };
        let (out, layout_out) = reduce_axes_unraveled_arg_cpu_serial(a, la, axes, f_comp, f_eq)?;
        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }

    fn unraveled_argmax_all(&self, a: &<Self as DeviceRawAPI<T>>::Raw, la: &Layout<D>) -> Result<D> {
        let f_comp = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y > x)
            } else {
                Some(true)
            }
        };
        let f_eq = |x: Option<T>, y: T| -> Option<bool> {
            if let Some(x) = x {
                Some(y == x)
            } else {
                Some(false)
            }
        };
        let result = reduce_all_unraveled_arg_cpu_serial(a, la, f_comp, f_eq)?;
        Ok(result)
    }
}

impl<D> OpSumBoolAPI<D> for DeviceCpuSerial
where
    D: DimAPI,
{
    fn sum_all(&self, a: &Vec<bool>, la: &Layout<D>) -> Result<usize> {
        let f_init = || 0;
        let f = |acc, x| match x {
            true => acc + 1,
            false => acc,
        };
        let f_sum = |acc1, acc2| acc1 + acc2;
        let f_out = |acc| acc;

        reduce_all_cpu_serial(a, la, f_init, f, f_sum, f_out)
    }

    fn sum_axes(
        &self,
        a: &Vec<bool>,
        la: &Layout<D>,
        axes: &[isize],
    ) -> Result<(Storage<DataOwned<Vec<usize>>, usize, Self>, Layout<IxD>)> {
        let f_init = || 0;
        let f = |acc, x| match x {
            true => acc + 1,
            false => acc,
        };
        let f_sum = |acc1, acc2| acc1 + acc2;
        let f_out = |acc| acc;

        let (out, layout_out) = reduce_axes_cpu_serial(a, &la.to_dim()?, axes, f_init, f, f_sum, f_out)?;
        Ok((Storage::new(out.into(), self.clone()), layout_out))
    }
}

impl<TA, TB, TE, D> OpAllCloseAPI<TA, TB, TE, D> for DeviceCpuSerial
where
    TA: Clone + DTypePromoteAPI<TB>,
    TB: Clone,
    <TA as DTypePromoteAPI<TB>>::Res: ExtNum<AbsOut: DTypeCastAPI<TE>>,
    TE: ExtFloat + Add<TE, Output = TE> + Mul<TE, Output = TE> + PartialOrd + Clone,
    D: DimAPI,
{
    fn allclose_all(
        &self,
        a: &<Self as DeviceRawAPI<TA>>::Raw,
        la: &Layout<D>,
        b: &<Self as DeviceRawAPI<TB>>::Raw,
        lb: &Layout<D>,
        isclose_args: &IsCloseArgs<TE>,
    ) -> Result<bool> {
        use rstsr_dtype_traits::isclose;

        if la.size() == 0 || lb.size() == 0 {
            rstsr_raise!(InvalidValue, "zero-size array is not supported for allclose")?;
        }

        let f_init = || true;
        let f = |acc: bool, (a_elem, b_elem): (TA, TB)| {
            let result = isclose(&a_elem, &b_elem, isclose_args);
            acc && result
        };
        let f_sum = |acc1: bool, acc2: bool| acc1 && acc2;
        let f_out = |acc: bool| acc;

        reduce_all_binary_cpu_serial(a, la, b, lb, f_init, f, f_sum, f_out)
    }

    fn allclose_axes(
        &self,
        _a: &<Self as DeviceRawAPI<TA>>::Raw,
        _la: &Layout<D>,
        _b: &<Self as DeviceRawAPI<TB>>::Raw,
        _lb: &Layout<D>,
        _axes: &[isize],
        _isclose_args: &IsCloseArgs<TE>,
    ) -> Result<(Storage<DataOwned<<Self as DeviceRawAPI<bool>>::Raw>, bool, Self>, Layout<IxD>)> {
        unimplemented!("This function (`allclose_axes`) is not planned to be implemented yet.");
    }
}
