Skip to main content

laddu_core/parameters/
parameter.rs

1//! Parameter handles, assembled parameter storage, and parameter identifiers.
2
3use std::{hash::Hash, sync::Arc};
4
5use parking_lot::Mutex;
6use serde::{Deserialize, Serialize};
7
8#[derive(Clone, Default, Serialize, Deserialize, Debug)]
9struct ParameterMetadata {
10    /// The name of the parameter.
11    name: String,
12    /// If `Some`, this parameter is fixed to the given value. If `None`, it is free.
13    fixed: Option<f64>,
14    /// If `Some`, this is used for the initial value of the parameter in fits. If `None`, the user
15    /// must provide the initial value on their own.
16    initial: Option<f64>,
17    /// Optional bounds which may be automatically used by optimizers. `None` represents no bound
18    /// in the given direction.
19    bounds: (Option<f64>, Option<f64>),
20    /// An optional unit string which may be used to annotate the parameter.
21    unit: Option<String>,
22    /// Optional LaTeX representation of the parameter.
23    latex: Option<String>,
24    /// Optional description of the parameter.
25    description: Option<String>,
26}
27
28/// An enum containing either a named free parameter or a constant value.
29#[derive(Clone, Default, Serialize, Deserialize, Debug)]
30pub struct Parameter(Arc<Mutex<ParameterMetadata>>);
31
32impl PartialEq for Parameter {
33    fn eq(&self, other: &Self) -> bool {
34        self.0.lock().name == other.0.lock().name
35    }
36}
37impl Eq for Parameter {}
38impl Hash for Parameter {
39    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
40        self.0.lock().name.hash(state);
41    }
42}
43
44/// Helper trait to convert values to bounds-like [`Option<f64>`].
45pub trait IntoBound {
46    /// Convert to a bound.
47    fn into_bound(self) -> Option<f64>;
48}
49impl IntoBound for f64 {
50    fn into_bound(self) -> Option<f64> {
51        Some(self)
52    }
53}
54impl IntoBound for Option<f64> {
55    fn into_bound(self) -> Option<f64> {
56        self
57    }
58}
59
60impl Parameter {
61    /// Create a free (floating) parameter with the given name.
62    pub fn new(name: impl Into<String>) -> Self {
63        Self(Arc::new(Mutex::new(ParameterMetadata {
64            name: name.into(),
65            ..Default::default()
66        })))
67    }
68
69    /// Create a fixed parameter with the given name and value.
70    pub fn new_fixed(name: impl Into<String>, value: f64) -> Self {
71        Self(Arc::new(Mutex::new(ParameterMetadata {
72            name: name.into(),
73            fixed: Some(value),
74            ..Default::default()
75        })))
76    }
77
78    /// Return the current parameter name.
79    pub fn name(&self) -> String {
80        self.0.lock().name.clone()
81    }
82
83    /// Return the fixed value when the parameter is fixed.
84    pub fn fixed(&self) -> Option<f64> {
85        self.0.lock().fixed
86    }
87
88    /// Return the current initial value, if one is set.
89    pub fn initial(&self) -> Option<f64> {
90        self.0.lock().initial
91    }
92
93    /// Return the current lower and upper bounds.
94    pub fn bounds(&self) -> (Option<f64>, Option<f64>) {
95        self.0.lock().bounds
96    }
97
98    /// Return the optional unit label.
99    pub fn unit(&self) -> Option<String> {
100        self.0.lock().unit.clone()
101    }
102
103    /// Return the optional LaTeX label.
104    pub fn latex(&self) -> Option<String> {
105        self.0.lock().latex.clone()
106    }
107
108    /// Return the optional human-readable description.
109    pub fn description(&self) -> Option<String> {
110        self.0.lock().description.clone()
111    }
112
113    /// Helper method to set the name of a parameter.
114    pub(crate) fn set_name(&self, name: impl Into<String>) {
115        self.0.lock().name = name.into();
116    }
117
118    /// Helper method to set the fixed value of a parameter.
119    pub fn set_fixed_value(&self, value: Option<f64>) {
120        let mut guard = self.0.lock();
121        if let Some(value) = value {
122            guard.fixed = Some(value);
123            guard.initial = Some(value);
124        } else {
125            guard.fixed = None;
126        }
127    }
128
129    /// Helper method to set the initial value of a parameter.
130    ///
131    /// # Panics
132    ///
133    /// This method panics if the parameter is fixed.
134    pub fn set_initial(&self, value: f64) {
135        assert!(
136            self.is_free(),
137            "cannot manually set `initial` on a fixed parameter"
138        );
139        self.0.lock().initial = Some(value);
140    }
141
142    /// Helper method to set the bounds of a parameter.
143    pub fn set_bounds<L, U>(&self, min: L, max: U)
144    where
145        L: IntoBound,
146        U: IntoBound,
147    {
148        self.0.lock().bounds = (IntoBound::into_bound(min), IntoBound::into_bound(max));
149    }
150
151    /// Helper method to set the unit of a parameter.
152    pub fn set_unit(&self, unit: impl Into<String>) {
153        self.0.lock().unit = Some(unit.into());
154    }
155
156    /// Helper method to set the LaTeX representation of a parameter.
157    pub fn set_latex(&self, latex: impl Into<String>) {
158        self.0.lock().latex = Some(latex.into());
159    }
160
161    /// Helper method to set the description of a parameter.
162    pub fn set_description(&self, description: impl Into<String>) {
163        self.0.lock().description = Some(description.into());
164    }
165
166    /// Is this parameter free?
167    pub fn is_free(&self) -> bool {
168        self.0.lock().fixed.is_none()
169    }
170
171    /// Is this parameter fixed?
172    pub fn is_fixed(&self) -> bool {
173        self.0.lock().fixed.is_some()
174    }
175}
176
177/// Convenience macro for creating parameters. Usage:
178/// `parameter!("name")` for a free parameter, or `parameter!("name", 1.0)` for a fixed one.
179#[macro_export]
180macro_rules! parameter {
181    ($name:expr) => {{
182        $crate::parameters::Parameter::new($name)
183    }};
184
185    ($name:expr, $value:expr) => {{
186        let p = $crate::parameters::Parameter::new($name);
187        p.set_fixed_value(Some($value));
188        p
189    }};
190
191    ($name:expr, $($rest:tt)+) => {{
192        let p = $crate::parameters::Parameter::new($name);
193        $crate::parameter!(@parse p, [fixed = false, initial = false]; $($rest)+);
194        p
195    }};
196
197    (@parse $p:ident, [fixed = $f:tt, initial = $i:tt]; ) => {};
198
199    (@parse $p:ident, [fixed = false, initial = false]; fixed : $value:expr $(, $($rest:tt)*)?) => {{
200        $p.set_fixed_value(Some($value));
201        $crate::parameter!(@parse $p, [fixed = true, initial = false]; $($($rest)*)?);
202    }};
203
204    (@parse $p:ident, [fixed = false, initial = false]; initial : $value:expr $(, $($rest:tt)*)?) => {{
205        $p.set_initial($value);
206        $crate::parameter!(@parse $p, [fixed = false, initial = true]; $($($rest)*)?);
207    }};
208
209    (@parse $p:ident, [fixed = true, initial = false]; initial : $value:expr $(, $($rest:tt)*)?) => {
210        compile_error!("parameter!: cannot specify both `fixed` and `initial`");
211    };
212
213    (@parse $p:ident, [fixed = false, initial = true]; fixed : $value:expr $(, $($rest:tt)*)?) => {
214        compile_error!("parameter!: cannot specify both `fixed` and `initial`");
215    };
216
217    (@parse $p:ident, [fixed = $f:tt, initial = $i:tt]; bounds : ($min:expr, $max:expr) $(, $($rest:tt)*)?) => {{
218        $p.set_bounds($min, $max);
219        $crate::parameter!(@parse $p, [fixed = $f, initial = $i]; $($($rest)*)?);
220    }};
221
222    (@parse $p:ident, [fixed = $f:tt, initial = $i:tt]; unit : $value:expr $(, $($rest:tt)*)?) => {{
223        $p.set_unit($value);
224        $crate::parameter!(@parse $p, [fixed = $f, initial = $i]; $($($rest)*)?);
225    }};
226
227    (@parse $p:ident, [fixed = $f:tt, initial = $i:tt]; latex : $value:expr $(, $($rest:tt)*)?) => {{
228        $p.set_latex($value);
229        $crate::parameter!(@parse $p, [fixed = $f, initial = $i]; $($($rest)*)?);
230    }};
231
232    (@parse $p:ident, [fixed = $f:tt, initial = $i:tt]; description : $value:expr $(, $($rest:tt)*)?) => {{
233        $p.set_description($value);
234        $crate::parameter!(@parse $p, [fixed = $f, initial = $i]; $($($rest)*)?);
235    }};
236}