///|
// All factors are nonnegative; pre-scan zeros to avoid spurious overflow.
// Reject unrepresentable positive intermediates rather than invent absorption.
fn monomial(
  rate : Double,
  state : Array[Double],
  powers : Array[Int32],
) -> Double raise KineticsError {
  if rate == 0.0 {
    return 0.0
  }
  for i in 0.. 0 && state[i] == 0.0 {
      return 0.0
    }
  }
  let mut result = rate
  for i in 0.. Array[Double] raise KineticsError {
  self.validate_concentrations(concentrations)
  let values : Array[Double] = []
  for r in self.reactions {
    let value = monomial(r.rate, concentrations, r.input)
    values.push(value)
  }
  values
}

///|
pub fn Network::drift(
  self : Network,
  concentrations : Array[Double],
) -> Array[Double] raise KineticsError {
  let fluxes = self.fluxes(concentrations)
  let result = Array::make(self.names.length(), 0.0)
  for j in 0.. Array[Double] raise KineticsError {
  self.validate_counts(state)
  let values : Array[Double] = []
  for r in self.reactions {
    let mut order = 0
    let mut disabled = false
    for i in 0.. 4 {
      raise Invalid("unsupported reaction order")
    }
    finite(value, "propensity")
    values.push(value)
  }
  values
}

///|
pub fn Network::total_propensity(
  self : Network,
  state : Array[Int32],
) -> Double raise KineticsError {
  let mut total = 0.0
  for a in self.propensities(state) {
    total += a
    finite(total, "total propensity")
  }
  total
}

///|
/// Counts drift, not concentration drift; finite-copy corrections are retained.
pub fn Network::count_drift(
  self : Network,
  state : Array[Int32],
) -> Array[Double] raise KineticsError {
  let a = self.propensities(state)
  let result = Array::make(self.names.length(), 0.0)
  for j in 0..