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}