Skip to main content

runmat_runtime/builtins/control/
dcgain.rs

1//! DC gain 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::{output_complex_scalar, TfModel};
14use crate::builtins::control::type_resolvers::dcgain_type;
15use crate::BuiltinResult;
16
17const DCGAIN_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
18    name: "gain",
19    ty: BuiltinParamType::Any,
20    arity: BuiltinParamArity::Required,
21    default: None,
22    description: "Steady-state gain evaluated at s=0 for continuous-time or z=1 for discrete-time.",
23}];
24const DCGAIN_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 DCGAIN_SIGNATURES: [BuiltinSignatureDescriptor; 1] = [BuiltinSignatureDescriptor {
32    label: "gain = dcgain(sys)",
33    inputs: &DCGAIN_INPUTS,
34    outputs: &DCGAIN_OUTPUT,
35}];
36const DCGAIN_ERRORS: [BuiltinErrorDescriptor; 4] = [
37    BuiltinErrorDescriptor {
38        code: "RM.DCGAIN.INVALID_ARGUMENT",
39        identifier: Some("RunMat:dcgain:InvalidArgument"),
40        when: "Input does not match supported invocation forms.",
41        message: "dcgain: invalid argument",
42    },
43    BuiltinErrorDescriptor {
44        code: "RM.DCGAIN.INVALID_MODEL",
45        identifier: Some("RunMat:dcgain:InvalidModel"),
46        when: "Input system is not a valid SISO tf object.",
47        message: "dcgain: invalid model",
48    },
49    BuiltinErrorDescriptor {
50        code: "RM.DCGAIN.UNSUPPORTED_MODEL",
51        identifier: Some("RunMat:dcgain:UnsupportedModel"),
52        when: "Model form is not supported by the current implementation.",
53        message: "dcgain: unsupported model",
54    },
55    BuiltinErrorDescriptor {
56        code: "RM.DCGAIN.INTERNAL",
57        identifier: Some("RunMat:dcgain:Internal"),
58        when: "Gain evaluation failed internally.",
59        message: "dcgain: internal error",
60    },
61];
62pub const DCGAIN_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
63    signatures: &DCGAIN_SIGNATURES,
64    output_mode: BuiltinOutputMode::Fixed,
65    completion_policy: BuiltinCompletionPolicy::Public,
66    errors: &DCGAIN_ERRORS,
67};
68
69#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::control::dcgain")]
70pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
71    name: "dcgain",
72    op_kind: GpuOpKind::Custom("control-dc-gain"),
73    supported_precisions: &[],
74    broadcast: BroadcastSemantics::None,
75    provider_hooks: &[],
76    constant_strategy: ConstantStrategy::InlineLiteral,
77    residency: ResidencyPolicy::GatherImmediately,
78    nan_mode: ReductionNaN::Include,
79    two_pass_threshold: None,
80    workgroup_size: None,
81    accepts_nan_mode: false,
82    notes: "dcgain evaluates host-side transfer-function metadata.",
83};
84
85#[runmat_macros::register_fusion_spec(builtin_path = "crate::builtins::control::dcgain")]
86pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
87    name: "dcgain",
88    shape: ShapeRequirements::Any,
89    constant_strategy: ConstantStrategy::InlineLiteral,
90    elementwise: None,
91    reduction: None,
92    emits_nan: false,
93    notes: "dcgain is scalar model analysis and is not fused.",
94};
95
96#[runtime_builtin(
97    name = "dcgain",
98    category = "control",
99    summary = "Evaluate steady-state gain of SISO transfer-function models.",
100    keywords = "dcgain,control system,steady state,transfer function,tf",
101    type_resolver(dcgain_type),
102    descriptor(crate::builtins::control::dcgain::DCGAIN_DESCRIPTOR),
103    builtin_path = "crate::builtins::control::dcgain"
104)]
105async fn dcgain_builtin(sys: Value) -> BuiltinResult<Value> {
106    let model = TfModel::from_value_async(sys, "dcgain").await?;
107    Ok(output_complex_scalar(model.dc_gain()?))
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use futures::executor::block_on;
114    use runmat_builtins::Tensor;
115
116    #[test]
117    fn continuous_dcgain_evaluates_at_zero() {
118        let sys = block_on(crate::call_builtin_async(
119            "tf",
120            &[
121                Value::Num(2.0),
122                Value::Tensor(Tensor::new(vec![1.0, 3.0], vec![1, 2]).unwrap()),
123            ],
124        ))
125        .expect("tf");
126        let Value::Num(gain) = block_on(dcgain_builtin(sys)).expect("dcgain") else {
127            panic!("expected scalar gain");
128        };
129        assert!((gain - 2.0 / 3.0).abs() < 1.0e-12);
130    }
131}