Skip to main content

runmat_runtime/builtins/math/optim/
optimset.rs

1//! Minimal MATLAB-compatible `optimset` options struct builder.
2
3use runmat_builtins::{StructValue, Value};
4use runmat_macros::runtime_builtin;
5
6use crate::builtins::common::spec::{
7    BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
8    ReductionNaN, ResidencyPolicy, ShapeRequirements,
9};
10use crate::builtins::math::optim::common::{field_name, optim_error};
11use crate::builtins::math::optim::type_resolvers::optim_options_type;
12use crate::BuiltinResult;
13
14const NAME: &str = "optimset";
15
16#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::math::optim::optimset")]
17pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
18    name: "optimset",
19    op_kind: GpuOpKind::Custom("options"),
20    supported_precisions: &[],
21    broadcast: BroadcastSemantics::None,
22    provider_hooks: &[],
23    constant_strategy: ConstantStrategy::InlineLiteral,
24    residency: ResidencyPolicy::InheritInputs,
25    nan_mode: ReductionNaN::Include,
26    two_pass_threshold: None,
27    workgroup_size: None,
28    accepts_nan_mode: false,
29    notes: "Host metadata construction. GPU values used as option payloads are preserved without gathering.",
30};
31
32#[runmat_macros::register_fusion_spec(builtin_path = "crate::builtins::math::optim::optimset")]
33pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
34    name: "optimset",
35    shape: ShapeRequirements::Any,
36    constant_strategy: ConstantStrategy::InlineLiteral,
37    elementwise: None,
38    reduction: None,
39    emits_nan: false,
40    notes: "Option struct construction is host metadata work and does not fuse.",
41};
42
43#[runtime_builtin(
44    name = "optimset",
45    category = "math/optim",
46    summary = "Create or update an optimization options structure for fzero and fsolve.",
47    keywords = "optimset,options,TolX,TolFun,MaxIter,Display",
48    type_resolver(optim_options_type),
49    builtin_path = "crate::builtins::math::optim::optimset"
50)]
51async fn optimset_builtin(rest: Vec<Value>) -> BuiltinResult<Value> {
52    let mut fields = StructValue::new();
53    let mut args = rest.into_iter();
54
55    if let Some(first) = args.next() {
56        match first {
57            Value::Struct(existing) => fields = existing,
58            other => {
59                let second = args.next().ok_or_else(|| {
60                    optim_error(NAME, "optimset: expected option name/value pairs")
61                })?;
62                let name = field_name(&other)?;
63                fields.insert(canonical_option_name(&name), second);
64            }
65        }
66    }
67
68    let remaining = args.collect::<Vec<_>>();
69    if remaining.len() % 2 != 0 {
70        return Err(optim_error(
71            NAME,
72            "optimset: expected option name/value pairs",
73        ));
74    }
75    for pair in remaining.chunks(2) {
76        let name = field_name(&pair[0])?;
77        fields.insert(canonical_option_name(&name), pair[1].clone());
78    }
79
80    Ok(Value::Struct(fields))
81}
82
83fn canonical_option_name(name: &str) -> String {
84    match name.to_ascii_lowercase().as_str() {
85        "tolx" => "TolX".to_string(),
86        "tolfun" => "TolFun".to_string(),
87        "maxiter" => "MaxIter".to_string(),
88        "maxfunevals" => "MaxFunEvals".to_string(),
89        "display" => "Display".to_string(),
90        _ => name.to_string(),
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use futures::executor::block_on;
98
99    #[test]
100    fn optimset_builds_struct_from_pairs() {
101        let value = block_on(optimset_builtin(vec![
102            Value::from("TolX"),
103            Value::Num(1.0e-8),
104            Value::from("Display"),
105            Value::from("off"),
106        ]))
107        .unwrap();
108        match value {
109            Value::Struct(options) => {
110                assert!(matches!(options.fields.get("TolX"), Some(Value::Num(_))));
111                assert!(matches!(
112                    options.fields.get("Display"),
113                    Some(Value::String(_))
114                ));
115            }
116            other => panic!("unexpected value {other:?}"),
117        }
118    }
119}