runmat_runtime/builtins/logical/tests/
isnumeric.rs1use runmat_accelerate_api::GpuTensorHandle;
4use runmat_builtins::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};
12#[cfg(feature = "doc_export")]
13use crate::register_builtin_doc_text;
14use crate::{register_builtin_fusion_spec, register_builtin_gpu_spec};
15
16#[cfg(feature = "doc_export")]
17pub const DOC_MD: &str = r#"---
18title: "isnumeric"
19category: "logical/tests"
20keywords: ["isnumeric", "numeric type", "type predicate", "gpuArray isnumeric", "MATLAB isnumeric"]
21summary: "Return true when a value is stored as numeric data (real or complex)."
22references: []
23gpu_support:
24 elementwise: false
25 reduction: false
26 precisions: ["f32", "f64"]
27 broadcasting: "none"
28 notes: "Queries device metadata; falls back to runtime residency tracking when provider hooks are absent."
29fusion:
30 elementwise: false
31 reduction: false
32 max_inputs: 1
33 constants: "inline"
34requires_feature: null
35tested:
36 unit: "builtins::logical::tests::isnumeric::tests"
37 integration: "builtins::logical::tests::isnumeric::tests::gpu_numeric_and_logical_handles"
38 gpu: "builtins::logical::tests::isnumeric::tests::isnumeric_wgpu_handles_respect_metadata"
39 doc: "builtins::logical::tests::isnumeric::tests::doc_examples_present"
40---
41
42# What does the `isnumeric` function do in MATLAB / RunMat?
43`tf = isnumeric(x)` returns a logical scalar that is `true` when `x` stores numeric data and
44`false` otherwise. Numeric data includes doubles, singles, integers, and complex numbers, as
45well as dense numeric arrays that live on the CPU or GPU.
46
47## How does the `isnumeric` function behave in MATLAB / RunMat?
48- Every built-in numeric class (`double`, `single`, signed/unsigned integer types) returns
49 `true`, including complex scalars.
50- Real and complex numeric arrays return `true` regardless of dimensionality or residency on
51 the CPU or GPU.
52- `gpuArray` values rely on provider metadata: numeric handles return `true`, while logical
53 masks constructed on the GPU return `false`.
54- Logical values, characters, strings, tables, cell arrays, structs, objects, and function
55 handles return `false`.
56- The result is always a logical scalar.
57
58## Examples of using the `isnumeric` function in MATLAB / RunMat
59
60### Checking if a scalar double is numeric
61
62```matlab
63tf = isnumeric(42);
64```
65
66Expected output:
67
68```matlab
69tf =
70 1
71```
72
73### Detecting numeric matrices
74
75```matlab
76A = [1 2 3; 4 5 6];
77tf = isnumeric(A);
78```
79
80Expected output:
81
82```matlab
83tf =
84 1
85```
86
87### Testing complex numbers for numeric storage
88
89```matlab
90z = 1 + 2i;
91tf = isnumeric(z);
92```
93
94Expected output:
95
96```matlab
97tf =
98 1
99```
100
101### Logical arrays are not numeric
102
103```matlab
104mask = logical([1 0 1]);
105tf = isnumeric(mask);
106```
107
108Expected output:
109
110```matlab
111tf =
112 0
113```
114
115### Character vectors and strings return false
116
117```matlab
118chars = ['R' 'u' 'n'];
119name = "RunMat";
120tf_chars = isnumeric(chars);
121tf_string = isnumeric(name);
122```
123
124Expected output:
125
126```matlab
127tf_chars =
128 0
129
130tf_string =
131 0
132```
133
134### Evaluating `gpuArray` inputs
135
136```matlab
137G = gpuArray(rand(4));
138mask = G > 0.5;
139tf_numeric = isnumeric(G);
140tf_mask = isnumeric(mask);
141```
142
143Expected output:
144
145```matlab
146tf_numeric =
147 1
148
149tf_mask =
150 0
151```
152
153## `isnumeric` Function GPU Execution Behaviour
154When RunMat Accelerate is active, `isnumeric` first checks provider metadata via the
155`logical_islogical` hook to determine whether a `gpuArray` handle was created as a logical
156mask. Providers that supply the hook can answer the query without copying data back to the
157host. When the hook is absent, RunMat consults its residency metadata and only gathers the
158value to host memory when the residency tag is missing, ensuring the builtin always succeeds.
159
160## GPU residency in RunMat (Do I need `gpuArray`?)
161You generally do **not** need to call `gpuArray` manually. RunMat's auto-offload planner keeps
162numeric tensors on the GPU across fused expressions whenever that improves performance.
163Explicit `gpuArray` and `gather` calls remain available for compatibility with MATLAB scripts
164that manage residency themselves.
165
166## FAQ
167
168### Does `isnumeric` ever return an array?
169No. The builtin always returns a logical scalar, even when the input is an array.
170
171### Are complex tensors considered numeric?
172Yes. Real and complex tensors both return `true`, matching MATLAB semantics.
173
174### Does `isnumeric` gather GPU data back to the host?
175Only when residency metadata is unavailable. Providers that expose type metadata let RunMat
176answer the query without host↔device transfers.
177
178### Do logical masks return `true`?
179No. Logical scalars and logical arrays return `false`. Use `islogical` if you need to detect
180logical storage explicitly.
181
182### What about character vectors or string arrays?
183They return `false`, just like in MATLAB. Characters and strings are text types rather than
184numeric arrays.
185
186### Do cell arrays or structs ever count as numeric?
187No. Containers and objects always return `false`.
188
189### Is there a difference between CPU and GPU numeric arrays?
190No. Both host and device numeric arrays return `true`; only logical GPU handles report `false`.
191
192## See Also
193[islogical](./islogical), [isreal](./isreal), [isfinite](./isfinite), [gpuArray](../../acceleration/gpu/gpuArray), [gather](../../acceleration/gpu/gather)
194
195## Source & Feedback
196- The full source code for the implementation of the `isnumeric` function is available at: [`crates/runmat-runtime/src/builtins/logical/tests/isnumeric.rs`](https://github.com/runmat-org/runmat/blob/main/crates/runmat-runtime/src/builtins/logical/tests/isnumeric.rs)
197- Found a bug or behavioural difference? Please [open an issue](https://github.com/runmat-org/runmat/issues/new/choose) with details and a minimal repro.
198"#;
199
200pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
201 name: "isnumeric",
202 op_kind: GpuOpKind::Custom("metadata"),
203 supported_precisions: &[ScalarType::F32, ScalarType::F64],
204 broadcast: BroadcastSemantics::None,
205 provider_hooks: &[ProviderHook::Custom("logical_islogical")],
206 constant_strategy: ConstantStrategy::InlineLiteral,
207 residency: ResidencyPolicy::GatherImmediately,
208 nan_mode: ReductionNaN::Include,
209 two_pass_threshold: None,
210 workgroup_size: None,
211 accepts_nan_mode: false,
212 notes:
213 "Uses provider metadata to distinguish logical gpuArrays from numeric ones; otherwise falls back to runtime residency tracking.",
214};
215
216register_builtin_gpu_spec!(GPU_SPEC);
217
218pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
219 name: "isnumeric",
220 shape: ShapeRequirements::Any,
221 constant_strategy: ConstantStrategy::InlineLiteral,
222 elementwise: None,
223 reduction: None,
224 emits_nan: false,
225 notes: "Type check executed outside fusion; planners treat it as a scalar metadata query.",
226};
227
228register_builtin_fusion_spec!(FUSION_SPEC);
229
230#[cfg(feature = "doc_export")]
231register_builtin_doc_text!("isnumeric", DOC_MD);
232
233#[runtime_builtin(
234 name = "isnumeric",
235 category = "logical/tests",
236 summary = "Return true when a value is stored as numeric data.",
237 keywords = "isnumeric,numeric,type,gpu",
238 accel = "metadata"
239)]
240fn isnumeric_builtin(value: Value) -> Result<Value, String> {
241 match value {
242 Value::GpuTensor(handle) => isnumeric_gpu(handle),
243 other => Ok(Value::Bool(isnumeric_value(&other))),
244 }
245}
246
247fn isnumeric_gpu(handle: GpuTensorHandle) -> Result<Value, String> {
248 if let Some(provider) = runmat_accelerate_api::provider() {
249 if let Ok(flag) = provider.logical_islogical(&handle) {
250 return Ok(Value::Bool(!flag));
251 }
252 }
253
254 if runmat_accelerate_api::handle_is_logical(&handle) {
255 return Ok(Value::Bool(false));
256 }
257
258 let gpu_value = Value::GpuTensor(handle.clone());
260 if let Ok(gathered) = gpu_helpers::gather_value(&gpu_value) {
261 return isnumeric_host(gathered);
262 }
263
264 Ok(Value::Bool(true))
265}
266
267fn isnumeric_host(value: Value) -> Result<Value, String> {
268 if matches!(value, Value::GpuTensor(_)) {
269 return Err("isnumeric: internal error, GPU value reached host path".to_string());
270 }
271 Ok(Value::Bool(isnumeric_value(&value)))
272}
273
274fn isnumeric_value(value: &Value) -> bool {
275 matches!(
276 value,
277 Value::Num(_)
278 | Value::Int(_)
279 | Value::Complex(_, _)
280 | Value::Tensor(_)
281 | Value::ComplexTensor(_)
282 )
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use crate::builtins::common::test_support;
289 use runmat_accelerate_api::HostTensorView;
290 use runmat_builtins::{
291 CellArray, CharArray, Closure, ComplexTensor, HandleRef, IntValue, Listener, LogicalArray,
292 MException, ObjectInstance, StringArray, StructValue, Tensor,
293 };
294 use runmat_gc_api::GcPtr;
295
296 #[test]
297 fn numeric_scalars_return_true() {
298 assert_eq!(
299 isnumeric_builtin(Value::Num(3.5)).unwrap(),
300 Value::Bool(true)
301 );
302 assert_eq!(
303 isnumeric_builtin(Value::Int(IntValue::I16(7))).unwrap(),
304 Value::Bool(true)
305 );
306 assert_eq!(
307 isnumeric_builtin(Value::Complex(1.0, -2.0)).unwrap(),
308 Value::Bool(true)
309 );
310 }
311
312 #[test]
313 fn numeric_tensors_return_true() {
314 let tensor = Tensor::new(vec![1.0, 2.0, 3.0], vec![3, 1]).unwrap();
315 assert_eq!(
316 isnumeric_builtin(Value::Tensor(tensor)).unwrap(),
317 Value::Bool(true)
318 );
319
320 let complex = ComplexTensor::new(vec![(1.0, 2.0), (3.0, 4.0)], vec![2, 1]).unwrap();
321 assert_eq!(
322 isnumeric_builtin(Value::ComplexTensor(complex)).unwrap(),
323 Value::Bool(true)
324 );
325 }
326
327 #[test]
328 fn non_numeric_types_return_false() {
329 assert_eq!(
330 isnumeric_builtin(Value::Bool(true)).unwrap(),
331 Value::Bool(false)
332 );
333
334 let logical = LogicalArray::new(vec![1, 0], vec![2, 1]).unwrap();
335 assert_eq!(
336 isnumeric_builtin(Value::LogicalArray(logical)).unwrap(),
337 Value::Bool(false)
338 );
339
340 let chars = CharArray::new("rm".chars().collect(), 1, 2).unwrap();
341 assert_eq!(
342 isnumeric_builtin(Value::CharArray(chars)).unwrap(),
343 Value::Bool(false)
344 );
345
346 assert_eq!(
347 isnumeric_builtin(Value::String("runmat".into())).unwrap(),
348 Value::Bool(false)
349 );
350 assert_eq!(
351 isnumeric_builtin(Value::Struct(StructValue::new())).unwrap(),
352 Value::Bool(false)
353 );
354 let string_array =
355 StringArray::new(vec!["foo".into(), "bar".into()], vec![1, 2]).expect("string array");
356 assert_eq!(
357 isnumeric_builtin(Value::StringArray(string_array)).unwrap(),
358 Value::Bool(false)
359 );
360 let cell =
361 CellArray::new(vec![Value::Num(1.0), Value::Bool(false)], 1, 2).expect("cell array");
362 assert_eq!(
363 isnumeric_builtin(Value::Cell(cell)).unwrap(),
364 Value::Bool(false)
365 );
366 let object = ObjectInstance::new("runmat.MockObject".into());
367 assert_eq!(
368 isnumeric_builtin(Value::Object(object)).unwrap(),
369 Value::Bool(false)
370 );
371 assert_eq!(
372 isnumeric_builtin(Value::FunctionHandle("runmat_fun".into())).unwrap(),
373 Value::Bool(false)
374 );
375 let closure = Closure {
376 function_name: "anon".into(),
377 captures: vec![Value::Num(1.0)],
378 };
379 assert_eq!(
380 isnumeric_builtin(Value::Closure(closure)).unwrap(),
381 Value::Bool(false)
382 );
383 let handle = HandleRef {
384 class_name: "runmat.Handle".into(),
385 target: GcPtr::null(),
386 valid: true,
387 };
388 assert_eq!(
389 isnumeric_builtin(Value::HandleObject(handle)).unwrap(),
390 Value::Bool(false)
391 );
392 let listener = Listener {
393 id: 1,
394 target: GcPtr::null(),
395 event_name: "changed".into(),
396 callback: GcPtr::null(),
397 enabled: true,
398 valid: true,
399 };
400 assert_eq!(
401 isnumeric_builtin(Value::Listener(listener)).unwrap(),
402 Value::Bool(false)
403 );
404 assert_eq!(
405 isnumeric_builtin(Value::ClassRef("pkg.Class".into())).unwrap(),
406 Value::Bool(false)
407 );
408 let mex = MException::new("MATLAB:mock".into(), "message".into());
409 assert_eq!(
410 isnumeric_builtin(Value::MException(mex)).unwrap(),
411 Value::Bool(false)
412 );
413 }
414
415 #[test]
416 fn gpu_numeric_and_logical_handles() {
417 test_support::with_test_provider(|provider| {
418 let tensor = Tensor::new(vec![1.0, 2.0, 3.0], vec![3, 1]).unwrap();
419 let view = HostTensorView {
420 data: &tensor.data,
421 shape: &tensor.shape,
422 };
423 let numeric_handle = provider.upload(&view).expect("upload");
424 let numeric = isnumeric_builtin(Value::GpuTensor(numeric_handle.clone())).unwrap();
425 assert_eq!(numeric, Value::Bool(true));
426
427 let logical_value = gpu_helpers::logical_gpu_value(numeric_handle.clone());
428 let logical = isnumeric_builtin(logical_value).unwrap();
429 assert_eq!(logical, Value::Bool(false));
430
431 runmat_accelerate_api::clear_handle_logical(&numeric_handle);
432 provider.free(&numeric_handle).ok();
433 });
434 }
435
436 #[test]
437 #[cfg(feature = "wgpu")]
438 fn isnumeric_wgpu_handles_respect_metadata() {
439 let _ = runmat_accelerate::backend::wgpu::provider::register_wgpu_provider(
440 runmat_accelerate::backend::wgpu::provider::WgpuProviderOptions::default(),
441 );
442 let provider = runmat_accelerate_api::provider().expect("wgpu provider");
443
444 let data = vec![1.0, 2.0, 3.0, 4.0];
445 let shape = vec![4, 1];
446 let view = HostTensorView {
447 data: &data,
448 shape: &shape,
449 };
450 let handle = provider.upload(&view).expect("upload");
451 let numeric = isnumeric_builtin(Value::GpuTensor(handle.clone())).unwrap();
452 assert_eq!(numeric, Value::Bool(true));
453
454 let logical_value = gpu_helpers::logical_gpu_value(handle.clone());
455 let logical = isnumeric_builtin(logical_value).unwrap();
456 assert_eq!(logical, Value::Bool(false));
457
458 runmat_accelerate_api::clear_handle_logical(&handle);
459 provider.free(&handle).ok();
460 }
461
462 #[test]
463 #[cfg(feature = "doc_export")]
464 fn doc_examples_present() {
465 let blocks = test_support::doc_examples(DOC_MD);
466 assert!(!blocks.is_empty());
467 }
468}