///|
fn jaccard_kernel(a : Int32, b : Int32) -> Double {
  let union = a | b
  if union == 0 {
    1.0
  } else {
    cardinality(a & b).to_double() / cardinality(union).to_double()
  }
}

///|
/// Jousselme distance with D(empty,empty)=1. Different frames are rejected.
/// Work is quadratic in the union of nonzero difference focals, not powerset.
pub fn jousselme(
  a : Mass,
  b : Mass,
  budget~ : Limits = default_limits(),
) -> Double raise BeliefError {
  same_frame(a, b)
  let left = a.dense(budget~)
  let right = b.dense(budget~)
  let differences : Array[(Int32, Double)] = []
  for mask = 0; mask < left.length(); mask = mask + 1 {
    let delta = left[mask] - right[mask]
    if delta != 0.0 {
      differences.push((mask, delta))
    }
  }
  let count = differences.length()
  if count == 0 {
    return 0.0
  }
  if count > budget.pairs / count {
    raise Budget("distance kernel exceeds pair budget")
  }
  let row_sums : Array[Double] = []
  for (mask, delta) in differences {
    let terms : Array[Double] = []
    for (other, other_delta) in differences {
      terms.push(delta * other_delta * jaccard_kernel(mask, other))
    }
    row_sums.push(stable_sum(terms))
  }
  let squared = stable_sum(row_sums) * 0.5
  if squared < -1.0e-12 {
    raise Invalid("distance numerical failure")
  }
  if squared <= 0.0 {
    0.0
  } else {
    squared.sqrt()
  }
}

///|
/// Total variation of focal-mass vectors (not a belief metric).
pub fn focal_total_variation(
  a : Mass,
  b : Mass,
  budget~ : Limits = default_limits(),
) -> Double raise BeliefError {
  same_frame(a, b)
  let left = a.dense(budget~)
  let right = b.dense(budget~)
  let terms : Array[Double] = []
  for mask = 0; mask < left.length(); mask = mask + 1 {
    terms.push(magnitude(left[mask] - right[mask]))
  }
  stable_sum(terms) * 0.5
}

///|
/// Largest difference over all belief queries, using bounded zeta transforms.
pub fn maximum_belief_difference(
  a : Mass,
  b : Mass,
  budget~ : Limits = default_limits(),
) -> (Int32, Double) raise BeliefError {
  same_frame(a, b)
  let left = a.belief_table(budget~)
  let right = b.belief_table(budget~)
  let mut event = 0
  let mut difference = 0.0
  for mask = 0; mask < left.length(); mask = mask + 1 {
    let candidate = magnitude(left[mask] - right[mask])
    if candidate > difference {
      event = mask
      difference = candidate
    }
  }
  (event, difference)
}