runmat_runtime/builtins/control/
pole.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::{output_complex_column, TfModel};
14use crate::builtins::control::type_resolvers::pole_type;
15use crate::BuiltinResult;
16
17const POLE_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
18 name: "p",
19 ty: BuiltinParamType::Any,
20 arity: BuiltinParamArity::Required,
21 default: None,
22 description: "Poles of the SISO tf model as a column vector.",
23}];
24const POLE_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 POLE_SIGNATURES: [BuiltinSignatureDescriptor; 1] = [BuiltinSignatureDescriptor {
32 label: "p = pole(sys)",
33 inputs: &POLE_INPUTS,
34 outputs: &POLE_OUTPUT,
35}];
36const POLE_ERRORS: [BuiltinErrorDescriptor; 3] = [
37 BuiltinErrorDescriptor {
38 code: "RM.POLE.INVALID_MODEL",
39 identifier: Some("RunMat:pole:InvalidModel"),
40 when: "Input system is not a valid SISO tf object.",
41 message: "pole: invalid model",
42 },
43 BuiltinErrorDescriptor {
44 code: "RM.POLE.UNSUPPORTED_MODEL",
45 identifier: Some("RunMat:pole:UnsupportedModel"),
46 when: "Model form is unsupported.",
47 message: "pole: unsupported model",
48 },
49 BuiltinErrorDescriptor {
50 code: "RM.POLE.INTERNAL",
51 identifier: Some("RunMat:pole:Internal"),
52 when: "Root calculation or output construction failed.",
53 message: "pole: internal error",
54 },
55];
56pub const POLE_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
57 signatures: &POLE_SIGNATURES,
58 output_mode: BuiltinOutputMode::Fixed,
59 completion_policy: BuiltinCompletionPolicy::Public,
60 errors: &POLE_ERRORS,
61};
62
63#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::control::pole")]
64pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
65 name: "pole",
66 op_kind: GpuOpKind::Custom("control-poles"),
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: "pole computes roots from host-side transfer-function metadata.",
77};
78
79#[runmat_macros::register_fusion_spec(builtin_path = "crate::builtins::control::pole")]
80pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
81 name: "pole",
82 shape: ShapeRequirements::Any,
83 constant_strategy: ConstantStrategy::InlineLiteral,
84 elementwise: None,
85 reduction: None,
86 emits_nan: false,
87 notes: "pole is model analysis and is not fused.",
88};
89
90#[runtime_builtin(
91 name = "pole",
92 category = "control",
93 summary = "Return poles of SISO transfer-function models.",
94 keywords = "pole,poles,control system,stability,transfer function,tf",
95 type_resolver(pole_type),
96 descriptor(crate::builtins::control::pole::POLE_DESCRIPTOR),
97 builtin_path = "crate::builtins::control::pole"
98)]
99async fn pole_builtin(sys: Value) -> BuiltinResult<Value> {
100 let model = TfModel::from_value_async(sys, "pole").await?;
101 output_complex_column(model.poles()?, "pole")
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 pole_returns_roots_of_denominator() {
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 let Value::Tensor(poles) = block_on(pole_builtin(sys)).expect("pole") else {
121 panic!("expected real poles");
122 };
123 assert_eq!(poles.shape, vec![2, 1]);
124 assert!(poles.data.iter().any(|p| (*p + 1.0).abs() < 1.0e-8));
125 assert!(poles.data.iter().any(|p| (*p + 2.0).abs() < 1.0e-8));
126 }
127}