///| Deterministic pseudo-random numbers for offline experiments. Model-serving

///| integrations should use their platform RNG, but fixtures need a small,

///|
/// portable generator whose state can be recorded in a benchmark report.
pub enum RngError {
  InvalidSeed(Int)
  InvalidBound(Int)
} derive(Eq, Debug)

///| Park-Miller's minimal standard generator, implemented with Schrage's

///|
/// method so intermediate multiplication stays within signed 32-bit range.
pub struct DeterministicRng {
  mut state : Int
}

///|
pub fn DeterministicRng::new(seed : Int) -> Result[DeterministicRng, RngError] {
  if seed <= 0 || seed >= 2147483647 {
    Err(InvalidSeed(seed))
  } else {
    Ok({ state: seed })
  }
}

///|
pub fn DeterministicRng::state(self : DeterministicRng) -> Int {
  self.state
}

///|
/// Advance once and return an integer in `[1, 2147483646]`.
pub fn DeterministicRng::next(self : DeterministicRng) -> Int {
  let quotient = self.state / 44488
  let remainder = self.state % 44488
  let candidate = 48271 * remainder - 3399 * quotient
  self.state = if candidate > 0 { candidate } else { candidate + 2147483647 }
  self.state
}

///|
/// Return a portable unit-interval value in `[0, 1)`.
pub fn DeterministicRng::next_unit(self : DeterministicRng) -> Double {
  (self.next() - 1).to_double() / 2147483646.0
}

///| Choose an integer in `[0, bound)` without introducing a dependency on an

///| OS RNG. For synthetic workloads the tiny modulo bias is irrelevant; the

///|
/// important property is exact replay from the same seed.
pub fn DeterministicRng::next_below(
  self : DeterministicRng,
  bound : Int,
) -> Result[Int, RngError] {
  if bound <= 0 {
    return Err(InvalidBound(bound))
  }
  Ok(self.next() % bound)
}

///|
/// Fill caller-owned fixture arrays with deterministic random thresholds.
pub fn DeterministicRng::uniforms(
  self : DeterministicRng,
  count : Int,
) -> Result[Array[Double], RngError] {
  if count < 0 {
    return Err(InvalidBound(count))
  }
  let values : Array[Double] = []
  for _ in 0..