Skip to main content

rustymath_kernels/
operation.rs

1//! Resolved numerical operations over shared [`Read`](crate::agreement::Read) plans. [`Kernel::try_resolve`] fixes the computing dtype, result dtype and character policy from an object's [`Formula`](crate::facts::Formula). The same definition supplies native execution and graph formulas.
2//!
3//! [`DType::computes_in`] promotes unsupported Boolean and integer inputs to i64, then to the NumPy float width. Unsupported character arithmetic is a domain error.
4use crate::buffer::{Buffer, DType, Element, FloatWidth, MapOutput, Output, Source, View, Width};
5use crate::domain::Range;
6use crate::error::{Error, Kind};
7use crate::gather::Writes;
8use crate::reduce::{Direction, Piece, Pieces, Seed, native_reduction};
9use crate::slice::Arg;
10use crate::{FUSED, Native, facts::Formula, recipe::NativeOps, tolerant::Banded};
11
12/// Numeric settings rebound for each execution, outside item loops. `tolerance` is the run's comparison tolerance: tolerant objects compare within it, and a kernel reads a float argument as a whole number within it (`Projection::Wholes`). BPL sets it nonzero only under Mathish, and Python only in Mathish mode.
13#[derive(Clone, Copy, Debug)]
14pub struct Settings { pub tolerance: crate::tolerant::Tolerance }
15impl Settings {
16    /// The scalar operations that a kernel runs with: the run's tolerance in place of each tolerant object's own.
17    pub fn ops(self) -> crate::recipe::NativeOps<{ crate::FUSED }> { crate::recipe::NativeOps { tolerance: Some(self.tolerance.value()) } }
18}
19
20/// How a kernel reads its inputs before its formula runs (`Kernel::projection`, `with_projected`).
21#[derive(Clone, Copy, PartialEq, Debug)]
22pub enum Projection {
23    /// As they are.
24    AsIs,
25    /// Characters as their codes, under a code policy, and floats as the whole numbers they equal within the tolerance.
26    Codes(crate::tolerant::Tolerance),
27    /// Floats as the whole numbers they equal within the tolerance, where the kernel computes at an integral dtype from float arguments.
28    Wholes(crate::tolerant::Tolerance),
29}
30impl Projection {
31    /// The tolerance within which this projection reads a float as a whole number, or `None` where it reads floats as they are.
32    fn wholes(self) -> Option<crate::tolerant::Tolerance> { match self { Self::AsIs => None, Self::Codes(t) | Self::Wholes(t) => Some(t) } }
33}
34
35/// A write's logical root, or storage transferred by its final reader.
36pub enum WriteRoot<'a> { Read(crate::agreement::Read<'a>), Owned(Buffer) }
37impl WriteRoot<'_> {
38    /// The written array's starting items, read at `dtype` as the combining kernel reads its inputs (`Kernel::projection`).
39    fn initialize(self, dtype: DType, checked: bool, projection: Projection, output: &mut Buffer) -> Result<(), Error> {
40        match (self, projection) {
41            (Self::Read(read), Projection::AsIs) => read.read_into(0..read.plan.len(), dtype, checked, output),
42            (Self::Read(read), projection) => with_projected(&[read], None, projection, |reads| reads[0].read_into(0..read.plan.len(), dtype, checked, output)),
43            (Self::Owned(buffer), _) if buffer.dtype() == dtype => { *output = buffer; Ok(()) }
44            (Self::Owned(buffer), Projection::AsIs) => { *output = Buffer::cast_results(buffer.view(), dtype, checked)?; Ok(()) }
45            (Self::Owned(buffer), projection) => {
46                *output = with_projected_view(buffer.view(), projection, |view| Buffer::cast_results(view, dtype, checked))??;
47                Ok(())
48            }
49        }
50    }
51}
52
53/// One resolved mathematical definition and its captured numerical policies.
54#[derive(Clone)]
55pub struct Kernel {
56    variant: crate::domain::Variant, result: DType, boolean_inputs: bool, repair: bool, raising: bool, wholes: bool, policy: Option<crate::characters::Policy>,
57    definition: std::sync::Arc<dyn Application>
58}
59
60impl std::fmt::Debug for Kernel {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        f.debug_struct("Kernel").field("variant", &self.variant).field("arity", &self.arity()).finish()
63    }
64}
65
66/// A resolved elementwise call. Kind selection is complete before this is executed.
67#[derive(Clone, Debug)]
68pub enum Map { Kernel(Kernel, Settings), Convert(crate::convert::Conversion, Settings, bool) }
69impl Map {
70    pub fn arity(&self) -> usize { match self { Self::Kernel(kernel, _) => kernel.arity(), Self::Convert(..) => 1 } }
71    /// Appends every result, including initialized fallback items on a numerical error.
72    /// Errors are execution failures, not a pre-write refusal to handle the call.
73    /// Both variants follow [`crate::error::checked`]: an enclosing checked scope owns recorded
74    /// numerical failures. Under an active error slot, `Ok` alone does not establish success.
75    /// Discard private outputs when that scope fails; neither `Err` nor a recorded failure is a route refusal.
76    pub fn run<'a, 'b>(
77        &self, inputs: &[crate::agreement::Read<'_>], range: std::ops::Range<usize>,
78        scratch: &'a mut crate::buffer::Scratch, mut output: MapOutput<'a, 'b>
79    ) -> Result<(), Error> {
80        if inputs.len() != self.arity() { output.fill(range.len()); return Err(Error::new(Kind::Length, "map arity does not match its operands")); }
81        match self {
82            Self::Kernel(kernel, settings) => kernel.map_in(inputs, range, *settings, scratch, output),
83            Self::Convert(conversion, settings, checked) => conversion.map_in(inputs[0], range, *checked, settings.tolerance, scratch, output)
84        }
85    }
86}
87
88trait Application: Send + Sync {
89    fn exact(&self, args: &[num_rational::BigRational]) -> Result<Option<num_rational::BigRational>, Error>;
90    fn has_exact(&self) -> bool;
91    fn integer(&self, variant: crate::domain::Variant, args: &[i64]) -> Result<i64, Error>;
92    fn real(&self, variant: crate::domain::Variant, args: &[f64], settings: Settings, repair: bool) -> Result<f64, Error>;
93    fn complex(&self, variant: crate::domain::Variant, args: &[num_complex::Complex64], settings: Settings) -> Result<num_complex::Complex64, Error>;
94    fn arity(&self) -> usize;
95    fn inputs(&self) -> &[crate::domain::Range];
96    fn ordered_kinds(&self, classes: [i32; 2]) -> Option<crate::characters::ByKinds>;
97    fn may_raise(&self, variant: crate::domain::Variant, ranges: &[crate::domain::Range]) -> bool;
98    fn partials(
99        &self, ops: &dyn crate::recipe::Ops<f64, V = crate::facts::Slot, B = crate::facts::Slot, I = crate::facts::Slot, W = crate::facts::Slot>,
100        x: &[crate::facts::Slot], result: crate::facts::Slot, needed: &[bool], output: &mut [Option<crate::facts::Slot>]);
101    fn range(&self, x: &[crate::domain::Range]) -> crate::domain::Range;
102    fn same(&self, x: Range) -> Option<Range>;
103    fn identity(&self) -> Option<f64>;
104    fn kept(&self, constants: &[Option<f64>]) -> Option<usize>;
105    fn reduction(&self, variant: crate::domain::Variant) -> Option<crate::reduce::Reduce>;
106    fn inverse_fold(&self) -> Option<crate::reduce::Reduce>;
107    fn truth_table(&self) -> Option<u8>;
108    fn product(
109        &self, variant: crate::domain::Variant, fold: Option<crate::reduce::Reduce>, boolean_inputs: bool, wanted: Option<DType>, settings: Settings
110    ) -> Option<NativeProduct>;
111    /// `judged`: whether each item's result is checked for errors, which a kernel skips where its arguments' types can't make one (`Kernel::may_raise`).
112    fn map(
113        &self, variant: crate::domain::Variant, inputs: Inputs<'_, '_>, range: std::ops::Range<usize>, settings: Settings, repair: bool, judged: bool,
114        output: &mut Output<'_, '_>) -> Result<(), Error>;
115    /// `route`, an ordered walk by this binary formula's per-item kernels at `variant`'s computing type (`binary_formula`).
116    fn binary(&self, variant: crate::domain::Variant, settings: Settings, route: Route<'_>, output: &mut Buffer) -> Result<(), Error>;
117}
118
119/// `object`, for arguments whose values lie in `inputs`, the ranges of their types.
120struct Definition<K, const N: usize> { object: K, inputs: [crate::domain::Range; N] }
121
122impl<K: crate::facts::Formula<N>, const N: usize> Application for Definition<K, N> {
123    fn exact(&self, args: &[num_rational::BigRational]) -> Result<Option<num_rational::BigRational>, Error> {
124        if args.len() != N { return Err(Error::new(Kind::Length, "scalar operand count changed")); }
125        self.object.exact(std::array::from_fn(|i| &args[i]))
126    }
127    fn has_exact(&self) -> bool { K::HAS_EXACT }
128    fn integer(&self, variant: crate::domain::Variant, args: &[i64]) -> Result<i64, Error> {
129        let x: [i64; N] = args.try_into().map_err(|_| Error::new(Kind::Length, "scalar operand count changed"))?;
130        let width = match variant.dtype {
131            DType::Integer(w) => w, DType::Boolean => Width::U8,
132            _ => return Err(Error::new(Kind::Domain, "an integer scalar call needs an integer computing dtype"))
133        };
134        with_width!(width, I => {
135            let ops = crate::recipe::IntegerOps::<I>::new(variant);
136            scalar_result(integer_value(&self.object, variant, &ops, x.map(<I as crate::recipe::Overflowing>::low))).map(Into::into)
137        })
138    }
139    fn real(&self, variant: crate::domain::Variant, args: &[f64], settings: Settings, repair: bool) -> Result<f64, Error> {
140        let x: [f64; N] = args.try_into().map_err(|_| Error::new(Kind::Length, "scalar operand count changed"))?;
141        let ops = settings.ops();
142        match variant.dtype {
143            DType::Float(FloatWidth::F64) => scalar_result(real_value(&self.object, variant, &ops, x, repair)),
144            DType::Float(width) => {
145                let value = scalar_result(real_value(&self.object, variant, &ops, x.map(|x| x as f32), repair))?;
146                Ok(if width == FloatWidth::F16 { half::f16::from_f32(value).to_f64() } else { f64::from(value) })
147            }
148            _ => Err(Error::new(Kind::Domain, "a real scalar call needs a floating computing dtype")),
149        }
150    }
151    fn complex(&self, variant: crate::domain::Variant, args: &[num_complex::Complex64], settings: Settings) -> Result<num_complex::Complex64, Error> {
152        let x: [num_complex::Complex64; N] = args.try_into().map_err(|_| Error::new(Kind::Length, "scalar operand count changed"))?;
153        let ops = settings.ops();
154        match variant.dtype {
155            DType::Complex(crate::buffer::ComplexWidth::F64) => scalar_result(complex_value(&self.object, variant, &ops, x)),
156            DType::Complex(crate::buffer::ComplexWidth::F32) => {
157                let value = scalar_result(complex_value(&self.object, variant, &ops, x.map(|x| num_complex::Complex32::new(x.re as f32, x.im as f32))))?;
158                Ok(num_complex::Complex64::new(f64::from(value.re), f64::from(value.im)))
159            }
160            _ => Err(Error::new(Kind::Domain, "a complex scalar call needs a complex computing dtype")),
161        }
162    }
163    fn arity(&self) -> usize { N }
164    fn inputs(&self) -> &[crate::domain::Range] { &self.inputs }
165    fn may_raise(&self, variant: crate::domain::Variant, ranges: &[crate::domain::Range]) -> bool {
166        if variant.dtype == DType::Boolean && ranges.iter().all(|r| r.within(0.0, 1.0)) { return boolean_raises(&self.object, variant); }
167        if matches!(variant.dtype, DType::Complex(_)) || variant.dtype.is_integral() && !K::INTEGER_FORMULA { return true; }
168        let Ok(args) = ranges.try_into() else { return true; };
169        // A float result raises only outside the domain that the formula declares (`Formula::domain`): range arithmetic can't show that a result isn't NaN.
170        if matches!(variant.dtype, DType::Float(_)) && self.object.domain(variant.dtype, args).is_some() { return false; }
171        self.object.emit_valid::<f64, _>(variant, &crate::recipe::RangeOps(variant.dtype), args, self.object.range(args))
172            .is_some_and(|(_, valid)| valid != Some(true))
173    }
174    fn partials(
175        &self, ops: &dyn crate::recipe::Ops<f64, V = crate::facts::Slot, B = crate::facts::Slot, I = crate::facts::Slot, W = crate::facts::Slot>,
176        x: &[crate::facts::Slot], result: crate::facts::Slot, needed: &[bool], output: &mut [Option<crate::facts::Slot>]) {
177        let (Ok(x), Ok(needed)) = (x.try_into(), needed.try_into()) else { output.fill(None); return; };
178        output.copy_from_slice(&self.object.emit_partials(ops, x, result, needed));
179    }
180    fn range(&self, x: &[crate::domain::Range]) -> crate::domain::Range { x.try_into().map_or(crate::domain::Range::ANY, |x| self.object.range(x)) }
181    fn same(&self, x: Range) -> Option<Range> { self.object.same(x) }
182    fn ordered_kinds(&self, classes: [i32; 2]) -> Option<crate::characters::ByKinds> {
183        if N != 2 || self.object.character_rule() != Some(crate::characters::Rule::Ordered) { return None; }
184        Some(crate::characters::ByKinds::ordered(classes, self.object.ordered_kinds(classes)?, K::BOOLEAN))
185    }
186    fn identity(&self) -> Option<f64> { self.object.identity().last().copied().flatten() }
187    fn kept(&self, constants: &[Option<f64>]) -> Option<usize> {
188        let identity = self.object.identity();
189        if N != 2 || constants.len() != 2 { return None; }
190        [1, 0].into_iter().find(|&i| constants[i].is_some() && constants[i] == identity[i]).map(|i| 1 - i)
191    }
192    fn reduction(&self, variant: crate::domain::Variant) -> Option<crate::reduce::Reduce> { reduction(&self.object, variant) }
193    fn inverse_fold(&self) -> Option<crate::reduce::Reduce> { self.object.inverse_fold() }
194    fn truth_table(&self) -> Option<u8> { self.object.truth_table() }
195    fn product(
196        &self, variant: crate::domain::Variant, fold: Option<crate::reduce::Reduce>, boolean_inputs: bool, wanted: Option<DType>, settings: Settings
197    ) -> Option<NativeProduct> { product_for::<K, N>(&self.object, variant, fold, boolean_inputs, wanted, settings) }
198    fn map(
199        &self, variant: crate::domain::Variant, inputs: Inputs<'_, '_>, range: std::ops::Range<usize>, settings: Settings, repair: bool, judged: bool,
200        output: &mut Output<'_, '_>) -> Result<(), Error> { map_formula::<K, N>(&self.object, variant, inputs, range, settings, repair, judged, output) }
201    fn binary(&self, variant: crate::domain::Variant, settings: Settings, route: Route<'_>, output: &mut Buffer) -> Result<(), Error> {
202        binary_formula::<K, N>(&self.object, variant, settings, route, output)
203    }
204}
205
206impl Kernel {
207    pub fn resolve<K: crate::facts::Formula<N>, const N: usize>(object: K, variant: crate::domain::Variant, types: [DType; N]) -> Result<Self, Error> {
208        Self::try_resolve(object, variant, types)?.ok_or_else(|| Error::new(Kind::Domain, "function has no numerical body for these arguments"))
209    }
210
211    pub fn circle(code: i64, inverse: bool, real_part: bool, variant: crate::domain::Variant, types: [DType; 1]) -> Result<Option<Self>, Error> {
212        macro_rules! picked {
213            ($normal:expr, $real:expr) => {
214                if real_part && !matches!(variant.dtype, DType::Complex(_)) {
215                    Self::try_resolve($real, variant, types)
216                } else {
217                    Self::try_resolve($normal, variant, types)
218                }
219            };
220        }
221        let Some(mut kernel) = (match (code, inverse) {
222            (0, _) => picked!(crate::Sqrt1mx2, crate::RealSqrt1mx2), (1, false) => picked!(crate::Sin, crate::Sin),
223            (2, false) => picked!(crate::Cos, crate::Cos), (3, false) => picked!(crate::Tan, crate::Tan),
224            (4, false) => picked!(crate::Sqrt1px2, crate::Sqrt1px2), (5, false) => picked!(crate::Sinh, crate::Sinh),
225            (6, false) => picked!(crate::Cosh, crate::Cosh), (7, false) => picked!(crate::Tanh, crate::Tanh),
226            (1, true) => picked!(crate::Asin, crate::RealAsin), (2, true) => picked!(crate::Acos, crate::RealAcos),
227            (3, true) => picked!(crate::Atan, crate::Atan), (4, true) => picked!(crate::Sqrtx2m1, crate::RealSqrtx2m1),
228            (5, true) => picked!(crate::Asinh, crate::Asinh), (6, true) => picked!(crate::Acosh, crate::RealAcosh),
229            (7, true) => picked!(crate::Atanh, crate::RealAtanh), (8, inverse) => picked!(crate::Sqrtm1mx2(inverse), crate::RealSqrtm1mx2(inverse)),
230            _ => return Ok(None)
231        })?
232        else { return Ok(None); };
233        if real_part && let DType::Complex(width) = kernel.result { kernel.result = DType::Float(width.into()); }
234        Ok(Some(kernel))
235    }
236    pub fn new<K: crate::facts::Formula<N>, const N: usize>(object: K, variant: crate::domain::Variant) -> Self {
237        Self::typed(object, variant, [variant.dtype; N])
238    }
239    /// The kernel for arguments of `types`, whose values bound the errors it can raise (`may_raise`).
240    fn typed<K: crate::facts::Formula<N>, const N: usize>(object: K, variant: crate::domain::Variant, types: [DType; N]) -> Self {
241        let result = object.result_dtype(variant.dtype);
242        let definition: std::sync::Arc<dyn Application> = std::sync::Arc::new(Definition::<K, N> { object, inputs: types.map(Range::of_dtype) });
243        let raising = definition.may_raise(variant, definition.inputs());
244        let wholes = variant.dtype.is_integral() && types.iter().any(|d| matches!(d, DType::Float(_)));
245        Self { repair: true, raising, result, policy: None, boolean_inputs: types.iter().all(|&d| d == DType::Boolean), wholes, variant, definition }
246    }
247    pub fn try_resolve<K: crate::facts::Formula<N>, const N: usize>(
248        object: K, mut variant: crate::domain::Variant, types: [DType; N]) -> Result<Option<Self>, Error> {
249        use crate::characters::{ByKinds, Policy};
250        let characters = if N == 2 {
251            object.character_policy([types[0], types[1]])?
252        } else if N == 1 && types[0] == DType::Character && object.character_rule() == Some(crate::characters::Rule::Identity) {
253            Some(Policy::Codes { computing: DType::Integer(Width::I32), result: DType::Character })
254        } else {
255            if types.contains(&DType::Character) { return Err(Error::new(Kind::Domain, "function does not take character arguments")); }
256            None
257        };
258        let result = match characters {
259            Some(Policy::Codes { computing, result }) => { variant.dtype = computing; result }
260            Some(Policy::Kinds(kind)) => {
261                let result = match kind { ByKinds::Truth(_) => DType::Boolean, ByKinds::Left => types[0], ByKinds::Right => types[1] };
262                variant.dtype = if matches!(kind, ByKinds::Truth(_)) || result == DType::Character { DType::Integer(Width::I32) } else { result };
263                result
264            }
265            None => {
266                if K::BOOLEAN && variant.dtype.is_integral() {
267                    let Some(dtype) = types.into_iter().try_fold(DType::Boolean, DType::common) else { return Ok(None); };
268                    variant.dtype = dtype;
269                }
270                let Some(dtype) = object.computing_dtype(variant.dtype) else { return Ok(None); };
271                variant.dtype = dtype;
272                object.result_dtype(variant.dtype)
273            }
274        };
275        let mut kernel = Self::typed(object, variant, types);
276        kernel.result = result;
277        kernel.policy = characters;
278        Ok(Some(kernel))
279    }
280    /// `op` resolved for arguments of types `types` in `variant`, as [`Kernel::try_resolve`] resolves its object: `Ok(None)` where it has no body for them, or `types` doesn't hold one type per argument. Non-generic, so every formula's kernel code compiles in rustymath alone.
281    pub fn of(op: crate::facts::Op, variant: crate::domain::Variant, types: &[DType]) -> Result<Option<Self>, Error> {
282        struct Resolve<'a>(crate::domain::Variant, &'a [DType]);
283        impl crate::facts::Visit for Resolve<'_> {
284            type Out = Result<Option<Kernel>, Error>;
285            fn visit<K: crate::facts::Formula<N>, const N: usize>(self, object: K) -> Self::Out {
286                let Ok(types) = self.1.try_into() else { return Ok(None) };
287                Kernel::try_resolve(object, self.0, types)
288            }
289        }
290        op.visit(types.len(), Resolve(variant, types))
291    }
292    /// The kernel of `op`'s partial in its argument `side` (`derivative::Slope`), whose arguments are `op`'s and then `op`'s result, one type each in `types`, resolved as [`Kernel::of`] resolves `op`. `Ok(None)` also where `op` takes three arguments.
293    pub fn slope(op: crate::facts::Op, side: usize, variant: crate::domain::Variant, types: &[DType]) -> Result<Option<Self>, Error> {
294        use crate::derivative::Slope;
295        struct Slopes<'a>(usize, crate::domain::Variant, &'a [DType]);
296        impl crate::facts::Visit for Slopes<'_> {
297            type Out = Result<Option<Kernel>, Error>;
298            fn visit<K: crate::facts::Formula<N>, const N: usize>(self, _: K) -> Self::Out { Ok(None) }
299            fn unary<K: crate::facts::Formula<1>>(self, object: K) -> Self::Out {
300                let Ok(types) = self.2.try_into() else { return Ok(None) };
301                Kernel::try_resolve(Slope(object), self.1, types)
302            }
303            fn binary<K: crate::facts::Formula<2>>(self, object: K) -> Self::Out {
304                let Ok(types) = self.2.try_into() else { return Ok(None) };
305                if self.0 == 0 { Kernel::try_resolve(Slope::<K, 0>(object), self.1, types) } else { Kernel::try_resolve(Slope::<K, 1>(object), self.1, types) }
306            }
307        }
308        op.visit(types.len().saturating_sub(1), Slopes(side, variant, types))
309    }
310    pub fn arity(&self) -> usize { self.definition.arity() }
311    pub fn result_dtype(&self) -> DType { self.result }
312    /// The resolved computing dtype and numerical policies.
313    pub fn variant(&self) -> crate::domain::Variant { self.variant }
314    /// Select whether this operation reports item errors.
315    pub fn with_checked(mut self, checked: bool) -> Self {
316        self.variant.checked = checked;
317        self.raising = self.definition.may_raise(self.variant, self.definition.inputs());
318        self
319    }
320    pub fn input_policy(&self) -> Option<crate::characters::Policy> { self.policy }
321    pub fn selected_input(&self) -> Option<usize> {
322        use crate::characters::{ByKinds, Policy};
323        match self.policy { Some(Policy::Kinds(ByKinds::Left)) => Some(0), Some(Policy::Kinds(ByKinds::Right)) => Some(1), _ => None }
324    }
325    /// Resolve an order-only operation on disjoint logical domains, before numeric promotion.
326    pub fn ordered(mut self, classes: &[Option<i32>], types: &[DType]) -> Self {
327        let [Some(left), Some(right)] = classes else { return self; };
328        if left == right { return self; }
329        let Some(policy) = self.definition.ordered_kinds([*left, *right]) else { return self; };
330        use crate::characters::{ByKinds, Policy};
331        self.result = match policy { ByKinds::Truth(_) => DType::Boolean, ByKinds::Left => types[0], ByKinds::Right => types[1] };
332        self.policy = Some(Policy::Kinds(policy));
333        self
334    }
335    pub fn exact(&self, args: &[num_rational::BigRational]) -> Result<Option<num_rational::BigRational>, Error> { self.definition.exact(args) }
336    pub fn has_exact(&self) -> bool { self.definition.has_exact() }
337    /// The formula on one item of each integer argument, at the kernel's integer computing width, with its checked or wrapping variant. A Boolean computing kind reads 0 and 1.
338    pub fn integers(&self, args: &[i64]) -> Result<i64, Error> { crate::error::checked(|| self.definition.integer(self.variant, args)) }
339    pub fn real(&self, args: &[f64], settings: Settings) -> Result<f64, Error> {
340        crate::error::checked(|| self.definition.real(self.variant, args, settings, self.repair))
341    }
342    pub fn complex(&self, args: &[num_complex::Complex64], settings: Settings) -> Result<num_complex::Complex64, Error> {
343        let value = crate::error::checked(|| self.definition.complex(self.variant, args, settings))?;
344        Ok(if matches!(self.result, DType::Complex(_)) { value } else { num_complex::Complex64::new(value.re, 0.0) })
345    }
346    fn kind_result<T: Copy>(&self, x: &[T], truth: impl FnOnce(bool) -> T) -> Option<T> {
347        use crate::characters::{ByKinds, Policy};
348        Some(match self.policy? {
349            Policy::Kinds(ByKinds::Truth(value)) => truth(value), Policy::Kinds(ByKinds::Left) => x[0], Policy::Kinds(ByKinds::Right) => x[1], _ => return None
350        })
351    }
352    /// Whether checked execution can record a numeric data error, for arguments that hold any values of their types.
353    pub fn may_raise(&self) -> bool { self.variant.checked && (self.result == DType::Character || self.wholes || self.raising) }
354    /// Whether checked execution can record a numeric data error where each argument holds values of its type within its range in `narrowed`, such as a constant's own value or the nonnegative numbers.
355    pub fn may_raise_on(&self, narrowed: &[Range]) -> bool {
356        self.variant.checked && (self.result == DType::Character || self.wholes || self.definition.may_raise(self.variant, &self.points(narrowed)))
357    }
358    /// The result bound on arguments within `narrowed`. `same` says both arguments denote one value, enabling the formula's same-input fact.
359    pub fn range_on(&self, narrowed: &[Range], same: bool) -> Range {
360        let points = self.points(narrowed);
361        if same && let [x, y] = points.as_slice() && let Some(range) = self.definition.same(x.meet(*y)) { return self.bound(range); }
362        self.range(&points)
363    }
364    /// Each argument's range: its type's values within its range in `narrowed`.
365    fn points(&self, narrowed: &[Range]) -> Vec<Range> { self.definition.inputs().iter().zip(narrowed).map(|(&range, &n)| range.meet(n)).collect() }
366    pub fn partials(
367        &self, ops: &dyn crate::recipe::Ops<f64, V = crate::facts::Slot, B = crate::facts::Slot, I = crate::facts::Slot, W = crate::facts::Slot>,
368        x: &[crate::facts::Slot], result: crate::facts::Slot, needed: &[bool], output: &mut [Option<crate::facts::Slot>]) {
369        if let Some(side) = self.kind_result(&[Some(0), Some(1)], |_| None) {
370            for (i, (&needed, output)) in needed.iter().zip(output).enumerate() { *output = needed.then(|| ops.number(u8::from(side == Some(i)) as f64)); }
371            return;
372        }
373        self.definition.partials(ops, x, result, needed, output)
374    }
375    pub fn range(&self, x: &[crate::domain::Range]) -> crate::domain::Range {
376        self.bound(self.kind_result(x, |value| Range::of(Some(u8::from(value) as f64))).unwrap_or_else(|| self.definition.range(x)))
377    }
378    /// A mathematical result interval as this kernel stores it: rounded to its width and widened for wrapping integers.
379    fn bound(&self, range: Range) -> Range {
380        if matches!(self.variant.dtype, DType::Complex(_)) { return Range::ANY; }
381        let range = range.at_width(self.result);
382        let bounds = Range::of_dtype(self.result);
383        // f64 endpoints cannot prove i64 wrapping bounds. Narrower integers are exact here until their result exceeds their type.
384        if self.variant.dtype.is_integral() && !self.variant.fails_on_overflow() && self.result.is_integral()
385            && (self.result == DType::Integer(Width::I64) || !range.within(bounds.lo, bounds.hi)) { bounds }
386        // Checked arithmetic can rerun wider; only a Boolean result is bounded independently of that attempt.
387        else if self.result == DType::Boolean { range.meet(bounds) } else { range }
388    }
389    pub fn identity(&self) -> Option<f64> { self.definition.identity() }
390    /// The argument that a dyadic call returns unchanged where `constants` gives the arguments that are one number, 0 for the left and 1 for the right, from the formula's identities: `x+0` and `0+x` keep `x`, `x-0` keeps `x`, and `0-x` keeps nothing.
391    pub fn kept(&self, constants: &[Option<f64>]) -> Option<usize> { self.definition.kept(constants) }
392    /// The reduction by which a fold through this kernel combines its items, as `reduction` gives it at the kernel's variant.
393    pub fn fold(&self) -> Option<crate::reduce::Reduce> { self.definition.reduction(self.variant) }
394    /// The variant a fold computes in. An unseeded fold of Booleans that counts its trues (`+/`) reads them as Booleans, with no cast to the integer it computes in.
395    fn fold_variant(&self, seed: &Seed<'_>) -> crate::domain::Variant {
396        let boolean = crate::domain::Variant { dtype: DType::Boolean, ..self.variant };
397        let counts = self.boolean_inputs && matches!(seed, Seed::Identity) && matches!(self.definition.reduction(boolean), Some(crate::reduce::Reduce::Sum));
398        if counts { boolean } else { self.variant }
399    }
400    /// The reduction that an unseeded right fold by this formula runs as an alternating one, as `-` runs as an alternating `+`.
401    pub fn inverse_fold(&self) -> Option<crate::reduce::Reduce> { self.definition.inverse_fold() }
402    /// The reduction that runs an unseeded right fold by this binary formula as an alternating fold (`inverse_fold`).
403    fn alternated(&self, seed: Seed<'_>, direction: Direction) -> Option<crate::reduce::Reduce> {
404        if self.arity() == 2 && direction == Direction::Right && matches!(seed, Seed::Identity) { self.inverse_fold() } else { None }
405    }
406    pub fn truth_table(&self) -> Option<u8> { self.definition.truth_table() }
407    pub fn total_dtype(&self) -> Option<DType> { self.fold()?.total_dtype(self.variant) }
408    pub fn fold_dtype(&self, input: DType, len: usize, seed: Option<DType>) -> DType {
409        self.total_dtype().unwrap_or_else(|| match (len, seed) { (0, Some(seed)) => seed, (1, None) if input != DType::Boolean => input, _ => self.result })
410    }
411    /// The scan's result dtype. A seeded comparison stores only Boolean results; without a seed, each lane retains its original first item. Native reductions can accumulate at a wider type.
412    pub fn scan_dtype(&self, seeded: bool) -> DType {
413        if seeded && self.result == DType::Boolean { return DType::Boolean; }
414        let carry = match self.variant.dtype { DType::Float(FloatWidth::F16) => DType::Float(FloatWidth::F32), dtype => dtype };
415        self.total_dtype().unwrap_or_else(|| carry.common(self.result).unwrap_or(self.result))
416    }
417    /// The dtype of this map kernel's product with `fold` over `plan`: its native product's result where one runs (`product_for`), and otherwise `fold`'s total of this kernel's results along the contracted axes (`fold_dtype`), as a fold of them gives.
418    pub fn product_dtype(&self, fold: &Kernel, plan: &crate::linalg::ProductPlan) -> DType {
419        self.native_product(self.variant, fold, self.boolean_inputs, None, Settings { tolerance: crate::tolerant::Tolerance::EXACT })
420            .map_or_else(|| fold.fold_dtype(self.result, plan.contracted_len(), None), |p| p.result)
421    }
422    fn native_product(
423        &self, variant: crate::domain::Variant, fold: &Kernel, boolean_inputs: bool, wanted: Option<DType>, settings: Settings) -> Option<NativeProduct> {
424        if matches!(self.policy, Some(crate::characters::Policy::Kinds(_))) { return None; }
425        self.definition.product(variant, fold.fold(), boolean_inputs, wanted, settings)
426    }
427    /// How this kernel reads its inputs (`with_projected`) in a run with `settings`: characters as codes under a code policy, floats as the whole numbers they equal within `settings.tolerance` where it computes at an integral dtype from float arguments, and otherwise as they are.
428    fn projection(&self, settings: Settings) -> Projection {
429        match self.policy {
430            Some(crate::characters::Policy::Codes { .. }) => Projection::Codes(settings.tolerance),
431            None if self.wholes => Projection::Wholes(settings.tolerance), _ => Projection::AsIs
432        }
433    }
434    /// `call` on `inputs` and `seed`'s read as this kernel reads them in a run with `settings` (`projection`): over `range`, or over the whole of each read where `None`. `range` applies to every read, so a call with a seed passes `None`. `call` runs in an error slot ([`crate::error::checked`]): an error that a kernel records, such as an integer overflow that a native body reports, becomes its result. Every kernel call on arrays comes through here.
435    fn projected<R>(
436        &self, inputs: &[crate::agreement::Read<'_>], range: Option<std::ops::Range<usize>>, seed: Seed<'_>, settings: Settings,
437        call: impl FnOnce(&[crate::agreement::Read<'_>], Seed<'_>) -> Result<R, Error>) -> Result<R, Error> {
438        crate::error::checked(|| {
439            let projection = self.projection(settings);
440            if projection == Projection::AsIs { return call(inputs, seed); }
441            let mut all = inputs.to_vec();
442            all.extend(seed.read());
443            with_projected(&all, range, projection, |projected| {
444                let seed = match seed {
445                    Seed::Initial(_) => Seed::Initial(projected[inputs.len()]), Seed::Tail(_) => Seed::Tail(projected[inputs.len()]),
446                    Seed::Identity => Seed::Identity
447                };
448                call(&projected[..inputs.len()], seed)
449            })
450        })
451    }
452    pub fn map(&self, inputs: &[crate::agreement::Read<'_>], range: std::ops::Range<usize>, settings: Settings, output: &mut Buffer) -> Result<(), Error> {
453        output.clear();
454        self.mapping(Inputs(inputs), range, settings, MapOutput::Buffer(output), None)
455    }
456
457    /// Appends a mapped range using the caller's execution-local conversion buffers.
458    pub fn map_in<'a, 'b>(
459        &self, inputs: &[crate::agreement::Read<'_>], range: std::ops::Range<usize>, settings: Settings,
460        scratch: &'a mut crate::buffer::Scratch, output: MapOutput<'a, 'b>
461    ) -> Result<(), Error> {
462        self.mapping(Inputs(inputs), range, settings, output, Some(scratch))
463    }
464
465    fn mapping<'a, 'b>(
466        &self, inputs: Inputs<'_, '_>, range: std::ops::Range<usize>, settings: Settings, mut target: MapOutput<'a, 'b>,
467        scratch: Option<&'a mut crate::buffer::Scratch>
468    ) -> Result<(), Error> {
469        use crate::characters::{ByKinds, Policy};
470        if inputs.len() != self.arity() { target.fill(range.len()); return Err(Error::new(Kind::Length, "kernel operand count changed")); }
471        if range.is_empty() { return Ok(()); }
472        let mut output = Output { target, result_dtype: self.result, checked: self.variant.fails_on_overflow(), scratch };
473        crate::error::checked(|| match self.policy {
474            Some(Policy::Kinds(ByKinds::Truth(value))) => map::constant(value, range.len(), &mut output),
475            Some(Policy::Kinds(side)) => map::copied(inputs.selected(usize::from(side == ByKinds::Right)), range, self.variant.checked, &mut output),
476            _ => {
477                let mut entered = false;
478                let result = self.projected(inputs.0, Some(range.clone()), Seed::Identity, settings, |reads, _| {
479                    entered = true;
480                    self.definition.map(self.variant, Inputs(reads), range.clone(), settings, self.repair, self.may_raise(), &mut output)
481                });
482                if !entered { output.target.fill(range.len()); }
483                result
484            }
485        })
486    }
487    pub fn fold_pieces(
488        &self, dims: [usize; 3], seed: Seed<'_>, direction: Direction, settings: Settings, pieces: &Pieces<'_>, output: &mut Buffer) -> Result<(), Error> {
489        let dtype = output.dtype();
490        let variant = self.fold_variant(&seed);
491        let projection = self.projection(settings);
492        let projected = |range, f: &mut dyn FnMut(usize, Piece<'_>)| {
493            pieces(range, &mut |range, piece| match piece {
494                Piece::Items(view) => {
495                    let n = view.len();
496                    // A piece that isn't whole numbers records its error, and the fold reads zeros in its place.
497                    if let Err(error) = with_projected_view(view, projection, |view| f(range, Piece::Items(view))) {
498                        crate::error::record(error);
499                        f(range, Piece::Items(View::Integers(crate::buffer::Ints::I64(&vec![0; n]))));
500                    }
501                }
502                Piece::Total(total) => f(range, Piece::Total(total)),
503            })
504        };
505        let pieces: &Pieces<'_> = if projection == Projection::AsIs { pieces } else { &projected };
506        self.projected(&[], None, seed, settings, |_, seed| {
507            if let Some(fold) = self.definition.reduction(variant) { return fold.run_pieces(variant, dims, seed, pieces, output); }
508            if let Some(fold) = self.alternated(seed, direction) && let Some(buffer) = fold.inverse_pieces(self.variant, dims, pieces)? {
509                *output = buffer;
510                return Ok(());
511            }
512            self.definition.binary(self.variant, settings, Route::Fold { dims, seed, direction, pieces }, output)
513        })?;
514        finish(output, dtype, self.variant)
515    }
516    pub fn scan(
517        &self, dims: [usize; 3], seed: Seed<'_>, direction: Direction, settings: Settings, input: crate::agreement::Read<'_>, output: &mut Buffer
518    ) -> Result<(), Error> {
519        let dtype = output.dtype();
520        let seeded = seed.read().is_some();
521        self.projected(&[input], None, seed, settings, |inputs, seed| {
522            if let Some(fold) = self.fold() { return fold.run_scan(self.variant, dims, seed, direction, inputs[0], output); }
523            if direction == Direction::Left && self.inverse_fold() == Some(crate::reduce::Reduce::Sum)
524                && crate::reduce::negated_scan(self.variant, dims, seed, inputs[0], output)? { return Ok(()); }
525            self.definition.binary(self.variant, settings, Route::Scan { dims, seed, direction, input: inputs[0] }, output)
526        })?;
527        // A seed makes every output a formula result; a Boolean formula already guarantees 0 or 1.
528        let checked = self.variant.checked && !(seeded && dtype == DType::Boolean && self.result == DType::Boolean);
529        finish(output, dtype, crate::domain::Variant { checked, ..self.variant })
530    }
531    /// Each item of `input`, an array of shape `dims`, combined with the item a row before it along the middle axis, and each lane's first row with `seed`, or kept as it is without one, as an inverse scan runs: `flip` swaps the arguments, so the earlier item comes second. The results take `output`'s dtype, so a comparison's 0s and 1s and an unseeded first row keep the items' type where the caller asks for it.
532    pub fn adjacent(
533        &self, dims: [usize; 3], seed: Option<crate::agreement::Read<'_>>, flip: bool, settings: Settings, input: crate::agreement::Read<'_>,
534        output: &mut Buffer) -> Result<(), Error> {
535        let dtype = output.dtype();
536        self.projected(&[input], None, seed.map_or(Seed::Identity, Seed::Initial), settings, |inputs, seed| {
537            let seed = seed.read();
538            self.definition.binary(self.variant, settings, Route::Adjacent { dims, seed, flip, input: inputs[0] }, output)
539        })?;
540        finish(output, dtype, self.variant)
541    }
542    /// Folds along `axis` of `input`, an array of `shape`, or all its items for `None`, into `output`. Whole-array commutative folds may read a contiguous block in storage order; ordered folds retain logical order. A returned order reads `output` in the result's order, for folds that stay in their source's order (`Buffer::ordered`).
543    pub fn fold_read(
544        &self, shape: &[usize], axis: Option<usize>, seed: Seed<'_>, direction: Direction, settings: Settings, input: crate::agreement::Read<'_>,
545        output: &mut Buffer) -> Result<Option<Vec<crate::gather::Steps>>, Error> {
546        let dtype = output.dtype();
547        let variant = self.fold_variant(&seed);
548        let order = self.projected(&[input], None, seed, settings, |inputs, seed| {
549            let input = inputs[0];
550            if let Some(fold) = self.definition.reduction(variant) { return fold.run_read(variant, shape, axis, seed, input, output); }
551            let flat = [crate::layout::count(shape)];
552            let (shape, axis) = axis.map_or((flat.as_slice(), 0), |axis| (shape, axis));
553            if let Some(fold) = self.alternated(seed, direction) && let Some(buffer) = fold.inverse_read(variant, shape, axis, input)? {
554                *output = buffer;
555                return Ok(None);
556            }
557            let pieces = |range: std::ops::Range<usize>, emit: &mut dyn FnMut(usize, Piece<'_>)| {
558                let (start, mut scratch) = (range.start, Buffer::with_capacity(variant.dtype, 0));
559                emit(start, Piece::Items(input.part_view(range, variant.dtype, variant.checked, &mut scratch)?));
560                Ok(())
561            };
562            self.definition
563                .binary(variant, settings, Route::Fold { dims: crate::layout::Lanes::new(shape, axis)?.dims(), seed, direction, pieces: &pieces }, output)
564                .map(|()| None)
565        })?;
566        finish(output, dtype, self.variant).map(|()| order)
567    }
568}
569
570pub fn choose(
571    selector: (crate::agreement::Read<'_>, DType), choices: &[crate::agreement::Read<'_>], range: std::ops::Range<usize>, dtype: DType, checked: bool,
572    output: &mut Buffer) -> Result<(), Error> {
573    let reads = || std::iter::once(selector.0).chain(choices.iter().copied());
574    let len = reads().map(|read| read.plan.len()).find(|&n| n != 1).unwrap_or(1);
575    if reads().any(|read| read.plan.len() != 1 && read.plan.len() != len) { return Err(Error::new(Kind::Length, "selection operands have different lengths")); }
576    if range.start > range.end || (len != 1 && range.end > len) { return Err(Error::new(Kind::Length, "selection range exceeds its operands")); }
577    if output.dtype() != dtype { *output = Buffer::with_capacity(dtype, range.len()); }
578    with_dtype!(dtype, T => choose_typed::<T>(selector, choices, range, checked, T::vec_mut(output).expect("selection output dtype")))
579}
580
581fn choose_typed<T: crate::buffer::Cast>(
582    selector: (crate::agreement::Read<'_>, DType), choices: &[crate::agreement::Read<'_>], range: std::ops::Range<usize>, checked: bool, output: &mut Vec<T>
583) -> Result<(), Error> {
584    output.clear();
585    let start = range.start;
586    crate::error::checked(|| {
587        crate::parallel::extended(output, range.len(), crate::CHUNK, &|range, slots| {
588            let part = |read: crate::agreement::Read<'_>| if read.plan.len() == 1 { 0..1 } else { start + range.start..start + range.end };
589            let mut storage: Vec<Vec<T>> = choices.iter().map(|_| Vec::new()).collect();
590            let args = choices.iter().zip(&mut storage).map(|(read, scratch)| read.part::<T>(part(*read), checked, scratch)).collect::<Result<Vec<_>, _>>();
591            // A Boolean mask between two whole choices picks each item in one branch-free loop, with no positions.
592            if selector.1 == DType::Boolean && let Ok([Arg::Many(no), Arg::Many(yes)]) = args.as_deref() && no.len() == range.len() && yes.len() == range.len()
593            {
594                let mut bits = Vec::new();
595                if let Ok(Arg::Many(mask)) = selector.0.part::<bool>(part(selector.0), checked, &mut bits) && mask.len() == range.len() {
596                    return unsafe {
597                        slots.fill(range.len(), |out| {
598                            for (((slot, &m), &n), &y) in out.iter_mut().zip(mask.iter()).zip(no.iter()).zip(yes.iter()) {
599                                slot.write(core::hint::select_unpredictable(m, y, n));
600                            }
601                            true
602                        })
603                    };
604                }
605            }
606            let mut indices = Buffer::with_capacity(selector.1, 0);
607            let positions = selector.0.part_view(part(selector.0), selector.1, false, &mut indices)
608                .and_then(|view| crate::structure::positions(view, choices.len(), crate::tolerant::Tolerance::EXACT));
609            match positions.and_then(|positions| args.map(|args| (positions, args))) {
610                Ok((positions, args)) => unsafe {
611                    slots.fill(range.len(), |out| {
612                        for (i, slot) in out.iter_mut().enumerate() {
613                            let position = positions[if positions.len() == 1 { 0 } else { i }];
614                            let value = match args.get(position) {
615                                Some(Arg::One(value)) => *value, Some(Arg::Many(values)) => values[if values.len() == 1 { 0 } else { i }], None => T::FILL
616                            };
617                            slot.write(value);
618                        }
619                        true
620                    })
621                },
622                Err(error) => {
623                    crate::error::record(error);
624                    unsafe { slots.fill(range.len(), |out| { for slot in out { slot.write(T::FILL); } false }) }
625                }
626            }
627        });
628        Ok(())
629    })
630}
631
632/// The magnitude below which every whole float converts to an `i64`.
633const I64_LIMIT: f64 = 9_223_372_036_854_775_808.0;
634
635/// `values` as the whole numbers they equal within tolerance `t` (`tolerant::wholes`), as an integer computation reads float arguments. BPL passes the comparison tolerance, which only Mathish makes nonzero. A fraction, or a magnitude of 2⁶³ or more, is DOMAIN.
636pub fn whole_numbers<F: crate::Float>(t: crate::tolerant::Tolerance, values: &[F]) -> Result<Vec<i64>, Error> {
637    crate::tolerant::wholes(t, values, I64_LIMIT).map(|(numbers, _)| numbers)
638        .ok_or_else(|| Error::new(Kind::Domain, "an integer computation's float argument must be a whole number"))
639}
640
641/// `f` on `view` as an integer computation reads it (`with_projected`): characters as their codes under `Projection::Codes`, and floats as the whole numbers they equal within the projection's tolerance (`whole_numbers`).
642fn projected_view(view: View<'_>, projection: Projection) -> Result<crate::buffer::Items<'_>, Error> {
643    use crate::buffer::{IntBuf, Items};
644    let view = if matches!(projection, Projection::Codes(_)) { crate::characters::as_codes(view) } else { view };
645    let Some(t) = projection.wholes().filter(|_| matches!(view.dtype(), DType::Float(_))) else { return Ok(Items::Borrowed(view)) };
646    let mut floats = Vec::new();
647    <f64 as crate::buffer::Cast>::extend_cast(view, view.len(), true, &mut floats)?;
648    Ok(Items::Owned(Buffer::Integers(IntBuf::I64(whole_numbers(t, &floats)?))))
649}
650
651fn with_projected_view<R>(view: View<'_>, projection: Projection, f: impl FnOnce(View<'_>) -> R) -> Result<R, Error> {
652    Ok(f(projected_view(view, projection)?.view()))
653}
654
655/// `call` on `inputs` as an integer computation reads them: characters as their codes under `Projection::Codes`, and floats as the whole numbers they equal within the projection's tolerance (`whole_numbers`), as `i64`, over `range`, or over the whole of each input where `None`.
656fn with_projected<R>(
657    inputs: &[crate::agreement::Read<'_>], range: Option<std::ops::Range<usize>>, projection: Projection,
658    call: impl FnOnce(&[crate::agreement::Read<'_>]) -> Result<R, Error>) -> Result<R, Error> {
659    use crate::{agreement::{Binding, Read, ReadPlan}, buffer::{FloatBuf, IntBuf}};
660    let Some(t) = projection.wholes() else { return call(inputs) };
661    let codes = matches!(projection, Projection::Codes(_));
662    let sources: Vec<Vec<_>> = if codes { inputs.iter().map(|r| r.sources.iter().map(|s| s.as_codes()).collect()).collect() } else { Vec::new() };
663    // Character reads keep their registered character sources, including empty buffers.
664    let code_plans = if !codes { Vec::new() } else {
665        inputs.iter().map(|read| {
666            let mut characters = false;
667            read.plan.try_for_each_source(&mut |source, _| {
668                characters |= read.sources.get(source).is_some_and(|binding| binding.view.dtype() == DType::Character);
669                Ok::<_, Error>(())
670            })?;
671            Ok(read.plan.as_codes(characters))
672        }).collect::<Result<Vec<_>, Error>>()?
673    };
674    let mut projected: Vec<Read<'_>> =
675        if codes { sources.iter().zip(&code_plans).map(|(sources, plan)| Read { sources, plan }).collect() } else { inputs.to_vec() };
676    if projected.iter().all(|r| r.sources.iter().all(|s| !matches!(s.view.dtype(), DType::Float(_)))) { return call(&projected); }
677    let converted = projected.iter().map(|read| {
678        if !read.sources.iter().any(|s| matches!(s.view.dtype(), DType::Float(_))) { return Ok(None); }
679        let range = range.clone().unwrap_or(0..read.plan.len());
680        let mut floats = Buffer::with_capacity(DType::Float(FloatWidth::F64), range.len());
681        read.read_into(range.clone(), floats.dtype(), true, &mut floats)?;
682        let Buffer::Floats(FloatBuf::F64(values)) = &floats else { unreachable!("a read into 64-bit floats gives them") };
683        Ok(Some((range.start, Buffer::Integers(IntBuf::I64(whole_numbers(t, values)?)))))
684    }).collect::<Result<Vec<_>, Error>>()?;
685    let bindings = converted.iter().zip(inputs)
686        .map(|(buffer, read)| buffer.as_ref().map(|(start, b)| Binding::window(b.view(), *start, read.plan.len())).transpose()).collect::<Result<Vec<_>, _>>()?;
687    let plans: Vec<_> = inputs.iter().map(|r| ReadPlan::input(0, r.plan.len())).collect();
688    for (i, binding) in bindings.iter().enumerate() {
689        if let Some(binding) = binding { projected[i] = Read { sources: std::slice::from_ref(binding), plan: &plans[i] }; }
690    }
691    call(&projected)
692}
693
694macro_rules! with_binary_body {
695    ($object:expr, $variant:expr, $settings:expr, |$T:ident, $apply:ident, $compare:ident| $body:expr) => {{
696        let (object, variant, settings) = ($object, $variant, $settings);
697        let ops = settings.ops();
698        match variant.dtype {
699            DType::Boolean | DType::Integer(_) => {
700                let width = match variant.dtype { DType::Integer(w) => w, _ => Width::U8 };
701                with_width!(width, $T => {
702                    let ops = crate::recipe::IntegerOps::<$T>::new(variant);
703                    let $apply = { #[inline(always)] |x: $T, y: $T| integer_value(object, variant, &ops, std::array::from_fn(|i| if i == 0 { x } else { y })) };
704                    let $compare = |_: $T, _: &[$T], _: bool, _: &mut Vec<bool>| None;
705                    $body
706                })
707            },
708            DType::Float(FloatWidth::F16 | FloatWidth::F32) => {
709                type $T = f32;
710                let $apply = { #[inline(always)] move |x: $T, y: $T| real_value(object, variant, &ops, std::array::from_fn(|i| if i == 0 { x } else { y }), true) };
711                let $compare = comparison_writer(object, &ops);
712                $body
713            },
714            DType::Float(FloatWidth::F64) => {
715                type $T = f64;
716                let $apply = { #[inline(always)] move |x: $T, y: $T| real_value(object, variant, &ops, std::array::from_fn(|i| if i == 0 { x } else { y }), true) };
717                let $compare = comparison_writer(object, &ops);
718                $body
719            },
720            DType::Complex(w) => with_complex_width!(w, $T => {
721                let $apply = { #[inline(always)] move |x: $T, y: $T| complex_value(object, variant, &ops, std::array::from_fn(|i| if i == 0 { x } else { y })) };
722                let $compare = |_: $T, _: &[$T], _: bool, _: &mut Vec<bool>| None;
723                $body
724            }),
725            DType::Character => Err(Error::new(Kind::Domain, "a numerical body needs code-point inputs for characters")),
726        }
727    }};
728}
729
730/// Compares a block against a constant carry through the same tolerance bands as an elementwise map.
731fn comparison_writer<K: Formula<N>, F: Native + Banded, const N: usize>(
732    object: &K, ops: &NativeOps<{ FUSED }>) -> impl Fn(F, &[F], bool, &mut Vec<bool>) -> Option<bool> + Copy {
733    let bands = object.bands().and_then(|(t, left, right)| Some((crate::facts::run_tolerance::<F, _>(ops, t)?, left, right)));
734    move |carry, items, flipped, out| {
735        let (t, left, right) = bands?;
736        let (x, y) = (Arg::One(carry), Arg::Many(items));
737        let (x, y) = if flipped { (y, x) } else { (x, y) };
738        let (at, n) = (out.len(), items.len());
739        out.reserve(n);
740        let ok = crate::tolerant::against(t, x, y, n, &mut out.spare_capacity_mut()[..n], left, right, F::banded)?;
741        // SAFETY: `against` writes all `n` slots when it returns `Some`.
742        unsafe { out.set_len(at + n) };
743        Some(ok)
744    }
745}
746
747/// The results of `step` for each pair of items of `xs` and `ys`, written to `out`, in one loop that carries each result's validity as a flag with no branch. A block with a failure finds the first again and gives its kind.
748fn paired<T: Copy>(step: impl Fn(T, T) -> (T, Option<Kind>)) -> impl Fn(&[T], &[T], &mut [std::mem::MaybeUninit<T>]) -> Result<(), Kind> {
749    move |xs: &[T], ys: &[T], out: &mut [std::mem::MaybeUninit<T>]| {
750        let mut ok = true;
751        for ((&x, &y), slot) in xs.iter().zip(ys).zip(out) {
752            let (value, failed) = step(x, y);
753            slot.write(value);
754            ok &= failed.is_none();
755        }
756        if ok { Ok(()) } else { xs.iter().zip(ys).find_map(|(&x, &y)| step(x, y).1).map_or(Ok(()), Err) }
757    }
758}
759
760/// The running results of `step` along each lane of `len` items in `data`, appended to `out` (`reduce::lanes_scan`): each lane's first item stepped from `carry`, or kept as it is without one, and each later item stepped from the result before it. Gives the kind of the first step that failed, which a second pass finds only where one did.
761fn chained<T: Copy>(step: impl Fn(T, T) -> (T, Option<Kind>) + Copy) -> impl Fn(Option<T>, &[T], usize, &mut Vec<T>) -> Option<Kind> {
762    move |carry: Option<T>, data: &[T], len: usize, out: &mut Vec<T>| {
763        let fits = move |x, y| { let (t, failed) = step(x, y); (t, failed.is_none()) };
764        if crate::reduce::lanes_scan(data, len, out, move |x| carry.map_or((x, true), |c| fits(c, x)), fits) { return None; }
765        data.chunks(len).find_map(|lane| {
766            let (mut total, mut failure) = carry.map_or((lane[0], None), |c| step(c, lane[0]));
767            for &x in &lane[1..] {
768                if failure.is_some() { break; }
769                (total, failure) = step(total, x);
770            }
771            failure
772        })
773    }
774}
775
776/// The fold of `items` from the right onto `last`: `last` stepped by each item in turn, from the last item to the first, as `step(total, item)`. Gives the kind of the first step that failed.
777fn folded<T: Copy>(step: impl Fn(T, T) -> (T, Option<Kind>)) -> impl Fn(&[T], T) -> (T, Option<Kind>) {
778    move |items: &[T], last: T| {
779        items.iter().rev().fold((last, None), |(total, failure), &x| {
780            let (t, f) = step(total, x);
781            (t, failure.or(f))
782        })
783    }
784}
785
786/// Report whether a kernel reads its operands through a non-contiguous plan.
787pub fn report_maps<'a>(plans: impl IntoIterator<Item = &'a crate::agreement::ReadPlan>, range: std::ops::Range<usize>) {
788    if crate::events::running() && plans.into_iter().any(|plan| plan.contiguous(range.clone()).is_none()) {
789        crate::events::taken(crate::events::Route::MappedKernel, range.len());
790    }
791}
792
793/// The error that `object` finds in `result` from arguments `x` under a checked `variant`: the verdict on one item of `real_value` and `integer_value`, and of `map_values`' second loop.
794#[inline(always)]
795fn verdict<K: crate::facts::Formula<N>, F: crate::Float, O: crate::recipe::Ops<F, B = bool> + ?Sized, const N: usize>(
796    object: &K, variant: crate::domain::Variant, ops: &O, x: [O::V; N], result: O::V) -> Option<Kind> {
797    object.emit_valid(variant, ops, x, result).and_then(|(kind, valid)| (!valid).then_some(kind))
798}
799
800/// Whether a complex `result` from arguments `x` is valid: a formula that `FAILS_NAN` fails on a NaN part from arguments with no NaN part.
801#[inline(always)]
802fn complex_valid<K: crate::facts::Formula<N>, F: crate::Native, const N: usize>(
803    ops: &crate::recipe::NativeOps<{ crate::FUSED }>, x: [num_complex::Complex<F>; N], result: num_complex::Complex<F>) -> bool {
804    !K::FAILS_NAN || crate::facts::nan_valid(ops, x.into_iter().flat_map(|x| [x.re, x.im]), [result.re, result.im])
805}
806
807#[inline(always)]
808fn real_value<K, F, const N: usize>(
809    object: &K, variant: crate::domain::Variant, ops: &crate::recipe::NativeOps<{ crate::FUSED }>, x: [F; N], repair: bool) -> (F, Option<Kind>) where
810    K: crate::facts::Formula<N>, F: crate::Native {
811    let result = if repair && object.emit_repair(ops, x) == Some(true) { crate::facts::Formula::repair_routine(object).map(|r| r.apply(x[0])) }
812    else { object.real(ops, x) };
813    let Some(result) = result else { return (F::zero(), variant.checked.then_some(Kind::Domain)); };
814    let error = if variant.checked { verdict(object, variant, ops, x, result) } else { None };
815    (result, error)
816}
817
818#[inline(always)]
819fn integer_value<K, I, const N: usize>(object: &K, variant: crate::domain::Variant, ops: &crate::recipe::IntegerOps<I>, x: [I; N]) -> (I, Option<Kind>) where
820    K: crate::facts::Formula<N>, I: crate::recipe::Overflowing {
821    let Some(result) = object.integer(ops, x) else { return (I::FILL, variant.checked.then_some(Kind::Domain)); };
822    let error = if variant.checked { verdict(object, variant, ops, x, result) } else { None };
823    (result, error)
824}
825
826/// Whether `object`'s integer body at the Boolean `variant` raises for some Boolean arguments: it fails, or a formula that doesn't give truths gives a number other than 0 or 1, which a Boolean result can't hold. It tries all 2^N arguments.
827fn boolean_raises<K: crate::facts::Formula<N>, const N: usize>(object: &K, variant: crate::domain::Variant) -> bool {
828    let ops = crate::recipe::IntegerOps::<u8>::new(variant);
829    (0..1usize << N).any(|bits| {
830        let (value, error) = integer_value(object, variant, &ops, std::array::from_fn(|i| ((bits >> i) & 1) as u8));
831        error.is_some() || !K::BOOLEAN && value > 1
832    })
833}
834
835#[inline(always)]
836fn complex_value<K, F, const N: usize>(
837    object: &K, variant: crate::domain::Variant, ops: &crate::recipe::NativeOps<{ crate::FUSED }>, x: [num_complex::Complex<F>; N]
838) -> (num_complex::Complex<F>, Option<Kind>) where K: crate::facts::Formula<N>, F: crate::Native {
839    let Some(result) = object.complex(ops, x) else { return (num_complex::Complex::new(F::zero(), F::zero()), variant.checked.then_some(Kind::Domain)); };
840    let valid = !variant.checked || complex_valid::<K, F, N>(ops, x, result);
841    (result, (!valid).then_some(Kind::Domain))
842}
843
844fn scalar_result<T>((value, error): (T, Option<Kind>)) -> Result<T, Error> {
845    match error { Some(kind) => Err(Error::new(kind, "numerical argument is outside the function's domain")), None => Ok(value) }
846}
847
848/// The reduction by which a fold by `object` at `variant` combines its items: for Booleans the formula's `truth`, which counts for `+` and otherwise names the reduction of its truth table, before its `fold` for numbers.
849fn reduction<K: crate::facts::Formula<N>, const N: usize>(object: &K, variant: crate::domain::Variant) -> Option<crate::reduce::Reduce> {
850    if variant.dtype == DType::Boolean { object.truth().or_else(|| object.fold()) } else { object.fold() }
851}
852
853/// `output`, a kernel's results at `variant`, as `dtype`, which a caller can ask for narrower than the kernel's result type. An integer that doesn't fit fails where `variant.fails_on_overflow()`, and wraps otherwise.
854fn finish(output: &mut Buffer, dtype: DType, variant: crate::domain::Variant) -> Result<(), Error> {
855    if output.dtype() != dtype { *output = Buffer::cast_results(output.view(), dtype, variant.fails_on_overflow())?; }
856    Ok(())
857}
858
859mod at;
860mod binary;
861mod fold;
862mod map;
863mod product;
864pub use {at::*, product::*};
865use {binary::*, fold::*, map::{Inputs, map_formula}};