///|
/// Park-Miller minimal standard using Schrage arithmetic, not cryptographic.
pub struct Random {
  priv mut state : Int32
}

///|
pub fn Random::new(seed : Int32) -> Random raise KineticsError {
  if seed <= 0 || seed >= 2147483647 {
    raise Invalid("seed must be 1..2147483646")
  }
  { state: seed, }
}

///|
pub fn Random::next_int(self : Random) -> Int32 {
  let hi = self.state / 127773
  let lo = self.state % 127773
  let next = 16807 * lo - 2836 * hi
  self.state = if next <= 0 { next + 2147483647 } else { next }
  self.state
}

///|
pub fn Random::uniform(self : Random) -> Double {
  self.next_int().to_double() / 2147483647.0
}

///|
pub fn Random::seed_state(self : Random) -> Int32 {
  self.state
}

///|
pub fn Random::exponential(
  self : Random,
  rate : Double,
) -> Double raise KineticsError {
  finite(rate, "exponential rate")
  if rate <= 0.0 {
    raise Invalid("exponential rate must be positive")
  }
  let value = -@math.ln(self.uniform()) / rate
  finite(value, "exponential waiting time")
  if value <= 0.0 {
    raise Numerical("waiting time underflow")
  }
  value
}

///|
pub fn Random::categorical(
  self : Random,
  weights : Array[Double],
) -> Int32 raise KineticsError {
  if weights.is_empty() || weights.length() > 100000 {
    raise Budget("categorical size")
  }
  let mut total = 0.0
  let mut last = -1
  for i in 0.. 0.0 {
      last = i
    }
    total += weights[i]
    finite(total, "categorical sum")
  }
  if total <= 0.0 {
    raise Invalid("categorical needs positive mass")
  }
  let target = self.uniform() * total
  let mut cumulative = 0.0
  for i in 0..