///|
pub fn KernelDiagnostics::entry_count(self : KernelDiagnostics) -> Int {
  self.matrix_entry_count
}

///|
pub fn KernelDiagnostics::minimum(self : KernelDiagnostics) -> Double {
  self.smallest_entry
}

///|
pub fn KernelDiagnostics::maximum(self : KernelDiagnostics) -> Double {
  self.largest_entry
}

///|
pub fn KernelDiagnostics::mean(self : KernelDiagnostics) -> Double {
  self.average_entry
}

///|
pub fn KernelDiagnostics::diagonal_mean(self : KernelDiagnostics) -> Double {
  self.average_diagonal
}

///|
pub fn KernelDiagnostics::maximum_symmetry_error(
  self : KernelDiagnostics,
) -> Double {
  self.largest_symmetry_error
}

///|
/// Computes finite summary statistics for the checked kernel matrix.
pub fn kernel_diagnostics(
  data : Dataset,
  kernel : Kernel,
) -> Result[KernelDiagnostics, SvmError] {
  let matrix = match kernel_matrix(kernel, data) {
    Err(error) => return Err(error)
    Ok(value) => value
  }
  let count = matrix.length()
  let mut minimum = matrix[0][0]
  let mut maximum = matrix[0][0]
  let mut total = 0.0
  let mut diagonal_total = 0.0
  let mut symmetry_error = 0.0
  for row = 0; row < count; row = row + 1 {
    diagonal_total = diagonal_total + matrix[row][row]
    for column = 0; column < count; column = column + 1 {
      let value = matrix[row][column]
      if value < minimum {
        minimum = value
      }
      if value > maximum {
        maximum = value
      }
      total = total + value
      let difference = value - matrix[column][row]
      let error = if difference < 0.0 { -difference } else { difference }
      if error > symmetry_error {
        symmetry_error = error
      }
    }
  }
  Ok({
    matrix_entry_count: count * count,
    smallest_entry: minimum,
    largest_entry: maximum,
    average_entry: total / (count * count).to_double(),
    average_diagonal: diagonal_total / count.to_double(),
    largest_symmetry_error: symmetry_error,
  })
}

///|
pub fn BinaryModelDiagnostics::support_vectors(
  self : BinaryModelDiagnostics,
) -> Int {
  self.retained_count
}

///|
pub fn BinaryModelDiagnostics::negative_support_vectors(
  self : BinaryModelDiagnostics,
) -> Int {
  self.lower_class_count
}

///|
pub fn BinaryModelDiagnostics::positive_support_vectors(
  self : BinaryModelDiagnostics,
) -> Int {
  self.upper_class_count
}

///|
pub fn BinaryModelDiagnostics::minimum_alpha(
  self : BinaryModelDiagnostics,
) -> Double {
  self.smallest_alpha
}

///|
pub fn BinaryModelDiagnostics::maximum_alpha(
  self : BinaryModelDiagnostics,
) -> Double {
  self.largest_alpha
}

///|
pub fn BinaryModelDiagnostics::mean_alpha(
  self : BinaryModelDiagnostics,
) -> Double {
  self.average_alpha
}

///|
/// Summarizes alpha magnitudes and original-class support counts.
pub fn binary_model_diagnostics(model : BinaryModel) -> BinaryModelDiagnostics {
  let labels = model.support_labels()
  let alphas = model.support_alphas()
  let mut lower_count = 0
  let mut upper_count = 0
  let mut minimum = alphas[0]
  let mut maximum = alphas[0]
  let mut total = 0.0
  for index, alpha in alphas {
    if labels[index] == model.negative_class() {
      lower_count = lower_count + 1
    } else {
      upper_count = upper_count + 1
    }
    if alpha < minimum {
      minimum = alpha
    }
    if alpha > maximum {
      maximum = alpha
    }
    total = total + alpha
  }
  {
    retained_count: alphas.length(),
    lower_class_count: lower_count,
    upper_class_count: upper_count,
    smallest_alpha: minimum,
    largest_alpha: maximum,
    average_alpha: total / alphas.length().to_double(),
  }
}

///|
pub fn MarginDiagnostics::observation_count(self : MarginDiagnostics) -> Int {
  self.fitted_observation_count
}

///|
pub fn MarginDiagnostics::misclassified_count(self : MarginDiagnostics) -> Int {
  self.incorrect_count
}

///|
pub fn MarginDiagnostics::margin_violation_count(
  self : MarginDiagnostics,
) -> Int {
  self.violating_count
}

///|
pub fn MarginDiagnostics::minimum_margin(self : MarginDiagnostics) -> Double {
  self.smallest_margin
}

///|
pub fn MarginDiagnostics::maximum_margin(self : MarginDiagnostics) -> Double {
  self.largest_margin
}

///|
pub fn MarginDiagnostics::mean_margin(self : MarginDiagnostics) -> Double {
  self.average_margin
}

///|
/// Computes y*f(x) for observations belonging to the model's two classes.
pub fn training_margin_diagnostics(
  model : BinaryModel,
  data : Dataset,
) -> Result[MarginDiagnostics, SvmError] {
  if data.feature_count() != model.feature_count() {
    return Err(
      PredictionDimensionMismatch(model.feature_count(), data.feature_count()),
    )
  }
  let rows = data.features()
  let labels = data.labels()
  let mut minimum = 0.0
  let mut maximum = 0.0
  let mut total = 0.0
  let mut incorrect = 0
  let mut violations = 0
  for index, label in labels {
    let sign = if label == model.negative_class() {
      -1.0
    } else if label == model.positive_class() {
      1.0
    } else {
      return Err(UnknownClassLabel(label))
    }
    let decision = match model.decision_value(rows[index]) {
      Err(error) => return Err(error)
      Ok(value) => value
    }
    let margin = sign * decision
    if index == 0 || margin < minimum {
      minimum = margin
    }
    if index == 0 || margin > maximum {
      maximum = margin
    }
    if margin <= 0.0 {
      incorrect = incorrect + 1
    }
    if margin < 1.0 {
      violations = violations + 1
    }
    total = total + margin
  }
  Ok({
    fitted_observation_count: rows.length(),
    incorrect_count: incorrect,
    violating_count: violations,
    smallest_margin: minimum,
    largest_margin: maximum,
    average_margin: total / rows.length().to_double(),
  })
}

///|
pub fn MulticlassModelDiagnostics::class_count(
  self : MulticlassModelDiagnostics,
) -> Int {
  self.fitted_class_count
}

///|
pub fn MulticlassModelDiagnostics::support_counts(
  self : MulticlassModelDiagnostics,
) -> Array[Int] {
  self.retained_support_counts.copy()
}

///|
pub fn MulticlassModelDiagnostics::convergence_reports(
  self : MulticlassModelDiagnostics,
) -> Array[ConvergenceReport] {
  self.fitted_convergence_reports.copy()
}

///|
pub fn MulticlassModelDiagnostics::total_support_vectors(
  self : MulticlassModelDiagnostics,
) -> Int {
  self.retained_support_total
}

///|
pub fn MulticlassModelDiagnostics::all_converged(
  self : MulticlassModelDiagnostics,
) -> Bool {
  self.every_model_converged
}

///|
/// Aggregates support counts and convergence reports in sorted class order.
pub fn multiclass_model_diagnostics(
  model : MulticlassModel,
) -> MulticlassModelDiagnostics {
  let models = model.binary_models()
  let counts : Array[Int] = []
  let reports : Array[ConvergenceReport] = []
  let mut total = 0
  let mut converged = true
  for binary in models {
    let count = binary.support_vector_count()
    let report = binary.convergence()
    counts.push(count)
    reports.push(report)
    total = total + count
    if !report.converged() {
      converged = false
    }
  }
  {
    fitted_class_count: model.model_count(),
    retained_support_counts: counts,
    fitted_convergence_reports: reports,
    retained_support_total: total,
    every_model_converged: converged,
  }
}