Skip to content

Commit

Permalink
Refactor footpring
Browse files Browse the repository at this point in the history
  • Loading branch information
reinterpretcat committed Nov 25, 2024
1 parent 95644bc commit cb8eab3
Show file tree
Hide file tree
Showing 11 changed files with 69 additions and 80 deletions.
6 changes: 3 additions & 3 deletions experiments/heuristic-research/src/solver/vrp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub use self::population::get_population_fitness_fn;

use super::*;
use std::io::BufWriter;
use vrp_scientific::core::models::common::FootprintContext;
use vrp_scientific::core::models::common::Footprint;
use vrp_scientific::core::prelude::*;
use vrp_scientific::core::solver::RefinementContext;
use vrp_scientific::lilim::{LilimProblem, LilimSolution};
Expand Down Expand Up @@ -42,9 +42,9 @@ pub fn solve_vrp(
is_experimental,
..Environment::new_with_time_quota(Some(300))
});
let context = FootprintContext::new(problem.as_ref());
let footprint = Footprint::new(problem.as_ref());
let population =
get_population(context, population_type, problem.goal.clone(), environment.clone(), selection_size);
get_population(footprint, population_type, problem.goal.clone(), environment.clone(), selection_size);
let telemetry_mode = TelemetryMode::OnlyLogging { logger: logger.clone(), log_best: 100, log_population: 1000 };

let config = VrpConfigBuilder::new(problem.clone())
Expand Down
2 changes: 1 addition & 1 deletion rosomaxa/src/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,8 +455,8 @@ impl Solver {
VectorContext::new(
objective.clone(),
get_default_population(
VectorRosomaxaContext,
objective.clone(),
VectorRosomaxaContext,
environment.clone(),
selection_size,
),
Expand Down
4 changes: 2 additions & 2 deletions rosomaxa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,8 @@ pub fn get_default_selection_size(environment: &Environment) -> usize {

/// Gets default population algorithm.
pub fn get_default_population<C, O, S>(
context: C,
objective: Arc<O>,
footprint: C,
environment: Arc<Environment>,
selection_size: usize,
) -> Box<dyn HeuristicPopulation<Objective = O, Individual = S>>
Expand All @@ -306,7 +306,7 @@ where
Box::new(Greedy::new(objective, 1, None))
} else {
let config = RosomaxaConfig::new_with_defaults(selection_size);
let population = Rosomaxa::new(context, objective, environment, config)
let population = Rosomaxa::new(footprint, objective, environment, config)
.expect("cannot create rosomaxa with default configuration");

Box::new(population)
Expand Down
2 changes: 1 addition & 1 deletion rosomaxa/tests/helpers/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub fn create_heuristic_context_with_solutions(solutions: Vec<Vec<Float>>) -> Ve
let selection_size = get_default_selection_size(environment.as_ref());

let mut population =
get_default_population(VectorRosomaxaContext, objective.clone(), environment.clone(), selection_size);
get_default_population(objective.clone(), VectorRosomaxaContext, environment.clone(), selection_size);

let solutions = solutions
.into_iter()
Expand Down
2 changes: 1 addition & 1 deletion rosomaxa/tests/unit/evolution/telemetry_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn can_update_statistic() {
let environment = Arc::new(Environment::default());
let objective = create_example_objective();
let selection_size = get_default_selection_size(environment.as_ref());
let population = get_default_population(VectorRosomaxaContext, objective.clone(), environment, selection_size);
let population = get_default_population(objective.clone(), VectorRosomaxaContext, environment, selection_size);
let population = population.as_ref();

let mut telemetry = Telemetry::new(TelemetryMode::None);
Expand Down
6 changes: 3 additions & 3 deletions vrp-cli/src/commands/solve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use vrp_cli::core::solver::TargetHeuristic;
use vrp_cli::extensions::solve::config::create_builder_from_config_file;
use vrp_cli::extensions::solve::formats::*;
use vrp_core::construction::heuristics::InsertionContext;
use vrp_core::models::common::FootprintContext;
use vrp_core::models::common::Footprint;
use vrp_core::prelude::*;
use vrp_core::rosomaxa::{evolution::*, get_default_population, get_default_selection_size};
use vrp_core::solver::*;
Expand Down Expand Up @@ -409,8 +409,8 @@ fn get_population(mode: Option<&String>, problem: &Problem, environment: Arc<Env
match mode.map(String::as_str) {
Some("deep") => Box::new(ElitismPopulation::new(objective, environment.random.clone(), 4, selection_size)),
_ => {
let context = FootprintContext::new(problem);
get_default_population(context, objective, environment, selection_size)
let footprint = Footprint::new(problem);
get_default_population(objective, footprint, environment, selection_size)
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions vrp-cli/src/extensions/solve/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use serde::Deserialize;
use std::io::{BufReader, Read};
use std::sync::Arc;
use vrp_core::construction::heuristics::InsertionContext;
use vrp_core::models::common::FootprintContext;
use vrp_core::models::common::Footprint;
use vrp_core::models::GoalContext;
use vrp_core::prelude::*;
use vrp_core::rosomaxa::evolution::{InitialOperator, TelemetryMode};
Expand Down Expand Up @@ -513,8 +513,8 @@ fn configure_from_evolution(
config.exploration_ratio = *exploration_ratio;
}

let context = FootprintContext::new(problem.as_ref());
Box::new(RosomaxaPopulation::new(context, problem.goal.clone(), environment.clone(), config)?)
let footprint = Footprint::new(problem.as_ref());
Box::new(RosomaxaPopulation::new(footprint, problem.goal.clone(), environment.clone(), config)?)
}
};

Expand Down
44 changes: 13 additions & 31 deletions vrp-core/src/models/common/footprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@ use crate::prelude::*;
use rosomaxa::population::RosomaxaContext;
use rosomaxa::utils::fold_reduce;

custom_solution_state!(Footprint typeof Footprint);

/// Defines a low-dimensional representation of multiple solutions.
#[derive(Clone, Debug)]
pub struct Footprint {
/// repr here is the same adjacency matrix as in [Shadow], but instead of storing bits
/// we store here the number of times the edge was present in multiple solutions.
repr: Vec<u8>,
dimension: usize,
counter: usize,
}

impl Footprint {
/// Creates a new instance of a `Snapshot`.
pub fn new(problem: &Problem) -> Self {
let dim = get_dimension(problem);
Self { repr: vec![0; dim * dim], dimension: dim }
Self { repr: vec![0; dim * dim], dimension: dim, counter: 0 }
}

/// Adds shadow to the footprint.
Expand Down Expand Up @@ -59,7 +62,7 @@ impl Footprint {
}

/// Estimates the cost of a solution as a sum of all edge costs.
pub fn estimate(&self, solution_ctx: &SolutionContext) -> usize {
pub fn estimate_solution(&self, solution_ctx: &SolutionContext) -> usize {
solution_ctx
.routes
.iter()
Expand All @@ -75,6 +78,11 @@ impl Footprint {
.sum()
}

/// Estimates how frequently the edge between two locations was present in multiple solutions.
pub fn estimate_edge(&self, from: Location, to: Location) -> usize {
self.get(from, to) as usize
}

/// Returns dimension of adjacency matrix.
pub fn dimension(&self) -> usize {
self.dimension
Expand Down Expand Up @@ -134,33 +142,7 @@ impl From<&InsertionContext> for Shadow {
}
}

custom_solution_state!(Footprint typeof FootprintContext);

/// Provides a way to use footprint within rosomaxa algorithm.
#[derive(Clone, Debug)]
pub struct FootprintContext {
counter: usize,
footprint: Footprint,
}

impl FootprintContext {
/// Creates a new instance of a `FootprintContext`.
pub fn new(problem: &Problem) -> Self {
Self { counter: 0, footprint: Footprint::new(problem) }
}

/// Estimates how frequently the edge between two locations was present in multiple solutions.
pub fn estimate_edge(&self, from: Location, to: Location) -> usize {
self.footprint.get(from, to) as usize
}

/// Estimates entire solution cost based on the footprint similarity.
pub fn estimate_solution(&self, solution_ctx: &SolutionContext) -> usize {
self.footprint.estimate(solution_ctx)
}
}

impl RosomaxaContext for FootprintContext {
impl RosomaxaContext for Footprint {
type Solution = InsertionContext;

fn on_change(&mut self, solutions: &[Self::Solution]) {
Expand All @@ -170,7 +152,7 @@ impl RosomaxaContext for FootprintContext {

// we need to forget the footprint from time to time to keep it sensitive to new solutions
if self.counter == FOOTPRINT_FORGET_RATE {
self.footprint.forget();
self.forget();
self.counter = 0;
} else {
self.counter += 1;
Expand All @@ -190,7 +172,7 @@ impl RosomaxaContext for FootprintContext {
left
},
);
self.footprint.union(&footprint);
self.union(&footprint);
}
}

Expand Down
2 changes: 1 addition & 1 deletion vrp-core/src/models/goal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl GoalContextBuilder {

fn get_heuristic_goal(features: &[Feature]) -> GenericResult<Goal> {
const KNOWN_EDGE_FEATURE_NAME: &str = "known_edge";
const KEEP_SOLUTION_FITNESS: bool = true;
const KEEP_SOLUTION_FITNESS: bool = false;

let mut objective_names =
features.iter().filter(|f| f.objective.is_some()).map(|f| f.name.as_str()).collect::<Vec<_>>();
Expand Down
60 changes: 29 additions & 31 deletions vrp-core/src/solver/heuristic.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::*;
use crate::construction::heuristics::*;
use crate::models::common::{FootprintContext, FootprintSolutionState};
use crate::models::common::FootprintSolutionState;
use crate::models::{Extras, GoalContext};
use crate::rosomaxa::get_default_selection_size;
use crate::solver::search::*;
Expand Down Expand Up @@ -31,7 +31,7 @@ pub type GreedyPopulation = Greedy<GoalContext, InsertionContext>;
/// A type for elitism population.
pub type ElitismPopulation = Elitism<GoalContext, InsertionContext>;
/// A type for rosomaxa population.
pub type RosomaxaPopulation = Rosomaxa<FootprintContext, GoalContext, InsertionContext>;
pub type RosomaxaPopulation = Rosomaxa<Footprint, GoalContext, InsertionContext>;

/// A type alias for domain specific termination type.
pub type DynTermination = dyn Termination<Context = RefinementContext, Objective = GoalContext> + Send + Sync;
Expand Down Expand Up @@ -100,8 +100,8 @@ impl VrpConfigBuilder {
let heuristic = self.heuristic.unwrap_or_else(|| get_default_heuristic(problem.clone(), environment.clone()));

let selection_size = get_default_selection_size(environment.as_ref());
let context = FootprintContext::new(problem.as_ref());
let population = get_default_population(context, problem.goal.clone(), environment.clone(), selection_size);
let footprint = Footprint::new(problem.as_ref());
let population = get_default_population(problem.goal.clone(), footprint, environment.clone(), selection_size);

Ok(ProblemConfigBuilder::default()
.with_heuristic(heuristic)
Expand Down Expand Up @@ -189,7 +189,7 @@ pub fn create_elitism_population(
custom_solution_state!(SolutionWeights typeof Vec<Float>);

impl RosomaxaSolution for InsertionContext {
type Context = FootprintContext;
type Context = Footprint;

fn on_init(&mut self, context: &Self::Context) {
// built a feature vector which is used to classify solution in population
Expand Down Expand Up @@ -293,39 +293,37 @@ mod builder {
environment: Arc<Environment>,
) -> InitialOperators<RefinementContext, GoalContext, InsertionContext> {
let random = environment.random.clone();
let wrap = |recreate: Arc<dyn Recreate>| Box::new(RecreateInitialOperator::new(recreate));

let mut main: InitialOperators<_, _, _> = vec![
(wrap(Arc::new(RecreateWithCheapest::new(random.clone()))), 1),
let wrap: fn(
Arc<dyn Recreate>,
) -> Box<
dyn InitialOperator<Context = RefinementContext, Objective = GoalContext, Solution = InsertionContext>
+ Send
+ Sync,
> = |recreate| Box::new(RecreateInitialOperator::new(recreate));

std::iter::once({
// main stable constructive heuristics
(wrap(Arc::new(RecreateWithCheapest::new(random.clone()))), 1)
})
.chain(
// alternative constructive heuristics
get_recreate_with_alternative_goal(problem.goal.as_ref(), {
let random = random.clone();
move || RecreateWithCheapest::new(random.clone())
})
.map(|recreate| (wrap(recreate), 1)),
)
.chain([
// additional constructive heuristics
(wrap(Arc::new(RecreateWithFarthest::new(random.clone()))), 1),
(wrap(Arc::new(RecreateWithRegret::new(2, 3, random.clone()))), 1),
(wrap(Arc::new(RecreateWithGaps::new(1, (problem.jobs.size() / 10).max(1), random.clone()))), 1),
(wrap(Arc::new(RecreateWithSkipBest::new(1, 2, random.clone()))), 1),
(wrap(Arc::new(RecreateWithBlinks::new_with_defaults(random.clone()))), 1),
(wrap(Arc::new(RecreateWithPerturbation::new_with_defaults(random.clone()))), 1),
(wrap(Arc::new(RecreateWithNearestNeighbor::new(random.clone()))), 1),
];

let alternatives = get_recreate_with_alternative_goal(problem.goal.as_ref(), {
move || RecreateWithCheapest::new(random.clone())
})
.map(|recreate| {
let init_operator: Box<
dyn InitialOperator<Context = RefinementContext, Objective = GoalContext, Solution = InsertionContext>
+ Send
+ Sync,
> = wrap(recreate);

(init_operator, 1)
})
.collect::<InitialOperators<_, _, _>>();

if alternatives.is_empty() {
main
} else {
main.splice(1..1, alternatives);
main
}
])
.collect()
}

/// Create default processing.
Expand Down
15 changes: 12 additions & 3 deletions vrp-core/src/solver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
extern crate rand;

use crate::construction::heuristics::InsertionContext;
use crate::models::common::{Footprint, FootprintSolutionState, Shadow};
use crate::models::{GoalContext, Problem, Solution};
use crate::solver::search::Recreate;
use rosomaxa::evolution::*;
Expand All @@ -96,6 +97,8 @@ pub struct RefinementContext {
pub environment: Arc<Environment>,
/// A collection of data associated with a refinement process.
pub state: HashMap<String, Box<dyn Any + Sync + Send>>,
/// Keeps track of the initial footprint.
initial_footprint: Footprint,
/// Provides some basic implementation of context functionality.
inner_context: TelemetryHeuristicContext<GoalContext, InsertionContext>,
}
Expand All @@ -118,9 +121,10 @@ impl RefinementContext {
telemetry_mode: TelemetryMode,
environment: Arc<Environment>,
) -> Self {
let initial_footprint = Footprint::new(&problem);
let inner_context =
TelemetryHeuristicContext::new(problem.goal.clone(), population, telemetry_mode, environment.clone());
Self { problem, environment, inner_context, state: Default::default() }
Self { problem, environment, inner_context, state: Default::default(), initial_footprint }
}

/// Adds solution to population.
Expand Down Expand Up @@ -157,7 +161,10 @@ impl HeuristicContext for RefinementContext {
self.inner_context.environment()
}

fn on_initial(&mut self, solution: Self::Solution, item_time: Timer) {
fn on_initial(&mut self, mut solution: Self::Solution, item_time: Timer) {
self.initial_footprint.add(&Shadow::from(&solution));
solution.solution.state.set_footprint(self.initial_footprint.clone());

self.inner_context.on_initial(solution, item_time)
}

Expand Down Expand Up @@ -205,7 +212,9 @@ impl InitialOperator for RecreateInitialOperator {
type Solution = InsertionContext;

fn create(&self, heuristic_ctx: &Self::Context) -> Self::Solution {
let insertion_ctx = InsertionContext::new(heuristic_ctx.problem.clone(), heuristic_ctx.environment.clone());
let mut insertion_ctx = InsertionContext::new(heuristic_ctx.problem.clone(), heuristic_ctx.environment.clone());
insertion_ctx.solution.state.set_footprint(heuristic_ctx.initial_footprint.clone());

self.recreate.run(heuristic_ctx, insertion_ctx)
}
}
Expand Down

0 comments on commit cb8eab3

Please sign in to comment.