Skip to main content

runmat_runtime/builtins/logical/tests/
issparse.rs

1//! MATLAB-compatible `issparse` builtin for RunMat sparse matrix values.
2
3use runmat_builtins::{
4    BuiltinCompletionPolicy, BuiltinDescriptor, BuiltinErrorDescriptor, BuiltinOutputMode,
5    BuiltinParamArity, BuiltinParamDescriptor, BuiltinParamType, BuiltinSignatureDescriptor,
6    ResolveContext, Type, Value,
7};
8use runmat_macros::runtime_builtin;
9
10use crate::builtins::common::spec::{
11    BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
12    ReductionNaN, ResidencyPolicy, ShapeRequirements,
13};
14use crate::BuiltinResult;
15
16#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::logical::tests::issparse")]
17pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
18    name: "issparse",
19    op_kind: GpuOpKind::Custom("metadata"),
20    supported_precisions: &[],
21    broadcast: BroadcastSemantics::None,
22    provider_hooks: &[],
23    constant_strategy: ConstantStrategy::InlineLiteral,
24    residency: ResidencyPolicy::GatherImmediately,
25    nan_mode: ReductionNaN::Include,
26    two_pass_threshold: None,
27    workgroup_size: None,
28    accepts_nan_mode: false,
29    notes:
30        "Reports whether the value is a host sparse matrix; dense gpuArray handles are not sparse.",
31};
32
33#[runmat_macros::register_fusion_spec(builtin_path = "crate::builtins::logical::tests::issparse")]
34pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
35    name: "issparse",
36    shape: ShapeRequirements::Any,
37    constant_strategy: ConstantStrategy::InlineLiteral,
38    elementwise: None,
39    reduction: None,
40    emits_nan: false,
41    notes: "Metadata query that executes outside fusion and returns a scalar logical.",
42};
43
44const ISSPARSE_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
45    name: "tf",
46    ty: BuiltinParamType::LogicalArray,
47    arity: BuiltinParamArity::Required,
48    default: None,
49    description: "True when input is a sparse matrix.",
50}];
51
52const ISSPARSE_INPUTS: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
53    name: "A",
54    ty: BuiltinParamType::Any,
55    arity: BuiltinParamArity::Required,
56    default: None,
57    description: "Input value to test.",
58}];
59
60const ISSPARSE_SIGNATURES: [BuiltinSignatureDescriptor; 1] = [BuiltinSignatureDescriptor {
61    label: "tf = issparse(A)",
62    inputs: &ISSPARSE_INPUTS,
63    outputs: &ISSPARSE_OUTPUT,
64}];
65
66const ISSPARSE_ERRORS: [BuiltinErrorDescriptor; 0] = [];
67
68pub const ISSPARSE_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
69    signatures: &ISSPARSE_SIGNATURES,
70    output_mode: BuiltinOutputMode::Fixed,
71    completion_policy: BuiltinCompletionPolicy::Public,
72    errors: &ISSPARSE_ERRORS,
73};
74
75#[runtime_builtin(
76    name = "issparse",
77    category = "logical/tests",
78    summary = "Return true when a value is a sparse matrix.",
79    keywords = "issparse,sparse,matrix,type,logical",
80    accel = "metadata",
81    type_resolver(bool_scalar_type),
82    descriptor(crate::builtins::logical::tests::issparse::ISSPARSE_DESCRIPTOR),
83    builtin_path = "crate::builtins::logical::tests::issparse"
84)]
85async fn issparse_builtin(value: Value) -> BuiltinResult<Value> {
86    Ok(Value::Bool(matches!(value, Value::SparseTensor(_))))
87}
88
89fn bool_scalar_type(_: &[Type], _context: &ResolveContext) -> Type {
90    Type::Bool
91}
92
93#[cfg(test)]
94pub(crate) mod tests {
95    use super::*;
96    use crate::builtins::common::test_support;
97    use futures::executor::block_on;
98    use runmat_accelerate_api::HostTensorView;
99    use runmat_builtins::{
100        CellArray, CharArray, ComplexTensor, IntValue, LogicalArray, MException, ObjectInstance,
101        SparseTensor, StringArray, StructValue, SymbolicExpr, Tensor, Value,
102    };
103
104    fn run_issparse(value: Value) -> BuiltinResult<Value> {
105        block_on(super::issparse_builtin(value))
106    }
107
108    #[test]
109    fn issparse_type_returns_bool() {
110        assert_eq!(
111            super::bool_scalar_type(&[Type::Unknown], &ResolveContext::new(Vec::new())),
112            Type::Bool
113        );
114    }
115
116    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
117    #[test]
118    fn sparse_tensors_report_true() {
119        let sparse = SparseTensor::new(3, 2, vec![0, 1, 2], vec![1, 2], vec![4.0, -1.0]).unwrap();
120        assert_eq!(
121            run_issparse(Value::SparseTensor(sparse)).unwrap(),
122            Value::Bool(true)
123        );
124        assert_eq!(
125            run_issparse(Value::SparseTensor(SparseTensor::zeros(4, 5))).unwrap(),
126            Value::Bool(true)
127        );
128    }
129
130    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
131    #[test]
132    fn dense_numeric_and_logical_values_report_false() {
133        assert_eq!(
134            run_issparse(Value::Int(IntValue::I32(1))).unwrap(),
135            Value::Bool(false)
136        );
137        assert_eq!(run_issparse(Value::Num(1.0)).unwrap(), Value::Bool(false));
138        assert_eq!(
139            run_issparse(Value::Complex(1.0, -2.0)).unwrap(),
140            Value::Bool(false)
141        );
142        assert_eq!(run_issparse(Value::Bool(true)).unwrap(), Value::Bool(false));
143        assert_eq!(
144            run_issparse(Value::LogicalArray(
145                LogicalArray::new(vec![1, 0], vec![1, 2]).unwrap()
146            ))
147            .unwrap(),
148            Value::Bool(false)
149        );
150        assert_eq!(
151            run_issparse(Value::Tensor(
152                Tensor::new(vec![1.0, 0.0], vec![1, 2]).unwrap()
153            ))
154            .unwrap(),
155            Value::Bool(false)
156        );
157        assert_eq!(
158            run_issparse(Value::ComplexTensor(
159                ComplexTensor::new(vec![(1.0, 2.0)], vec![1, 1]).unwrap()
160            ))
161            .unwrap(),
162            Value::Bool(false)
163        );
164    }
165
166    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
167    #[test]
168    fn containers_text_and_objects_report_false() {
169        let cell = CellArray::new(vec![Value::Num(1.0)], 1, 1).unwrap();
170        let string = Value::String("abc".into());
171        let chars = CharArray::new_row("abc");
172        let strings = StringArray::new(vec!["abc".into()], vec![1, 1]).unwrap();
173        let structure = StructValue::new();
174        let object = ObjectInstance::new("Example".into());
175        assert_eq!(run_issparse(Value::Cell(cell)).unwrap(), Value::Bool(false));
176        assert_eq!(run_issparse(string).unwrap(), Value::Bool(false));
177        assert_eq!(
178            run_issparse(Value::CharArray(chars)).unwrap(),
179            Value::Bool(false)
180        );
181        assert_eq!(
182            run_issparse(Value::StringArray(strings)).unwrap(),
183            Value::Bool(false)
184        );
185        assert_eq!(
186            run_issparse(Value::Struct(structure)).unwrap(),
187            Value::Bool(false)
188        );
189        assert_eq!(
190            run_issparse(Value::Object(object)).unwrap(),
191            Value::Bool(false)
192        );
193        assert_eq!(
194            run_issparse(Value::FunctionHandle("sin".into())).unwrap(),
195            Value::Bool(false)
196        );
197        assert_eq!(
198            run_issparse(Value::Symbolic(SymbolicExpr::variable("x"))).unwrap(),
199            Value::Bool(false)
200        );
201        assert_eq!(
202            run_issparse(Value::ClassRef("Example".into())).unwrap(),
203            Value::Bool(false)
204        );
205        assert_eq!(
206            run_issparse(Value::MException(MException::new(
207                "RunMat:test".into(),
208                "not sparse".into()
209            )))
210            .unwrap(),
211            Value::Bool(false)
212        );
213    }
214
215    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
216    #[test]
217    fn gpu_handles_report_false() {
218        test_support::with_test_provider(|provider| {
219            let tensor = Tensor::new(vec![1.0, 2.0, 3.0], vec![3, 1]).unwrap();
220            let view = HostTensorView {
221                data: &tensor.data,
222                shape: &tensor.shape,
223            };
224            let handle = provider.upload(&view).expect("upload");
225            let result = run_issparse(Value::GpuTensor(handle.clone())).expect("issparse");
226            assert_eq!(result, Value::Bool(false));
227            provider.free(&handle).ok();
228        });
229    }
230}