///|
pub(all) struct PicQuality {
  finite : Bool
  nonnegative_density : Bool
  energy_drift : Double
  charge_error : Double
} derive(Debug, ToJson)

///|
pub fn relative_energy_drift(initial : Double, ending : Double) -> Double {
  if initial == 0.0 {
    (ending - initial).abs()
  } else {
    (ending - initial).abs() / initial.abs()
  }
}

///|
pub fn distribution_is_nonnegative(distribution : ArrayView[Double]) -> Bool {
  for value in distribution {
    if value < 0.0 || value != value {
      return false
    }
  }
  true
}

///|
pub fn distribution_mass_error(
  config : VlasovConfig,
  left : ArrayView[Double],
  right : ArrayView[Double],
) -> Double {
  (distribution_sum(config, left) - distribution_sum(config, right)).abs()
}

///|
fn array_is_finite(values : ArrayView[Double]) -> Bool {
  for value in values {
    if value != value || value.abs() > 1.0e300 {
      return false
    }
  }
  true
}

///|
pub fn assess_pic_quality(state : PicState) -> PicQuality {
  let density = particle_number_density(state.grid, state.particles)
  let finite = array_is_finite(density) &&
    array_is_finite(state.field.electric) &&
    array_is_finite(state.field.potential) &&
    state.time == state.time
  let energy = kinetic_energy(state.particles) + field_energy(state.field)
  {
    finite,
    nonnegative_density: distribution_is_nonnegative(density),
    energy_drift: relative_energy_drift(energy, energy),
    charge_error: total_charge(state.grid, state.field.charge_density).abs(),
  }
}