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