#![allow(missing_docs)]
#[cfg(test)]
#[path = "../../../tests/unit/extensions/solve/config_test.rs"]
mod config_test;
extern crate serde_json;
use serde::Deserialize;
use std::io::{BufReader, Read};
use std::sync::Arc;
use vrp_core::models::common::SingleDimLoad;
use vrp_core::models::Problem;
use vrp_core::solver::mutation::*;
use vrp_core::solver::population::*;
use vrp_core::solver::{Builder, Telemetry, TelemetryMode};
use vrp_core::utils::{get_cpus, DefaultRandom};
#[derive(Clone, Deserialize, Debug)]
pub struct Config {
pub evolution: Option<EvolutionConfig>,
pub mutation: Option<MutationType>,
pub termination: Option<TerminationConfig>,
pub telemetry: Option<TelemetryConfig>,
}
#[derive(Clone, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct EvolutionConfig {
initial: Option<InitialConfig>,
population: Option<PopulationType>,
}
#[derive(Clone, Deserialize, Debug)]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
pub enum PopulationType {
#[serde(rename(deserialize = "elitism"))]
#[serde(rename_all = "camelCase")]
Elitism {
max_size: Option<usize>,
selection_size: Option<usize>,
},
#[serde(rename(deserialize = "rosomaxa"))]
#[serde(rename_all = "camelCase")]
Rosomaxa {
selection_size: Option<usize>,
max_elite_size: Option<usize>,
max_node_size: Option<usize>,
spread_factor: Option<f64>,
reduction_factor: Option<f64>,
distribution_factor: Option<f64>,
learning_rate: Option<f64>,
rebalance_memory: Option<usize>,
rebalance_count: Option<usize>,
exploration_ratio: Option<f64>,
},
}
#[derive(Clone, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct InitialConfig {
pub size: Option<usize>,
pub methods: Option<Vec<RecreateMethod>>,
}
#[derive(Clone, Deserialize, Debug)]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
pub enum SelectionType {
#[serde(rename(deserialize = "naive"))]
Naive {
offspring_size: Option<usize>,
},
}
#[derive(Clone, Deserialize, Debug)]
#[serde(tag = "type")]
pub enum MutationType {
#[serde(rename(deserialize = "composite"))]
Composite {
probability: f64,
inners: Vec<MutationType>,
},
#[serde(rename(deserialize = "local-search"))]
LocalSearch {
probability: f64,
times: MinMaxConfig,
operators: Vec<LocalOperatorType>,
},
#[serde(rename(deserialize = "ruin-recreate"))]
RuinRecreate {
probability: f64,
ruins: Vec<RuinGroupConfig>,
recreates: Vec<RecreateMethod>,
},
}
#[derive(Clone, Deserialize, Debug)]
pub struct RuinGroupConfig {
methods: Vec<RuinMethod>,
weight: usize,
}
#[derive(Clone, Deserialize, Debug)]
#[serde(tag = "type")]
pub enum RuinMethod {
#[serde(rename(deserialize = "adjusted-string"))]
AdjustedString { probability: f64, lmax: usize, cavg: usize, alpha: f64 },
#[serde(rename(deserialize = "neighbour"))]
Neighbour { probability: f64, min: usize, max: usize, threshold: f64 },
#[serde(rename(deserialize = "random-job"))]
RandomJob { probability: f64, min: usize, max: usize, threshold: f64 },
#[serde(rename(deserialize = "random-route"))]
RandomRoute { probability: f64, min: usize, max: usize, threshold: f64 },
#[serde(rename(deserialize = "worst-job"))]
WorstJob { probability: f64, min: usize, max: usize, threshold: f64, skip: usize },
#[serde(rename(deserialize = "cluster"))]
Cluster { probability: f64, min: usize, max: usize, threshold: f64, cmin: usize, cmax: usize },
}
#[derive(Clone, Deserialize, Debug)]
#[serde(tag = "type")]
pub enum RecreateMethod {
#[serde(rename(deserialize = "cheapest"))]
Cheapest { weight: usize },
#[serde(rename(deserialize = "skip-best"))]
SkipBest { weight: usize, start: usize, end: usize },
#[serde(rename(deserialize = "blinks"))]
Blinks { weight: usize },
#[serde(rename(deserialize = "gaps"))]
Gaps { weight: usize, min: usize },
#[serde(rename(deserialize = "nearest"))]
Nearest { weight: usize },
#[serde(rename(deserialize = "farthest"))]
Farthest { weight: usize },
#[serde(rename(deserialize = "perturbation"))]
Perturbation { weight: usize, probability: f64, min: f64, max: f64 },
#[serde(rename(deserialize = "regret"))]
Regret { weight: usize, start: usize, end: usize },
}
#[derive(Clone, Deserialize, Debug)]
#[serde(tag = "type")]
pub enum LocalOperatorType {
#[serde(rename(deserialize = "inter-route-best"))]
InterRouteBest { weight: usize, noise: NoiseConfig },
#[serde(rename(deserialize = "inter-route-random"))]
InterRouteRandom { weight: usize, noise: NoiseConfig },
#[serde(rename(deserialize = "intra-route-random"))]
IntraRouteRandom { weight: usize, noise: NoiseConfig },
#[serde(rename(deserialize = "push-route-departure"))]
PushRouteDeparture { weight: usize, offset: f64 },
}
#[derive(Clone, Deserialize, Debug)]
pub struct NoiseConfig {
probability: f64,
min: f64,
max: f64,
}
#[derive(Clone, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct TerminationConfig {
pub max_time: Option<usize>,
pub max_generations: Option<usize>,
pub variation: Option<VariationConfig>,
}
#[derive(Clone, Deserialize, Debug)]
pub struct VariationConfig {
sample: usize,
cv: f64,
}
#[derive(Clone, Deserialize, Debug)]
pub struct TelemetryConfig {
logging: Option<LoggingConfig>,
metrics: Option<MetricsConfig>,
}
#[derive(Clone, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct LoggingConfig {
enabled: bool,
log_best: Option<usize>,
log_population: Option<usize>,
dump_population: Option<bool>,
}
#[derive(Clone, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct MetricsConfig {
enabled: bool,
track_population: Option<usize>,
}
#[derive(Clone, Deserialize, Debug, Eq, PartialEq)]
pub struct MinMaxConfig {
pub min: usize,
pub max: usize,
}
#[derive(Clone, Deserialize, Debug, Eq, PartialEq)]
pub struct NameWeight {
pub name: String,
pub weight: usize,
}
impl Default for Config {
fn default() -> Self {
Self { evolution: None, mutation: None, termination: None, telemetry: None }
}
}
fn configure_from_evolution(
mut builder: Builder,
population_config: &Option<EvolutionConfig>,
problem: Arc<Problem>,
) -> Result<Builder, String> {
if let Some(config) = population_config {
if let Some(initial) = &config.initial {
builder = builder.with_init_params(
initial.size,
initial
.methods
.as_ref()
.map(|methods| methods.iter().map(|method| create_recreate_method(method)).collect()),
);
}
if let Some(variation) = &config.population {
let random = Arc::new(DefaultRandom::default());
let population = match &variation {
PopulationType::Elitism { max_size, selection_size } => Box::new(Elitism::new(
problem,
random,
max_size.unwrap_or(4),
selection_size.unwrap_or_else(get_cpus),
)),
PopulationType::Rosomaxa {
max_elite_size,
max_node_size,
spread_factor,
reduction_factor,
distribution_factor,
learning_rate,
selection_size,
rebalance_memory,
rebalance_count,
exploration_ratio,
} => {
let mut config = RosomaxaConfig::default();
if let Some(max_elite_size) = max_elite_size {
config.elite_size = *max_elite_size;
}
if let Some(max_node_size) = max_node_size {
config.node_size = *max_node_size;
}
if let Some(spread_factor) = spread_factor {
config.spread_factor = *spread_factor;
}
if let Some(reduction_factor) = reduction_factor {
config.reduction_factor = *reduction_factor;
}
if let Some(distribution_factor) = distribution_factor {
config.distribution_factor = *distribution_factor;
}
if let Some(learning_rate) = learning_rate {
config.learning_rate = *learning_rate;
}
if let Some(selection_size) = selection_size {
config.selection_size = *selection_size;
}
if let Some(rebalance_memory) = rebalance_memory {
config.rebalance_memory = *rebalance_memory;
}
if let Some(rebalance_count) = rebalance_count {
config.rebalance_count = *rebalance_count;
}
if let Some(exploration_ratio) = exploration_ratio {
config.exploration_ratio = *exploration_ratio;
}
Rosomaxa::new_with_fallback(problem, random, config)
}
};
builder = builder.with_population(population);
}
}
Ok(builder)
}
fn configure_from_mutation(mut builder: Builder, mutation_config: &Option<MutationType>) -> Result<Builder, String> {
if let Some(config) = mutation_config {
let mutation = create_mutation(&builder.config.problem, config)?.0;
builder = builder.with_mutation(mutation)
}
Ok(builder)
}
fn configure_from_termination(
mut builder: Builder,
termination_config: &Option<TerminationConfig>,
) -> Result<Builder, String> {
if let Some(config) = termination_config {
builder = builder.with_max_time(config.max_time);
builder = builder.with_max_generations(config.max_generations);
builder = builder.with_cost_variation(config.variation.as_ref().map(|v| (v.sample, v.cv)));
}
Ok(builder)
}
fn create_recreate_method(method: &RecreateMethod) -> (Box<dyn Recreate + Send + Sync>, usize) {
match method {
RecreateMethod::Cheapest { weight } => (Box::new(RecreateWithCheapest::default()), *weight),
RecreateMethod::Farthest { weight } => (Box::new(RecreateWithFarthest::default()), *weight),
RecreateMethod::SkipBest { weight, start, end } => (Box::new(RecreateWithSkipBest::new(*start, *end)), *weight),
RecreateMethod::Blinks { weight } => (Box::new(RecreateWithBlinks::<SingleDimLoad>::default()), *weight),
RecreateMethod::Gaps { weight, min } => (Box::new(RecreateWithGaps::new(*min)), *weight),
RecreateMethod::Nearest { weight } => (Box::new(RecreateWithNearestNeighbor::default()), *weight),
RecreateMethod::Regret { weight, start, end } => (Box::new(RecreateWithRegret::new(*start, *end)), *weight),
RecreateMethod::Perturbation { weight, probability, min, max } => {
(Box::new(RecreateWithPerturbation::new(*probability, *min, *max)), *weight)
}
}
}
fn create_mutation(
problem: &Arc<Problem>,
mutation: &MutationType,
) -> Result<(Arc<dyn Mutation + Send + Sync>, f64), String> {
Ok(match mutation {
MutationType::RuinRecreate { probability, ruins, recreates } => {
let ruin = Box::new(CompositeRuin::new(ruins.iter().map(|g| create_ruin_group(problem, g)).collect()));
let recreate =
Box::new(CompositeRecreate::new(recreates.iter().map(|r| create_recreate_method(r)).collect()));
(Arc::new(RuinAndRecreate::new(recreate, ruin)), *probability)
}
MutationType::LocalSearch { probability, times, operators: inners } => {
let operator = create_local_search(times, inners);
(Arc::new(LocalSearch::new(operator)), *probability)
}
MutationType::Composite { probability, inners } => {
let inners =
inners.iter().map(|mutation| create_mutation(problem, mutation)).collect::<Result<Vec<_>, _>>()?;
(Arc::new(CompositeMutation::new(vec![(inners, 1)])), *probability)
}
})
}
fn create_ruin_group(problem: &Arc<Problem>, group: &RuinGroupConfig) -> RuinGroup {
(group.methods.iter().map(|r| create_ruin_method(problem, r)).collect(), group.weight)
}
fn create_ruin_method(problem: &Arc<Problem>, method: &RuinMethod) -> (Arc<dyn Ruin + Send + Sync>, f64) {
match method {
RuinMethod::AdjustedString { probability, lmax, cavg, alpha } => {
(Arc::new(AdjustedStringRemoval::new(*lmax, *cavg, *alpha)), *probability)
}
RuinMethod::Neighbour { probability, min, max, threshold } => {
(Arc::new(NeighbourRemoval::new(JobRemovalLimit::new(*min, *max, *threshold))), *probability)
}
RuinMethod::RandomJob { probability, min, max, threshold } => {
(Arc::new(RandomJobRemoval::new(JobRemovalLimit::new(*min, *max, *threshold))), *probability)
}
RuinMethod::RandomRoute { probability, min, max, threshold } => {
(Arc::new(RandomRouteRemoval::new(*min, *max, *threshold)), *probability)
}
RuinMethod::WorstJob { probability, min, max, threshold, skip: worst_skip } => {
(Arc::new(WorstJobRemoval::new(*worst_skip, JobRemovalLimit::new(*min, *max, *threshold))), *probability)
}
RuinMethod::Cluster { probability, min, max, threshold, cmin, cmax } => (
Arc::new(ClusterRemoval::new(problem.clone(), *cmin..*cmax, JobRemovalLimit::new(*min, *max, *threshold))),
*probability,
),
}
}
fn create_local_search(times: &MinMaxConfig, inners: &[LocalOperatorType]) -> Box<dyn LocalOperator + Send + Sync> {
let operators = inners
.iter()
.map::<(Box<dyn LocalOperator + Send + Sync>, usize), _>(|op| match op {
LocalOperatorType::InterRouteBest { weight, noise } => {
(Box::new(ExchangeInterRouteBest::new(noise.probability, noise.min, noise.max)), *weight)
}
LocalOperatorType::InterRouteRandom { weight, noise } => {
(Box::new(ExchangeInterRouteRandom::new(noise.probability, noise.min, noise.max)), *weight)
}
LocalOperatorType::IntraRouteRandom { weight, noise } => {
(Box::new(ExchangeIntraRouteRandom::new(noise.probability, noise.min, noise.max)), *weight)
}
LocalOperatorType::PushRouteDeparture { weight, offset } => {
(Box::new(PushRouteDeparture::new(*offset)), *weight)
}
})
.collect::<Vec<_>>();
Box::new(CompositeLocalOperator::new(operators, times.min, times.max))
}
fn configure_from_telemetry(builder: Builder, telemetry_config: &Option<TelemetryConfig>) -> Result<Builder, String> {
const LOG_BEST: usize = 100;
const LOG_POPULATION: usize = 1000;
const TRACK_POPULATION: usize = 1000;
let create_logger = || Arc::new(|msg: &str| println!("{}", msg));
let create_metrics = |track_population: &Option<usize>| TelemetryMode::OnlyMetrics {
track_population: track_population.unwrap_or(TRACK_POPULATION),
};
let create_logging = |log_best: &Option<usize>, log_population: &Option<usize>, dump_population: &Option<bool>| {
TelemetryMode::OnlyLogging {
logger: create_logger(),
log_best: log_best.unwrap_or(LOG_BEST),
log_population: log_population.unwrap_or(LOG_POPULATION),
dump_population: dump_population.unwrap_or(false),
}
};
let telemetry_mode = match telemetry_config.as_ref().map(|t| (&t.logging, &t.metrics)) {
Some((None, Some(MetricsConfig { enabled, track_population }))) if *enabled => create_metrics(track_population),
Some((Some(LoggingConfig { enabled, log_best, log_population, dump_population }), None)) if *enabled => {
create_logging(log_best, log_population, dump_population)
}
Some((
Some(LoggingConfig { enabled: logging_enabled, log_best, log_population, dump_population }),
Some(MetricsConfig { enabled: metrics_enabled, track_population }),
)) => match (logging_enabled, metrics_enabled) {
(true, true) => TelemetryMode::All {
logger: create_logger(),
log_best: log_best.unwrap_or(LOG_BEST),
log_population: log_population.unwrap_or(LOG_POPULATION),
track_population: track_population.unwrap_or(TRACK_POPULATION),
dump_population: dump_population.unwrap_or(false),
},
(true, false) => create_logging(log_best, log_population, dump_population),
(false, true) => create_metrics(track_population),
_ => TelemetryMode::None,
},
_ => TelemetryMode::None,
};
Ok(builder.with_telemetry(Telemetry::new(telemetry_mode)))
}
pub fn read_config<R: Read>(reader: BufReader<R>) -> Result<Config, String> {
serde_json::from_reader(reader).map_err(|err| format!("cannot deserialize config: '{}'", err))
}
pub fn create_builder_from_config_file<R: Read>(
problem: Arc<Problem>,
reader: BufReader<R>,
) -> Result<Builder, String> {
read_config(reader).and_then(|config| create_builder_from_config(problem, &config))
}
pub fn create_builder_from_config(problem: Arc<Problem>, config: &Config) -> Result<Builder, String> {
let mut builder = Builder::new(problem.clone());
builder = configure_from_telemetry(builder, &config.telemetry)?;
builder = configure_from_evolution(builder, &config.evolution, problem)?;
builder = configure_from_mutation(builder, &config.mutation)?;
builder = configure_from_termination(builder, &config.termination)?;
Ok(builder)
}