///|
/// A copy of mass values in integer-mask order, including empty set at index 0.
pub fn Mass::dense(
  self : Mass,
  budget~ : Limits = default_limits(),
) -> Array[Double] raise BeliefError {
  budget.check_dense(self.domain)
  let result = Array::make(self.domain.universe() + 1, 0.0)
  for (mask, value) in self.focals {
    result[mask] = value
  }
  result
}

///|
fn lattice_transform(
  f : Frame,
  values : Array[Double],
  inverse : Bool,
  supersets : Bool,
  budget : Limits,
) -> Array[Double] raise BeliefError {
  budget.check_dense(f)
  if values.length() != f.universe() + 1 {
    raise Invalid("lattice vector shape must equal powerset size")
  }
  for value in values {
    if !finite(value) {
      raise Invalid("lattice transform requires finite values")
    }
  }
  let result = values.copy()
  for bit = 0; bit < f.size(); bit = bit + 1 {
    let flag = 1 << bit
    for mask = 0; mask < result.length(); mask = mask + 1 {
      let active = if supersets {
        (mask & flag) == 0
      } else {
        (mask & flag) != 0
      }
      if active {
        let other = if supersets { mask | flag } else { mask ^ flag }
        result[mask] = if inverse {
          result[mask] - result[other]
        } else {
          result[mask] + result[other]
        }
        if !finite(result[mask]) {
          raise Invalid("lattice transform overflow")
        }
      }
    }
  }
  result
}

///|
pub fn subset_zeta(
  f : Frame,
  values : Array[Double],
  budget~ : Limits = default_limits(),
) -> Array[Double] raise BeliefError {
  lattice_transform(f, values, false, false, budget)
}

///|
pub fn subset_mobius(
  f : Frame,
  values : Array[Double],
  budget~ : Limits = default_limits(),
) -> Array[Double] raise BeliefError {
  lattice_transform(f, values, true, false, budget)
}

///|
pub fn superset_zeta(
  f : Frame,
  values : Array[Double],
  budget~ : Limits = default_limits(),
) -> Array[Double] raise BeliefError {
  lattice_transform(f, values, false, true, budget)
}

///|
pub fn superset_mobius(
  f : Frame,
  values : Array[Double],
  budget~ : Limits = default_limits(),
) -> Array[Double] raise BeliefError {
  lattice_transform(f, values, true, true, budget)
}

///|
pub fn Mass::belief_table(
  self : Mass,
  budget~ : Limits = default_limits(),
) -> Array[Double] raise BeliefError {
  let result = subset_zeta(self.domain, self.dense(budget~), budget~)
  let conflict = self.conflict()
  for i = 0; i < result.length(); i = i + 1 {
    result[i] = result[i] - conflict
  }
  result
}

///|
pub fn Mass::commonality_table(
  self : Mass,
  budget~ : Limits = default_limits(),
) -> Array[Double] raise BeliefError {
  superset_zeta(self.domain, self.dense(budget~), budget~)
}

///|
/// Invert a normalized commonality vector; negative rounding noise <=1e-12
/// may be clamped, but inconsistent vectors are rejected, never renormalized.
pub fn from_commonality(
  f : Frame,
  values : Array[Double],
  budget~ : Limits = default_limits(),
) -> Mass raise BeliefError {
  let bins = superset_mobius(f, values, budget~)
  let entries : Array[(Int32, Double)] = []
  for mask = 0; mask < bins.length(); mask = mask + 1 {
    let value = bins[mask]
    if value < -1.0e-12 || value > 1.0 + 1.0e-12 {
      raise Invalid("commonality vector does not invert to a valid mass")
    }
    if value > 0.0 {
      entries.push((mask, if value > 1.0 { 1.0 } else { value }))
    }
  }
  mass(f, entries, budget~)
}