use crate::builtins::common::shape::{value_dimensions, value_numel};
use crate::builtins::common::spec::{
BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
ReductionNaN, ResidencyPolicy, ShapeRequirements,
};
use runmat_builtins::{ResolveContext, Type, Value};
use runmat_macros::runtime_builtin;
#[runmat_macros::register_gpu_spec(
builtin_path = "crate::builtins::array::introspection::isscalar"
)]
pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
name: "isscalar",
op_kind: GpuOpKind::Custom("metadata"),
supported_precisions: &[],
broadcast: BroadcastSemantics::None,
provider_hooks: &[],
constant_strategy: ConstantStrategy::InlineLiteral,
residency: ResidencyPolicy::GatherImmediately,
nan_mode: ReductionNaN::Include,
two_pass_threshold: None,
workgroup_size: None,
accepts_nan_mode: false,
notes: "Inspects tensor metadata; downloads handles only when providers omit shapes.",
};
#[runmat_macros::register_fusion_spec(
builtin_path = "crate::builtins::array::introspection::isscalar"
)]
pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
name: "isscalar",
shape: ShapeRequirements::Any,
constant_strategy: ConstantStrategy::InlineLiteral,
elementwise: None,
reduction: None,
emits_nan: false,
notes: "Metadata query that returns a host logical scalar for fusion planning.",
};
#[runtime_builtin(
name = "isscalar",
category = "array/introspection",
summary = "Return true when a value has exactly one element and unit dimensions.",
keywords = "isscalar,scalar,metadata query,gpu,logical",
accel = "metadata",
type_resolver(bool_scalar_type),
builtin_path = "crate::builtins::array::introspection::isscalar"
)]
async fn isscalar_builtin(value: Value) -> crate::BuiltinResult<Value> {
Ok(Value::Bool(value_is_scalar(&value).await?))
}
fn bool_scalar_type(_args: &[Type], _context: &ResolveContext) -> Type {
Type::Bool
}
async fn value_is_scalar(value: &Value) -> crate::BuiltinResult<bool> {
if value_numel(value).await? != 1 {
return Ok(false);
}
Ok(value_dimensions(value)
.await?
.into_iter()
.all(|dim| dim == 1))
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::builtins::common::test_support;
use futures::executor::block_on;
fn isscalar_builtin(value: Value) -> crate::BuiltinResult<Value> {
block_on(super::isscalar_builtin(value))
}
#[cfg(feature = "wgpu")]
use runmat_accelerate::backend::wgpu::provider as wgpu_provider;
use runmat_builtins::{CellArray, CharArray, ResolveContext, StructValue, Tensor, Type};
#[test]
fn isscalar_type_returns_bool() {
assert_eq!(
super::bool_scalar_type(&[Type::Num], &ResolveContext::new(Vec::new())),
Type::Bool
);
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn isscalar_numeric_scalar_returns_true() {
let result = isscalar_builtin(Value::Num(5.0)).expect("isscalar");
assert_eq!(result, Value::Bool(true));
let bool_result = isscalar_builtin(Value::Bool(true)).expect("isscalar bool");
assert_eq!(bool_result, Value::Bool(true));
let complex_result = isscalar_builtin(Value::Complex(2.0, -3.0)).expect("isscalar complex");
assert_eq!(complex_result, Value::Bool(true));
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn isscalar_vector_returns_false() {
let tensor = Tensor::new(vec![1.0, 2.0, 3.0], vec![3, 1]).unwrap();
let result = isscalar_builtin(Value::Tensor(tensor)).expect("isscalar");
assert_eq!(result, Value::Bool(false));
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn isscalar_char_array_obeys_dimensions() {
let single = CharArray::new(vec!['a'], 1, 1).unwrap();
let row = CharArray::new_row("RunMat");
let single_result = isscalar_builtin(Value::CharArray(single)).expect("isscalar single");
let row_result = isscalar_builtin(Value::CharArray(row)).expect("isscalar row");
assert_eq!(single_result, Value::Bool(true));
assert_eq!(row_result, Value::Bool(false));
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn isscalar_string_scalar_true_but_empty_array_false() {
let scalar = runmat_builtins::StringArray::new(vec!["RunMat".into()], vec![1, 1]).unwrap();
let empty = runmat_builtins::StringArray::new(Vec::new(), vec![0, 1]).unwrap();
let scalar_result =
isscalar_builtin(Value::StringArray(scalar)).expect("isscalar string scalar");
let empty_result =
isscalar_builtin(Value::StringArray(empty)).expect("isscalar string empty");
let string_value_result =
isscalar_builtin(Value::String("scalar".into())).expect("isscalar string value");
assert_eq!(scalar_result, Value::Bool(true));
assert_eq!(empty_result, Value::Bool(false));
assert_eq!(string_value_result, Value::Bool(true));
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn isscalar_cell_and_struct_follow_dimensions() {
let cell = CellArray::new(vec![Value::Num(1.0)], 1, 1).unwrap();
let not_scalar_cell = CellArray::new(vec![Value::Num(1.0), Value::Num(2.0)], 1, 2).unwrap();
let mut struct_scalar = StructValue::new();
struct_scalar.fields.insert("value".into(), Value::Num(1.0));
let scalar_cell = isscalar_builtin(Value::Cell(cell)).expect("isscalar cell");
let nonscalar_cell =
isscalar_builtin(Value::Cell(not_scalar_cell)).expect("isscalar non-scalar cell");
let struct_result =
isscalar_builtin(Value::Struct(struct_scalar)).expect("isscalar struct");
assert_eq!(scalar_cell, Value::Bool(true));
assert_eq!(nonscalar_cell, Value::Bool(false));
assert_eq!(struct_result, Value::Bool(true));
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
fn isscalar_gpu_tensor_checks_dimensions() {
test_support::with_test_provider(|provider| {
let scalar_tensor = Tensor::new(vec![1.0], vec![1, 1]).unwrap();
let vector_tensor = Tensor::new(vec![1.0, 2.0], vec![2, 1]).unwrap();
let scalar_view = runmat_accelerate_api::HostTensorView {
data: &scalar_tensor.data,
shape: &scalar_tensor.shape,
};
let vector_view = runmat_accelerate_api::HostTensorView {
data: &vector_tensor.data,
shape: &vector_tensor.shape,
};
let scalar_handle = provider.upload(&scalar_view).expect("upload scalar");
let vector_handle = provider.upload(&vector_view).expect("upload vector");
let scalar_result =
isscalar_builtin(Value::GpuTensor(scalar_handle)).expect("isscalar gpu scalar");
let vector_result =
isscalar_builtin(Value::GpuTensor(vector_handle)).expect("isscalar gpu vector");
assert_eq!(scalar_result, Value::Bool(true));
assert_eq!(vector_result, Value::Bool(false));
});
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[test]
#[cfg(feature = "wgpu")]
fn isscalar_wgpu_provider_respects_metadata() {
let _ =
wgpu_provider::register_wgpu_provider(wgpu_provider::WgpuProviderOptions::default());
let provider = runmat_accelerate_api::provider().expect("wgpu provider");
let tensor = Tensor::new(vec![1.0], vec![1, 1]).unwrap();
let view = runmat_accelerate_api::HostTensorView {
data: &tensor.data,
shape: &tensor.shape,
};
let handle = provider.upload(&view).expect("upload");
assert_eq!(
handle.shape,
vec![1, 1],
"provider should supply tensor shape"
);
let result = isscalar_builtin(Value::GpuTensor(handle)).expect("isscalar");
assert_eq!(result, Value::Bool(true));
}
}