///|
priv struct PrimeSearch {
  builder : Builder
  memo : Map[Int32, Array[Array[Int32]]]
  limit : Int32
  mut work : Int64
  mut stored : Int32
}

///|
fn add_prime(
  search : PrimeSearch,
  terms : Array[Array[Int32]],
  term : Array[Int32],
) -> Unit raise GraphError {
  // Bound subset comparisons as well as the number of stored candidates.
  search.work += 1L + terms.length().to_int64() * (term.length() + 1).to_int64()
  if search.work > 1000000L {
    raise ResourceLimit
  }
  insert_minimal(terms, term, search.limit)
}

///|
fn primes(
  search : PrimeSearch,
  id : Int32,
) -> Array[Array[Int32]] raise GraphError {
  if id == 0 {
    return []
  }
  if id == 1 {
    return [[]]
  }
  if search.memo.get(id) is Some(terms) {
    return terms
  }
  let node = search.builder.nodes[id]
  // Terms independent of this variable must imply both cofactors.
  let intersection = apply(search.builder, true, node.low, node.high)
  let result = primes(search, intersection).copy()
  for
    (child, literal) in [
      (node.low, -(node.variable + 1)),
      (node.high, node.variable + 1),
    ] {
    for suffix in primes(search, child) {
      let term = [literal]
      for value in suffix {
        term.push(value)
      }
      add_prime(search, result, term)
    }
  }
  search.stored += result.length()
  if search.stored > 16000 {
    raise ResourceLimit
  }
  search.memo[id] = result
  result
}

///|
/// Signed one-based literals: -1 means event 0 is false, +1 means it is true.
/// A constant true function returns one empty term. Resource limits raise an error.
pub fn FaultGraph::prime_implicants(
  self : FaultGraph,
  limit~ : Int32 = 256,
) -> Array[Array[Int32]] raise GraphError {
  if limit < 1 || limit > 1024 || self.nodes.length() > 10000 {
    raise ResourceLimit
  }
  let unique = Map([])
  for i in 2..