///|
/// Equation plus independently supplied exact nodal solution.
pub struct Manufactured {
  problem : Problem
  exact : Field
}

///|
/// Discrete consistency fixture: useful for variable conductivity algebra,
/// but not an independent test of truncation error.
pub fn manufacture_discrete(
  operator : Operator,
  exact : Field,
) -> Manufactured raise SolverError {
  require_same(operator.grid, exact.grid)
  { problem: Problem::new(operator, operator.apply(exact), exact), exact, }
}

///|
/// Analytic -Laplace(x^4+y^4)+c*(x^4+y^4), unit conductivity.
/// Continuous forcing is intentionally NOT obtained by applying the stencil.
pub fn manufacture_quartic(
  grid : Grid,
  reaction~ : Double = 0.0,
) -> Manufactured raise SolverError {
  let operator = Operator::constant(grid, reaction~)
  let exact = Field::sample(grid, fn(x, y) { x * x * x * x + y * y * y * y })
  let source = Field::sample(grid, fn(x, y) {
    -12.0 * (x * x + y * y) + reaction * (x * x * x * x + y * y * y * y)
  })
  { problem: Problem::new(operator, source, exact), exact, }
}

///|
pub fn Manufactured::problem(self : Manufactured) -> Problem {
  self.problem
}

///|
pub fn Manufactured::exact(self : Manufactured) -> Field {
  self.exact
}

///|
/// RMS interior error. Boundary discrepancy is reported separately by Problem.
pub fn Manufactured::error(
  self : Manufactured,
  solution : Field,
) -> Double raise SolverError {
  solution.rms_error(self.exact)
}

///|
/// Observed order from two errors at a known refinement ratio, using log.
pub fn refinement_order(
  coarse_error : Double,
  fine_error : Double,
  ratio~ : Double = 2.0,
) -> Double raise SolverError {
  require_positive(coarse_error, "coarse error")
  require_positive(fine_error, "fine error")
  require_positive(ratio, "refinement ratio")
  if ratio <= 1.0 {
    raise Invalid("refinement ratio must exceed one")
  }
  let result = @math.ln(coarse_error / fine_error) / @math.ln(ratio)
  require_finite(result, "refinement order")
  result
}