Skip to main content

nucleide_vr_tools/
lib.rs

1//! Monte Carlo variance-reduction utilities built on [`mcnp_io`] meshtal data.
2//!
3//! - [`magic`] — MAGIC weight-window generation operating on native
4//!   [`nucleide_mcnp_io::meshtal::MeshTallyData`] instead of MOAB-tagged meshes.
5//! - [`sampling`] — Walker/Vose alias-table source sampling plus a
6//!   voxel-level `MeshSourceSampler` with ANALOG / UNIFORM / USER bias modes.
7
8pub mod magic;
9pub mod sampling;
10
11pub use magic::{magic, magic_with, MagicOutput, MagicParams, MagicSelection};
12pub use sampling::{AliasTable, MeshSourceSampler, Mode, SampledVoxel};
13
14/// Errors raised by variance-reduction tools.
15#[derive(Debug, Clone, PartialEq)]
16pub enum Error {
17    /// Tally carries no volume elements.
18    EmptyTally,
19    /// A requested array has the wrong length.
20    LengthMismatch { expected: usize, got: usize },
21    /// Every flux value feeding one energy bin is non-positive, so the
22    /// MAGIC normalization `value / (2 * max)` would divide by zero.
23    /// (Rather than silently emitting `inf`/`nan`, this is an error.)
24    ZeroMaxFlux { energy_group: usize },
25    /// PDF input is empty.
26    EmptyPdf,
27    /// PDF contains a negative entry.
28    NegativePdf { index: usize, value: f64 },
29    /// PDF contains a non-finite (NaN/infinite) entry.
30    NonFinitePdf { index: usize },
31    /// PDF sums to zero (or negatively); cannot normalize.
32    ZeroSumPdf,
33    /// Tally or user density contains a negative value.
34    NegativeTally { index: usize, value: f64 },
35    /// Tally array contains a non-finite (NaN/infinite) value.
36    NonFiniteTally { field: &'static str, index: usize },
37}
38
39impl std::fmt::Display for Error {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            Error::EmptyTally => write!(f, "tally contains no volume elements"),
43            Error::LengthMismatch { expected, got } => {
44                write!(f, "length mismatch: expected {expected}, got {got}")
45            }
46            Error::ZeroMaxFlux { energy_group } => write!(
47                f,
48                "energy group {energy_group} has no positive flux; \
49                 weight-window normalization would divide by zero"
50            ),
51            Error::EmptyPdf => write!(f, "pdf must contain at least one value"),
52            Error::NegativePdf { index, value } => {
53                write!(f, "pdf[{index}] = {value} is negative")
54            }
55            Error::NonFinitePdf { index } => write!(f, "pdf[{index}] is not finite"),
56            Error::ZeroSumPdf => write!(f, "pdf sums to zero; cannot normalize"),
57            Error::NegativeTally { index, value } => write!(
58                f,
59                "tally/density value at index {index} = {value} is negative"
60            ),
61            Error::NonFiniteTally { field, index } => {
62                write!(f, "{field}[{index}] is not finite")
63            }
64        }
65    }
66}
67
68impl std::error::Error for Error {}
69
70#[cfg(test)]
71mod tests {
72    use super::Error;
73
74    #[test]
75    fn error_display_covers_all_variants() {
76        let cases: Vec<(Error, &str)> = vec![
77            (Error::EmptyTally, "no volume elements"),
78            (
79                Error::LengthMismatch {
80                    expected: 3,
81                    got: 5,
82                },
83                "expected 3, got 5",
84            ),
85            (Error::ZeroMaxFlux { energy_group: 2 }, "energy group 2"),
86            (Error::EmptyPdf, "at least one value"),
87            (
88                Error::NegativePdf {
89                    index: 1,
90                    value: -0.5,
91                },
92                "pdf[1]",
93            ),
94            (Error::NonFinitePdf { index: 0 }, "pdf[0]"),
95            (Error::ZeroSumPdf, "sums to zero"),
96            (
97                Error::NegativeTally {
98                    index: 4,
99                    value: -1.0,
100                },
101                "index 4",
102            ),
103            (
104                Error::NonFiniteTally {
105                    field: "flux",
106                    index: 7,
107                },
108                "flux[7]",
109            ),
110        ];
111        for (err, needle) in cases {
112            assert!(
113                format!("{err}").contains(needle),
114                "display of {err:?} should contain {needle:?}"
115            );
116            // Ensure the std::error::Error impl is linked.
117            let _: &dyn std::error::Error = &err;
118        }
119    }
120}