runmat_runtime/builtins/math/optim/
optimset.rs1use 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}