use super::*;
#[test]
fn test_sobol_advanced_dimensions_differ() {
let sequence_type = EnhancedSequenceType::SobolAdvanced {
owen_scrambling: false,
digital_shift: false,
nested_scrambling: false,
};
let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(sequence_type, 4, EnhancedQMCConfig::default())
.expect("generator should construct");
let points = generator.generate(16).expect("generation should succeed");
let mut saw_differing_point = false;
for i in 1..points.nrows() {
let row = points.row(i);
let first = row[0];
if row.iter().any(|&v| (v - first).abs() > 1e-12) {
saw_differing_point = true;
break;
}
}
assert!(
saw_differing_point,
"every dimension was identical for every point (index>0): {points:?}"
);
}
#[test]
fn test_sobol_advanced_dimensions_not_perfectly_correlated() {
let sequence_type = EnhancedSequenceType::SobolAdvanced {
owen_scrambling: false,
digital_shift: false,
nested_scrambling: false,
};
let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(sequence_type, 3, EnhancedQMCConfig::default())
.expect("generator should construct");
let n = 64;
let points = generator.generate(n).expect("generation should succeed");
let col0: Vec<f64> = points.column(0).to_vec();
let col1: Vec<f64> = points.column(1).to_vec();
let corr = pearson_correlation(&col0, &col1);
assert!(
corr.abs() < 0.5,
"dimensions 0 and 1 should not be near-perfectly correlated, got r={corr}"
);
}
#[test]
fn test_sobol_advanced_low_discrepancy_grid_coverage() {
let sequence_type = EnhancedSequenceType::SobolAdvanced {
owen_scrambling: false,
digital_shift: false,
nested_scrambling: false,
};
let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(sequence_type, 2, EnhancedQMCConfig::default())
.expect("generator should construct");
let n = 64;
let points = generator.generate(n).expect("generation should succeed");
let grid = 4usize;
let mut counts = [[0usize; 4]; 4];
for row in points.rows() {
let cx = ((row[0] * grid as f64) as usize).min(grid - 1);
let cy = ((row[1] * grid as f64) as usize).min(grid - 1);
counts[cx][cy] += 1;
}
let expected_per_cell = n / (grid * grid);
let max_count = counts.iter().flatten().copied().max().expect("non-empty");
let empty_cells = counts.iter().flatten().filter(|&&c| c == 0).count();
assert!(
max_count <= expected_per_cell * 4 + 2,
"one grid cell is wildly over-represented (max={max_count}, expected~{expected_per_cell}): {counts:?}"
);
assert!(
empty_cells <= 8,
"too many completely empty grid cells ({empty_cells}/16), sequence looks degenerate: {counts:?}"
);
}
#[test]
fn test_sobol_advanced_points_in_unit_cube() {
let sequence_type = EnhancedSequenceType::SobolAdvanced {
owen_scrambling: true,
digital_shift: true,
nested_scrambling: false,
};
let mut generator: EnhancedQMCGenerator<f64> = EnhancedQMCGenerator::new(
sequence_type,
5,
EnhancedQMCConfig {
seed: Some(42),
..Default::default()
},
)
.expect("generator should construct");
let points = generator.generate(32).expect("generation should succeed");
for &v in points.iter() {
assert!((0.0..1.0).contains(&v), "value {v} out of [0, 1) range");
}
}
#[test]
fn test_enhanced_sobol_convenience_fn_dimensions_differ() {
let points: Array2<f64> =
enhanced_sobol(16, 4, false, Some(7)).expect("enhanced_sobol should succeed");
let mut saw_differing_point = false;
for i in 1..points.nrows() {
let row = points.row(i);
let first = row[0];
if row.iter().any(|&v| (v - first).abs() > 1e-12) {
saw_differing_point = true;
break;
}
}
assert!(
saw_differing_point,
"enhanced_sobol produced identical values across dimensions: {points:?}"
);
}
#[test]
fn test_sobol_direction_numbers_vary_by_dimension() {
let sequence_type = EnhancedSequenceType::SobolAdvanced {
owen_scrambling: false,
digital_shift: false,
nested_scrambling: false,
};
let generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(sequence_type, 4, EnhancedQMCConfig::default())
.expect("generator should construct");
assert_eq!(generator.sobol_direction_numbers.len(), 4);
for dim in 1..4 {
assert_ne!(
generator.sobol_direction_numbers[dim], generator.sobol_direction_numbers[0],
"dimension {dim}'s direction numbers must differ from dimension 0's"
);
for other in (dim + 1)..4 {
assert_ne!(
generator.sobol_direction_numbers[dim], generator.sobol_direction_numbers[other],
"dimension {dim}'s direction numbers must differ from dimension {other}'s"
);
}
}
}
fn pearson_correlation(x: &[f64], y: &[f64]) -> f64 {
let n = x.len() as f64;
let mean_x = x.iter().sum::<f64>() / n;
let mean_y = y.iter().sum::<f64>() / n;
let mut cov = 0.0;
let mut var_x = 0.0;
let mut var_y = 0.0;
for i in 0..x.len() {
let dx = x[i] - mean_x;
let dy = y[i] - mean_y;
cov += dx * dy;
var_x += dx * dx;
var_y += dy * dy;
}
if var_x <= 0.0 || var_y <= 0.0 {
return 1.0; }
cov / (var_x.sqrt() * var_y.sqrt())
}
mod niederreiter_fix_tests {
use super::*;
fn niederreiter_type(matrix_optimization: bool) -> EnhancedSequenceType {
EnhancedSequenceType::Niederreiter {
base_strategy: BaseSelectionStrategy::Automatic,
matrix_optimization,
}
}
#[test]
fn test_niederreiter_generating_matrices_vary_by_dimension() {
let generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(niederreiter_type(true), 4, EnhancedQMCConfig::default())
.expect("generator should construct");
assert_eq!(generator.niederreiter_generating_matrices.len(), 4);
for dim in 1..4 {
assert_ne!(
generator.niederreiter_generating_matrices[dim],
generator.niederreiter_generating_matrices[0],
"dimension {dim}'s Niederreiter generating matrix must differ from dimension 0's"
);
}
}
#[test]
fn test_matrix_optimization_flag_changes_generating_matrices() {
let gen_with: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(niederreiter_type(true), 3, EnhancedQMCConfig::default())
.expect("generator should construct");
let gen_without: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(niederreiter_type(false), 3, EnhancedQMCConfig::default())
.expect("generator should construct");
assert_ne!(
gen_with.niederreiter_generating_matrices, gen_without.niederreiter_generating_matrices,
"matrix_optimization flag should genuinely affect the generating matrices"
);
}
#[test]
fn test_niederreiter_does_not_match_old_halton_style_bug() {
fn radical_inverse(index: usize, base: u32) -> f64 {
let mut result = 0.0;
let mut fraction = 1.0 / base as f64;
let mut i = index;
while i > 0 {
result += (i % base as usize) as f64 * fraction;
i /= base as usize;
fraction /= base as f64;
}
result
}
let primes = [2u32, 3, 5];
let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(niederreiter_type(true), 3, EnhancedQMCConfig::default())
.expect("generator should construct");
let points = generator.generate(20).expect("generation should succeed");
let mut differs_somewhere = false;
for index in 1..20 {
for dim in 0..3 {
let old_buggy_value = radical_inverse(index, primes[dim]);
if (points[[index, dim]] - old_buggy_value).abs() > 1e-9 {
differs_somewhere = true;
}
}
}
assert!(
differs_somewhere,
"Niederreiter output still matches the old per-dimension-radical-inverse (Halton) bug"
);
}
#[test]
fn test_niederreiter_column_means_near_half() {
let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(niederreiter_type(true), 3, EnhancedQMCConfig::default())
.expect("generator should construct");
let points = generator.generate(200).expect("generation should succeed");
for j in 0..3 {
let mean = points.column(j).iter().sum::<f64>() / points.nrows() as f64;
assert!(
(mean - 0.5).abs() < 0.2,
"column {j} mean {mean} not reasonably close to 0.5"
);
}
}
#[test]
fn test_niederreiter_low_discrepancy_grid_coverage() {
let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(niederreiter_type(true), 2, EnhancedQMCConfig::default())
.expect("generator should construct");
let n = 64;
let points = generator.generate(n).expect("generation should succeed");
let grid = 4usize;
let mut counts = [[0usize; 4]; 4];
for row in points.rows() {
let cx = ((row[0] * grid as f64) as usize).min(grid - 1);
let cy = ((row[1] * grid as f64) as usize).min(grid - 1);
counts[cx][cy] += 1;
}
let expected_per_cell = n / (grid * grid);
let max_count = counts.iter().flatten().copied().max().expect("non-empty");
let empty_cells = counts.iter().flatten().filter(|&&c| c == 0).count();
assert!(
max_count <= expected_per_cell * 4 + 2,
"one grid cell is wildly over-represented (max={max_count}): {counts:?}"
);
assert!(
empty_cells <= 8,
"too many completely empty grid cells ({empty_cells}/16): {counts:?}"
);
}
#[test]
fn test_niederreiter_points_in_unit_interval() {
let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(niederreiter_type(true), 5, EnhancedQMCConfig::default())
.expect("generator should construct");
let points = generator.generate(30).expect("generation should succeed");
for &v in points.iter() {
assert!((0.0..1.0).contains(&v), "value {v} out of [0, 1) range");
}
}
}
mod faure_fix_tests {
use super::*;
fn faure_type() -> EnhancedSequenceType {
EnhancedSequenceType::FaureImproved {
permutation_optimization: false,
radical_inverse_improvements: false,
}
}
#[test]
fn test_faure_base_is_smallest_prime_geq_dimension() {
let cases = [
(1usize, 2u64),
(2, 2),
(3, 3),
(4, 5),
(5, 5),
(6, 7),
(7, 7),
(10, 11),
];
for (dimension, expected_base) in cases {
let generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(faure_type(), dimension, EnhancedQMCConfig::default())
.expect("generator should construct");
assert_eq!(generator.faure_base, expected_base, "dimension={dimension}");
}
}
#[test]
fn test_faure_dimension0_matches_plain_van_der_corput() {
fn radical_inverse(index: usize, base: u64) -> f64 {
let mut result = 0.0;
let mut fraction = 1.0 / base as f64;
let mut i = index as u64;
while i > 0 {
result += (i % base) as f64 * fraction;
i /= base;
fraction /= base as f64;
}
result
}
let dimension = 3; let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(faure_type(), dimension, EnhancedQMCConfig::default())
.expect("generator should construct");
assert_eq!(generator.faure_base, 3);
let points = generator.generate(20).expect("generation should succeed");
for index in 0..20 {
let expected = radical_inverse(index, 3);
assert!(
(points[[index, 0]] - expected).abs() < 1e-9,
"dimension 0 at index {index}: expected plain van der Corput {expected}, got {}",
points[[index, 0]]
);
}
}
#[test]
fn test_faure_dimensions_differ_and_do_not_match_old_identical_dimension_bug() {
fn radical_inverse(index: usize, base: u64) -> f64 {
let mut result = 0.0;
let mut fraction = 1.0 / base as f64;
let mut i = index as u64;
while i > 0 {
result += (i % base) as f64 * fraction;
i /= base;
fraction /= base as f64;
}
result
}
let dimension = 4; let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(faure_type(), dimension, EnhancedQMCConfig::default())
.expect("generator should construct");
assert_eq!(generator.faure_base, 5);
let points = generator.generate(30).expect("generation should succeed");
let mut matched_old_bug_everywhere = true;
let mut saw_dimension_variety = false;
for index in 1..30 {
let old_bug_value = radical_inverse(index, 5);
let first = points[[index, 0]];
for dim in 0..dimension {
if (points[[index, dim]] - old_bug_value).abs() > 1e-9 {
matched_old_bug_everywhere = false;
}
if dim > 0 && (points[[index, dim]] - first).abs() > 1e-9 {
saw_dimension_variety = true;
}
}
}
assert!(
!matched_old_bug_everywhere,
"Faure output still matches the old identical-radical-inverse-per-dimension bug"
);
assert!(
saw_dimension_variety,
"every dimension was identical (degenerate Faure output)"
);
}
#[test]
fn test_faure_low_discrepancy_grid_coverage() {
let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(faure_type(), 2, EnhancedQMCConfig::default())
.expect("generator should construct");
let n = 64;
let points = generator.generate(n).expect("generation should succeed");
let grid = 4usize;
let mut counts = [[0usize; 4]; 4];
for row in points.rows() {
let cx = ((row[0] * grid as f64) as usize).min(grid - 1);
let cy = ((row[1] * grid as f64) as usize).min(grid - 1);
counts[cx][cy] += 1;
}
let expected_per_cell = n / (grid * grid);
let max_count = counts.iter().flatten().copied().max().expect("non-empty");
let empty_cells = counts.iter().flatten().filter(|&&c| c == 0).count();
assert!(
max_count <= expected_per_cell * 4 + 2,
"one grid cell is wildly over-represented (max={max_count}): {counts:?}"
);
assert!(
empty_cells <= 8,
"too many completely empty grid cells ({empty_cells}/16): {counts:?}"
);
}
#[test]
fn test_faure_points_in_unit_interval() {
let sequence_type = EnhancedSequenceType::FaureImproved {
permutation_optimization: true,
radical_inverse_improvements: true,
};
let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(sequence_type, 6, EnhancedQMCConfig::default())
.expect("generator should construct");
let points = generator.generate(25).expect("generation should succeed");
for &v in points.iter() {
assert!((0.0..1.0).contains(&v), "value {v} out of [0, 1) range");
}
}
}
mod digital_net_fix_tests {
use super::*;
fn net_params(base: usize) -> DigitalNetParams {
DigitalNetParams {
t: 0,
m: 32,
s: 3,
base,
}
}
#[test]
fn test_digital_net_sobol_matches_compute_sobol_advanced_directly() {
let digital_seq = EnhancedSequenceType::DigitalNet {
net_params: net_params(2),
construction_method: NetConstructionMethod::Sobol,
};
let mut digital_gen: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(digital_seq, 3, EnhancedQMCConfig::default())
.expect("generator should construct");
let digital_points = digital_gen.generate(10).expect("generation should succeed");
let sobol_seq = EnhancedSequenceType::SobolAdvanced {
owen_scrambling: false,
digital_shift: false,
nested_scrambling: false,
};
let mut sobol_gen: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(sobol_seq, 3, EnhancedQMCConfig::default())
.expect("generator should construct");
let sobol_points = sobol_gen.generate(10).expect("generation should succeed");
for i in 0..10 {
for j in 0..3 {
assert!(
(digital_points[[i, j]] - sobol_points[[i, j]]).abs() < 1e-12,
"DigitalNet(Sobol) should match compute_sobol_advanced exactly at ({i},{j})"
);
}
}
}
#[test]
fn test_digital_net_niederreiter_xing_matches_niederreiter_construction() {
let digital_seq = EnhancedSequenceType::DigitalNet {
net_params: net_params(2),
construction_method: NetConstructionMethod::NiederreiterXing,
};
let mut digital_gen: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(digital_seq, 3, EnhancedQMCConfig::default())
.expect("generator should construct");
let digital_points = digital_gen.generate(10).expect("generation should succeed");
let niederreiter_seq = EnhancedSequenceType::Niederreiter {
base_strategy: BaseSelectionStrategy::Automatic,
matrix_optimization: true,
};
let mut niederreiter_gen: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(niederreiter_seq, 3, EnhancedQMCConfig::default())
.expect("generator should construct");
let niederreiter_points = niederreiter_gen
.generate(10)
.expect("generation should succeed");
for i in 0..10 {
for j in 0..3 {
assert!(
(digital_points[[i, j]] - niederreiter_points[[i, j]]).abs() < 1e-12,
"DigitalNet(NiederreiterXing) should match the real Niederreiter \
construction at ({i},{j})"
);
}
}
}
#[test]
fn test_digital_net_polynomial_lattice_returns_honest_error_not_silent_sobol_fallback() {
let sequence_type = EnhancedSequenceType::DigitalNet {
net_params: net_params(2),
construction_method: NetConstructionMethod::PolynomialLattice,
};
let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(sequence_type, 3, EnhancedQMCConfig::default())
.expect("generator should construct");
let result = generator.generate(10);
assert!(
result.is_err(),
"PolynomialLattice is not implemented and must return an error, not silently \
fall back to a different construction"
);
}
#[test]
fn test_digital_net_finite_field_returns_honest_error_not_silent_sobol_fallback() {
let sequence_type = EnhancedSequenceType::DigitalNet {
net_params: net_params(2),
construction_method: NetConstructionMethod::FiniteField,
};
let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(sequence_type, 3, EnhancedQMCConfig::default())
.expect("generator should construct");
let result = generator.generate(10);
assert!(
result.is_err(),
"FiniteField is not implemented and must return an error, not silently fall \
back to a different construction"
);
}
#[test]
fn test_digital_net_non_base2_returns_honest_error() {
let sequence_type = EnhancedSequenceType::DigitalNet {
net_params: net_params(3),
construction_method: NetConstructionMethod::Sobol,
};
let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(sequence_type, 3, EnhancedQMCConfig::default())
.expect("generator should construct");
let result = generator.generate(10);
assert!(
result.is_err(),
"base-3 DigitalNet should be rejected honestly"
);
}
}
mod hybrid_fix_tests {
use super::*;
fn primary_type() -> EnhancedSequenceType {
EnhancedSequenceType::SobolAdvanced {
owen_scrambling: false,
digital_shift: false,
nested_scrambling: false,
}
}
fn secondary_type() -> EnhancedSequenceType {
EnhancedSequenceType::Niederreiter {
base_strategy: BaseSelectionStrategy::Automatic,
matrix_optimization: true,
}
}
fn generate_all(
combination: HybridCombinationStrategy,
n: usize,
dimension: usize,
) -> (Array2<f64>, Array2<f64>, Array2<f64>) {
let hybrid_type = EnhancedSequenceType::Hybrid {
primary: Box::new(primary_type()),
secondary: Box::new(secondary_type()),
combination,
};
let mut hybrid_gen: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(hybrid_type, dimension, EnhancedQMCConfig::default())
.expect("generator should construct");
let hybrid_points = hybrid_gen.generate(n).expect("generation should succeed");
let mut primary_gen: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(primary_type(), dimension, EnhancedQMCConfig::default())
.expect("generator should construct");
let primary_points = primary_gen.generate(n).expect("generation should succeed");
let mut secondary_gen: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(secondary_type(), dimension, EnhancedQMCConfig::default())
.expect("generator should construct");
let secondary_points = secondary_gen
.generate(n)
.expect("generation should succeed");
(hybrid_points, primary_points, secondary_points)
}
#[test]
fn test_hybrid_interleave_alternates_whole_points_by_index_parity() {
let (hybrid, primary, secondary) =
generate_all(HybridCombinationStrategy::Interleave, 10, 3);
for i in 0..10 {
let expected = if i % 2 == 0 {
primary.row(i)
} else {
secondary.row(i)
};
for j in 0..3 {
assert!(
(hybrid[[i, j]] - expected[j]).abs() < 1e-12,
"Interleave row {i} col {j}: expected {}, got {}",
expected[j],
hybrid[[i, j]]
);
}
}
}
#[test]
fn test_hybrid_weighted_matches_linear_blend_of_primary_and_secondary() {
let weight = 0.3;
let (hybrid, primary, secondary) =
generate_all(HybridCombinationStrategy::Weighted(weight), 10, 3);
for i in 0..10 {
for j in 0..3 {
let expected = weight * primary[[i, j]] + (1.0 - weight) * secondary[[i, j]];
assert!(
(hybrid[[i, j]] - expected).abs() < 1e-9,
"Weighted row {i} col {j}: expected {expected}, got {}",
hybrid[[i, j]]
);
}
}
}
#[test]
fn test_hybrid_dimension_alternation_alternates_by_dimension_parity() {
let (hybrid, primary, secondary) =
generate_all(HybridCombinationStrategy::DimensionAlternation, 10, 3);
for i in 0..10 {
for j in 0..3 {
let expected = if j % 2 == 0 {
primary[[i, j]]
} else {
secondary[[i, j]]
};
assert!(
(hybrid[[i, j]] - expected).abs() < 1e-12,
"DimensionAlternation row {i} col {j}: expected {expected}, got {}",
hybrid[[i, j]]
);
}
}
}
#[test]
fn test_hybrid_adaptive_equals_equal_weight_blend() {
let (hybrid, primary, secondary) = generate_all(HybridCombinationStrategy::Adaptive, 10, 3);
for i in 0..10 {
for j in 0..3 {
let expected = 0.5 * primary[[i, j]] + 0.5 * secondary[[i, j]];
assert!(
(hybrid[[i, j]] - expected).abs() < 1e-9,
"Adaptive row {i} col {j}: expected {expected}, got {}",
hybrid[[i, j]]
);
}
}
}
#[test]
fn test_hybrid_does_not_match_old_always_sobol_bug() {
let (hybrid, _primary, _secondary) =
generate_all(HybridCombinationStrategy::DimensionAlternation, 10, 3);
let old_bug_type = EnhancedSequenceType::SobolAdvanced {
owen_scrambling: true,
digital_shift: true,
nested_scrambling: false,
};
let mut old_bug_gen: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(old_bug_type, 3, EnhancedQMCConfig::default())
.expect("generator should construct");
let old_bug_points = old_bug_gen.generate(10).expect("generation should succeed");
let mut differs_somewhere = false;
for i in 0..10 {
for j in 0..3 {
if (hybrid[[i, j]] - old_bug_points[[i, j]]).abs() > 1e-9 {
differs_somewhere = true;
}
}
}
assert!(
differs_somewhere,
"Hybrid output still matches the old always-Sobol(owen=true,shift=true) bug"
);
}
}
mod quality_metrics_fix_tests {
use super::*;
#[test]
fn test_quality_metrics_no_longer_hardcoded_zero_for_real_sequence() {
let sequence_type = EnhancedSequenceType::SobolAdvanced {
owen_scrambling: false,
digital_shift: false,
nested_scrambling: false,
};
let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(sequence_type, 3, EnhancedQMCConfig::default())
.expect("generator should construct");
let _points = generator.generate(64).expect("generation should succeed");
let metrics = generator.quality_metrics();
assert!(
metrics.wraparound_discrepancy > 0.0,
"wraparound_discrepancy is still hardcoded to 0.0"
);
assert!(metrics.diaphony > 0.0, "diaphony is still hardcoded to 0.0");
assert!(
metrics.figure_of_merit > 0.0,
"figure_of_merit is still hardcoded to 0.0"
);
assert!(metrics.star_discrepancy >= 0.0);
assert!(
metrics.wraparound_discrepancy < 2.0,
"wraparound_discrepancy implausibly large for a real QMC sequence: {}",
metrics.wraparound_discrepancy
);
assert!(
metrics.diaphony < 2.0,
"diaphony implausibly large for a real QMC sequence: {}",
metrics.diaphony
);
}
#[test]
fn test_figure_of_merit_equals_max_of_the_other_three_metrics() {
let sequence_type = EnhancedSequenceType::FaureImproved {
permutation_optimization: false,
radical_inverse_improvements: false,
};
let mut generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(sequence_type, 2, EnhancedQMCConfig::default())
.expect("generator should construct");
let _points = generator.generate(40).expect("generation should succeed");
let metrics = generator.quality_metrics();
let expected_max = metrics
.star_discrepancy
.max(metrics.wraparound_discrepancy)
.max(metrics.diaphony);
assert!((metrics.figure_of_merit - expected_max).abs() < 1e-12);
}
#[test]
fn test_quality_metrics_distinguish_degenerate_from_real_sequence() {
let n = 48;
let d = 2;
let sequence_type = EnhancedSequenceType::SobolAdvanced {
owen_scrambling: false,
digital_shift: false,
nested_scrambling: false,
};
let mut real_generator: EnhancedQMCGenerator<f64> =
EnhancedQMCGenerator::new(sequence_type, d, EnhancedQMCConfig::default())
.expect("generator should construct");
let _real_points = real_generator
.generate(n)
.expect("generation should succeed");
let real_metrics = real_generator.quality_metrics().clone();
let mut degenerate = Array2::<f64>::zeros((n, d));
for i in 0..n {
let v = i as f64 / n as f64;
degenerate[[i, 0]] = v;
degenerate[[i, 1]] = v;
}
let mut degenerate_generator: EnhancedQMCGenerator<f64> = EnhancedQMCGenerator::new(
EnhancedSequenceType::SobolAdvanced {
owen_scrambling: false,
digital_shift: false,
nested_scrambling: false,
},
d,
EnhancedQMCConfig::default(),
)
.expect("generator should construct");
degenerate_generator
.assess_quality(°enerate)
.expect("assess_quality should succeed");
let degenerate_metrics = degenerate_generator.quality_metrics();
assert!(
degenerate_metrics.diaphony > real_metrics.diaphony,
"degenerate (all-diagonal) sequence should have HIGHER diaphony than a real \
sequence: degenerate={} real={}",
degenerate_metrics.diaphony,
real_metrics.diaphony
);
assert!(
degenerate_metrics.figure_of_merit > real_metrics.figure_of_merit,
"degenerate sequence should have a worse (larger) figure_of_merit: degenerate={} \
real={}",
degenerate_metrics.figure_of_merit,
real_metrics.figure_of_merit
);
}
}