deep_causality_physics/nuclear/
quantities.rs1use crate::error::PhysicsError;
7
8#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
10pub struct AmountOfSubstance(f64);
11
12impl AmountOfSubstance {
13 pub fn new(val: f64) -> Result<Self, PhysicsError> {
14 if val < 0.0 {
15 return Err(PhysicsError::PhysicalInvariantBroken(
16 "Negative AmountOfSubstance".into(),
17 ));
18 }
19 Ok(Self(val))
20 }
21 pub fn new_unchecked(val: f64) -> Self {
22 Self(val)
23 }
24 pub fn value(&self) -> f64 {
25 self.0
26 }
27}
28impl From<AmountOfSubstance> for f64 {
29 fn from(val: AmountOfSubstance) -> Self {
30 val.0
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
36pub struct HalfLife(f64);
37
38impl HalfLife {
39 pub fn new(val: f64) -> Result<Self, PhysicsError> {
44 if !val.is_finite() {
45 return Err(PhysicsError::PhysicalInvariantBroken(format!(
46 "HalfLife must be finite: {}",
47 val
48 )));
49 }
50 if val <= 0.0 {
51 return Err(PhysicsError::PhysicalInvariantBroken(
52 "HalfLife must be positive (zero implies infinite decay rate)".into(),
53 ));
54 }
55 Ok(Self(val))
56 }
57 pub fn new_unchecked(val: f64) -> Self {
58 Self(val)
59 }
60 pub fn value(&self) -> f64 {
61 self.0
62 }
63}
64impl From<HalfLife> for f64 {
65 fn from(val: HalfLife) -> Self {
66 val.0
67 }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
72pub struct Activity(f64);
73
74impl Activity {
75 pub fn new(val: f64) -> Result<Self, PhysicsError> {
76 if val < 0.0 {
77 return Err(PhysicsError::PhysicalInvariantBroken(
78 "Negative Activity".into(),
79 ));
80 }
81 Ok(Self(val))
82 }
83 pub fn new_unchecked(val: f64) -> Self {
84 Self(val)
85 }
86 pub fn value(&self) -> f64 {
87 self.0
88 }
89}
90impl From<Activity> for f64 {
91 fn from(val: Activity) -> Self {
92 val.0
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
98pub struct EnergyDensity(f64);
99
100impl EnergyDensity {
101 pub fn new(val: f64) -> Result<Self, PhysicsError> {
102 if val < 0.0 {
103 return Err(PhysicsError::PhysicalInvariantBroken(
104 "Negative EnergyDensity".into(),
105 ));
106 }
107 Ok(Self(val))
108 }
109 pub fn new_unchecked(val: f64) -> Self {
110 Self(val)
111 }
112 pub fn value(&self) -> f64 {
113 self.0
114 }
115}
116impl From<EnergyDensity> for f64 {
117 fn from(val: EnergyDensity) -> Self {
118 val.0
119 }
120}
121
122use core::ops::{Add, Sub};
127
128#[derive(Debug, Clone, Copy, Default, PartialEq)]
133pub struct FourMomentum {
134 e: f64,
136 px: f64,
138 py: f64,
140 pz: f64,
142}
143
144impl FourMomentum {
145 pub const fn new(e: f64, px: f64, py: f64, pz: f64) -> Self {
147 Self { e, px, py, pz }
148 }
149
150 pub fn from_mass_and_momentum(mass: f64, px: f64, py: f64, pz: f64) -> Self {
154 let p_sq = px * px + py * py + pz * pz;
155 let e = (mass * mass + p_sq).sqrt();
156 Self { e, px, py, pz }
157 }
158
159 pub const fn at_rest(mass: f64) -> Self {
161 Self {
162 e: mass,
163 px: 0.0,
164 py: 0.0,
165 pz: 0.0,
166 }
167 }
168
169 pub const fn e(&self) -> f64 {
171 self.e
172 }
173
174 pub const fn px(&self) -> f64 {
176 self.px
177 }
178
179 pub const fn py(&self) -> f64 {
181 self.py
182 }
183
184 pub const fn pz(&self) -> f64 {
186 self.pz
187 }
188
189 pub fn invariant_mass_squared(&self) -> f64 {
191 self.e * self.e - self.px * self.px - self.py * self.py - self.pz * self.pz
192 }
193
194 pub fn invariant_mass(&self) -> f64 {
198 let m_sq = self.invariant_mass_squared();
199 if m_sq > 0.0 { m_sq.sqrt() } else { 0.0 }
200 }
201
202 pub fn momentum_magnitude(&self) -> f64 {
204 (self.px * self.px + self.py * self.py + self.pz * self.pz).sqrt()
205 }
206
207 pub fn transverse_momentum(&self) -> f64 {
209 (self.px * self.px + self.py * self.py).sqrt()
210 }
211
212 pub fn transverse_mass(&self) -> f64 {
214 let m_sq = self.invariant_mass_squared();
215 let pt_sq = self.px * self.px + self.py * self.py;
216 (m_sq + pt_sq).sqrt()
217 }
218
219 pub fn rapidity(&self) -> f64 {
223 let denom = self.e - self.pz;
224 if denom.abs() < 1e-10 {
225 return 0.0;
226 }
227 0.5 * ((self.e + self.pz) / denom).ln()
228 }
229
230 pub fn pseudorapidity(&self) -> f64 {
234 let p = self.momentum_magnitude();
235 if p.abs() < 1e-10 {
236 return 0.0;
237 }
238 0.5 * ((p + self.pz) / (p - self.pz)).ln()
239 }
240
241 pub fn phi(&self) -> f64 {
243 self.py.atan2(self.px)
244 }
245
246 pub fn lightcone_plus(&self) -> f64 {
248 self.e + self.pz
249 }
250
251 pub fn lightcone_minus(&self) -> f64 {
253 self.e - self.pz
254 }
255
256 pub fn boost_z(&self, beta: f64) -> Self {
258 let gamma = 1.0 / (1.0 - beta * beta).sqrt();
259 let e_new = gamma * (self.e - beta * self.pz);
260 let pz_new = gamma * (self.pz - beta * self.e);
261 Self {
262 e: e_new,
263 px: self.px,
264 py: self.py,
265 pz: pz_new,
266 }
267 }
268}
269
270impl Add for FourMomentum {
271 type Output = Self;
272
273 fn add(self, rhs: Self) -> Self::Output {
274 Self {
275 e: self.e + rhs.e,
276 px: self.px + rhs.px,
277 py: self.py + rhs.py,
278 pz: self.pz + rhs.pz,
279 }
280 }
281}
282
283impl Sub for FourMomentum {
284 type Output = Self;
285
286 fn sub(self, rhs: Self) -> Self::Output {
287 Self {
288 e: self.e - rhs.e,
289 px: self.px - rhs.px,
290 py: self.py - rhs.py,
291 pz: self.pz - rhs.pz,
292 }
293 }
294}
295
296#[derive(Debug, Clone, PartialEq)]
298pub struct Hadron {
299 pdg_id: i32,
301 momentum: FourMomentum,
303}
304
305impl Hadron {
306 pub const fn new(pdg_id: i32, momentum: FourMomentum) -> Self {
308 Self { pdg_id, momentum }
309 }
310
311 pub const fn pdg_id(&self) -> i32 {
313 self.pdg_id
314 }
315
316 pub const fn momentum(&self) -> FourMomentum {
318 self.momentum
319 }
320
321 pub fn mass(&self) -> f64 {
323 self.momentum.invariant_mass()
324 }
325
326 pub fn energy(&self) -> f64 {
328 self.momentum.e()
329 }
330
331 pub fn pt(&self) -> f64 {
333 self.momentum.transverse_momentum()
334 }
335
336 pub fn rapidity(&self) -> f64 {
338 self.momentum.rapidity()
339 }
340}
341
342#[derive(Debug, Clone, PartialEq)]
349pub struct LundParameters {
350 kappa: f64,
352 lund_a: f64,
354 lund_b: f64,
356 sigma_pt: f64,
358 strange_suppression: f64,
360 diquark_suppression: f64,
362 vector_meson_fraction: f64,
364 min_invariant_mass: f64,
366}
367
368impl Default for LundParameters {
369 fn default() -> Self {
373 Self {
374 kappa: 1.0, lund_a: 0.68, lund_b: 0.98, sigma_pt: 0.36, strange_suppression: 0.30, diquark_suppression: 0.10, vector_meson_fraction: 0.50, min_invariant_mass: 0.5, }
383 }
384}
385
386impl LundParameters {
387 #[allow(clippy::too_many_arguments)]
389 pub const fn new(
390 kappa: f64,
391 lund_a: f64,
392 lund_b: f64,
393 sigma_pt: f64,
394 strange_suppression: f64,
395 diquark_suppression: f64,
396 vector_meson_fraction: f64,
397 min_invariant_mass: f64,
398 ) -> Self {
399 Self {
400 kappa,
401 lund_a,
402 lund_b,
403 sigma_pt,
404 strange_suppression,
405 diquark_suppression,
406 vector_meson_fraction,
407 min_invariant_mass,
408 }
409 }
410
411 pub const fn kappa(&self) -> f64 {
413 self.kappa
414 }
415
416 pub const fn lund_a(&self) -> f64 {
418 self.lund_a
419 }
420
421 pub const fn lund_b(&self) -> f64 {
423 self.lund_b
424 }
425
426 pub const fn sigma_pt(&self) -> f64 {
428 self.sigma_pt
429 }
430
431 pub const fn strange_suppression(&self) -> f64 {
433 self.strange_suppression
434 }
435
436 pub const fn diquark_suppression(&self) -> f64 {
438 self.diquark_suppression
439 }
440
441 pub const fn vector_meson_fraction(&self) -> f64 {
443 self.vector_meson_fraction
444 }
445
446 pub const fn min_invariant_mass(&self) -> f64 {
448 self.min_invariant_mass
449 }
450}