///|
/// Validate paired 2D/3D correspondences before estimation.
pub fn validate_correspondences3d2d(
  points3 : ArrayView[Point3],
  points2 : ArrayView[Point2],
  minimum : Int,
) -> Bool {
  points3.length() == points2.length() &&
  points3.length() >= minimum &&
  points3.length() > 0
}

///|
pub fn duplicate_point_indices(
  points : ArrayView[Point2],
  tolerance : Double,
) -> Array[Int] {
  let result : Array[Int] = []
  for i in 0.. Array[Point2] {
  let result : Array[Point2] = []
  for point in points {
    let mut duplicate = false
    for kept in result {
      if point.minus(kept).norm() <= tolerance {
        duplicate = true
        break
      }
    }
    if !duplicate {
      result.push(point)
    }
  }
  result
}

///|
pub fn has_non_collinear_triplet(
  points : ArrayView[Point2],
  tolerance : Double,
) -> Bool {
  for i in 0.. tolerance {
          return true
        }
      }
    }
  }
  false
}

///|
pub fn normalized_residuals(
  residuals : ArrayView[Double],
  scale : Double,
) -> Array[Double] {
  let result : Array[Double] = []
  let divisor = scale.abs().max(0.000000000001)
  for residual in residuals {
    result.push(residual / divisor)
  }
  result
}

///|
pub fn max_residual(residuals : ArrayView[Double]) -> Double {
  let mut result = 0.0
  for residual in residuals {
    result = result.max(abs(residual))
  }
  result
}

///|
pub fn residual_percentile(
  residuals : ArrayView[Double],
  percentile : Double,
) -> Double raise GeometryError {
  let values : Array[Double] = []
  for residual in residuals {
    values.push(abs(residual))
  }
  percentile_range(values, percentile, percentile).min
}