1use 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#[derive(Clone, Copy, Debug)]
14pub struct Settings { pub tolerance: crate::tolerant::Tolerance }
15impl Settings {
16 pub fn ops(self) -> crate::recipe::NativeOps<{ crate::FUSED }> { crate::recipe::NativeOps { tolerance: Some(self.tolerance.value()) } }
18}
19
20#[derive(Clone, Copy, PartialEq, Debug)]
22pub enum Projection {
23 AsIs,
25 Codes(crate::tolerant::Tolerance),
27 Wholes(crate::tolerant::Tolerance),
29}
30impl Projection {
31 fn wholes(self) -> Option<crate::tolerant::Tolerance> { match self { Self::AsIs => None, Self::Codes(t) | Self::Wholes(t) => Some(t) } }
33}
34
35pub enum WriteRoot<'a> { Read(crate::agreement::Read<'a>), Owned(Buffer) }
37impl WriteRoot<'_> {
38 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#[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#[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 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 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 fn binary(&self, variant: crate::domain::Variant, settings: Settings, route: Route<'_>, output: &mut Buffer) -> Result<(), Error>;
117}
118
119struct 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 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 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 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 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 pub fn variant(&self) -> crate::domain::Variant { self.variant }
314 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 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 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 pub fn may_raise(&self) -> bool { self.variant.checked && (self.result == DType::Character || self.wholes || self.raising) }
354 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 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 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 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 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 else if self.result == DType::Boolean { range.meet(bounds) } else { range }
388 }
389 pub fn identity(&self) -> Option<f64> { self.definition.identity() }
390 pub fn kept(&self, constants: &[Option<f64>]) -> Option<usize> { self.definition.kept(constants) }
392 pub fn fold(&self) -> Option<crate::reduce::Reduce> { self.definition.reduction(self.variant) }
394 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 pub fn inverse_fold(&self) -> Option<crate::reduce::Reduce> { self.definition.inverse_fold() }
402 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 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 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 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 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 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 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 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 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 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 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
632const I64_LIMIT: f64 = 9_223_372_036_854_775_808.0;
634
635pub 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
641fn 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
655fn 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 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
730fn 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 unsafe { out.set_len(at + n) };
743 Some(ok)
744 }
745}
746
747fn 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
760fn 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
776fn 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
786pub 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#[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#[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
826fn 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
848fn 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
853fn 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}};