1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#[cfg(test)]
#[path = "../../tests/unit/validation/objectives_test.rs"]
mod objectives_test;

use super::*;
use crate::format::problem::Objective::*;

/// Checks that objective is not empty when specified.
fn check_e1600_empty_objective(objectives: &[&Objective]) -> Result<(), FormatError> {
    if objectives.is_empty() {
        Err(FormatError::new(
            "E1600".to_string(),
            "an empty objective specified".to_string(),
            "remove objectives property completely to use default".to_string(),
        ))
    } else {
        Ok(())
    }
}

/// Checks that each objective type specified only once.
fn check_e1601_duplicate_objectives(objectives: &[&Objective]) -> Result<(), FormatError> {
    let mut duplicates = objectives
        .iter()
        .fold(HashMap::new(), |mut acc, objective| {
            match objective {
                MinimizeCost => acc.entry("minimize-cost"),
                MinimizeTours => acc.entry("minimize-tours"),
                MaximizeTours => acc.entry("maximize-tours"),
                MinimizeUnassignedJobs { .. } => acc.entry("minimize-unassigned"),
                BalanceMaxLoad { .. } => acc.entry("balance-max-load"),
                BalanceActivities { .. } => acc.entry("balance-activities"),
                BalanceDistance { .. } => acc.entry("balance-distance"),
                BalanceDuration { .. } => acc.entry("balance-duration"),
            }
            .and_modify(|count| *count += 1)
            .or_insert(1_usize);

            acc
        })
        .iter()
        .filter_map(|(name, count)| if *count > 1 { Some((*name).to_string()) } else { None })
        .collect::<Vec<_>>();

    duplicates.sort();

    if duplicates.is_empty() {
        Ok(())
    } else {
        Err(FormatError::new(
            "E1601".to_string(),
            "duplicate objective specified".to_string(),
            "remove duplicate objectives".to_string(),
        ))
    }
}

/// Checks that cost objective is specified.
fn check_e1602_no_cost_value_objective(objectives: &[&Objective]) -> Result<(), FormatError> {
    let min_costs = objectives.iter().filter(|objective| matches!(objective, MinimizeCost)).count();

    if min_costs == 0 {
        Err(FormatError::new(
            "E1602".to_string(),
            "missing cost objective".to_string(),
            "specify 'minimize-cost' objective".to_string(),
        ))
    } else {
        Ok(())
    }
}

fn get_objectives<'a>(ctx: &'a ValidationContext) -> Option<Vec<&'a Objective>> {
    ctx.problem.objectives.as_ref().map(|objectives| {
        Some(&objectives.primary)
            .iter()
            .chain(objectives.secondary.as_ref().iter())
            .flat_map(|objectives| objectives.iter())
            .collect()
    })
}

pub fn validate_objectives(ctx: &ValidationContext) -> Result<(), Vec<FormatError>> {
    if let Some(objectives) = get_objectives(ctx) {
        combine_error_results(&[
            check_e1600_empty_objective(&objectives),
            check_e1601_duplicate_objectives(&objectives),
            check_e1602_no_cost_value_objective(&objectives),
        ])
    } else {
        Ok(())
    }
}