use super::*;
use rand::{seq::SliceRandom, RngExt, SeedableRng};
use rand_chacha::ChaCha8Rng;
use std::io::{BufRead, Read};
use std::ops::ControlFlow;
pub fn seeded_disorder(
c: &Configuration,
atoms: &[usize],
half_width: f64,
seed: u64,
) -> Result<Configuration, RmcError> {
c.validate()?;
super::structural::indices(atoms, c.atoms.len())?;
require(
half_width.is_finite() && (0. ..=1e6).contains(&half_width),
"invalid disorder half-width",
)?;
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let mut result = c.clone();
for &atom in atoms {
for x in &mut result.atoms[atom].position {
*x += half_width * (2. * rng.random::<f64>() - 1.);
}
}
result.validate()?;
Ok(result)
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SubstitutionResult {
pub configuration: Configuration,
pub atoms: Vec<usize>,
pub host: u8,
pub dopant: u8,
pub seed: u64,
}
pub fn seeded_substitution(
c: &Configuration,
host: u8,
dopant: u8,
count: usize,
seed: u64,
) -> Result<SubstitutionResult, RmcError> {
c.validate()?;
require(
host != dopant
&& [host, dopant]
.iter()
.all(|&z| crate::structure::Element::from_z(z).is_some()),
"invalid substitution elements",
)?;
let mut atoms: Vec<_> = c
.atoms
.iter()
.enumerate()
.filter_map(|(i, a)| (a.atomic_number == host).then_some(i))
.collect();
require(
count <= atoms.len(),
"substitution count exceeds host sites",
)?;
atoms.shuffle(&mut ChaCha8Rng::seed_from_u64(seed));
atoms.truncate(count);
atoms.sort_unstable();
let mut configuration = c.clone();
for &a in &atoms {
configuration.atoms[a].atomic_number = dopant;
}
Ok(SubstitutionResult {
configuration,
atoms,
host,
dopant,
seed,
})
}
pub fn suggested_supercell_repeats(
structure: &crate::structure::Structure,
minimum_span: f64,
) -> Result<[usize; 3], RmcError> {
require(
minimum_span.is_finite() && minimum_span > 0.,
"supercell span must be positive",
)?;
let lattice = crate::structure::Lattice::from_matrix(structure.lattice.matrix)
.map_err(|e| RmcError::Invalid(e.to_string()))?;
let repeats: [f64; 3] = std::array::from_fn(|i| {
(minimum_span / lattice.interplanar_spacing(i))
.ceil()
.max(1.)
});
require(
repeats.iter().all(|v| v.is_finite() && *v <= 10_000.),
"suggested supercell repeats exceed 10000",
)?;
Ok(repeats.map(|v| v as usize))
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CatalogueResourceEstimate {
pub atoms: usize,
pub absorbers: usize,
pub catalogue_paths: usize,
pub active_paths: usize,
pub path_vertices: usize,
pub spectrum_payload_bytes: usize,
}
pub fn estimate_catalogue_resources(
c: &Configuration,
absorbers: &[usize],
settings: &PathCatalogueSettings,
k_points: usize,
) -> Result<CatalogueResourceEstimate, RmcError> {
c.validate()?;
super::structural::indices(absorbers, c.atoms.len())?;
require(
(2..=1_000_000).contains(&k_points),
"invalid estimate k point count",
)?;
let mut result = CatalogueResourceEstimate {
atoms: c.atoms.len(),
absorbers: absorbers.len(),
catalogue_paths: 0,
active_paths: 0,
path_vertices: 0,
spectrum_payload_bytes: 0,
};
for &absorber in absorbers {
let catalogue = PathCatalogue::new(c.clone(), absorber, settings.clone())?;
result.catalogue_paths = result
.catalogue_paths
.checked_add(catalogue.paths().len())
.ok_or_else(|| RmcError::Invalid("catalogue estimate overflow".into()))?;
for path in catalogue.paths() {
result.path_vertices = result
.path_vertices
.checked_add(path.scatterers.len())
.ok_or_else(|| RmcError::Invalid("path vertex estimate overflow".into()))?;
result.active_paths += usize::from(path.half_length(c)? <= settings.radius);
}
}
result.spectrum_payload_bytes = result
.catalogue_paths
.checked_mul(k_points)
.and_then(|n| n.checked_mul(8))
.ok_or_else(|| RmcError::Invalid("spectrum payload estimate overflow".into()))?;
Ok(result)
}
pub fn stream_xyz_spectra<R: BufRead, C: ExafsCalculator + ?Sized>(
mut reader: R,
problem: &EnsembleProblem,
component: usize,
settings: &SessionSettings,
calculator: &mut C,
mut visitor: impl FnMut(usize, EnsembleState) -> Result<ControlFlow<()>, RmcError>,
) -> Result<usize, RmcError> {
require(
component < problem.structures.len(),
"trajectory component out of range",
)?;
let mut template = problem.clone();
super::session::normalize(&mut template.structures)?;
let prepared = super::session::prepare(&template, settings)?;
let reference = &template.structures[component].configuration;
let mut emitted = 0;
loop {
let first = loop {
let Some(line) = line(&mut reader)? else {
return Ok(emitted);
};
if !line.trim().is_empty() {
break line;
}
};
let count = first
.trim()
.parse::<usize>()
.map_err(|_| RmcError::Invalid(format!("XYZ frame {emitted} needs an atom count")))?;
require(
count == reference.atoms.len(),
format!("XYZ frame {emitted} changed atom count"),
)?;
let comment = line(&mut reader)?
.ok_or_else(|| RmcError::Invalid(format!("truncated XYZ frame {emitted} comment")))?;
let mut text = first;
text.push_str(&comment);
for _ in 0..count {
text.push_str(
&line(&mut reader)?
.ok_or_else(|| RmcError::Invalid(format!("truncated XYZ frame {emitted}")))?,
);
}
let xyz = crate::structure::parse_xyz(&text)
.map_err(|e| RmcError::Invalid(format!("XYZ frame {emitted}: {e}")))?;
let mut configuration = Configuration::from_xyz(&xyz)?;
configuration.cell = reference.cell;
require(
configuration.atoms.len() == count
&& configuration
.atoms
.iter()
.zip(&reference.atoms)
.all(|(a, b)| a.atomic_number == b.atomic_number),
format!("XYZ frame {emitted} changed species/order"),
)?;
let mut structures = template.structures.clone();
structures[component].configuration = configuration;
require(
settings.constraints.allowed(
&structures,
&template.structures,
&settings.moves,
None,
)?,
format!("XYZ frame {emitted} violates fixed-reference constraints"),
)?;
let state = super::session::evaluate_prepared(
&template, settings, &prepared, structures, None, calculator,
)?;
let flow = visitor(emitted, state)?;
emitted += 1;
if flow.is_break() {
return Ok(emitted);
}
}
}
fn line(reader: &mut impl BufRead) -> Result<Option<String>, RmcError> {
let mut text = String::new();
let bytes = reader
.by_ref()
.take(16385)
.read_line(&mut text)
.map_err(|e| RmcError::Invalid(format!("trajectory read: {e}")))?;
require(bytes <= 16384, "XYZ line exceeds 16 KiB")?;
Ok((bytes > 0).then_some(text))
}