///|
/// Finite-source M/M/c/c loss model. Idle sources attempt at per_source rate;
/// busy sources do not. Time-full probability differs from arrival blocking.
pub(all) struct Engset {
  sources : Int
  channels : Int
  per_source : Double
  service : Double
} derive(Debug, ToJson)

///|
pub(all) struct EngsetMetrics {
  states : Array[Double]
  mean_busy : Double
  time_full : Double
  arrival_blocking : Double
  attempted_rate : Double
  carried_rate : Double
} derive(Debug, ToJson)

///|
pub fn Engset::analyze(self : Engset) -> EngsetMetrics raise QueueError {
  if self.sources < 1 ||
    self.sources > 10000 ||
    self.channels < 0 ||
    self.channels > self.sources {
    raise Limit("Engset sources 1..10000, channels 0..sources")
  }
  rate(self.per_source, "per-source attempt rate")
  rate(self.service, "service")
  positive(self.service, "service")
  let birth = Array::makei(self.channels, n => {
    (self.sources - n).to_double() * self.per_source
  })
  let death = Array::makei(self.channels, n => {
    (n + 1).to_double() * self.service
  })
  let states = stationary_birth_death(birth, death)
  let mut mean_busy = 0.0
  let mut attempt_weight = 0.0
  for n in 0..