runmat_runtime/builtins/logical/tests/
isnumeric.rs1use runmat_accelerate_api::GpuTensorHandle;
4use runmat_builtins::{ResolveContext, Type, Value};
5use runmat_macros::runtime_builtin;
6
7use crate::builtins::common::gpu_helpers;
8use crate::builtins::common::spec::{
9 BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
10 ProviderHook, ReductionNaN, ResidencyPolicy, ScalarType, ShapeRequirements,
11};
12use crate::{build_runtime_error, BuiltinResult, RuntimeError};
13
14#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::logical::tests::isnumeric")]
15pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
16 name: "isnumeric",
17 op_kind: GpuOpKind::Custom("metadata"),
18 supported_precisions: &[ScalarType::F32, ScalarType::F64],
19 broadcast: BroadcastSemantics::None,
20 provider_hooks: &[ProviderHook::Custom("logical_islogical")],
21 constant_strategy: ConstantStrategy::InlineLiteral,
22 residency: ResidencyPolicy::GatherImmediately,
23 nan_mode: ReductionNaN::Include,
24 two_pass_threshold: None,
25 workgroup_size: None,
26 accepts_nan_mode: false,
27 notes:
28 "Uses provider metadata to distinguish logical gpuArrays from numeric ones; otherwise falls back to runtime residency tracking.",
29};
30
31#[runmat_macros::register_fusion_spec(builtin_path = "crate::builtins::logical::tests::isnumeric")]
32pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
33 name: "isnumeric",
34 shape: ShapeRequirements::Any,
35 constant_strategy: ConstantStrategy::InlineLiteral,
36 elementwise: None,
37 reduction: None,
38 emits_nan: false,
39 notes: "Type check executed outside fusion; planners treat it as a scalar metadata query.",
40};
41
42const BUILTIN_NAME: &str = "isnumeric";
43const IDENTIFIER_INTERNAL: &str = "RunMat:isnumeric:InternalError";
44
45#[runtime_builtin(
46 name = "isnumeric",
47 category = "logical/tests",
48 summary = "Return true when a value is stored as numeric data.",
49 keywords = "isnumeric,numeric,type,gpu",
50 accel = "metadata",
51 type_resolver(bool_scalar_type),
52 builtin_path = "crate::builtins::logical::tests::isnumeric"
53)]
54async fn isnumeric_builtin(value: Value) -> BuiltinResult<Value> {
55 match value {
56 Value::GpuTensor(handle) => isnumeric_gpu(handle).await,
57 other => Ok(Value::Bool(isnumeric_value(&other))),
58 }
59}
60
61fn bool_scalar_type(_: &[Type], _context: &ResolveContext) -> Type {
62 Type::Bool
63}
64
65async fn isnumeric_gpu(handle: GpuTensorHandle) -> BuiltinResult<Value> {
66 if let Some(provider) = runmat_accelerate_api::provider() {
67 if let Ok(flag) = provider.logical_islogical(&handle) {
68 return Ok(Value::Bool(!flag));
69 }
70 }
71
72 if runmat_accelerate_api::handle_is_logical(&handle) {
73 return Ok(Value::Bool(false));
74 }
75
76 let gpu_value = Value::GpuTensor(handle.clone());
78 let gathered = gpu_helpers::gather_value_async(&gpu_value)
79 .await
80 .map_err(|err| internal_error(format!("isnumeric: {err}")))?;
81 isnumeric_host(gathered)
82}
83
84fn isnumeric_host(value: Value) -> BuiltinResult<Value> {
85 if matches!(value, Value::GpuTensor(_)) {
86 return Err(internal_error(
87 "isnumeric: internal error, GPU value reached host path",
88 ));
89 }
90 Ok(Value::Bool(isnumeric_value(&value)))
91}
92
93fn isnumeric_value(value: &Value) -> bool {
94 matches!(
95 value,
96 Value::Num(_)
97 | Value::Int(_)
98 | Value::Complex(_, _)
99 | Value::Tensor(_)
100 | Value::ComplexTensor(_)
101 )
102}
103
104fn internal_error(message: impl Into<String>) -> RuntimeError {
105 build_runtime_error(message)
106 .with_identifier(IDENTIFIER_INTERNAL)
107 .with_builtin(BUILTIN_NAME)
108 .build()
109}
110
111#[cfg(test)]
112pub(crate) mod tests {
113 use super::*;
114 use crate::builtins::common::test_support;
115 use futures::executor::block_on;
116 use runmat_accelerate_api::HostTensorView;
117 use runmat_builtins::{
118 CellArray, CharArray, Closure, ComplexTensor, HandleRef, IntValue, Listener, LogicalArray,
119 MException, ObjectInstance, ResolveContext, StringArray, StructValue, Tensor, Type,
120 };
121 use runmat_gc_api::GcPtr;
122
123 fn run_isnumeric(value: Value) -> BuiltinResult<Value> {
124 block_on(super::isnumeric_builtin(value))
125 }
126
127 #[test]
128 fn isnumeric_type_returns_bool() {
129 assert_eq!(
130 bool_scalar_type(&[Type::Num], &ResolveContext::new(Vec::new())),
131 Type::Bool
132 );
133 }
134
135 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
136 #[test]
137 fn numeric_scalars_return_true() {
138 assert_eq!(run_isnumeric(Value::Num(3.5)).unwrap(), Value::Bool(true));
139 assert_eq!(
140 run_isnumeric(Value::Int(IntValue::I16(7))).unwrap(),
141 Value::Bool(true)
142 );
143 assert_eq!(
144 run_isnumeric(Value::Complex(1.0, -2.0)).unwrap(),
145 Value::Bool(true)
146 );
147 }
148
149 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
150 #[test]
151 fn numeric_tensors_return_true() {
152 let tensor = Tensor::new(vec![1.0, 2.0, 3.0], vec![3, 1]).unwrap();
153 assert_eq!(
154 run_isnumeric(Value::Tensor(tensor)).unwrap(),
155 Value::Bool(true)
156 );
157
158 let complex = ComplexTensor::new(vec![(1.0, 2.0), (3.0, 4.0)], vec![2, 1]).unwrap();
159 assert_eq!(
160 run_isnumeric(Value::ComplexTensor(complex)).unwrap(),
161 Value::Bool(true)
162 );
163 }
164
165 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
166 #[test]
167 fn non_numeric_types_return_false() {
168 assert_eq!(
169 run_isnumeric(Value::Bool(true)).unwrap(),
170 Value::Bool(false)
171 );
172
173 let logical = LogicalArray::new(vec![1, 0], vec![2, 1]).unwrap();
174 assert_eq!(
175 run_isnumeric(Value::LogicalArray(logical)).unwrap(),
176 Value::Bool(false)
177 );
178
179 let chars = CharArray::new("rm".chars().collect(), 1, 2).unwrap();
180 assert_eq!(
181 run_isnumeric(Value::CharArray(chars)).unwrap(),
182 Value::Bool(false)
183 );
184
185 assert_eq!(
186 run_isnumeric(Value::String("runmat".into())).unwrap(),
187 Value::Bool(false)
188 );
189 assert_eq!(
190 run_isnumeric(Value::Struct(StructValue::new())).unwrap(),
191 Value::Bool(false)
192 );
193 let string_array =
194 StringArray::new(vec!["foo".into(), "bar".into()], vec![1, 2]).expect("string array");
195 assert_eq!(
196 run_isnumeric(Value::StringArray(string_array)).unwrap(),
197 Value::Bool(false)
198 );
199 let cell =
200 CellArray::new(vec![Value::Num(1.0), Value::Bool(false)], 1, 2).expect("cell array");
201 assert_eq!(
202 run_isnumeric(Value::Cell(cell)).unwrap(),
203 Value::Bool(false)
204 );
205 let object = ObjectInstance::new("runmat.MockObject".into());
206 assert_eq!(
207 run_isnumeric(Value::Object(object)).unwrap(),
208 Value::Bool(false)
209 );
210 assert_eq!(
211 run_isnumeric(Value::FunctionHandle("runmat_fun".into())).unwrap(),
212 Value::Bool(false)
213 );
214 let closure = Closure {
215 function_name: "anon".into(),
216 captures: vec![Value::Num(1.0)],
217 };
218 assert_eq!(
219 run_isnumeric(Value::Closure(closure)).unwrap(),
220 Value::Bool(false)
221 );
222 let handle = HandleRef {
223 class_name: "runmat.Handle".into(),
224 target: GcPtr::null(),
225 valid: true,
226 };
227 assert_eq!(
228 run_isnumeric(Value::HandleObject(handle)).unwrap(),
229 Value::Bool(false)
230 );
231 let listener = Listener {
232 id: 1,
233 target: GcPtr::null(),
234 event_name: "changed".into(),
235 callback: GcPtr::null(),
236 enabled: true,
237 valid: true,
238 };
239 assert_eq!(
240 run_isnumeric(Value::Listener(listener)).unwrap(),
241 Value::Bool(false)
242 );
243 assert_eq!(
244 run_isnumeric(Value::ClassRef("pkg.Class".into())).unwrap(),
245 Value::Bool(false)
246 );
247 let mex = MException::new("RunMat:mock".into(), "message".into());
248 assert_eq!(
249 run_isnumeric(Value::MException(mex)).unwrap(),
250 Value::Bool(false)
251 );
252 }
253
254 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
255 #[test]
256 fn gpu_numeric_and_logical_handles() {
257 test_support::with_test_provider(|provider| {
258 let tensor = Tensor::new(vec![1.0, 2.0, 3.0], vec![3, 1]).unwrap();
259 let view = HostTensorView {
260 data: &tensor.data,
261 shape: &tensor.shape,
262 };
263 let numeric_handle = provider.upload(&view).expect("upload");
264 let numeric = run_isnumeric(Value::GpuTensor(numeric_handle.clone())).unwrap();
265 assert_eq!(numeric, Value::Bool(true));
266
267 let logical_value = gpu_helpers::logical_gpu_value(numeric_handle.clone());
268 let logical = run_isnumeric(logical_value).unwrap();
269 assert_eq!(logical, Value::Bool(false));
270
271 runmat_accelerate_api::clear_handle_logical(&numeric_handle);
272 provider.free(&numeric_handle).ok();
273 });
274 }
275
276 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
277 #[test]
278 #[cfg(feature = "wgpu")]
279 fn isnumeric_wgpu_handles_respect_metadata() {
280 let _ = runmat_accelerate::backend::wgpu::provider::register_wgpu_provider(
281 runmat_accelerate::backend::wgpu::provider::WgpuProviderOptions::default(),
282 );
283 let provider = runmat_accelerate_api::provider().expect("wgpu provider");
284
285 let data = vec![1.0, 2.0, 3.0, 4.0];
286 let shape = vec![4, 1];
287 let view = HostTensorView {
288 data: &data,
289 shape: &shape,
290 };
291 let handle = provider.upload(&view).expect("upload");
292 let numeric = run_isnumeric(Value::GpuTensor(handle.clone())).unwrap();
293 assert_eq!(numeric, Value::Bool(true));
294
295 let logical_value = gpu_helpers::logical_gpu_value(handle.clone());
296 let logical = run_isnumeric(logical_value).unwrap();
297 assert_eq!(logical, Value::Bool(false));
298
299 runmat_accelerate_api::clear_handle_logical(&handle);
300 provider.free(&handle).ok();
301 }
302}