///|
struct WeightTable {
destroy_weights : Array[Double]
repair_weights : Array[Double]
} derive(Debug)
///|
fn destroy_operator_at(index : Int) -> @core.DestroyOperator {
match index {
0 => @core.DestroyOperator::Random
1 => @core.DestroyOperator::Shaw
2 => @core.DestroyOperator::Worst
3 => @core.DestroyOperator::Route
4 => @core.DestroyOperator::Cluster
_ => @core.DestroyOperator::TimeCritical
}
}
///|
fn repair_operator_at(index : Int) -> @core.RepairOperator {
match index {
0 => @core.RepairOperator::Greedy
1 => @core.RepairOperator::Regret2
2 => @core.RepairOperator::DemandFirst
_ => @core.RepairOperator::TimeWindow
}
}
///|
fn roulette_pick(weights : Array[Double], rng : @rng.Rng) -> Int {
let total = weights.fold(init=0.0, fn(sum, item) { sum + item })
if total <= 0.0 {
return 0
}
let threshold = rng.next_double() * total
let mut cumulative = 0.0
for index in 0.. Int {
let customer_count = instance.customer_count()
let lower = config.min_destroy.max(1).min(customer_count)
let upper = config.max_destroy.max(lower).min(customer_count)
if upper == lower {
lower
} else {
lower + rng.next_int(upper - lower + 1)
}
}
///|
fn reward_for(
candidate : @core.Solution,
current : @core.Solution,
best : @core.Solution,
) -> Double {
if candidate.objective < best.objective {
9.0
} else if candidate.objective < current.objective {
5.0
} else if candidate.feasible {
2.0
} else {
0.6
}
}
///|
fn accept_candidate(
candidate : @core.Solution,
current : @core.Solution,
temperature : Double,
rng : @rng.Rng,
) -> Bool {
if candidate.objective <= current.objective {
true
} else {
let delta = candidate.objective - current.objective
let probability = @math.exp(-(delta / temperature.max(0.001)))
rng.next_double() < probability
}
}
///|
pub fn solve_with_seed(
instance : @core.VrpInstance,
seed_solution : @core.Solution,
config? : @core.SolveConfig = @core.SolveConfig::default(),
) -> @core.SolveResult {
let mut current = seed_solution
let mut best = seed_solution
let mut temperature = config.initial_temperature
let weights = {
destroy_weights: [1.0, 1.0, 1.0, 0.6, 0.6, 0.6],
repair_weights: [1.0, 1.0, 0.6, 0.6],
}
let rng = @rng.Rng::new(
instance.customer_count() * 7919 +
config.iterations * 97 +
current.routes.length() * 31,
)
let mut accepted_moves = 0
let mut improved_moves = 0
let mut local_improvements = 0
let mut evaluated_candidates = 0
let mut best_iteration = 0
for iteration in 0.. 0 {
local_search(
instance,
repaired,
config,
LocalSearchConfig::default().tune(max_rounds=config.local_search_rounds),
)
} else {
repaired
}
if candidate.objective < repaired.objective {
local_improvements += 1
}
evaluated_candidates += 1
if accept_candidate(candidate, current, temperature, rng) {
current = candidate
accepted_moves += 1
if current.objective < best.objective {
best = current
improved_moves += 1
best_iteration = iteration + 1
}
let reward = reward_for(current, seed_solution, best)
weights.destroy_weights[destroy_index] = 0.75 *
weights.destroy_weights[destroy_index] +
0.25 * reward
weights.repair_weights[repair_index] = 0.75 *
weights.repair_weights[repair_index] +
0.25 * reward
} else {
weights.destroy_weights[destroy_index] = 0.9 *
weights.destroy_weights[destroy_index] +
0.1 * 0.5
weights.repair_weights[repair_index] = 0.9 *
weights.repair_weights[repair_index] +
0.1 * 0.5
}
temperature *= config.cooling_rate
}
let stats = @core.SolveStats::new(
config.iterations,
accepted_moves,
improved_moves,
best_iteration,
seed_solution.objective,
best.objective,
local_improvements~,
evaluated_candidates~,
)
@core.SolveResult::new(instance, seed_solution, best, stats)
}
///|
pub fn solve(
instance : @core.VrpInstance,
config? : @core.SolveConfig = @core.SolveConfig::default(),
) -> @core.SolveResult {
let seed_solution = @constructive.clarke_wright_seed(instance, config~)
solve_with_seed(instance, seed_solution, config~)
}