problemreductions/models/misc/
square_tiling.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry, ProblemSizeFieldEntry};
9use crate::traits::Problem;
10use crate::types::Or;
11use serde::de::Error as _;
12use serde::{Deserialize, Deserializer, Serialize};
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "SquareTiling",
17 display_name: "Square Tiling",
18 aliases: &["WangTiling"],
19 dimensions: &[],
20 module_path: module_path!(),
21 description: "Place colored square tiles on an N x N grid with matching edge colors",
22 fields: &[
23 FieldInfo { name: "num_colors", type_name: "usize", description: "Number of colors" },
24 FieldInfo { name: "tiles", type_name: "Vec<(usize, usize, usize, usize)>", description: "Collection of tile types (top, right, bottom, left)" },
25 FieldInfo { name: "grid_size", type_name: "usize", description: "Grid dimension N for N x N tiling" },
26 ],
27 }
28}
29
30inventory::submit! {
31 ProblemSizeFieldEntry {
32 name: "SquareTiling",
33 fields: &["num_colors", "num_tiles", "grid_size"],
34 }
35}
36
37pub type Tile = (usize, usize, usize, usize);
40
41#[derive(Debug, Clone, Serialize)]
42pub struct SquareTiling {
43 num_colors: usize,
44 tiles: Vec<Tile>,
45 grid_size: usize,
46}
47
48impl SquareTiling {
49 fn validate_inputs(num_colors: usize, tiles: &[Tile], grid_size: usize) -> Result<(), String> {
50 if num_colors == 0 {
51 return Err("SquareTiling requires at least one color".to_string());
52 }
53 if tiles.is_empty() {
54 return Err("SquareTiling requires at least one tile".to_string());
55 }
56 if grid_size == 0 {
57 return Err("SquareTiling requires grid_size >= 1".to_string());
58 }
59 for (i, &(top, right, bottom, left)) in tiles.iter().enumerate() {
60 if top >= num_colors
61 || right >= num_colors
62 || bottom >= num_colors
63 || left >= num_colors
64 {
65 return Err(format!(
66 "Tile {} has color(s) out of range 0..{}",
67 i, num_colors
68 ));
69 }
70 }
71 Ok(())
72 }
73
74 pub fn try_new(num_colors: usize, tiles: Vec<Tile>, grid_size: usize) -> Result<Self, String> {
76 Self::validate_inputs(num_colors, &tiles, grid_size)?;
77 Ok(Self {
78 num_colors,
79 tiles,
80 grid_size,
81 })
82 }
83
84 pub fn new(num_colors: usize, tiles: Vec<Tile>, grid_size: usize) -> Self {
91 Self::try_new(num_colors, tiles, grid_size).unwrap_or_else(|message| panic!("{message}"))
92 }
93
94 pub fn num_colors(&self) -> usize {
96 self.num_colors
97 }
98
99 pub fn num_tiles(&self) -> usize {
101 self.tiles.len()
102 }
103
104 pub fn grid_size(&self) -> usize {
106 self.grid_size
107 }
108
109 pub fn tiles(&self) -> &[Tile] {
111 &self.tiles
112 }
113
114 fn is_valid_tiling(&self, config: &[usize]) -> bool {
120 let n = self.grid_size;
121 if config.len() != n * n {
122 return false;
123 }
124
125 for &tile_idx in config {
127 if tile_idx >= self.tiles.len() {
128 return false;
129 }
130 }
131
132 for i in 0..n {
134 for j in 0..n - 1 {
135 let left_tile = self.tiles[config[i * n + j]];
136 let right_tile = self.tiles[config[i * n + j + 1]];
137 if left_tile.1 != right_tile.3 {
138 return false;
139 }
140 }
141 }
142
143 for i in 0..n - 1 {
145 for j in 0..n {
146 let upper_tile = self.tiles[config[i * n + j]];
147 let lower_tile = self.tiles[config[(i + 1) * n + j]];
148 if upper_tile.2 != lower_tile.0 {
149 return false;
150 }
151 }
152 }
153
154 true
155 }
156}
157
158#[derive(Deserialize)]
159struct SquareTilingData {
160 num_colors: usize,
161 tiles: Vec<Tile>,
162 grid_size: usize,
163}
164
165impl<'de> Deserialize<'de> for SquareTiling {
166 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
167 where
168 D: Deserializer<'de>,
169 {
170 let data = SquareTilingData::deserialize(deserializer)?;
171 Self::try_new(data.num_colors, data.tiles, data.grid_size).map_err(D::Error::custom)
172 }
173}
174
175impl Problem for SquareTiling {
176 const NAME: &'static str = "SquareTiling";
177 type Value = Or;
178
179 fn variant() -> Vec<(&'static str, &'static str)> {
180 crate::variant_params![]
181 }
182
183 fn dims(&self) -> Vec<usize> {
184 vec![self.tiles.len(); self.grid_size * self.grid_size]
185 }
186
187 fn evaluate(&self, config: &[usize]) -> Or {
188 Or(self.is_valid_tiling(config))
189 }
190}
191
192crate::declare_variants! {
193 default SquareTiling => "num_tiles^(grid_size^2)",
194}
195
196#[cfg(feature = "example-db")]
197pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
198 vec![crate::example_db::specs::ModelExampleSpec {
199 id: "square_tiling",
200 instance: Box::new(SquareTiling::new(
201 3,
202 vec![(0, 1, 2, 0), (0, 0, 2, 1), (2, 1, 0, 0), (2, 0, 0, 1)],
203 2,
204 )),
205 optimal_config: vec![0, 1, 2, 3],
206 optimal_value: serde_json::json!(true),
207 }]
208}
209
210#[cfg(test)]
211#[path = "../../unit_tests/models/misc/square_tiling.rs"]
212mod tests;