///|
/// Coordinate accessors keep the record representation private while making
/// the public API usable from other packages.
pub fn Depot::x_coord(self : Depot) -> Double {
  self.x
}

///|
pub fn Depot::y_coord(self : Depot) -> Double {
  self.y
}

///|
pub fn Depot::ready_time(self : Depot) -> Double {
  self.ready_time
}

///|
pub fn Depot::due_time(self : Depot) -> Double {
  self.due_time
}

///|
pub fn Depot::coordinates(self : Depot) -> (Double, Double) {
  (self.x, self.y)
}

///|
pub fn Customer::external_id(self : Customer) -> Int {
  self.external_id
}

///|
pub fn Customer::x_coord(self : Customer) -> Double {
  self.x
}

///|
pub fn Customer::y_coord(self : Customer) -> Double {
  self.y
}

///|
pub fn Customer::coordinates(self : Customer) -> (Double, Double) {
  (self.x, self.y)
}

///|
pub fn Customer::demand(self : Customer) -> Double {
  self.demand
}

///|
pub fn Customer::ready_time(self : Customer) -> Double {
  self.ready_time
}

///|
pub fn Customer::due_time(self : Customer) -> Double {
  self.due_time
}

///|
pub fn Customer::service_time(self : Customer) -> Double {
  self.service_time
}

///|
pub fn VehicleProfile::capacity(self : VehicleProfile) -> Double {
  self.capacity
}

///|
pub fn VehicleProfile::fleet_size(self : VehicleProfile) -> Int {
  self.fleet_size
}

///|
pub fn VehicleProfile::max_route_duration(self : VehicleProfile) -> Double {
  self.max_route_duration
}

///|
pub fn VrpInstance::instance_name(self : VrpInstance) -> String {
  self.name
}

///|
pub fn VrpInstance::variant(self : VrpInstance) -> ProblemVariant {
  self.variant
}

///|
pub fn VrpInstance::distance_metric(self : VrpInstance) -> DistanceMetric {
  self.metric
}

///|
pub fn VrpInstance::depot(self : VrpInstance) -> Depot {
  self.depot
}

///|
pub fn VrpInstance::vehicle(self : VrpInstance) -> VehicleProfile {
  self.vehicle
}

///|
pub fn VrpInstance::customers(self : VrpInstance) -> Array[Customer] {
  self.customers.copy()
}

///|
pub fn VrpInstance::index_of_label(self : VrpInstance, label : Int) -> Int? {
  for index, customer in self.customers {
    if customer.external_id == label {
      return Some(index)
    }
  }
  None
}

///|
pub fn VrpInstance::total_demand(self : VrpInstance) -> Double {
  self.customers.fold(init=0.0, fn(total, customer) { total + customer.demand })
}

///|
pub fn VrpInstance::average_demand(self : VrpInstance) -> Double {
  if self.customers.is_empty() {
    0.0
  } else {
    self.total_demand() / Double::from_int(self.customer_count())
  }
}

///|
pub fn RouteMetrics::distance(self : RouteMetrics) -> Double {
  self.distance
}

///|
pub fn RouteMetrics::duration(self : RouteMetrics) -> Double {
  self.duration
}

///|
pub fn RouteMetrics::waiting_time(self : RouteMetrics) -> Double {
  self.waiting_time
}

///|
pub fn RouteMetrics::load(self : RouteMetrics) -> Double {
  self.load
}

///|
pub fn RouteMetrics::capacity_violation(self : RouteMetrics) -> Double {
  self.capacity_violation
}

///|
pub fn RouteMetrics::time_violation(self : RouteMetrics) -> Double {
  self.time_violation
}

///|
pub fn RouteMetrics::duration_violation(self : RouteMetrics) -> Double {
  self.duration_violation
}

///|
pub fn RouteMetrics::is_feasible(self : RouteMetrics) -> Bool {
  self.feasible
}

///|
pub fn RouteMetrics::arrivals(self : RouteMetrics) -> Array[Double] {
  self.arrivals.copy()
}

///|
pub fn RoutePlan::stops(self : RoutePlan) -> Array[Int] {
  self.stops.copy()
}

///|
pub fn RoutePlan::stop_count(self : RoutePlan) -> Int {
  self.stops.length()
}

///|
pub fn RoutePlan::stop_at(self : RoutePlan, position : Int) -> Int? {
  self.stops.get(position)
}

///|
pub fn RoutePlan::contains(self : RoutePlan, customer_index : Int) -> Bool {
  self.stops.contains(customer_index)
}

///|
pub fn RoutePlan::metrics(self : RoutePlan) -> RouteMetrics {
  {
    distance: self.metrics.distance,
    duration: self.metrics.duration,
    waiting_time: self.metrics.waiting_time,
    load: self.metrics.load,
    capacity_violation: self.metrics.capacity_violation,
    time_violation: self.metrics.time_violation,
    duration_violation: self.metrics.duration_violation,
    feasible: self.metrics.feasible,
    arrivals: self.metrics.arrivals.copy(),
  }
}

///|
pub fn Solution::route_count(self : Solution) -> Int {
  self.routes.length()
}

///|
pub fn Solution::route_at(self : Solution, index : Int) -> RoutePlan? {
  self.routes.get(index).map(fn(route) { copy_route_plan(route) })
}

///|
pub fn Solution::routes(self : Solution) -> Array[RoutePlan] {
  self.routes.map(fn(route) { copy_route_plan(route) })
}

///|
pub fn Solution::unassigned_customers(self : Solution) -> Array[Int] {
  self.unassigned.copy()
}

///|
pub fn Solution::total_distance(self : Solution) -> Double {
  self.total_distance
}

///|
pub fn Solution::objective_value(self : Solution) -> Double {
  self.objective
}

///|
pub fn Solution::is_feasible(self : Solution) -> Bool {
  self.feasible
}

///|
pub fn Solution::total_capacity_violation(self : Solution) -> Double {
  self.total_capacity_violation
}

///|
pub fn Solution::total_time_violation(self : Solution) -> Double {
  self.total_time_violation
}

///|
pub fn Solution::total_duration_violation(self : Solution) -> Double {
  self.total_duration_violation
}

///|
pub fn CandidateInsertion::route_index(self : CandidateInsertion) -> Int {
  self.route_index
}

///|
pub fn CandidateInsertion::position(self : CandidateInsertion) -> Int {
  self.position
}

///|
pub fn CandidateInsertion::objective(self : CandidateInsertion) -> Double {
  self.objective
}

///|
pub fn CandidateInsertion::result(self : CandidateInsertion) -> Solution {
  self.solution.copy()
}

///|
pub fn SolveConfig::local_search_rounds(self : SolveConfig) -> Int {
  self.local_search_rounds
}

///|
pub fn SolveStats::accepted_moves(self : SolveStats) -> Int {
  self.accepted_moves
}

///|
pub fn SolveStats::improved_moves(self : SolveStats) -> Int {
  self.improved_moves
}

///|
pub fn SolveStats::local_improvements(self : SolveStats) -> Int {
  self.local_improvements
}

///|
pub fn SolveStats::evaluated_candidates(self : SolveStats) -> Int {
  self.evaluated_candidates
}

///|
pub fn SolveStats::best_iteration(self : SolveStats) -> Int {
  self.best_iteration
}

///|
pub fn SolveStats::seed_objective(self : SolveStats) -> Double {
  self.seed_objective
}

///|
pub fn SolveStats::best_objective(self : SolveStats) -> Double {
  self.best_objective
}