///|
/// Disjoint concatenation; right element j becomes left.size+j.
pub fn Matroid::direct_sum(
  self : Matroid,
  right : Matroid,
) -> Matroid raise MatroidError {
  check_size(self.size + right.size)
  {
    size: self.size + right.size,
    model: "direct-sum",
    evaluate: xs => {
      let a = []
      let b = []
      for e in xs {
        if e < self.size {
          a.push(e)
        } else {
          b.push(e - self.size)
        }
      }
      (self.evaluate)(a) + (right.evaluate)(b)
    },
  }
}

///|
pub fn Matroid::add_loop(self : Matroid) -> Matroid raise MatroidError {
  self.direct_sum(Matroid::zero(1))
}

///|
pub fn Matroid::add_coloop(self : Matroid) -> Matroid raise MatroidError {
  self.direct_sum(Matroid::free(1))
}

///|
/// Adds a parallel copy at element n. A copy of a loop is a loop.
pub fn Matroid::parallel_extension(
  self : Matroid,
  element : Int32,
) -> Matroid raise MatroidError {
  if element < 0 || element >= self.size {
    raise InvalidInput("parallel source outside ground")
  }
  check_size(self.size + 1)
  {
    size: self.size + 1,
    model: "parallel-extension",
    evaluate: xs => {
      let mapped = []
      for e in xs {
        let label = if e == self.size { element } else { e }
        if !has(mapped, label) {
          mapped.push(label)
        }
      }
      (self.evaluate)(mapped)
    },
  }
}

///|
/// Free extension at n lies in full closure but in no proper flat.
pub fn Matroid::free_extension(self : Matroid) -> Matroid raise MatroidError {
  check_size(self.size + 1)
  let full = self.full_rank()
  {
    size: self.size + 1,
    model: "free-extension",
    evaluate: xs => {
      let base = without(xs, self.size)
      let r = (self.evaluate)(base)
      if has(xs, self.size) {
        (r + 1).min(full)
      } else {
        r
      }
    },
  }
}

///|
/// Series extension is dual to a parallel extension.
pub fn Matroid::series_extension(
  self : Matroid,
  element : Int32,
) -> Matroid raise MatroidError {
  self.dual().parallel_extension(element).dual()
}

///|
/// Connectivity across an explicit bipartition, lambda(A)=r(A)+r(E-A)-r(E).
pub fn Matroid::connectivity(
  self : Matroid,
  subset : Array[Int32],
) -> Int32 raise MatroidError {
  let xs = checked_set(self.size, subset)
  (self.evaluate)(xs) +
  (self.evaluate)(complement(self.size, xs)) -
  self.full_rank()
}