Skip to main content

runmat_runtime/builtins/control/
isstable.rs

1//! Stability test for SISO transfer-function models.
2
3use runmat_builtins::{
4    BuiltinCompletionPolicy, BuiltinDescriptor, BuiltinErrorDescriptor, BuiltinOutputMode,
5    BuiltinParamArity, BuiltinParamDescriptor, BuiltinParamType, BuiltinSignatureDescriptor, Value,
6};
7use runmat_macros::runtime_builtin;
8
9use crate::builtins::common::spec::{
10    BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
11    ReductionNaN, ResidencyPolicy, ShapeRequirements,
12};
13use crate::builtins::control::tf_model::TfModel;
14use crate::builtins::control::type_resolvers::isstable_type;
15use crate::BuiltinResult;
16
17const ISSTABLE_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
18    name: "tf",
19    ty: BuiltinParamType::LogicalArray,
20    arity: BuiltinParamArity::Required,
21    default: None,
22    description: "True when all continuous poles are in the open left-half plane or all discrete poles are inside the unit circle.",
23}];
24const ISSTABLE_INPUTS: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
25    name: "sys",
26    ty: BuiltinParamType::Any,
27    arity: BuiltinParamArity::Required,
28    default: None,
29    description: "SISO tf model.",
30}];
31const ISSTABLE_SIGNATURES: [BuiltinSignatureDescriptor; 1] = [BuiltinSignatureDescriptor {
32    label: "tf = isstable(sys)",
33    inputs: &ISSTABLE_INPUTS,
34    outputs: &ISSTABLE_OUTPUT,
35}];
36const ISSTABLE_ERRORS: [BuiltinErrorDescriptor; 3] = [
37    BuiltinErrorDescriptor {
38        code: "RM.ISSTABLE.INVALID_MODEL",
39        identifier: Some("RunMat:isstable:InvalidModel"),
40        when: "Input system is not a valid SISO tf object.",
41        message: "isstable: invalid model",
42    },
43    BuiltinErrorDescriptor {
44        code: "RM.ISSTABLE.UNSUPPORTED_MODEL",
45        identifier: Some("RunMat:isstable:UnsupportedModel"),
46        when: "Model form is unsupported.",
47        message: "isstable: unsupported model",
48    },
49    BuiltinErrorDescriptor {
50        code: "RM.ISSTABLE.INTERNAL",
51        identifier: Some("RunMat:isstable:Internal"),
52        when: "Pole calculation failed.",
53        message: "isstable: internal error",
54    },
55];
56pub const ISSTABLE_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
57    signatures: &ISSTABLE_SIGNATURES,
58    output_mode: BuiltinOutputMode::Fixed,
59    completion_policy: BuiltinCompletionPolicy::Public,
60    errors: &ISSTABLE_ERRORS,
61};
62
63#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::control::isstable")]
64pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
65    name: "isstable",
66    op_kind: GpuOpKind::Custom("control-stability"),
67    supported_precisions: &[],
68    broadcast: BroadcastSemantics::None,
69    provider_hooks: &[],
70    constant_strategy: ConstantStrategy::InlineLiteral,
71    residency: ResidencyPolicy::GatherImmediately,
72    nan_mode: ReductionNaN::Include,
73    two_pass_threshold: None,
74    workgroup_size: None,
75    accepts_nan_mode: false,
76    notes: "isstable analyzes host-side transfer-function metadata.",
77};
78
79#[runmat_macros::register_fusion_spec(builtin_path = "crate::builtins::control::isstable")]
80pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
81    name: "isstable",
82    shape: ShapeRequirements::Any,
83    constant_strategy: ConstantStrategy::InlineLiteral,
84    elementwise: None,
85    reduction: None,
86    emits_nan: false,
87    notes: "isstable returns scalar model metadata and is not fused.",
88};
89
90#[runtime_builtin(
91    name = "isstable",
92    category = "control",
93    summary = "Test stability of SISO transfer-function models.",
94    keywords = "isstable,control system,stability,poles,tf",
95    type_resolver(isstable_type),
96    descriptor(crate::builtins::control::isstable::ISSTABLE_DESCRIPTOR),
97    builtin_path = "crate::builtins::control::isstable"
98)]
99async fn isstable_builtin(sys: Value) -> BuiltinResult<Value> {
100    let model = TfModel::from_value_async(sys, "isstable").await?;
101    Ok(Value::Bool(model.is_stable()?))
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use futures::executor::block_on;
108    use runmat_builtins::Tensor;
109
110    #[test]
111    fn stable_continuous_model_returns_true() {
112        let sys = block_on(crate::call_builtin_async(
113            "tf",
114            &[
115                Value::Num(1.0),
116                Value::Tensor(Tensor::new(vec![1.0, 3.0, 2.0], vec![1, 3]).unwrap()),
117            ],
118        ))
119        .expect("tf");
120        assert_eq!(
121            block_on(isstable_builtin(sys)).expect("isstable"),
122            Value::Bool(true)
123        );
124    }
125}