1use serde::{Deserialize, Serialize};
2
3use laddu_core::{
4 amplitudes::{Amplitude, AmplitudeID, ParameterLike},
5 data::Event,
6 resources::{Cache, ParameterID, Parameters, Resources},
7 utils::{
8 functions::{blatt_weisskopf, breakup_momentum},
9 variables::{Mass, Variable},
10 },
11 Complex, DVector, Float, LadduError, PI,
12};
13
14#[cfg(feature = "python")]
15use laddu_python::{
16 amplitudes::{PyAmplitude, PyParameterLike},
17 utils::variables::PyMass,
18};
19#[cfg(feature = "python")]
20use pyo3::prelude::*;
21
22#[derive(Clone, Serialize, Deserialize)]
32pub struct BreitWigner {
33 name: String,
34 mass: ParameterLike,
35 width: ParameterLike,
36 pid_mass: ParameterID,
37 pid_width: ParameterID,
38 l: usize,
39 daughter_1_mass: Mass,
40 daughter_2_mass: Mass,
41 resonance_mass: Mass,
42}
43impl BreitWigner {
44 pub fn new(
48 name: &str,
49 mass: ParameterLike,
50 width: ParameterLike,
51 l: usize,
52 daughter_1_mass: &Mass,
53 daughter_2_mass: &Mass,
54 resonance_mass: &Mass,
55 ) -> Box<Self> {
56 Self {
57 name: name.to_string(),
58 mass,
59 width,
60 pid_mass: ParameterID::default(),
61 pid_width: ParameterID::default(),
62 l,
63 daughter_1_mass: daughter_1_mass.clone(),
64 daughter_2_mass: daughter_2_mass.clone(),
65 resonance_mass: resonance_mass.clone(),
66 }
67 .into()
68 }
69}
70
71#[typetag::serde]
72impl Amplitude for BreitWigner {
73 fn register(&mut self, resources: &mut Resources) -> Result<AmplitudeID, LadduError> {
74 self.pid_mass = resources.register_parameter(&self.mass);
75 self.pid_width = resources.register_parameter(&self.width);
76 resources.register_amplitude(&self.name)
77 }
78
79 fn compute(&self, parameters: &Parameters, event: &Event, _cache: &Cache) -> Complex<Float> {
80 let mass = self.resonance_mass.value(event);
81 let mass0 = parameters.get(self.pid_mass).abs();
82 let width0 = parameters.get(self.pid_width).abs();
83 let mass1 = self.daughter_1_mass.value(event);
84 let mass2 = self.daughter_2_mass.value(event);
85 let q0 = breakup_momentum(mass0, mass1, mass2);
86 let q = breakup_momentum(mass, mass1, mass2);
87 let f0 = blatt_weisskopf(mass0, mass1, mass2, self.l);
88 let f = blatt_weisskopf(mass, mass1, mass2, self.l);
89 let width = width0 * (mass0 / mass) * (q / q0) * (f / f0).powi(2);
90 let n = Float::sqrt(mass0 * width0 / PI);
91 let d = Complex::new(mass0.powi(2) - mass.powi(2), -(mass0 * width));
92 Complex::from(f * n) / d
93 }
94
95 fn compute_gradient(
96 &self,
97 parameters: &Parameters,
98 event: &Event,
99 cache: &Cache,
100 gradient: &mut DVector<Complex<Float>>,
101 ) {
102 let mut indices = Vec::with_capacity(2);
103 if let ParameterID::Parameter(index) = self.pid_mass {
104 indices.push(index)
105 }
106 if let ParameterID::Parameter(index) = self.pid_width {
107 indices.push(index)
108 }
109 self.central_difference_with_indices(&indices, parameters, event, cache, gradient)
110 }
111}
112
113#[cfg(feature = "python")]
144#[pyfunction(name = "BreitWigner")]
145pub fn py_breit_wigner(
146 name: &str,
147 mass: PyParameterLike,
148 width: PyParameterLike,
149 l: usize,
150 daughter_1_mass: &PyMass,
151 daughter_2_mass: &PyMass,
152 resonance_mass: &PyMass,
153) -> PyAmplitude {
154 PyAmplitude(BreitWigner::new(
155 name,
156 mass.0,
157 width.0,
158 l,
159 &daughter_1_mass.0,
160 &daughter_2_mass.0,
161 &resonance_mass.0,
162 ))
163}
164
165#[cfg(test)]
166mod tests {
167 use std::sync::Arc;
168
169 use super::*;
170 use approx::assert_relative_eq;
171 use laddu_core::{data::test_dataset, parameter, Manager, Mass};
172
173 #[test]
174 fn test_bw_evaluation() {
175 let mut manager = Manager::default();
176 let amp = BreitWigner::new(
177 "bw",
178 parameter("mass"),
179 parameter("width"),
180 2,
181 &Mass::new([2]),
182 &Mass::new([3]),
183 &Mass::new([2, 3]),
184 );
185 let aid = manager.register(amp).unwrap();
186
187 let dataset = Arc::new(test_dataset());
188 let expr = aid.into();
189 let model = manager.model(&expr);
190 let evaluator = model.load(&dataset);
191
192 let result = evaluator.evaluate(&[1.5, 0.3]);
193
194 assert_relative_eq!(result[0].re, 1.45856917, epsilon = Float::EPSILON.sqrt());
195 assert_relative_eq!(result[0].im, 1.4107341, epsilon = Float::EPSILON.sqrt());
196 }
197
198 #[test]
199 fn test_bw_gradient() {
200 let mut manager = Manager::default();
201 let amp = BreitWigner::new(
202 "bw",
203 parameter("mass"),
204 parameter("width"),
205 2,
206 &Mass::new([2]),
207 &Mass::new([3]),
208 &Mass::new([2, 3]),
209 );
210 let aid = manager.register(amp).unwrap();
211
212 let dataset = Arc::new(test_dataset());
213 let expr = aid.into();
214 let model = manager.model(&expr);
215 let evaluator = model.load(&dataset);
216 dbg!(Mass::new([2, 3]).value_on(&dataset));
217
218 let result = evaluator.evaluate_gradient(&[1.7, 0.3]);
219 assert_relative_eq!(result[0][0].re, -2.410585, epsilon = Float::EPSILON.cbrt());
220 assert_relative_eq!(result[0][0].im, -1.8880913, epsilon = Float::EPSILON.cbrt());
221 assert_relative_eq!(result[0][1].re, 1.0467031, epsilon = Float::EPSILON.cbrt());
222 assert_relative_eq!(result[0][1].im, 1.3683612, epsilon = Float::EPSILON.cbrt());
223 }
224}