///|
pub fn ConvergenceReport::iterations(self : ConvergenceReport) -> Int {
  self.completed_iterations
}

///|
pub fn ConvergenceReport::pair_updates(self : ConvergenceReport) -> Int {
  self.completed_pair_updates
}

///|
pub fn ConvergenceReport::support_vectors(self : ConvergenceReport) -> Int {
  self.retained_support_vectors
}

///|
pub fn ConvergenceReport::max_kkt_violation(self : ConvergenceReport) -> Double {
  self.largest_kkt_violation
}

///|
pub fn ConvergenceReport::dual_objective(self : ConvergenceReport) -> Double {
  self.final_dual_objective
}

///|
pub fn ConvergenceReport::converged(self : ConvergenceReport) -> Bool {
  self.reached_stopping_condition
}

///|
pub fn BinaryModel::negative_class(self : BinaryModel) -> Int {
  self.lower_class
}

///|
pub fn BinaryModel::positive_class(self : BinaryModel) -> Int {
  self.upper_class
}

///|
pub fn BinaryModel::feature_count(self : BinaryModel) -> Int {
  self.input_columns
}

///|
pub fn BinaryModel::kernel(self : BinaryModel) -> Kernel {
  self.model_kernel
}

///|
pub fn BinaryModel::bias(self : BinaryModel) -> Double {
  self.bias_value
}

///|
pub fn BinaryModel::support_vector_count(self : BinaryModel) -> Int {
  self.support_rows.length()
}

///|
pub fn BinaryModel::support_vectors(self : BinaryModel) -> Array[Array[Double]] {
  copy_matrix(self.support_rows)
}

///|
pub fn BinaryModel::support_labels(self : BinaryModel) -> Array[Int] {
  self.support_class_labels.copy()
}

///|
pub fn BinaryModel::support_alphas(self : BinaryModel) -> Array[Double] {
  self.support_alpha_values.copy()
}

///|
pub fn BinaryModel::convergence(self : BinaryModel) -> ConvergenceReport {
  self.convergence_data
}

///|
fn checked_prediction_row(
  row : Array[Double],
  expected : Int,
) -> Result[Unit, SvmError] {
  if row.length() != expected {
    return Err(PredictionDimensionMismatch(expected, row.length()))
  }
  for column, value in row {
    if !finite_double(value) {
      return Err(NonFiniteFeature(0, column))
    }
  }
  Ok(())
}

///|
/// Returns the signed decision value; positive values select the higher class.
pub fn BinaryModel::decision_value(
  self : BinaryModel,
  row : Array[Double],
) -> Result[Double, SvmError] {
  match checked_prediction_row(row, self.input_columns) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let mut total = self.bias_value
  for index = 0; index < self.support_rows.length(); index = index + 1 {
    let value = match
      kernel_value(self.model_kernel, self.support_rows[index], row) {
      Err(error) => return Err(error)
      Ok(value) => value
    }
    total = total + self.signed_coefficients[index] * value
  }
  if !finite_double(total) {
    return Err(NumericFailure("binary decision"))
  }
  Ok(total)
}

///|
pub fn BinaryModel::predict(
  self : BinaryModel,
  row : Array[Double],
) -> Result[Int, SvmError] {
  match self.decision_value(row) {
    Err(error) => Err(error)
    Ok(value) =>
      Ok(if value >= 0.0 { self.upper_class } else { self.lower_class })
  }
}

///|
pub fn BinaryModel::predict_batch(
  self : BinaryModel,
  rows : Array[Array[Double]],
) -> Result[Array[Int], SvmError] {
  let predictions : Array[Int] = []
  for row in rows {
    match self.predict(row) {
      Err(error) => return Err(error)
      Ok(value) => predictions.push(value)
    }
  }
  Ok(predictions)
}