Skip to content

Commit cf9a48d

Browse files
Improve heuristics
1 parent 7cbf0c8 commit cf9a48d

1 file changed

Lines changed: 25 additions & 42 deletions

File tree

vrp-core/src/solver/heuristic.rs

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -502,11 +502,9 @@ mod dynamic {
502502
/// Internal `WeightedRuin`/`WeightedRecreate` weight for weak members.
503503
const WEAK_BUNDLE_WEIGHT: usize = 1;
504504

505-
/// Wraps every primary ruin in a `CompositeRuin` with a small-probability `extra_random_job`
506-
/// companion to mimic today's "small chaos" baseline. `random_job` is its own primary, so we
507-
/// skip the wrapper for it to avoid double-counting random destruction.
505+
/// Wraps every primary ruin in a `CompositeRuin` with a small-probability `extra`.
508506
fn wrap_with_extra(ruin: Arc<dyn Ruin>, name: &str, extra: Arc<dyn Ruin>) -> Arc<dyn Ruin> {
509-
if name == "random_job" {
507+
if name == "random_job" || name == "random_route" {
510508
ruin
511509
} else {
512510
Arc::new(CompositeRuin::new(vec![(ruin, 1.), (extra, 0.1)]))
@@ -558,17 +556,14 @@ mod dynamic {
558556
as Arc<dyn Ruin>
559557
};
560558

559+
// random_job is omitted as a primary weak-tier as it seems not good to be used alone,
560+
// but it is still included as a small-probability companion in ruin bundles via `wrap_with_extra`.
561561
vec![
562562
(
563563
create_weighted(|limits| Arc::new(NeighbourRemoval::new(limits))),
564564
"neighbour".to_string(),
565565
WEAK_ARM_PRIOR,
566566
),
567-
(
568-
create_weighted(|limits| Arc::new(RandomJobRemoval::new(limits))),
569-
"random_job".to_string(),
570-
WEAK_ARM_PRIOR,
571-
),
572567
(
573568
create_weighted(|limits| Arc::new(RandomRouteRemoval::new(limits))),
574569
"random_route".to_string(),
@@ -577,10 +572,7 @@ mod dynamic {
577572
]
578573
}
579574

580-
fn get_strong_recreates(
581-
problem: &Problem,
582-
random: Arc<dyn Random>,
583-
) -> Vec<(Arc<dyn Recreate>, String, Float)> {
575+
fn get_strong_recreates(problem: &Problem, random: Arc<dyn Random>) -> Vec<(Arc<dyn Recreate>, String, Float)> {
584576
let blinks: Arc<dyn Recreate> = Arc::new(RecreateWithBlinks::new_with_defaults(random.clone()));
585577
let cheapest: Arc<dyn Recreate> = Arc::new(RecreateWithCheapest::new(random.clone()));
586578
let regret: Arc<dyn Recreate> = Arc::new(RecreateWithRegret::new(1, 3, random.clone()));
@@ -643,13 +635,9 @@ mod dynamic {
643635
) -> Arc<dyn Ruin> {
644636
let mut bundle: Vec<(Arc<dyn Ruin>, usize)> = Vec::with_capacity(strong.len() + weak.len());
645637
bundle.extend(
646-
strong
647-
.iter()
648-
.map(|(r, n, _)| (wrap_with_extra(r.clone(), n, extra.clone()), STRONG_BUNDLE_WEIGHT)),
649-
);
650-
bundle.extend(
651-
weak.iter().map(|(r, n, _)| (wrap_with_extra(r.clone(), n, extra.clone()), WEAK_BUNDLE_WEIGHT)),
638+
strong.iter().map(|(r, n, _)| (wrap_with_extra(r.clone(), n, extra.clone()), STRONG_BUNDLE_WEIGHT)),
652639
);
640+
bundle.extend(weak.iter().map(|(r, n, _)| (wrap_with_extra(r.clone(), n, extra.clone()), WEAK_BUNDLE_WEIGHT)));
653641
Arc::new(WeightedRuin::new(bundle))
654642
}
655643

@@ -707,7 +695,13 @@ mod dynamic {
707695
let strong_recreates = get_strong_recreates(problem.as_ref(), random.clone());
708696
let weak_recreates = get_weak_recreates(random.clone());
709697

710-
let extra_random_job: Arc<dyn Ruin> = Arc::new(RandomJobRemoval::new(small_limits));
698+
// 3:1 weighted mix of random_job and random_route — applied at 0.1 probability to every
699+
// primary ruin via wrap_with_extra. Random_route occasionally forces job redistribution
700+
// across routes, which scattered random_job removal can't trigger.
701+
let extra_random_job: Arc<dyn Ruin> = Arc::new(WeightedRuin::new(vec![
702+
(Arc::new(RandomJobRemoval::new(small_limits.clone())) as Arc<dyn Ruin>, 3),
703+
(Arc::new(RandomRouteRemoval::new(small_limits)) as Arc<dyn Ruin>, 1),
704+
]));
711705

712706
// Strong cartesian: every strong ruin × every strong recreate. Ruins are wrapped with the
713707
// small `extra_random_job` companion (today's "small chaos" baseline).
@@ -739,17 +733,12 @@ mod dynamic {
739733
.iter()
740734
.map::<(TargetSearchOperator, String, Float), _>(|(ruin, name, weight)| {
741735
let primary = wrap_with_extra(ruin.clone(), name, extra_random_job.clone());
742-
(
743-
Arc::new(RuinAndRecreate::new(primary, weak_ruin_recreate_bundle.clone())),
744-
name.clone(),
745-
*weight,
746-
)
736+
(Arc::new(RuinAndRecreate::new(primary, weak_ruin_recreate_bundle.clone())), name.clone(), *weight)
747737
})
748738
.collect::<Vec<_>>();
749739

750740
// Weak recreate arms: each weak recreate paired with a strong-heavy WeightedRuin bundle.
751-
let weak_recreate_ruin_bundle =
752-
build_weak_ruin_bundle(&strong_ruins, &weak_ruins, extra_random_job);
741+
let weak_recreate_ruin_bundle = build_weak_ruin_bundle(&strong_ruins, &weak_ruins, extra_random_job);
753742
let weak_recreate_ops = weak_recreates
754743
.iter()
755744
.map::<(TargetSearchOperator, String, Float), _>(|(recreate, name, weight)| {
@@ -782,8 +771,6 @@ mod dynamic {
782771
}
783772

784773
/// Creates a default ruin-and-recreate operator for internal use (e.g., decompose search, infeasible search).
785-
/// Uses the same tier philosophy as the main bandit: strong members dominate (2:1 over weak)
786-
/// inside both the ruin and recreate bundles, with the SISR pair (asr, blinks) further boosted.
787774
pub fn create_default_inner_ruin_recreate(
788775
problem: Arc<Problem>,
789776
environment: Arc<Environment>,
@@ -792,36 +779,32 @@ mod dynamic {
792779
let random = environment.random.clone();
793780

794781
let strong_ruins = get_strong_ruins(problem.clone(), &normal_limits, &small_limits);
795-
let weak_ruins = get_weak_ruins(&normal_limits, &small_limits);
796-
let strong_recreates = get_strong_recreates(problem.as_ref(), random.clone());
797-
let weak_recreates = get_weak_recreates(random);
782+
let strong_recreates = get_strong_recreates(problem.as_ref(), random);
798783

799-
let extra_random_job: Arc<dyn Ruin> = Arc::new(RandomJobRemoval::new(small_limits));
784+
// 3:1 mix of random_job and random_route as the small-chaos companion (matches outer pool).
785+
let extra_random: Arc<dyn Ruin> = Arc::new(WeightedRuin::new(vec![
786+
(Arc::new(RandomJobRemoval::new(small_limits.clone())) as Arc<dyn Ruin>, 3),
787+
(Arc::new(RandomRouteRemoval::new(small_limits)) as Arc<dyn Ruin>, 1),
788+
]));
800789

801-
// Map bandit-prior weights to integer bundle weights, preserving the SISR boost
802-
// (asr=3, blinks=3) and the strong/weak split. Weak members at half-weight after rounding,
803-
// so we use an explicit table instead of casting Float→usize.
790+
// Map bandit-prior weights to integer bundle weights, preserving the SISR boost (asr=3, blinks=3).
804791
let to_bundle_weight = |float_weight: Float| -> usize {
805792
if float_weight >= SISR_BOOST_WEIGHT {
806793
SISR_BOOST_WEIGHT as usize * STRONG_BUNDLE_WEIGHT
807-
} else if float_weight >= STRONG_WEIGHT {
808-
STRONG_BUNDLE_WEIGHT
809794
} else {
810-
WEAK_BUNDLE_WEIGHT
795+
STRONG_BUNDLE_WEIGHT
811796
}
812797
};
813798

814799
let weighted_ruins: Vec<(Arc<dyn Ruin>, usize)> = strong_ruins
815800
.iter()
816-
.chain(weak_ruins.iter())
817801
.map(|(ruin, name, weight)| {
818-
(wrap_with_extra(ruin.clone(), name, extra_random_job.clone()), to_bundle_weight(*weight))
802+
(wrap_with_extra(ruin.clone(), name, extra_random.clone()), to_bundle_weight(*weight))
819803
})
820804
.collect();
821805

822806
let weighted_recreates: Vec<(Arc<dyn Recreate>, usize)> = strong_recreates
823807
.iter()
824-
.chain(weak_recreates.iter())
825808
.map(|(recreate, _, weight)| (recreate.clone(), to_bundle_weight(*weight)))
826809
.collect();
827810

0 commit comments

Comments
 (0)