///|
/// Individual reciprocal reflection. Both Friedel partners are retained.
pub struct Reflection {
  index : Hkl
  d : Double
  intensity : Double
  angle : Double?
  wavelength : Double
} derive(Debug)

///|
/// Simulate a complete bounded reciprocal sphere. Centering is a filter, NOT expansion.
pub fn Metric::simulate(
  self : Metric,
  sites : Array[Site],
  d_min : Double,
  wavelength : Double,
  centering? : Centering = Primitive,
  max_candidates? : Int = 200000,
  max_terms? : Int = 2000000,
) -> Result[Array[Reflection], Problem] {
  if !finite(wavelength) || wavelength <= 0.0 {
    return Err(Invalid("positive finite wavelength required"))
  }
  if sites.length() > 10000 || max_terms <= 0 || max_terms > 20000000 {
    return Err(Budget("invalid phase-sum work budget"))
  }
  let indices = match self.reflections(d_min, max_candidates~) {
    Ok(x) => x
    Err(e) => return Err(e)
  }
  let selected = indices.filter(i => centering.allows(i))
  if selected.length().to_int64() * sites.length().to_int64() >
    max_terms.to_int64() {
    return Err(Budget("phase-sum work exceeds budget"))
  }
  let out = []
  for i in selected {
    let d = self.spacing(i)
    let a = match self.amplitude(i, sites) {
      Ok(x) => x
      Err(e) => return Err(e)
    }
    let angle = match two_theta(d, wavelength) {
      Ok(x) => x
      Err(e) => return Err(e)
    }
    out.push({ index: i, d, intensity: a.intensity(), angle, wavelength, })
  }
  Ok(out)
}