Skip to main content

use_quantum/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4//! Small quantum physics scalar helpers.
5
6pub mod prelude;
7
8/// Planck constant `h`, in joule seconds.
9///
10/// Broader physical constants belong in the top-level `use-constants` set.
11pub const PLANCK_CONSTANT: f64 = 6.626_070_15e-34;
12
13/// Reduced Planck constant `hbar`, in joule seconds.
14///
15/// Broader physical constants belong in the top-level `use-constants` set.
16pub const REDUCED_PLANCK_CONSTANT: f64 = 1.054_571_817e-34;
17
18/// Speed of light in vacuum, in meters per second.
19///
20/// Broader physical constants belong in the top-level `use-constants` set.
21pub const SPEED_OF_LIGHT: f64 = 299_792_458.0;
22
23/// Elementary charge, in coulombs.
24///
25/// Broader physical constants belong in the top-level `use-constants` set.
26pub const ELEMENTARY_CHARGE: f64 = 1.602_176_634e-19;
27
28/// Electron rest mass, in kilograms.
29///
30/// Broader physical constants belong in the top-level `use-constants` set.
31pub const ELECTRON_MASS: f64 = 9.109_383_701_5e-31;
32
33/// Bohr radius `a0`, in meters.
34///
35/// Broader physical constants belong in the top-level `use-constants` set.
36pub const BOHR_RADIUS: f64 = 5.291_772_109_03e-11;
37
38/// Hydrogen Rydberg energy magnitude, in electron volts.
39///
40/// Broader physical constants belong in the top-level `use-constants` set.
41pub const RYDBERG_ENERGY_EV: f64 = 13.605_693_122_994;
42
43fn is_nonnegative_finite(value: f64) -> bool {
44    value.is_finite() && value >= 0.0
45}
46
47fn is_positive_finite(value: f64) -> bool {
48    value.is_finite() && value > 0.0
49}
50
51fn finite_result(value: f64) -> Option<f64> {
52    value.is_finite().then_some(value)
53}
54
55fn principal_squared(principal: u32) -> Option<f64> {
56    if principal == 0 {
57        return None;
58    }
59
60    let principal = f64::from(principal);
61    finite_result(principal * principal)
62}
63
64fn momentum_magnitude_from_mass_velocity(mass: f64, velocity: f64) -> Option<f64> {
65    if !is_positive_finite(mass) || !velocity.is_finite() {
66        return None;
67    }
68
69    let speed = velocity.abs();
70    if speed == 0.0 {
71        return None;
72    }
73
74    let momentum = mass * speed;
75    (momentum.is_finite() && momentum > 0.0).then_some(momentum)
76}
77
78/// Computes photon energy from frequency using `E = h * f`.
79///
80/// Returns `None` when `frequency` is negative, when the input is not finite, or when the
81/// computed result is not finite.
82///
83/// # Examples
84///
85/// ```rust
86/// use use_quantum::{PLANCK_CONSTANT, photon_energy_from_frequency};
87///
88/// let energy = photon_energy_from_frequency(1.0).ok_or("expected valid frequency")?;
89///
90/// assert_eq!(energy, PLANCK_CONSTANT);
91/// # Ok::<(), &'static str>(())
92/// ```
93#[must_use]
94pub fn photon_energy_from_frequency(frequency: f64) -> Option<f64> {
95    if !is_nonnegative_finite(frequency) {
96        return None;
97    }
98
99    finite_result(PLANCK_CONSTANT * frequency)
100}
101
102/// Computes photon energy from wavelength using `E = h * c / lambda`.
103///
104/// Returns `None` when `wavelength` is not positive and finite, or when the computed result is
105/// not finite.
106///
107/// # Examples
108///
109/// ```rust
110/// use use_quantum::photon_energy_from_wavelength;
111///
112/// let energy = photon_energy_from_wavelength(500.0e-9).ok_or("expected valid wavelength")?;
113///
114/// assert!(energy > 0.0);
115/// # Ok::<(), &'static str>(())
116/// ```
117#[must_use]
118pub fn photon_energy_from_wavelength(wavelength: f64) -> Option<f64> {
119    if !is_positive_finite(wavelength) {
120        return None;
121    }
122
123    finite_result((PLANCK_CONSTANT * SPEED_OF_LIGHT) / wavelength)
124}
125
126/// Computes frequency from photon energy using `f = E / h`.
127///
128/// Returns `None` when `energy` is negative, when the input is not finite, or when the computed
129/// result is not finite.
130#[must_use]
131pub fn frequency_from_photon_energy(energy: f64) -> Option<f64> {
132    if !is_nonnegative_finite(energy) {
133        return None;
134    }
135
136    finite_result(energy / PLANCK_CONSTANT)
137}
138
139/// Computes wavelength from photon energy using `lambda = h * c / E`.
140///
141/// Returns `None` when `energy` is not positive and finite, or when the computed result is not
142/// finite.
143#[must_use]
144pub fn wavelength_from_photon_energy(energy: f64) -> Option<f64> {
145    if !is_positive_finite(energy) {
146        return None;
147    }
148
149    finite_result((PLANCK_CONSTANT * SPEED_OF_LIGHT) / energy)
150}
151
152/// Computes photon momentum from wavelength using `p = h / lambda`.
153///
154/// Returns `None` when `wavelength` is not positive and finite, or when the computed result is
155/// not finite.
156#[must_use]
157pub fn photon_momentum_from_wavelength(wavelength: f64) -> Option<f64> {
158    if !is_positive_finite(wavelength) {
159        return None;
160    }
161
162    finite_result(PLANCK_CONSTANT / wavelength)
163}
164
165/// Computes photon momentum from energy using `p = E / c`.
166///
167/// Returns `None` when `energy` is negative, when the input is not finite, or when the computed
168/// result is not finite.
169#[must_use]
170pub fn photon_momentum_from_energy(energy: f64) -> Option<f64> {
171    if !is_nonnegative_finite(energy) {
172        return None;
173    }
174
175    finite_result(energy / SPEED_OF_LIGHT)
176}
177
178/// Converts joules to electron volts using `eV = J / e`.
179///
180/// Returns `None` when `joules` is negative, when the input is not finite, or when the computed
181/// result is not finite.
182#[must_use]
183pub fn joules_to_electron_volts(joules: f64) -> Option<f64> {
184    if !is_nonnegative_finite(joules) {
185        return None;
186    }
187
188    finite_result(joules / ELEMENTARY_CHARGE)
189}
190
191/// Converts electron volts to joules using `J = eV * e`.
192///
193/// Returns `None` when `electron_volts` is negative, when the input is not finite, or when the
194/// computed result is not finite.
195#[must_use]
196pub fn electron_volts_to_joules(electron_volts: f64) -> Option<f64> {
197    if !is_nonnegative_finite(electron_volts) {
198        return None;
199    }
200
201    finite_result(electron_volts * ELEMENTARY_CHARGE)
202}
203
204/// Computes de Broglie wavelength from momentum magnitude using `lambda = h / p`.
205///
206/// Returns `None` when `momentum` is not positive and finite, or when the computed result is not
207/// finite.
208#[must_use]
209pub fn de_broglie_wavelength(momentum: f64) -> Option<f64> {
210    if !is_positive_finite(momentum) {
211        return None;
212    }
213
214    finite_result(PLANCK_CONSTANT / momentum)
215}
216
217/// Computes de Broglie wavelength from mass and velocity using `lambda = h / (m * |v|)`.
218///
219/// Returns `None` when `mass` is not positive and finite, when `velocity` is zero or not finite,
220/// or when the computed result is not finite.
221///
222/// # Examples
223///
224/// ```rust
225/// use use_quantum::{PLANCK_CONSTANT, de_broglie_wavelength_from_mass_velocity};
226///
227/// let wavelength =
228///     de_broglie_wavelength_from_mass_velocity(2.0, 3.0).ok_or("expected valid inputs")?;
229///
230/// assert!((wavelength - (PLANCK_CONSTANT / 6.0)).abs() < 1.0e-12);
231/// # Ok::<(), &'static str>(())
232/// ```
233#[must_use]
234pub fn de_broglie_wavelength_from_mass_velocity(mass: f64, velocity: f64) -> Option<f64> {
235    de_broglie_wavelength(momentum_magnitude_from_mass_velocity(mass, velocity)?)
236}
237
238/// Computes momentum magnitude from a de Broglie wavelength using `p = h / lambda`.
239///
240/// Returns `None` when `wavelength` is not positive and finite, or when the computed result is
241/// not finite.
242#[must_use]
243pub fn momentum_from_de_broglie_wavelength(wavelength: f64) -> Option<f64> {
244    photon_momentum_from_wavelength(wavelength)
245}
246
247/// Computes angular frequency from energy using `omega = E / hbar`.
248///
249/// Returns `None` when `energy` is negative, when the input is not finite, or when the computed
250/// result is not finite.
251#[must_use]
252pub fn angular_frequency_from_energy(energy: f64) -> Option<f64> {
253    if !is_nonnegative_finite(energy) {
254        return None;
255    }
256
257    finite_result(energy / REDUCED_PLANCK_CONSTANT)
258}
259
260/// Computes energy from angular frequency using `E = hbar * omega`.
261///
262/// Returns `None` when `angular_frequency` is negative, when the input is not finite, or when
263/// the computed result is not finite.
264#[must_use]
265pub fn energy_from_angular_frequency(angular_frequency: f64) -> Option<f64> {
266    if !is_nonnegative_finite(angular_frequency) {
267        return None;
268    }
269
270    finite_result(REDUCED_PLANCK_CONSTANT * angular_frequency)
271}
272
273fn minimum_conjugate_uncertainty(uncertainty: f64) -> Option<f64> {
274    if !is_positive_finite(uncertainty) {
275        return None;
276    }
277
278    finite_result(REDUCED_PLANCK_CONSTANT / (2.0 * uncertainty))
279}
280
281/// Computes the minimum position uncertainty estimate from `delta x >= hbar / (2 * delta p)`.
282///
283/// Returns `None` when `momentum_uncertainty` is not positive and finite, or when the computed
284/// result is not finite.
285///
286/// # Examples
287///
288/// ```rust
289/// use use_quantum::{REDUCED_PLANCK_CONSTANT, minimum_position_uncertainty};
290///
291/// let position_uncertainty =
292///     minimum_position_uncertainty(REDUCED_PLANCK_CONSTANT).ok_or("expected valid input")?;
293///
294/// assert!((position_uncertainty - 0.5).abs() < 1.0e-12);
295/// # Ok::<(), &'static str>(())
296/// ```
297#[must_use]
298pub fn minimum_position_uncertainty(momentum_uncertainty: f64) -> Option<f64> {
299    minimum_conjugate_uncertainty(momentum_uncertainty)
300}
301
302/// Computes the minimum momentum uncertainty estimate from `delta p >= hbar / (2 * delta x)`.
303///
304/// Returns `None` when `position_uncertainty` is not positive and finite, or when the computed
305/// result is not finite.
306#[must_use]
307pub fn minimum_momentum_uncertainty(position_uncertainty: f64) -> Option<f64> {
308    minimum_conjugate_uncertainty(position_uncertainty)
309}
310
311/// Computes the minimum energy uncertainty estimate from `delta E >= hbar / (2 * delta t)`.
312///
313/// Returns `None` when `time_uncertainty` is not positive and finite, or when the computed
314/// result is not finite.
315#[must_use]
316pub fn minimum_energy_uncertainty(time_uncertainty: f64) -> Option<f64> {
317    minimum_conjugate_uncertainty(time_uncertainty)
318}
319
320/// Computes the minimum time uncertainty estimate from `delta t >= hbar / (2 * delta E)`.
321///
322/// Returns `None` when `energy_uncertainty` is not positive and finite, or when the computed
323/// result is not finite.
324#[must_use]
325pub fn minimum_time_uncertainty(energy_uncertainty: f64) -> Option<f64> {
326    minimum_conjugate_uncertainty(energy_uncertainty)
327}
328
329/// Computes the hydrogen-like Bohr orbit radius for `Z = 1` using `r_n = a0 * n^2`.
330///
331/// Returns `None` when `n == 0` or when the computed result is not finite.
332#[must_use]
333pub fn bohr_orbit_radius(n: u32) -> Option<f64> {
334    finite_result(BOHR_RADIUS * principal_squared(n)?)
335}
336
337/// Computes the hydrogen energy level in electron volts using `E_n = -Ry / n^2`.
338///
339/// Returns `None` when `n == 0` or when the computed result is not finite.
340///
341/// # Examples
342///
343/// ```rust
344/// use use_quantum::{RYDBERG_ENERGY_EV, hydrogen_energy_level_ev};
345///
346/// assert_eq!(hydrogen_energy_level_ev(1), Some(-RYDBERG_ENERGY_EV));
347/// ```
348#[must_use]
349pub fn hydrogen_energy_level_ev(n: u32) -> Option<f64> {
350    finite_result(-RYDBERG_ENERGY_EV / principal_squared(n)?)
351}
352
353/// Computes the absolute transition energy between two hydrogen energy levels in electron volts.
354///
355/// Returns `None` when either quantum number is zero or when the computed result is not finite.
356/// Returns `Some(0.0)` when the levels are equal.
357#[must_use]
358pub fn hydrogen_transition_energy_ev(initial_n: u32, final_n: u32) -> Option<f64> {
359    if initial_n == 0 || final_n == 0 {
360        return None;
361    }
362
363    if initial_n == final_n {
364        return Some(0.0);
365    }
366
367    let initial = hydrogen_energy_level_ev(initial_n)?;
368    let final_ = hydrogen_energy_level_ev(final_n)?;
369
370    finite_result((final_ - initial).abs())
371}
372
373/// Computes the photon wavelength for a hydrogen transition in meters.
374///
375/// Returns `None` when either quantum number is zero, when the transition energy is zero, or
376/// when any intermediate conversion is not finite.
377///
378/// # Examples
379///
380/// ```rust
381/// use use_quantum::hydrogen_transition_wavelength;
382///
383/// let wavelength =
384///     hydrogen_transition_wavelength(2, 1).ok_or("expected valid transition")?;
385///
386/// assert!(wavelength.is_finite() && wavelength > 0.0);
387/// # Ok::<(), &'static str>(())
388/// ```
389#[must_use]
390pub fn hydrogen_transition_wavelength(initial_n: u32, final_n: u32) -> Option<f64> {
391    let transition_energy_ev = hydrogen_transition_energy_ev(initial_n, final_n)?;
392    if transition_energy_ev == 0.0 {
393        return None;
394    }
395
396    wavelength_from_photon_energy(electron_volts_to_joules(transition_energy_ev)?)
397}
398
399/// Quantum numbers for a single-electron atomic-state style validation helper.
400#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
401pub struct QuantumNumbers {
402    /// Principal quantum number `n`.
403    pub principal: u32,
404    /// Azimuthal quantum number `l`.
405    pub azimuthal: u32,
406    /// Magnetic quantum number `m_l`.
407    pub magnetic: i32,
408    /// Twice the spin projection. `1` means `+1/2`, `-1` means `-1/2`.
409    pub spin_twice: i8,
410}
411
412/// Returns `true` when `n >= 1`.
413#[must_use]
414pub const fn is_valid_principal_quantum_number(n: u32) -> bool {
415    n >= 1
416}
417
418/// Returns `true` when `n >= 1` and `l < n`.
419#[must_use]
420pub const fn is_valid_azimuthal_quantum_number(n: u32, l: u32) -> bool {
421    is_valid_principal_quantum_number(n) && l < n
422}
423
424/// Returns `true` when `-l <= m_l <= l`.
425#[must_use]
426pub fn is_valid_magnetic_quantum_number(l: u32, m_l: i32) -> bool {
427    let l = i64::from(l);
428    let magnetic = i64::from(m_l);
429
430    (-l..=l).contains(&magnetic)
431}
432
433/// Returns `true` when the spin projection is one of `-1` or `1`.
434#[must_use]
435pub const fn is_valid_spin_twice(spin_twice: i8) -> bool {
436    matches!(spin_twice, -1 | 1)
437}
438
439/// Returns `true` when the supplied quantum-number combination is valid.
440#[must_use]
441pub fn is_valid_quantum_numbers(
442    principal: u32,
443    azimuthal: u32,
444    magnetic: i32,
445    spin_twice: i8,
446) -> bool {
447    is_valid_azimuthal_quantum_number(principal, azimuthal)
448        && is_valid_magnetic_quantum_number(azimuthal, magnetic)
449        && is_valid_spin_twice(spin_twice)
450}
451
452impl QuantumNumbers {
453    /// Creates validated quantum numbers.
454    ///
455    /// # Examples
456    ///
457    /// ```rust
458    /// use use_quantum::QuantumNumbers;
459    ///
460    /// let quantum_numbers = QuantumNumbers::new(2, 1, 0, 1);
461    ///
462    /// assert_eq!(
463    ///     quantum_numbers,
464    ///     Some(QuantumNumbers {
465    ///         principal: 2,
466    ///         azimuthal: 1,
467    ///         magnetic: 0,
468    ///         spin_twice: 1,
469    ///     })
470    /// );
471    /// ```
472    #[must_use]
473    pub fn new(principal: u32, azimuthal: u32, magnetic: i32, spin_twice: i8) -> Option<Self> {
474        is_valid_quantum_numbers(principal, azimuthal, magnetic, spin_twice).then_some(Self {
475            principal,
476            azimuthal,
477            magnetic,
478            spin_twice,
479        })
480    }
481
482    /// Returns the spin projection in units of `hbar`.
483    #[must_use]
484    pub fn spin_projection(&self) -> f64 {
485        f64::from(self.spin_twice) / 2.0
486    }
487}
488
489/// A lightweight photon wrapper stored by energy in joules.
490#[derive(Debug, Clone, Copy, PartialEq)]
491pub struct Photon {
492    /// Photon energy in joules.
493    pub energy_joules: f64,
494}
495
496impl Photon {
497    /// Creates a photon from non-negative finite energy in joules.
498    #[must_use]
499    pub fn from_energy_joules(energy_joules: f64) -> Option<Self> {
500        is_nonnegative_finite(energy_joules).then_some(Self { energy_joules })
501    }
502
503    /// Creates a photon from frequency.
504    #[must_use]
505    pub fn from_frequency(frequency: f64) -> Option<Self> {
506        Self::from_energy_joules(photon_energy_from_frequency(frequency)?)
507    }
508
509    /// Creates a photon from wavelength.
510    ///
511    /// # Examples
512    ///
513    /// ```rust
514    /// use use_quantum::Photon;
515    ///
516    /// let photon = Photon::from_wavelength(500.0e-9).ok_or("expected valid wavelength")?;
517    ///
518    /// assert!(photon.energy_joules() > 0.0);
519    /// # Ok::<(), &'static str>(())
520    /// ```
521    #[must_use]
522    pub fn from_wavelength(wavelength: f64) -> Option<Self> {
523        Self::from_energy_joules(photon_energy_from_wavelength(wavelength)?)
524    }
525
526    /// Returns the photon energy in joules.
527    #[must_use]
528    pub const fn energy_joules(&self) -> f64 {
529        self.energy_joules
530    }
531
532    /// Returns the photon energy in electron volts.
533    #[must_use]
534    pub fn energy_ev(&self) -> Option<f64> {
535        joules_to_electron_volts(self.energy_joules)
536    }
537
538    /// Returns the photon frequency in hertz.
539    #[must_use]
540    pub fn frequency(&self) -> Option<f64> {
541        frequency_from_photon_energy(self.energy_joules)
542    }
543
544    /// Returns the photon wavelength in meters.
545    #[must_use]
546    pub fn wavelength(&self) -> Option<f64> {
547        wavelength_from_photon_energy(self.energy_joules)
548    }
549
550    /// Returns the photon momentum magnitude in kilogram meters per second.
551    #[must_use]
552    pub fn momentum(&self) -> Option<f64> {
553        photon_momentum_from_energy(self.energy_joules)
554    }
555}
556
557/// A lightweight matter-wave wrapper stored by momentum magnitude.
558#[derive(Debug, Clone, Copy, PartialEq)]
559pub struct MatterWave {
560    /// Momentum magnitude in kilogram meters per second.
561    pub momentum: f64,
562}
563
564impl MatterWave {
565    /// Creates a matter wave from a positive finite momentum magnitude.
566    #[must_use]
567    pub fn from_momentum(momentum: f64) -> Option<Self> {
568        is_positive_finite(momentum).then_some(Self { momentum })
569    }
570
571    /// Creates a matter wave from mass and velocity magnitude.
572    ///
573    /// # Examples
574    ///
575    /// ```rust
576    /// use use_quantum::{MatterWave, PLANCK_CONSTANT};
577    ///
578    /// let wave = MatterWave::from_mass_velocity(2.0, 3.0).ok_or("expected valid inputs")?;
579    ///
580    /// assert!((wave.wavelength().ok_or("expected wavelength")? - (PLANCK_CONSTANT / 6.0)).abs() < 1.0e-12);
581    /// # Ok::<(), &'static str>(())
582    /// ```
583    #[must_use]
584    pub fn from_mass_velocity(mass: f64, velocity: f64) -> Option<Self> {
585        Self::from_momentum(momentum_magnitude_from_mass_velocity(mass, velocity)?)
586    }
587
588    /// Returns the de Broglie wavelength in meters.
589    #[must_use]
590    pub fn wavelength(&self) -> Option<f64> {
591        de_broglie_wavelength(self.momentum)
592    }
593}
594
595#[cfg(test)]
596#[allow(clippy::float_cmp)]
597mod tests {
598    use super::*;
599
600    fn approx_eq(left: f64, right: f64) -> bool {
601        let scale = left.abs().max(right.abs()).max(1.0);
602        (left - right).abs() <= 1.0e-12 * scale
603    }
604
605    fn assert_approx_eq(left: f64, right: f64) {
606        assert!(
607            approx_eq(left, right),
608            "left={left:e} right={right:e} delta={:e}",
609            (left - right).abs()
610        );
611    }
612
613    fn assert_some_approx_eq(value: Option<f64>, expected: f64) {
614        match value {
615            Some(actual) => assert_approx_eq(actual, expected),
616            None => panic!("expected Some({expected:e})"),
617        }
618    }
619
620    #[test]
621    fn photon_energy_helpers_cover_frequency_and_wavelength() {
622        assert_eq!(photon_energy_from_frequency(1.0), Some(PLANCK_CONSTANT));
623        assert_eq!(photon_energy_from_frequency(-1.0), None);
624
625        assert_some_approx_eq(
626            photon_energy_from_wavelength(SPEED_OF_LIGHT),
627            PLANCK_CONSTANT,
628        );
629        assert_eq!(photon_energy_from_wavelength(0.0), None);
630    }
631
632    #[test]
633    fn photon_frequency_and_wavelength_helpers_invert_energy() {
634        assert_some_approx_eq(frequency_from_photon_energy(PLANCK_CONSTANT), 1.0);
635        assert_eq!(frequency_from_photon_energy(-1.0), None);
636
637        assert_some_approx_eq(
638            wavelength_from_photon_energy(PLANCK_CONSTANT),
639            SPEED_OF_LIGHT,
640        );
641        assert_eq!(wavelength_from_photon_energy(0.0), None);
642    }
643
644    #[test]
645    fn photon_momentum_and_energy_conversion_helpers_work() {
646        assert_some_approx_eq(photon_momentum_from_wavelength(PLANCK_CONSTANT), 1.0);
647        assert_some_approx_eq(photon_momentum_from_energy(SPEED_OF_LIGHT), 1.0);
648
649        assert_some_approx_eq(joules_to_electron_volts(ELEMENTARY_CHARGE), 1.0);
650        assert_some_approx_eq(electron_volts_to_joules(1.0), ELEMENTARY_CHARGE);
651    }
652
653    #[test]
654    fn matter_wave_helpers_cover_momentum_and_mass_velocity() {
655        assert_some_approx_eq(de_broglie_wavelength(PLANCK_CONSTANT), 1.0);
656        assert_eq!(de_broglie_wavelength(0.0), None);
657
658        assert_some_approx_eq(
659            de_broglie_wavelength_from_mass_velocity(2.0, 3.0),
660            PLANCK_CONSTANT / 6.0,
661        );
662        assert_eq!(de_broglie_wavelength_from_mass_velocity(2.0, 0.0), None);
663        assert_eq!(de_broglie_wavelength_from_mass_velocity(0.0, 3.0), None);
664
665        assert_some_approx_eq(momentum_from_de_broglie_wavelength(PLANCK_CONSTANT), 1.0);
666    }
667
668    #[test]
669    fn reduced_planck_and_uncertainty_helpers_work() {
670        assert_some_approx_eq(angular_frequency_from_energy(REDUCED_PLANCK_CONSTANT), 1.0);
671        assert_some_approx_eq(energy_from_angular_frequency(1.0), REDUCED_PLANCK_CONSTANT);
672
673        assert_some_approx_eq(minimum_position_uncertainty(REDUCED_PLANCK_CONSTANT), 0.5);
674        assert_eq!(minimum_position_uncertainty(0.0), None);
675
676        assert_some_approx_eq(minimum_momentum_uncertainty(REDUCED_PLANCK_CONSTANT), 0.5);
677        assert_eq!(minimum_momentum_uncertainty(0.0), None);
678
679        assert_some_approx_eq(minimum_energy_uncertainty(REDUCED_PLANCK_CONSTANT), 0.5);
680        assert_eq!(minimum_energy_uncertainty(0.0), None);
681
682        assert_some_approx_eq(minimum_time_uncertainty(REDUCED_PLANCK_CONSTANT), 0.5);
683        assert_eq!(minimum_time_uncertainty(0.0), None);
684    }
685
686    #[test]
687    fn bohr_model_helpers_cover_levels_and_transitions() {
688        assert_some_approx_eq(bohr_orbit_radius(1), BOHR_RADIUS);
689        assert_some_approx_eq(bohr_orbit_radius(2), 4.0 * BOHR_RADIUS);
690        assert_eq!(bohr_orbit_radius(0), None);
691
692        assert_some_approx_eq(hydrogen_energy_level_ev(1), -RYDBERG_ENERGY_EV);
693        assert_some_approx_eq(hydrogen_energy_level_ev(2), -RYDBERG_ENERGY_EV / 4.0);
694        assert_eq!(hydrogen_energy_level_ev(0), None);
695
696        assert_some_approx_eq(hydrogen_transition_energy_ev(2, 1), 10.204_269_842_245_5);
697        assert_eq!(hydrogen_transition_energy_ev(1, 1), Some(0.0));
698        assert_eq!(hydrogen_transition_energy_ev(0, 1), None);
699
700        match hydrogen_transition_wavelength(2, 1) {
701            Some(wavelength) => assert!(wavelength.is_finite() && wavelength > 0.0),
702            None => panic!("expected a valid transition wavelength"),
703        }
704        assert_eq!(hydrogen_transition_wavelength(1, 1), None);
705    }
706
707    #[test]
708    fn quantum_number_helpers_validate_expected_ranges() {
709        assert!(is_valid_principal_quantum_number(1));
710        assert!(!is_valid_principal_quantum_number(0));
711
712        assert!(is_valid_azimuthal_quantum_number(1, 0));
713        assert!(!is_valid_azimuthal_quantum_number(1, 1));
714
715        assert!(is_valid_magnetic_quantum_number(1, -1));
716        assert!(is_valid_magnetic_quantum_number(1, 0));
717        assert!(is_valid_magnetic_quantum_number(1, 1));
718        assert!(!is_valid_magnetic_quantum_number(1, 2));
719
720        assert!(is_valid_spin_twice(1));
721        assert!(is_valid_spin_twice(-1));
722        assert!(!is_valid_spin_twice(0));
723
724        assert!(is_valid_quantum_numbers(2, 1, 0, 1));
725        assert!(!is_valid_quantum_numbers(2, 2, 0, 1));
726
727        match QuantumNumbers::new(2, 1, 0, 1) {
728            Some(quantum_numbers) => assert_eq!(quantum_numbers.spin_projection(), 0.5),
729            None => panic!("expected valid quantum numbers"),
730        }
731        assert_eq!(QuantumNumbers::new(2, 2, 0, 1), None);
732    }
733
734    #[test]
735    fn photon_wrapper_delegates_to_public_helpers() {
736        match Photon::from_frequency(1.0) {
737            Some(photon) => assert_eq!(photon.energy_joules(), PLANCK_CONSTANT),
738            None => panic!("expected a valid photon from frequency"),
739        }
740
741        match Photon::from_wavelength(SPEED_OF_LIGHT) {
742            Some(photon) => assert_some_approx_eq(photon.frequency(), 1.0),
743            None => panic!("expected a valid photon from wavelength"),
744        }
745
746        match Photon::from_energy_joules(PLANCK_CONSTANT) {
747            Some(photon) => assert_some_approx_eq(photon.wavelength(), SPEED_OF_LIGHT),
748            None => panic!("expected a valid photon from energy"),
749        }
750
751        assert_eq!(Photon::from_energy_joules(-1.0), None);
752    }
753
754    #[test]
755    fn matter_wave_wrapper_delegates_to_public_helpers() {
756        match MatterWave::from_momentum(PLANCK_CONSTANT) {
757            Some(wave) => assert_some_approx_eq(wave.wavelength(), 1.0),
758            None => panic!("expected a valid matter wave from momentum"),
759        }
760
761        match MatterWave::from_mass_velocity(2.0, 3.0) {
762            Some(wave) => assert_some_approx_eq(wave.wavelength(), PLANCK_CONSTANT / 6.0),
763            None => panic!("expected a valid matter wave from mass and velocity"),
764        }
765
766        assert_eq!(MatterWave::from_momentum(0.0), None);
767    }
768}