domino_lib/validate/
mod.rs

1use model::compute_model;
2
3use crate::utils::{DominoError, Model, Puzzle, Solution};
4
5mod model;
6
7/// Validates a given puzzle solution by computing a model and checking the objective value.
8///
9/// This function takes a reference to a `Puzzle` and a `Solution`, then performs the following steps:
10/// - Computes a string-based model representation using `compute_model()`.
11/// - Executes the computed model using `Model::execute()`.
12/// - Extracts the objective value from the solver result.
13/// - Compares the objective value against the expected missing tile count.
14/// - Returns `Ok(())` if the objective value matches the missing tile count, otherwise returns a `DominoError`.
15///
16/// # Arguments
17///
18/// * `puzzle` - A reference to the `Puzzle` structure representing the puzzle to be validated.
19/// * `solution` - A reference to the `Solution` structure representing the proposed solution.
20///
21/// # Returns
22///
23/// * `Ok(())` - If the computed objective value matches the expected number of missing tiles.
24/// * `Err(DominoError::ModelError)` - If the model execution fails or the objective value is incorrect.
25///
26/// # Errors
27///
28/// This function returns a `DominoError::ModelError` in the following cases:
29/// - If `compute_model()` returns an error.
30/// - If `Model::execute()` fails to execute the computed model.
31/// - If the extracted objective value does not match the expected missing tile count.
32pub fn validate_puzzle(puzzle: &Puzzle, solution: &Solution) -> Result<(), DominoError> {
33  // Compute a string-based model representation for the puzzle and solution.
34  let string_model = compute_model(puzzle, solution)?;
35
36  // Execute the model to obtain a solver result.
37  let solver_result = Model::execute(string_model.clone());
38
39  // Extract the objective value from the solver result.
40  // May also see the values of the variables through translator._get_variables() method
41  let objective_value = solver_result.map(|translator| translator.get_objective());
42
43  // Count the number of missing tiles in the puzzle.
44  let missing_tiles = puzzle.iter().filter(|tile| tile.is_none()).count() as f64;
45
46  // Validate the objective value against the expected missing tiles count.
47  if let Ok(objective) = objective_value {
48      if objective == missing_tiles {
49          Ok(())
50      } else {
51          Err(DominoError::ModelError(
52              "Invalid objective value".to_string(),
53          ))
54      }
55  } else {
56      Err(DominoError::ModelError(
57          "Model failed execution".to_string(),
58      ))
59  }
60}
61
62
63#[cfg(test)]
64mod tests {
65
66    use super::validate_puzzle;
67
68    #[test]
69    fn test_validate() {
70        // Invalid puzzle: Empty puzzle is not valid
71        let mut puzzle = vec![];
72        let mut solution = vec![];
73        assert!(validate_puzzle(&puzzle, &solution).is_err());
74
75        // Invalid puzzle: Double tiles do not imply any orientation of the eulerian cycle
76        puzzle = vec![Some((0, 0).into()), None, None, None, None, None, None];
77        solution = vec![
78            (0, 0).into(),
79            (0, 1).into(),
80            (1, 1).into(),
81            (1, 2).into(),
82            (2, 2).into(),
83            (2, 3).into(),
84            (3, 3).into(),
85            (3, 0).into(),
86        ];
87        assert!(validate_puzzle(&puzzle, &solution).is_err());
88
89        // Valid puzzle: One single tile that determines the orientation of the eulerian cycle
90        puzzle = vec![Some((0, 1).into()), None, None, None, None, None, None];
91        solution = vec![
92            (0, 1).into(),
93            (1, 1).into(),
94            (1, 2).into(),
95            (2, 2).into(),
96            (2, 3).into(),
97            (3, 3).into(),
98            (3, 0).into(),
99            (0, 0).into(),
100        ];
101        assert!(validate_puzzle(&puzzle, &solution).is_err());
102
103        // Valid puzzle with a single hole
104        puzzle = vec![
105            Some((0, 0).into()),
106            Some((0, 1).into()),
107            Some((1, 1).into()),
108            Some((1, 2).into()),
109            Some((2, 2).into()),
110            None,
111            None,
112            None,
113        ];
114        solution = vec![
115            (0, 0).into(),
116            (0, 1).into(),
117            (1, 1).into(),
118            (1, 2).into(),
119            (2, 2).into(),
120            (2, 3).into(),
121            (3, 3).into(),
122            (3, 0).into(),
123        ];
124        assert!(validate_puzzle(&puzzle, &solution).is_ok());
125
126        // Valid puzzle with multiple holes
127        puzzle = vec![
128            None,
129            Some((0, 1).into()),
130            None,
131            Some((1, 2).into()),
132            Some((2, 2).into()),
133            None,
134            None,
135            None,
136        ];
137        solution = vec![
138            (0, 0).into(),
139            (0, 1).into(),
140            (1, 1).into(),
141            (1, 2).into(),
142            (2, 2).into(),
143            (2, 3).into(),
144            (3, 3).into(),
145            (3, 0).into(),
146        ];
147        assert!(validate_puzzle(&puzzle, &solution).is_ok());
148
149        // Invalid puzzle for emptyness
150        puzzle = vec![None; 8];
151        solution = vec![
152            (0, 0).into(),
153            (0, 1).into(),
154            (1, 1).into(),
155            (1, 2).into(),
156            (2, 2).into(),
157            (2, 3).into(),
158            (3, 3).into(),
159            (3, 0).into(),
160        ];
161        assert!(validate_puzzle(&puzzle, &solution).is_err());
162
163        // Invalid puzzle for invalid size
164        puzzle = vec![None; 9];
165        solution = vec![
166            (0, 0).into(),
167            (0, 1).into(),
168            (1, 1).into(),
169            (1, 2).into(),
170            (2, 2).into(),
171            (2, 3).into(),
172            (3, 3).into(),
173            (3, 0).into(),
174            (0, 0).into(),
175        ];
176        assert!(validate_puzzle(&puzzle, &solution).is_err());
177    }
178}