1use crate::context::ExecutionContext;
36use crate::marshal::{register_typed_fn_1, register_typed_fn_2};
37use crate::module_exports::ModuleExports;
38use crate::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
39use shape_ast::error::{Result, ShapeError};
40use shape_value::KindedSlot;
41use std::sync::Arc;
42
43pub fn create_math_intrinsics_module() -> ModuleExports {
52 let mut module = ModuleExports::new("std::core::intrinsics::math");
53 module.description =
54 "Math intrinsics (typed entries; polymorphic-shape intrinsics stay as legacy bodies pending follow-on sub-decisions)"
55 .to_string();
56
57 register_typed_fn_1::<_, Arc<Vec<f64>>>(
60 &mut module,
61 "__intrinsic_mean",
62 "Mean (average) of a Vec<number>",
63 "input",
64 "Array<number>",
65 ConcreteType::Number,
66 |input, _ctx| {
67 let data = input.as_slice();
68 if data.is_empty() {
69 return Ok(TypedReturn::Concrete(ConcreteReturn::F64(f64::NAN)));
70 }
71 let sum: f64 = data.iter().sum();
72 Ok(TypedReturn::Concrete(ConcreteReturn::F64(
73 sum / data.len() as f64,
74 )))
75 },
76 );
77
78 register_typed_fn_1::<_, Arc<Vec<f64>>>(
79 &mut module,
80 "__intrinsic_variance",
81 "Population variance of a Vec<number>",
82 "input",
83 "Array<number>",
84 ConcreteType::Number,
85 |input, _ctx| {
86 let data = input.as_slice();
87 if data.is_empty() {
88 return Ok(TypedReturn::Concrete(ConcreteReturn::F64(f64::NAN)));
89 }
90 let mean: f64 = data.iter().sum::<f64>() / data.len() as f64;
91 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
92 let var = variance_avx2(data, mean);
93 #[cfg(not(all(target_arch = "x86_64", target_feature = "avx2")))]
94 let var: f64 = data.iter().map(|&x| (x - mean).powi(2)).sum::<f64>()
95 / data.len() as f64;
96 Ok(TypedReturn::Concrete(ConcreteReturn::F64(var)))
97 },
98 );
99
100 register_typed_fn_1::<_, Arc<Vec<f64>>>(
101 &mut module,
102 "__intrinsic_std",
103 "Population standard deviation of a Vec<number>",
104 "input",
105 "Array<number>",
106 ConcreteType::Number,
107 |input, _ctx| {
108 let data = input.as_slice();
109 if data.is_empty() {
110 return Ok(TypedReturn::Concrete(ConcreteReturn::F64(f64::NAN)));
111 }
112 let mean: f64 = data.iter().sum::<f64>() / data.len() as f64;
113 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
114 let var = variance_avx2(data, mean);
115 #[cfg(not(all(target_arch = "x86_64", target_feature = "avx2")))]
116 let var: f64 = data.iter().map(|&x| (x - mean).powi(2)).sum::<f64>()
117 / data.len() as f64;
118 Ok(TypedReturn::Concrete(ConcreteReturn::F64(var.sqrt())))
119 },
120 );
121
122 register_unary_f64_op(&mut module, "__intrinsic_sin", "Sine of x", f64::sin);
125 register_unary_f64_op(&mut module, "__intrinsic_cos", "Cosine of x", f64::cos);
126 register_unary_f64_op(&mut module, "__intrinsic_tan", "Tangent of x", f64::tan);
127 register_unary_f64_op(&mut module, "__intrinsic_asin", "Arc sine of x", f64::asin);
128 register_unary_f64_op(&mut module, "__intrinsic_acos", "Arc cosine of x", f64::acos);
129 register_unary_f64_op(&mut module, "__intrinsic_atan", "Arc tangent of x", f64::atan);
130 register_unary_f64_op(&mut module, "__intrinsic_sinh", "Hyperbolic sine of x", f64::sinh);
131 register_unary_f64_op(&mut module, "__intrinsic_cosh", "Hyperbolic cosine of x", f64::cosh);
132 register_unary_f64_op(&mut module, "__intrinsic_tanh", "Hyperbolic tangent of x", f64::tanh);
133
134 register_typed_fn_2::<_, f64, f64>(
135 &mut module,
136 "__intrinsic_atan2",
137 "Two-argument arc tangent (atan2(y, x))",
138 [("y", "number"), ("x", "number")],
139 ConcreteType::Number,
140 |y, x, _ctx| Ok(TypedReturn::Concrete(ConcreteReturn::F64(y.atan2(x)))),
141 );
142
143 register_typed_fn_1::<_, f64>(
146 &mut module,
147 "__intrinsic_from_char_code",
148 "Create a single-character string from a Unicode code point",
149 "code",
150 "number",
151 ConcreteType::String,
152 |code, _ctx| {
153 let ch = char::from_u32(code as u32).ok_or_else(|| {
154 format!(
155 "__intrinsic_from_char_code: invalid code point {}",
156 code as u32
157 )
158 })?;
159 Ok(TypedReturn::Concrete(ConcreteReturn::String(ch.to_string())))
160 },
161 );
162
163 module
164}
165
166fn register_unary_f64_op(
168 module: &mut ModuleExports,
169 name: &'static str,
170 desc: &'static str,
171 op: fn(f64) -> f64,
172) {
173 register_typed_fn_1::<_, f64>(
174 module,
175 name,
176 desc,
177 "x",
178 "number",
179 ConcreteType::Number,
180 move |x, _ctx| Ok(TypedReturn::Concrete(ConcreteReturn::F64(op(x)))),
181 );
182}
183
184pub fn intrinsic_min(_args: &[KindedSlot], _ctx: &mut ExecutionContext) -> Result<KindedSlot> {
197 Err(ShapeError::RuntimeError {
198 message: "intrinsic_min: pending Phase 2c intrinsic kind threading + M1-split — see ADR-006 §2.7.4".to_string(),
199 location: None,
200 })
201}
202
203pub fn intrinsic_max(_args: &[KindedSlot], _ctx: &mut ExecutionContext) -> Result<KindedSlot> {
207 Err(ShapeError::RuntimeError {
208 message: "intrinsic_max: pending Phase 2c intrinsic kind threading + M1-split — see ADR-006 §2.7.4".to_string(),
209 location: None,
210 })
211}
212
213pub fn intrinsic_char_code(
219 _args: &[KindedSlot],
220 _ctx: &mut ExecutionContext,
221) -> Result<KindedSlot> {
222 Err(ShapeError::RuntimeError {
223 message: "intrinsic_char_code: pending Phase 2c intrinsic kind threading — see ADR-006 §2.7.4".to_string(),
224 location: None,
225 })
226}
227
228pub fn intrinsic_bspline2_3d_batch(
237 _args: &[KindedSlot],
238 _ctx: &mut ExecutionContext,
239) -> Result<KindedSlot> {
240 Err(ShapeError::RuntimeError {
241 message: "intrinsic_bspline2_3d_batch: pending Phase 2c intrinsic kind threading + consumer audit — see ADR-006 §2.7.4".to_string(),
242 location: None,
243 })
244}
245
246#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
249fn variance_avx2(data: &[f64], mean: f64) -> f64 {
250 use std::simd::f64x4;
251
252 let chunks = data.chunks_exact(4);
253 let remainder = chunks.remainder();
254
255 let mean_vec = f64x4::splat(mean);
256 let mut var_sum = f64x4::splat(0.0);
257
258 for chunk in chunks {
259 let values = f64x4::from_slice(chunk);
260 let diff = values - mean_vec;
261 var_sum += diff * diff;
262 }
263
264 let vector_var = var_sum.reduce_sum();
265 let remainder_var: f64 = remainder.iter().map(|&x| (x - mean).powi(2)).sum();
266
267 (vector_var + remainder_var) / data.len() as f64
268}
269
270#[inline]
272fn bspline2_3d_batch_slice(
273 grid: &[f64],
274 nx: usize, ny: usize, nz: usize,
275 x_lo: f64, x_hi: f64, y_lo: f64, y_hi: f64, z_lo: f64, z_hi: f64,
276 pos: &[f64],
277) -> Vec<f64> {
278 let n = pos.len() / 3;
279 let nxm = (nx - 1) as f64;
280 let nym = (ny - 1) as f64;
281 let nzm = (nz - 1) as f64;
282 let inv_x = nxm / (x_hi - x_lo);
283 let inv_y = nym / (y_hi - y_lo);
284 let inv_z = nzm / (z_hi - z_lo);
285 let nyz = ny * nz;
286 let mut result = Vec::with_capacity(n);
287
288 for s in 0..n {
289 let i3 = s * 3;
290 let gx = ((pos[i3] - x_lo) * inv_x).clamp(0.0, nxm);
291 let gy = ((pos[i3 + 1] - y_lo) * inv_y).clamp(0.0, nym);
292 let gz = ((pos[i3 + 2] - z_lo) * inv_z).clamp(0.0, nzm);
293
294 let cx = (gx + 0.5).floor() as isize;
295 let cy = (gy + 0.5).floor() as isize;
296 let cz = (gz + 0.5).floor() as isize;
297 let tx = gx - cx as f64;
298 let ty = gy - cy as f64;
299 let tz = gz - cz as f64;
300
301 let (wx0, wx1, wx2) = bspline_weights(tx);
302 let (wy0, wy1, wy2) = bspline_weights(ty);
303 let (wz0, wz1, wz2) = bspline_weights(tz);
304
305 let ix = [
306 (cx - 1).max(0) as usize,
307 cx as usize,
308 (cx + 1).min(nx as isize - 1) as usize,
309 ];
310 let iy = [
311 (cy - 1).max(0) as usize,
312 cy as usize,
313 (cy + 1).min(ny as isize - 1) as usize,
314 ];
315 let iz = [
316 (cz - 1).max(0) as usize,
317 cz as usize,
318 (cz + 1).min(nz as isize - 1) as usize,
319 ];
320
321 let wx = [wx0, wx1, wx2];
322 let wy = [wy0, wy1, wy2];
323 let wz = [wz0, wz1, wz2];
324
325 let mut val = 0.0;
326 for a in 0..3 {
327 let rx = ix[a] * nyz;
328 for b in 0..3 {
329 let rxy = rx + iy[b] * nz;
330 let wxy = wx[a] * wy[b];
331 for c in 0..3 {
332 val += wxy * wz[c] * unsafe { *grid.get_unchecked(rxy + iz[c]) };
333 }
334 }
335 }
336 result.push(val);
337 }
338 result
339}
340
341#[inline]
344fn bspline2_3d_batch_fn(
345 grid: &dyn Fn(usize) -> f64,
346 nx: usize, ny: usize, nz: usize,
347 x_lo: f64, x_hi: f64, y_lo: f64, y_hi: f64, z_lo: f64, z_hi: f64,
348 pos: &[f64],
349) -> Vec<f64> {
350 let n = pos.len() / 3;
351 let nxm = (nx - 1) as f64;
352 let nym = (ny - 1) as f64;
353 let nzm = (nz - 1) as f64;
354 let inv_x = nxm / (x_hi - x_lo);
355 let inv_y = nym / (y_hi - y_lo);
356 let inv_z = nzm / (z_hi - z_lo);
357 let nyz = ny * nz;
358 let mut result = Vec::with_capacity(n);
359
360 for s in 0..n {
361 let i3 = s * 3;
362 let gx = ((pos[i3] - x_lo) * inv_x).clamp(0.0, nxm);
363 let gy = ((pos[i3 + 1] - y_lo) * inv_y).clamp(0.0, nym);
364 let gz = ((pos[i3 + 2] - z_lo) * inv_z).clamp(0.0, nzm);
365
366 let cx = (gx + 0.5).floor() as isize;
367 let cy = (gy + 0.5).floor() as isize;
368 let cz = (gz + 0.5).floor() as isize;
369 let tx = gx - cx as f64;
370 let ty = gy - cy as f64;
371 let tz = gz - cz as f64;
372
373 let (wx0, wx1, wx2) = bspline_weights(tx);
374 let (wy0, wy1, wy2) = bspline_weights(ty);
375 let (wz0, wz1, wz2) = bspline_weights(tz);
376
377 let ix = [
378 (cx - 1).max(0) as usize,
379 cx as usize,
380 (cx + 1).min(nx as isize - 1) as usize,
381 ];
382 let iy = [
383 (cy - 1).max(0) as usize,
384 cy as usize,
385 (cy + 1).min(ny as isize - 1) as usize,
386 ];
387 let iz = [
388 (cz - 1).max(0) as usize,
389 cz as usize,
390 (cz + 1).min(nz as isize - 1) as usize,
391 ];
392
393 let wx = [wx0, wx1, wx2];
394 let wy = [wy0, wy1, wy2];
395 let wz = [wz0, wz1, wz2];
396
397 let mut val = 0.0;
398 for a in 0..3 {
399 let rx = ix[a] * nyz;
400 for b in 0..3 {
401 let rxy = rx + iy[b] * nz;
402 let wxy = wx[a] * wy[b];
403 for c in 0..3 {
404 val += wxy * wz[c] * grid(rxy + iz[c]);
405 }
406 }
407 }
408 result.push(val);
409 }
410 result
411}
412
413#[inline(always)]
415fn bspline_weights(t: f64) -> (f64, f64, f64) {
416 (
417 0.5 * (0.5 - t) * (0.5 - t),
418 0.75 - t * t,
419 0.5 * (0.5 + t) * (0.5 + t),
420 )
421}