runmat_runtime/builtins/control/
isstable.rs1use 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}