Uğur Arıkan
RUST FORGE, 27-30 August 2025, Wellington
the domain
the domain
performance
monomorphization
the domain
speed as a requirement
performance
monomorphization
the domain
speed as a requirement
speed as a quality feature
we often cannot prove global optimality
⇩
the more we search, the better the solution gets
⇩
the faster we search, the better the solution gets
performance
monomorphization
problem variants
extensibility
the domain
problem variants
performance
monomorphization
problem variants
extensibility
the domain
problem variants
variants as criteria
treat each consideration as a criterion
C: set of all criteria
X ⊆ C: subset of criteria defining a specific problem variant
maintainability
productivity
performance
monomorphization
problem variants
extensibility
the domain
composing criteria
provided that we can solve:
can we also solve
variants as criteria
treat each consideration as a criterion
C: set of all criteria
X ⊆ C: subset of criteria defining a specific problem variant
Solution: a tour
⇨ 0-1-2-3-4-5
Solution: a tour
⇨ 0-1-2-3-4-5
Solution: a tour
⇨ 0-1-2-3-4-5Neighbor: a similar tour
⇨ 0-5-2-3-4-1
fn best_neighbor(tour: &Tour, cost: u64) -> Option<(Tour, u64)> { // INNER LOOP
let (mut best, mut cost) = (None, cost);
for neighbor in feasible_neighbors_of(tour) { // not red
if tour_cost(&neighbor) < cost {
best = Some(neighbor);
cost = tour_cost(&neighbor);
}
}
best.map(|best| (best, cost))
}
fn local_search(initial_tour: Tour) -> Tour { // OUTER LOOP
let (mut best, mut best_cost) = (initial_tour, tour_cost(&initial_tour));
while let Some((neighbor, cost)) = best_neighbor(&tour, cost) {
tour = neighbor;
best_cost = cost;
}
tour
}
Solution: a tour
⇨ 0-1-2-3-4-5Neighbor: a similar tour
⇨ 0-5-2-3-4-1Solution: a tour
⇨ 0-1-2-3-4-5Move: instructions to move to a neighbor
move city 5 to position 1
struct Tour { /**/ } // sequence of locations: 0-2-1-4-3
struct Move { /**/ } // instructions to move to a particular neighbor
impl Move {
// takes the tour to the particular neighbor
fn apply(&self, tour: &mut Tour) { /**/ }
}
Solution: a tour
⇨ 0-1-2-3-4-5Move: instructions to move to a neighbor
move city 5 to position 1
minimize the total tour duration
keep duration within allowed limit
...
minimize lateness of visits
minimize waiting time due to arriving early
...
struct DurationMatrix {
// data required to evaluate a tour
// wrt Duration criterion
//
// might be point-to-point duration computed
// via street routing
/*fields*/
}
// eg: a 4✕4 matrix
[
[0, 3, 2, 1],
[7, 0, 1, 5],
[2, 4, 0, 3],
[5, 1, 8, 0],
]
struct AvailableTimeSlots {
// data required to evaluate a tour
// wrt TimeWindows criterion
//
// might be a map of location to
// time slots that we can visit
// the corresponding location
/*fields*/
}
// eg: map of locations to available time slots
{
0 🡒 [{from: 10:00, to: 12:00}],
2 🡒 [
{from: 9:00, to: 10:00},
{from: 15:00, to: 17:00}
],
}
(input, tour) -> cost
fn tour_cost_duration(
input: &DurationMatrix,
tour: &Tour,
) -> u64 {
// sum all driving duration between all
// locations in the tour sequence
todo!()
}
fn tour_cost_tw(
input: &AvailableTimeSlots,
tour: &Tour,
) -> u64 {
// compute lateness & earliness of each
// location, compute sum of penalties
todo!()
}
(input, tour) -> moves & neighbor costs
fn moves_duration(
input: &DurationMatrix,
tour: &Tour,
) -> impl Iterator<Item = (Move, u64)> {
// create an iterator that yields
// all moves that takes the `tour`
// to all of its neighbors which
// are feasible wrt Duration criterion
// the second item of the pair is the
// tour cost of the neighbor wrt
// Duration criterion
todo!()
}
fn moves_tw(
input: &AvailableTimeSlots,
tour: &Tour,
) -> impl Iterator<Item = (Move, u64)> {
// create an iterator that yields
// all moves that takes the `tour`
// to all of its neighbors which
// are feasible wrt TimeWindows criterion
// the second item of the pair is the
// tour cost of the neighbor wrt
// TimeWindows criterion
todo!()
}
(input, tour, tour_cost) -> move to best neighbor with lower cost than tour
fn best_move_duration(
input: &DurationMatrix,
tour: &Tour,
cost: u64,
) -> Option<(Move, u64)> {
let mut best_cost = cost;
let mut best_move = None;
let moves = moves_duration(input, tour);
for (mv, cost) in moves {
if cost < best_cost {
best_move = Some(mv);
best_cost = cost;
}
}
best_move.map(|mv| (mv, best_cost))
}
fn best_move_tw(
input: &AvailableTimeSlots,
tour: &Tour,
cost: u64,
) -> Option<(Move, u64)> {
let mut best_cost = cost;
let mut best_move = None;
let moves = moves_tw(input, tour);
for (mv, cost) in moves {
if cost < best_cost {
best_move = Some(mv);
best_cost = cost;
}
}
best_move.map(|mv| (mv, best_cost))
}
(input, tour) -> local optimal tour
fn local_search_duration(
input: &DurationMatrix,
mut tour: Tour,
) -> Tour {
let mut cost =
tour_cost_duration(input, &tour);
while let Some((mv, neighbor_cost)) =
best_move_duration(input, &tour, cost)
{
mv.apply(&mut tour);
cost = neighbor_cost;
}
tour
}
fn local_search_tw(
input: &AvailableTimeSlots,
mut tour: Tour,
) -> Tour {
let mut cost =
tour_cost_tw(input, &tour);
while let Some((mv, neighbor_cost)) =
best_move_tw(input, &tour, cost)
{
mv.apply(&mut tour);
cost = neighbor_cost;
}
tour
}
composing criteria
provided that we can solve:
can we also solve
yes, but ...
fn tour_cost_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour
) -> u64 {
let cost_duration = tour_cost_duration(input_duration, tour);
let cost_tw = tour_cost_tw(input_tw, tour);
todo!()
}
fn moves_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour,
) -> impl Iterator<Item = (Move, u64)> {
let moves_duration = moves_duration(input_duration, tour);
let moves_tw = moves_tw(input_tw, tour);
todo!()
}
fn best_move_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour,
cost: u64,
) -> Option<(Move, u64)> {
let mut best_cost = cost;
let mut best_move = None;
for (mv, cost) in moves_duration_tw(input_duration, input_tw, tour) {
if cost < best_cost {
(best_cost, best_move) = (cost, Some(mv));
}
}
best_move
}
fn local_search_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
mut tour: Tour
) -> Tour {
let mut cost = tour_cost_duration_tw(input_duration, input_tw, &tour);
while let Some((mv, neighbor_cost)) =
best_move_duration_tw(input_duration, input_tw, &tour, cost)
{
mv.apply(&mut tour);
cost = neighbor_cost;
}
tour
}
yes, but not nice
what if we have other use cases for
fn tour_cost_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour
) -> u64 {
let cost_duration = tour_cost_duration(input_duration, tour);
let cost_tw = tour_cost_tw(input_tw, tour);
todo!()
}
fn moves_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour,
) -> impl Iterator<Item = (Move, u64)> {
let moves_duration = moves_duration(input_duration, tour);
let moves_tw = moves_tw(input_tw, tour);
todo!()
}
fn best_move_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour,
cost: u64,
) -> Option<(Move, u64)> {
let mut best_cost = cost;
let mut best_move = None;
for (mv, cost) in moves_duration_tw(input_duration, input_tw, tour) {
if cost < best_cost {
(best_cost, best_move) = (cost, Some(mv));
}
}
best_move
}
fn local_search_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
mut tour: Tour
) -> Tour {
let mut cost = tour_cost_duration_tw(input_duration, input_tw, &tour);
while let Some((mv, neighbor_cost)) =
best_move_duration_tw(input_duration, input_tw, &tour, cost)
{
mv.apply(&mut tour);
cost = neighbor_cost;
}
tour
}
C1. compose inputs
fn tour_cost_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour
) -> u64 {
let cost_duration = tour_cost_duration(input_duration, tour);
let cost_tw = tour_cost_tw(input_tw, tour);
todo!()
}
fn moves_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour,
) -> impl Iterator<Item = (Move, u64)> {
let moves_duration = moves_duration(input_duration, tour);
let moves_tw = moves_tw(input_tw, tour);
todo!()
}
fn best_move_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour,
cost: u64,
) -> Option<(Move, u64)> {
let mut best_cost = cost;
let mut best_move = None;
for (mv, cost) in moves_duration_tw(input_duration, input_tw, tour) {
if cost < best_cost {
(best_cost, best_move) = (cost, Some(mv));
}
}
best_move
}
fn local_search_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
mut tour: Tour
) -> Tour {
let mut cost = tour_cost_duration_tw(input_duration, input_tw, &tour);
while let Some((mv, neighbor_cost)) =
best_move_duration_tw(input_duration, input_tw, &tour, cost)
{
mv.apply(&mut tour);
cost = neighbor_cost;
}
tour
}
C2. compose costs
fn tour_cost_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour
) -> u64 {
let cost_duration = tour_cost_duration(input_duration, tour);
let cost_tw = tour_cost_tw(input_tw, tour);
todo!()
}
fn moves_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour,
) -> impl Iterator<Item = (Move, u64)> {
let moves_duration = moves_duration(input_duration, tour);
let moves_tw = moves_tw(input_tw, tour);
todo!()
}
fn best_move_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour,
cost: u64,
) -> Option<(Move, u64)> {
let mut best_cost = cost;
let mut best_move = None;
for (mv, cost) in moves_duration_tw(input_duration, input_tw, tour) {
if cost < best_cost {
(best_cost, best_move) = (cost, Some(mv));
}
}
best_move
}
fn local_search_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
mut tour: Tour
) -> Tour {
let mut cost = tour_cost_duration_tw(input_duration, input_tw, &tour);
while let Some((mv, neighbor_cost)) =
best_move_duration_tw(input_duration, input_tw, &tour, cost)
{
mv.apply(&mut tour);
cost = neighbor_cost;
}
tour
}
C3. compose moves
fn tour_cost_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour
) -> u64 {
let cost_duration = tour_cost_duration(input_duration, tour);
let cost_tw = tour_cost_tw(input_tw, tour);
todo!()
}
fn moves_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour,
) -> impl Iterator<Item = (Move, u64)> {
let moves_duration = moves_duration(input_duration, tour);
let moves_tw = moves_tw(input_tw, tour);
todo!()
}
fn best_move_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour,
cost: u64,
) -> Option<(Move, u64)> {
let mut best_cost = cost;
let mut best_move = None;
for (mv, cost) in moves_duration_tw(input_duration, input_tw, tour) {
if cost < best_cost {
(best_cost, best_move) = (cost, Some(mv));
}
}
best_move
}
fn local_search_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
mut tour: Tour
) -> Tour {
let mut cost = tour_cost_duration_tw(input_duration, input_tw, &tour);
while let Some((mv, neighbor_cost)) =
best_move_duration_tw(input_duration, input_tw, &tour, cost)
{
mv.apply(&mut tour);
cost = neighbor_cost;
}
tour
}
already generic 🗸
composing criteria
Criterion
abstraction
trait Criterion {
}
struct Duration;
impl Criterion for Duration { }
struct TimeWindows;
impl Criterion for TimeWindows { }
struct ComposedCriteria<X1, X2>(X1, X2)
where
X1: Criterion,
X2: Criterion;
impl<X1, X2> Criterion for ComposedCriteria<X1, X2>
where
X1: Criterion,
X2: Criterion;
{
}
fn tour_cost_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour
) -> u64 {
/**/
}
fn moves_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour,
) -> impl Iterator<Item = (Move, u64)> {
/**/
}
fn tour_cost_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour
) -> u64 {
/**/
}
fn moves_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour,
) -> impl Iterator<Item = (Move, u64)> {
/**/
}
fn tour_cost<X: Criterion>(
input: &X::Input,
tour: &Tour
) -> u64 {
/**/
}
fn moves<X: Criterion>(
input: &X::Input,
tour: &Tour,
) -> impl Iterator<Item = (Move, u64)> {
/**/
}
trait Criterion {
type Input;
}
trait Criterion {
type Input;
}
impl Criterion for Duration {
type Input = DurationMatrix;
}
impl Criterion for TimeWindows {
type Input = AvailableTimeSlots;
}
trait Criterion {
type Input;
}
impl Criterion for Duration {
type Input = DurationMatrix;
}
impl Criterion for TimeWindows {
type Input = AvailableTimeSlots;
}
impl<X1, X2> Criterion for ComposedCriteria<X1, X2>
where
X1: Criterion,
X2: Criterion;
{
type Input = ??;
}
trait Criterion {
type Input;
}
impl Criterion for Duration {
type Input = DurationMatrix;
}
impl Criterion for TimeWindows {
type Input = AvailableTimeSlots;
}
impl<X1, X2> Criterion for ComposedCriteria<X1, X2>
where
X1: Criterion,
X2: Criterion;
{
type Input = (X1::Input, X2::Input);
}
since ComposedCriteria: Criterion
it can recursively compose with itself as many times as we require
Duration
=> DurationMatrix
Duration
=> DurationMatrix
ComposedCriteria<Duration, TimeWindows>
=> (DurationMatrix, AvailableTimeSlots)
Duration
=> DurationMatrix
ComposedCriteria<Duration, TimeWindows>
=> (DurationMatrix, AvailableTimeSlots)
ComposedCriteria<ComposedCriteria<Duration, TimeWindows>, Capacity>
=> ((DurationMatrix, AvailableTimeSlots), CapacityDeltas)
Duration
=> DurationMatrix
ComposedCriteria<Duration, TimeWindows>
=> (DurationMatrix, AvailableTimeSlots)
ComposedCriteria<ComposedCriteria<Duration, TimeWindows>, Capacity>
=> ((DurationMatrix, AvailableTimeSlots), CapacityDeltas)
ComposedCriteria<ComposedCriteria<ComposedCriteria<X1, X2>, X3>, X4>
=> (((X1::Input, X2::Input), X3::Input), X4::Input)
fn tour_cost_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour
) -> u64 {
let cost_duration = tour_cost_duration(input_duration, tour);
let cost_tw = tour_cost_tw(input_tw, tour);
??
}
fn tour_cost_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour
) -> u64 {
let cost_duration = tour_cost_duration(input_duration, tour);
let cost_tw = tour_cost_tw(input_tw, tour);
cost_duration + cost_tw
}
fn tour_cost_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour
) -> u64 {
let cost_duration = tour_cost_duration(input_duration, tour);
let cost_tw = tour_cost_tw(input_tw, tour);
cost_duration + cost_tw
}
actually not that obvious, see multi-objective optimization
but we stick to additive costs here
trait Criterion {
type Input;
fn tour_cost(input: &Self::Input, tour: &Tour) -> u64;
}
trait Criterion {
type Input;
fn tour_cost(input: &Self::Input, tour: &Tour) -> u64;
}
impl Criterion for Duration {
type Input = DurationMatrix;
fn tour_cost(input: &Self::Input, tour: &Tour) -> u64 {
tour_cost_duration(input, tour)
}
}
impl Criterion for TimeWindows {
type Input = AvailableTimeSlots;
fn tour_cost(input: &Self::Input, tour: &Tour) -> u64 {
tour_cost_tw(input, tour)
}
}
trait Criterion {
type Input;
fn tour_cost(input: &Self::Input, tour: &Tour) -> u64;
}
impl<X1, X2> Criterion for ComposedCriteria<X1, X2>
where
X1: Criterion,
X2: Criterion;
{
type Input = (X1::Input, X2::Input);
fn tour_cost((input1, input2): &Self::Input, tour: &Tour) -> u64 {
X1::tour_cost(input1, tour) + X2::tour_cost(input2, tour)
}
}
fn moves_duration_tw(
input_duration: &DurationMatrix,
input_tw: &AvailableTimeSlots,
tour: &Tour,
) -> impl Iterator<Item = (Move, u64)> {
let duration_moves = duration_moves(input_duration, tour);
let tw_moves = tw_moves(input_tw, tour);
// ??
}
tour
has a fixed neighborhood that can be represented as a set of moves
some of these moves are feasible wrt Duration
and some are feasible wrt TimeWindows
tour
has a fixed neighborhood that can be represented as a set of moves
some of these moves are feasible wrt Duration
and some are feasible wrt TimeWindows
⇩
composition is the intersection of the subsets
tour
has a fixed neighborhood that can be represented as a set of moves
some of these moves are feasible wrt Duration
and some are feasible wrt TimeWindows
⇩
composition is the intersection of the subsets
challenge: intersect two iterators in linear time
trick: traverse neighbors in a standard order
Duration
TimeWindows
ComposedCriteria<Duration, TimeWindows>
Duration
TimeWindows
ComposedCriteria<Duration, TimeWindows>
Duration
TimeWindows
ComposedCriteria<Duration, TimeWindows>
Duration
TimeWindows
ComposedCriteria<Duration, TimeWindows>
Duration
TimeWindows
ComposedCriteria<Duration, TimeWindows>
Duration
TimeWindows
ComposedCriteria<Duration, TimeWindows>
Duration
TimeWindows
ComposedCriteria<Duration, TimeWindows>
enum ComposedNext {
OneIteratorConsumed,
BothYieldedSame(Move, u64),
FirstIteratorYieldedGreater(Move, u64),
SecondIteratorYieldedGreater(Move, u64),
}
impl ComposedNext {
fn new(next_move1: Option<(Move, u64)>, next_move2: Option<(Move, u64)>) -> Self {
match (next_move1, next_move2) {
(Some((x, cx)), Some((y, cy))) => match x.cmp(&y) {
Ordering::Equal => ComposedNext::BothYieldedSame(x, cx + cy),
Ordering::Greater => ComposedNext::FirstIteratorYieldedGreater(x, cx),
Ordering::Less => ComposedNext::SecondIteratorYieldedGreater(y, cy),
},
_ => ComposedNext::OneIteratorConsumed,
}
}
}
struct SortedIntersectingMoves<Iter1, Iter2>(Iter1, Iter2);
impl<Iter1, Iter2> Iterator for SortedIntersectingMoves<Iter1, Iter2>
where
Iter1: Iterator<Item = (Move, u64)>,
Iter2: Iterator<Item = (Move, u64)>,
{
type Item = (Move, u64);
fn next(&mut self) -> Option<Self::Item> {
let mut next = ComposedNext::new(self.0.next(), self.1.next());
loop {
match next {
ComposedNext::OneIteratorConsumed => return None,
ComposedNext::BothYieldedSame((mv, cost)) => return Some((mv, cost)),
ComposedNext::FirstIteratorYieldedGreater((x, cx)) => {
next = ComposedNext::new(Some((x, cx)), self.1.next());
}
ComposedNext::SecondIteratorYieldedGreater((y, cy)) => {
next = ComposedNext::new(self.0.next(), Some((y, cy)));
}
}
}
}
}
trait Criterion {
type Input;
fn tour_cost(input: &Self::Input, tour: &Tour) -> u64;
fn moves(input: &Self::Input, tour: &Tour) -> impl Iterator<Item = (Move, u64)>;
}
trait Criterion {
type Input;
fn tour_cost(input: &Self::Input, tour: &Tour) -> u64;
fn moves(input: &Self::Input, tour: &Tour) -> impl Iterator<Item = (Move, u64)>;
}
impl Criterion for Duration {
type Input = DurationMatrix;
fn tour_cost(input: &Self::Input, tour: &Tour) -> u64 {
tour_cost_duration(input, tour)
}
fn moves(input: &Self::Input, tour: &Tour) -> impl Iterator<Item = (Move, u64)> {
moves_duration(input, tour)
}
}
trait Criterion {
type Input;
fn tour_cost(input: &Self::Input, tour: &Tour) -> u64;
fn moves(input: &Self::Input, tour: &Tour) -> impl Iterator<Item = (Move, u64)>;
}
impl Criterion for TimeWindows {
type Input = AvailableTimeSlots;
fn tour_cost(input: &Self::Input, tour: &Tour) -> u64 {
tour_cost_tw(input, tour)
}
fn moves(input: &Self::Input, tour: &Tour) -> impl Iterator<Item = (Move, u64)> {
moves_tw(input, tour)
}
}
trait Criterion {
type Input;
fn tour_cost(input: &Self::Input, tour: &Tour) -> u64;
fn moves(input: &Self::Input, tour: &Tour) -> impl Iterator<Item = (Move, u64)>;
}
impl<X1, X2> Criterion for ComposedCriteria<X1, X2>
where
X1: Criterion,
X2: Criterion;
{
type Input = (X1::Input, X2::Input);
fn tour_cost((input1, input2): &Self::Input, tour: &Tour) -> u64 {
X1::tour_cost(input1, tour) + X2::tour_cost(input2, tour)
}
fn moves((input1, input2): &Self::Input, tour: &Tour) -> impl Iterator<Item = (Move, u64)> {
let moves1 = X1::moves(input1, tour);
let moves2 = X2::moves(input2, tour);
SortedIntersectingMoves(moves1, moves2)
}
}
since ComposedCriteria: Criterion
it can recursively compose with itself as many times as we require
SIM<A, B> := SortedIntersectingMoves<A, B>
Duration
=> DurationMoves
SIM<A, B> := SortedIntersectingMoves<A, B>
Duration
=> DurationMoves
ComposedCriteria<Duration, TimeWindows>
=> SIM<DurationMoves, TwMoves>
SIM<A, B> := SortedIntersectingMoves<A, B>
Duration
=> DurationMoves
ComposedCriteria<Duration, TimeWindows>
=> SIM<DurationMoves, TwMoves>
ComposedCriteria<ComposedCriteria<Duration, TimeWindows>, Capacity>
=> SIM<SIM<DurationMoves, TwMoves>, CapacityMoves>
SIM<A, B> := SortedIntersectingMoves<A, B>
Duration
=> DurationMoves
ComposedCriteria<Duration, TimeWindows>
=> SIM<DurationMoves, TwMoves>
ComposedCriteria<ComposedCriteria<Duration, TimeWindows>, Capacity>
=> SIM<SIM<DurationMoves, TwMoves>, CapacityMoves>
ComposedCriteria<ComposedCriteria<ComposedCriteria<X1, X2>, X3>, X4>
=> SIM<SIM<SIM<X1::Moves, X2::Moves>, X3::Moves>, X4::Moves>
fn best_move<X>(input: &X::Input, tour: &Tour, cost: u64) -> Option<(Move, u64)>
where X: Criterion
{
let mut best_cost = cost;
let mut best_move = None;
for (mv, cost) in X::moves(input, tour) {
if cost < best_cost {
best_cost = cost;
best_move = Some(mv);
}
}
best_move
}
fn local_search<X>(input: &X::Input, initial_tour: Tour) -> Tour
where X: Criterion
{
let mut tour = initial_tour;
let mut best_cost = X::tour_cost(input, &tour);
while let Some((mv, cost)) = best_move::<X>(input, &tour, best_cost) {
mv.apply(&mut tour);
best_cost = cost;
}
tour
}
we maintain each criterion independently
we maintain each criterion independently
in order to maintain the Duration
criterion, only the following is relevant
impl Criterion for Duration {
type Input = DurationMatrix;
fn tour_cost(input: &Self::Input, tour: &Tour) -> u64 {
tour_cost_duration(input, tour)
}
fn moves(input: &Self::Input, tour: &Tour) -> impl Iterator<Item = (Move, u64)> {
moves_duration(input, tour)
}
}
if we implemented 5 criteria
we can represent \(2^5-1=31\) problem variants
if we implemented 5 criteria
we can represent \(2^5-1=31\) problem variants
type X = Duration;
let input: DurationMatrix = ...;
let initial_tour: Tour = ...;
let optimal_tour = local_search::<X>(&input, initial_tour);
if we implemented 5 criteria
we can represent \(2^5-1=31\) problem variants
type X = ComposedCriteria<Duration, TimeWindows>
let input_duration: DurationMatrix = ...;
let input_tw: AvailableTimeSlots = ...;
let input = (input_duration, input_tw);
let initial_tour: Tour = ...;
let optimal_tour = local_search::<X>(&input, initial_tour);
if we implemented 5 criteria
we can represent \(2^5-1=31\) problem variants
type X = ComposedCriteria<ComposedCriteria<Duration, TimeWindows>, Capacity>
let input_duration: DurationMatrix = ...;
let input_tw: AvailableTimeSlots = ...;
let input_cap: CapacityDeltas = ...;
let input = ((input_duration, input_tw), input_cap);
let initial_tour: Tour = ...;
let optimal_tour = local_search::<X>(&input, initial_tour);
if we implemented 5 criteria
we can represent \(2^5-1=31\) problem variants
if we implemented 5 criteria
we can represent \(2^5-1=31\) problem variants
implementing the 6th criterion, increases this number to 63
⇩
makes it well-worth the effort to add new features
type X = ComposedCriteria<ComposedCriteria<Duration, TimeWindows>, Capacity>
let input_duration: DurationMatrix = ...;
let input_tw: AvailableTimeSlots = ...;
let input_cap: CapacityDeltas = ...;
let input = ((input_duration, input_tw), input_cap);
let initial_tour: Tour = ...;
let optimal_tour = local_search::<X>(&input, initial_tour);
type X = ComposedCriteria<
ComposedCriteria<
ComposedCriteria<ComposedCriteria<Duration, TimeWindows>, Capacity>,
Precedence,
>,
Trailer,
>;
let input_duration: DurationMatrix = ...;
let input_tw: AvailableTimeSlots = ...;
let input_cap: CapacityDeltas = ...;
let input_pre: PrecedenceRelations = ...;
let input_trl: TrailerChangePoints = ...;
let input = ((((input_duration, input_tw), input_cap), input_pre), input_trl);
let initial_tour: Tour = ...;
let optimal_tour = local_search::<X>(&input, initial_tour);
struct LocalSolver<X: Criterion> {
input: X::Input,
}
impl<X: Criterion> LocalSolver<X> {
fn new(input: X::Input) -> Self {
Self { input }
}
fn run(&self, tour: Tour) -> Tour {
local_search::<X>(&self.input, tour)
}
fn and_with<Y: Criterion>(self, input: Y::Input) -> LocalSolver<ComposedCriteria<X, Y>> {
LocalSolver::new((self.input, input))
}
}
let input_duration: DurationMatrix = ...;
let input_tw: AvailableTimeSlots = ...;
let input_cap: CapacityDeltas = ...;
let input_pre: PrecedenceRelations = ...;
let input_trl: TrailerChangePoints = ...;
let initial_tour: Tour = ...;
let solver = LocalSolver::<Duration>::new(input_duration)
.and_with::<TimeWindows>(input_tw)
.and_with::<Capacity>(input_cap)
.and_with::<Precedence>(input_pre)
.and_with::<Trailer>(input_trl);
let optimal_tour = solver.run(initial_tour);
thank you!