///|
/// Total scalar quantity in the domain.
pub fn ScalarSolver::mass(self : ScalarSolver) -> Double {
  sum_values(self.values)
}

///|
/// Scalar variance over all cells.
pub fn ScalarSolver::variance(self : ScalarSolver) -> Double {
  variance_value(self.values)
}

///|
/// True when every stored scalar is finite.
pub fn ScalarSolver::is_finite(self : ScalarSolver) -> Bool {
  let mut finite = true
  for value in self.values {
    finite = finite && !value.is_nan() && !value.is_inf()
  }
  finite
}

///|
/// Absolute error between the current mean and a target state.
pub fn ScalarSolver::steady_state_error(
  self : ScalarSolver,
  target~ : Double,
) -> Double {
  abs_double(self.mean() - target)
}

///|
/// Return the position of the first maximum scalar cell.
pub fn ScalarSolver::maximum_location(self : ScalarSolver) -> Point {
  let location = self.field().maximum_location()
  Point::new(x=location.x.to_double(), y=location.y.to_double())
}

///|
/// Return the position of the first minimum scalar cell.
pub fn ScalarSolver::minimum_location(self : ScalarSolver) -> Point {
  let location = self.field().minimum_location()
  Point::new(x=location.x.to_double(), y=location.y.to_double())
}

///|
/// Return the current scalar gradient field magnitude.
pub fn ScalarSolver::gradient_magnitude(self : ScalarSolver) -> Field2D {
  let field = self.field()
  let result = Field2D::new(size=self.size)
  for y in 0.. Field2D {
  self.field().laplacian()
}

///|
/// Count cells above a scalar threshold.
pub fn ScalarSolver::count_above(self : ScalarSolver, level~ : Double) -> Int {
  self.field().count_above(level~)
}

///|
/// True when no scalar is below a supplied lower bound.
pub fn ScalarSolver::is_above(self : ScalarSolver, lower~ : Double) -> Bool {
  let mut result = true
  for value in self.values {
    result = result && value >= lower
  }
  result
}

///|
/// Total absolute source strength currently configured.
pub fn ScalarSolver::source_mass(self : ScalarSolver) -> Double {
  let mut total = 0.0
  for value in self.source {
    total += abs_double(value)
  }
  total
}

///|
/// Difference between two scalar solver states.
pub fn scalar_state_error(left : ScalarSolver, right : ScalarSolver) -> Double {
  l2_error(left.values, right.values)
}

///|
/// Compute the scalar Péclet number for a characteristic length.
pub fn scalar_peclet_number(
  velocity~ : Double,
  length~ : Double,
  diffusivity~ : Double,
) -> Double {
  safe_divide(velocity * length, diffusivity, fallback=0.0)
}

///|
/// Compute the scalar diffusion time for a characteristic length.
pub fn scalar_diffusion_time(length~ : Double, diffusivity~ : Double) -> Double {
  safe_divide(length * length, diffusivity, fallback=0.0)
}

///|
/// Return a bounded scalar field with a new lower and upper range.
pub fn ScalarSolver::clamped_field(
  self : ScalarSolver,
  low~ : Double,
  high~ : Double,
) -> Field2D {
  self.field().clamp(low~, high~)
}

///|
/// Return the sum of scalar changes between two solver states.
pub fn scalar_mass_change(
  before : ScalarSolver,
  after : ScalarSolver,
) -> Double {
  after.mass() - before.mass()
}

///|
/// Return a finite scalar value or a fallback for diagnostics.
pub fn scalar_finite_or(value : Double, fallback~ : Double) -> Double {
  finite_or(value, fallback~)
}