150 lines
5.3 KiB
Rust
150 lines
5.3 KiB
Rust
use ceramic_radioactivity::{
|
|
calculate_sample, AcceptanceLimits, CalibrationParams, Conclusion, NuclideMeasurements,
|
|
SampleInput,
|
|
};
|
|
|
|
fn default_input() -> SampleInput {
|
|
SampleInput {
|
|
ra: NuclideMeasurements {
|
|
measured_values: vec![100.0, 102.0, 98.0, 101.0, 99.0, 100.0],
|
|
calibration: CalibrationParams {
|
|
factor: 0.916,
|
|
expanded_uncertainty_percent: 6.3,
|
|
coverage_factor: 2.0,
|
|
},
|
|
},
|
|
th: NuclideMeasurements {
|
|
measured_values: vec![110.0, 111.0, 109.0, 110.0, 112.0, 108.0],
|
|
calibration: CalibrationParams {
|
|
factor: 0.884,
|
|
expanded_uncertainty_percent: 6.9,
|
|
coverage_factor: 2.0,
|
|
},
|
|
},
|
|
k: NuclideMeasurements {
|
|
measured_values: vec![560.0, 565.0, 555.0, 562.0, 558.0, 561.0],
|
|
calibration: CalibrationParams {
|
|
factor: 0.961,
|
|
expanded_uncertainty_percent: 6.7,
|
|
coverage_factor: 2.0,
|
|
},
|
|
},
|
|
limits: AcceptanceLimits {
|
|
ira_limit: 1.0,
|
|
ir_limit: 1.0,
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn calculates_indices_and_ok_conclusion_for_six_measurements() {
|
|
let result = calculate_sample(default_input()).expect("valid sample should calculate");
|
|
|
|
assert_close(result.ra.mean_measured, 100.0, 1e-9);
|
|
assert_close(result.ra.mean_calibrated, 91.6, 1e-9);
|
|
assert_close(result.th.mean_calibrated, 97.24, 1e-9);
|
|
assert_close(result.k.mean_calibrated, 538.320_166_666_666_6, 1e-9);
|
|
assert_close(result.ira.value, 0.458, 1e-9);
|
|
assert_close(result.ir.value, 0.749_739_035_821_535_9, 1e-9);
|
|
assert_eq!(result.conclusion, Conclusion::Ok);
|
|
}
|
|
|
|
#[test]
|
|
fn asks_for_more_measurements_when_uncertainty_is_high_and_n_is_below_six() {
|
|
let mut input = default_input();
|
|
input.ra.measured_values = vec![10.0, 200.0, 400.0];
|
|
input.th.measured_values = vec![10.0, 200.0, 400.0];
|
|
input.k.measured_values = vec![10.0, 200.0, 400.0];
|
|
|
|
let result = calculate_sample(input).expect("valid sample should calculate");
|
|
|
|
assert_eq!(result.measurement_count, 3);
|
|
assert_eq!(result.conclusion, Conclusion::IncreaseMeasurementsToSix);
|
|
}
|
|
|
|
#[test]
|
|
fn asks_for_recalibration_when_uncertainty_is_high_after_six_measurements() {
|
|
let mut input = default_input();
|
|
input.ra.measured_values = vec![10.0, 200.0, 400.0, 10.0, 200.0, 400.0];
|
|
input.th.measured_values = vec![10.0, 200.0, 400.0, 10.0, 200.0, 400.0];
|
|
input.k.measured_values = vec![10.0, 200.0, 400.0, 10.0, 200.0, 400.0];
|
|
|
|
let result = calculate_sample(input).expect("valid sample should calculate");
|
|
|
|
assert_eq!(result.measurement_count, 6);
|
|
assert_eq!(result.conclusion, Conclusion::RecalibrateInstrument);
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_mismatched_measurement_counts() {
|
|
let mut input = default_input();
|
|
input.k.measured_values.pop();
|
|
|
|
let err = calculate_sample(input).expect_err("mismatched counts should fail");
|
|
|
|
assert!(err.to_string().contains("same measurement count"));
|
|
}
|
|
|
|
#[test]
|
|
fn monte_carlo_matches_analytical_mean_and_uncertainty() {
|
|
let result = calculate_sample(default_input()).expect("valid sample should calculate");
|
|
|
|
assert_eq!(result.mcm.iterations, 10_000);
|
|
|
|
// MCM 仿真均值/标准偏差应与 GUM 解析结果一致(仿真随机误差在 ~1% 量级)。
|
|
assert_close(result.mcm.ira.mean, result.ira.value, 5e-4);
|
|
assert_close(result.mcm.ir.mean, result.ir.value, 5e-4);
|
|
assert_close(
|
|
result.mcm.ira.std_dev,
|
|
result.ira.standard_uncertainty,
|
|
result.ira.standard_uncertainty * 0.05,
|
|
);
|
|
assert_close(
|
|
result.mcm.ir.std_dev,
|
|
result.ir.standard_uncertainty,
|
|
result.ir.standard_uncertainty * 0.05,
|
|
);
|
|
|
|
// 95% 包含区间应包住均值。
|
|
assert!(result.mcm.ira.p2_5 < result.mcm.ira.mean);
|
|
assert!(result.mcm.ira.mean < result.mcm.ira.p97_5);
|
|
|
|
// 默认样本的 IRa≈0.46、Ir≈0.75 都远低于标准值 1.0,合格概率应为 1。
|
|
assert_close(result.mcm.ira.pass_probability, 1.0, 1e-12);
|
|
assert_close(result.mcm.ir.pass_probability, 1.0, 1e-12);
|
|
assert_close(result.mcm.overall_pass_probability, 1.0, 1e-12);
|
|
assert_close(result.mcm.overall_fail_probability, 0.0, 1e-12);
|
|
}
|
|
|
|
#[test]
|
|
fn monte_carlo_is_deterministic_for_same_input() {
|
|
let first = calculate_sample(default_input()).expect("valid sample should calculate");
|
|
let second = calculate_sample(default_input()).expect("valid sample should calculate");
|
|
|
|
assert_eq!(first.mcm, second.mcm);
|
|
}
|
|
|
|
#[test]
|
|
fn monte_carlo_gives_about_half_pass_probability_when_limit_equals_mean() {
|
|
let mut input = default_input();
|
|
let analytical = calculate_sample(input.clone()).expect("valid sample should calculate");
|
|
|
|
// 将 IRa 标准值设为其均值,合格概率应接近 0.5。
|
|
input.limits.ira_limit = analytical.ira.value;
|
|
let result = calculate_sample(input).expect("valid sample should calculate");
|
|
|
|
assert_close(result.mcm.ira.pass_probability, 0.5, 0.03);
|
|
assert_close(
|
|
result.mcm.ira.fail_probability,
|
|
1.0 - result.mcm.ira.pass_probability,
|
|
1e-12,
|
|
);
|
|
}
|
|
|
|
fn assert_close(actual: f64, expected: f64, tolerance: f64) {
|
|
assert!(
|
|
(actual - expected).abs() <= tolerance,
|
|
"actual {actual} expected {expected} tolerance {tolerance}"
|
|
);
|
|
}
|