///|
/// All inclusion-minimal generators of a closed intent, not only minimum-size generators.
/// Inclusion-compatible enumeration prunes supersets of previously found generators.
pub fn Context::minimal_generators(
  self : Context,
  intent : Array[Int32],
  candidate_limit~ : Int32 = 65536,
  result_limit~ : Int32 = 4096,
) -> Array[Array[Int32]] raise FcaError {
  let target = checked_indices(intent, self.attribute_count())
  if !self.is_closed(target) {
    raise Invalid("generator target must be a closed intent")
  }
  if result_limit < 1 || result_limit > 65536 {
    raise Invalid("generator result limit must be 1..65536")
  }
  let count = subset_count(target.length(), candidate_limit)
  let result : Array[Array[Int32]] = []
  for mask = 0; mask < count; mask = mask + 1 {
    let candidate = mask_indices(mask, target.length()).map(fn(i) { target[i] })
    let mut dominated = false
    for known in result {
      if set_subset(known, candidate) {
        dominated = true
        break
      }
    }
    if !dominated && self.closure(candidate) == target {
      if result.length() >= result_limit {
        raise Budget("generator result budget exceeded")
      }
      result.push(candidate)
    }
  }
  result
}

///|
/// Exact closure rules from minimal generators; this is not the canonical base.
pub fn Context::generator_rules(
  self : Context,
  intent : Array[Int32],
  candidate_limit~ : Int32 = 65536,
) -> Array[Implication] raise FcaError {
  let target = checked_indices(intent, self.attribute_count())
  let generators = self.minimal_generators(target, candidate_limit~)
  generators.map(generator => {
    implication(generator, target, self.attribute_count())
  })
}

///|
/// Closed patterns meeting support; budget applies before filtering, not just to survivors.
pub fn Context::frequent_concepts(
  self : Context,
  min_support : Double,
  limit~ : Int32 = 4096,
) -> Array[Concept] raise FcaError {
  if !(min_support >= 0.0 && min_support <= 1.0) {
    raise Invalid("support threshold must be in 0..1")
  }
  let all = self.concepts(limit~)
  let result : Array[Concept] = []
  for concept in all {
    let support = if self.object_count() == 0 {
      0.0
    } else {
      concept.extent.length().to_double() / self.object_count().to_double()
    }
    if support >= min_support {
      result.push(concept)
    }
  }
  result
}