///|
fn unique_sorted(values : Array[Int]) -> Array[Int] {
let items = values.copy()
items.sort()
items.dedup()
items
}
///|
fn remove_customers(
instance : @core.VrpInstance,
solution : @core.Solution,
removed : Array[Int],
config : @core.SolveConfig,
) -> @core.Solution {
let removed_unique = unique_sorted(removed)
let routes = solution.routes.map(fn(route) {
route.stops.filter(fn(stop) { !removed_unique.contains(stop) })
})
let next_unassigned = unique_sorted(solution.unassigned + removed_unique)
@core.build_solution(instance, routes, next_unassigned, config)
}
///|
fn random_destroy_impl(
instance : @core.VrpInstance,
solution : @core.Solution,
remove_count : Int,
rng : @rng.Rng,
config : @core.SolveConfig,
) -> @core.Solution {
let pool = solution.assigned_customers()
let picks : Array[Int] = []
let available = pool.copy()
let target = remove_count.min(available.length())
for _ in 0.. Double {
let left_customer = instance.customer(left)
let right_customer = instance.customer(right)
instance.distance(left, right) +
Double::abs(left_customer.demand - right_customer.demand) * 1.5 +
Double::abs(left_customer.ready_time - right_customer.ready_time) * 0.1
}
///|
fn shaw_destroy_impl(
instance : @core.VrpInstance,
solution : @core.Solution,
remove_count : Int,
rng : @rng.Rng,
config : @core.SolveConfig,
) -> @core.Solution {
let assigned = solution.assigned_customers()
if assigned.is_empty() {
return solution
}
let seed = assigned[rng.next_int(assigned.length())]
let ranked = assigned.copy()
ranked.sort_by(fn(left, right) {
let left_score = customer_relatedness(instance, seed, left)
let right_score = customer_relatedness(instance, seed, right)
if left_score < right_score {
-1
} else if left_score > right_score {
1
} else {
0
}
})
let take = remove_count.min(ranked.length())
remove_customers(instance, solution, ranked[:take].to_owned(), config)
}
///|
fn marginal_cost(
instance : @core.VrpInstance,
solution : @core.Solution,
route_index : Int,
stop_position : Int,
config : @core.SolveConfig,
) -> (Int, Double) {
let customer = solution.routes[route_index].stops[stop_position]
let route_stops = solution.route_stops()
ignore(route_stops[route_index].remove(stop_position))
let next_solution = @core.build_solution(
instance,
route_stops,
solution.unassigned.copy(),
config,
)
(customer, solution.objective - next_solution.objective)
}
///|
fn worst_destroy_impl(
instance : @core.VrpInstance,
solution : @core.Solution,
remove_count : Int,
config : @core.SolveConfig,
) -> @core.Solution {
let scored : Array[(Int, Double)] = []
for route_index in 0.. right.1 {
-1
} else if left.1 < right.1 {
1
} else {
0
}
})
let picks = scored.map(fn(item) { item.0 })
let take = remove_count.min(picks.length())
remove_customers(instance, solution, picks[:take].to_owned(), config)
}
///|
fn destroy_with_rng(
instance : @core.VrpInstance,
solution : @core.Solution,
operator : @core.DestroyOperator,
remove_count : Int,
rng : @rng.Rng,
config : @core.SolveConfig,
) -> @core.Solution {
match operator {
@core.DestroyOperator::Random =>
random_destroy_impl(instance, solution, remove_count, rng, config)
@core.DestroyOperator::Shaw =>
shaw_destroy_impl(instance, solution, remove_count, rng, config)
@core.DestroyOperator::Worst =>
worst_destroy_impl(instance, solution, remove_count, config)
@core.DestroyOperator::Route =>
route_destroy_impl(instance, solution, remove_count, rng, config)
@core.DestroyOperator::Cluster =>
cluster_destroy_impl(instance, solution, remove_count, rng, config)
@core.DestroyOperator::TimeCritical =>
time_critical_destroy_impl(instance, solution, remove_count, config)
}
}
///|
pub fn destroy_solution(
instance : @core.VrpInstance,
solution : @core.Solution,
operator : @core.DestroyOperator,
remove_count : Int,
seed? : Int = 1,
config : @core.SolveConfig,
) -> @core.Solution {
let rng = @rng.Rng::new(seed)
destroy_with_rng(instance, solution, operator, remove_count, rng, config)
}