1use crate::registry::{FieldInfo, ProblemSchemaEntry};
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10use std::collections::HashSet;
11
12inventory::submit! {
13 ProblemSchemaEntry {
14 name: "ConjunctiveQueryFoldability",
15 display_name: "Conjunctive Query Foldability",
16 aliases: &[],
17 dimensions: &[],
18 category: crate::registry::ProblemCategory::Misc,
19 module_path: module_path!(),
20 description: "Determine if one conjunctive query can be folded into another by substituting undistinguished variables",
21 fields: &[
22 FieldInfo { name: "domain_size", type_name: "usize", description: "Size of the finite domain D" },
23 FieldInfo { name: "num_distinguished", type_name: "usize", description: "Number of distinguished variables X" },
24 FieldInfo { name: "num_undistinguished", type_name: "usize", description: "Number of undistinguished variables Y" },
25 FieldInfo { name: "relation_arities", type_name: "Vec<usize>", description: "Arity of each relation" },
26 FieldInfo { name: "query1_conjuncts", type_name: "Vec<(usize, Vec<Term>)>", description: "Atoms of query Q1" },
27 FieldInfo { name: "query2_conjuncts", type_name: "Vec<(usize, Vec<Term>)>", description: "Atoms of query Q2" },
28 ],
29 }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
34#[serde(tag = "type", content = "index")]
35pub enum Term {
36 Constant(usize),
38 Distinguished(usize),
40 Undistinguished(usize),
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
89#[serde(try_from = "ConjunctiveQueryFoldabilityData")]
90pub struct ConjunctiveQueryFoldability {
91 domain_size: usize,
93 num_distinguished: usize,
95 num_undistinguished: usize,
97 relation_arities: Vec<usize>,
99 query1_conjuncts: Vec<(usize, Vec<Term>)>,
101 query2_conjuncts: Vec<(usize, Vec<Term>)>,
103}
104
105#[derive(Deserialize)]
106struct ConjunctiveQueryFoldabilityData {
107 domain_size: usize,
108 num_distinguished: usize,
109 num_undistinguished: usize,
110 relation_arities: Vec<usize>,
111 query1_conjuncts: Vec<(usize, Vec<Term>)>,
112 query2_conjuncts: Vec<(usize, Vec<Term>)>,
113}
114
115impl TryFrom<ConjunctiveQueryFoldabilityData> for ConjunctiveQueryFoldability {
116 type Error = crate::registry::ConstructionError;
117 fn try_from(data: ConjunctiveQueryFoldabilityData) -> Result<Self, Self::Error> {
118 Self::try_new(
119 data.domain_size,
120 data.num_distinguished,
121 data.num_undistinguished,
122 data.relation_arities,
123 data.query1_conjuncts,
124 data.query2_conjuncts,
125 )
126 }
127}
128
129impl ConjunctiveQueryFoldability {
130 pub fn new(
150 domain_size: usize,
151 num_distinguished: usize,
152 num_undistinguished: usize,
153 relation_arities: Vec<usize>,
154 query1_conjuncts: Vec<(usize, Vec<Term>)>,
155 query2_conjuncts: Vec<(usize, Vec<Term>)>,
156 ) -> Self {
157 Self::try_new(
158 domain_size,
159 num_distinguished,
160 num_undistinguished,
161 relation_arities,
162 query1_conjuncts,
163 query2_conjuncts,
164 )
165 .unwrap_or_else(|error| panic!("{error}"))
166 }
167
168 fn try_new(
169 domain_size: usize,
170 num_distinguished: usize,
171 num_undistinguished: usize,
172 relation_arities: Vec<usize>,
173 query1_conjuncts: Vec<(usize, Vec<Term>)>,
174 query2_conjuncts: Vec<(usize, Vec<Term>)>,
175 ) -> Result<Self, crate::registry::ConstructionError> {
176 let instance = Self {
177 domain_size,
178 num_distinguished,
179 num_undistinguished,
180 relation_arities,
181 query1_conjuncts,
182 query2_conjuncts,
183 };
184 instance.validate()?;
185 Ok(instance)
186 }
187
188 fn validate(&self) -> Result<(), crate::registry::ConstructionError> {
190 for (query_name, conjuncts) in [
191 ("Q1", &self.query1_conjuncts),
192 ("Q2", &self.query2_conjuncts),
193 ] {
194 for (atom_idx, (rel_idx, args)) in conjuncts.iter().enumerate() {
195 if *rel_idx >= self.relation_arities.len() {
196 return Err(format!(
197 "Atom {atom_idx} of {query_name}: relation index {rel_idx} out of range \
198 (num_relations = {})",
199 self.relation_arities.len()
200 )
201 .into());
202 };
203 let arity = self.relation_arities[*rel_idx];
204 if args.len() != arity {
205 return Err(format!(
206 "Atom {atom_idx} of {query_name}: relation {rel_idx} has arity {arity} \
207 but got {} arguments",
208 args.len()
209 )
210 .into());
211 };
212 for term in args {
213 match term {
214 Term::Constant(i) => {
215 if *i >= self.domain_size {
216 return Err(format!(
217 "Atom {atom_idx} of {query_name}: Constant({i}) out of range \
218 (domain_size = {})",
219 self.domain_size
220 )
221 .into());
222 }
223 }
224 Term::Distinguished(i) => {
225 if *i >= self.num_distinguished {
226 return Err(format!("Atom {atom_idx} of {query_name}: Distinguished({i}) out of range \
227 (num_distinguished = {})", self.num_distinguished).into());
228 }
229 }
230 Term::Undistinguished(i) => {
231 if *i >= self.num_undistinguished {
232 return Err(format!("Atom {atom_idx} of {query_name}: Undistinguished({i}) out of range \
233 (num_undistinguished = {})", self.num_undistinguished).into());
234 }
235 }
236 }
237 }
238 }
239 }
240 Ok(())
241 }
242
243 pub fn domain_size(&self) -> usize {
245 self.domain_size
246 }
247
248 pub fn num_distinguished(&self) -> usize {
250 self.num_distinguished
251 }
252
253 pub fn num_undistinguished(&self) -> usize {
255 self.num_undistinguished
256 }
257
258 pub fn num_conjuncts_q1(&self) -> usize {
260 self.query1_conjuncts.len()
261 }
262
263 pub fn num_conjuncts_q2(&self) -> usize {
265 self.query2_conjuncts.len()
266 }
267
268 pub fn num_relations(&self) -> usize {
270 self.relation_arities.len()
271 }
272
273 pub fn relation_arities(&self) -> &[usize] {
275 &self.relation_arities
276 }
277
278 pub fn query1_conjuncts(&self) -> &[(usize, Vec<Term>)] {
280 &self.query1_conjuncts
281 }
282
283 pub fn query2_conjuncts(&self) -> &[(usize, Vec<Term>)] {
285 &self.query2_conjuncts
286 }
287
288 fn decode_substitution(&self, v: usize) -> Term {
295 if v < self.domain_size {
296 Term::Constant(v)
297 } else if v < self.domain_size + self.num_distinguished {
298 Term::Distinguished(v - self.domain_size)
299 } else {
300 Term::Undistinguished(v - self.domain_size - self.num_distinguished)
301 }
302 }
303
304 fn apply_substitution(&self, term: &Term, config: &[usize]) -> Term {
309 match term {
310 Term::Undistinguished(i) => self.decode_substitution(config[*i]),
311 other => other.clone(),
312 }
313 }
314}
315
316impl Problem for ConjunctiveQueryFoldability {
317 const NAME: &'static str = "ConjunctiveQueryFoldability";
318 type Solution = Vec<usize>;
319 type Value = crate::types::Or;
320
321 crate::problem_parameters![
322 ("domain_size", domain_size),
323 ("num_distinguished", num_distinguished),
324 ("num_undistinguished", num_undistinguished),
325 ("num_conjuncts_q1", num_conjuncts_q1),
326 ("num_conjuncts_q2", num_conjuncts_q2),
327 ("num_relations", num_relations),
328 ];
329
330 fn variant() -> Vec<(&'static str, &'static str)> {
331 crate::variant_params![]
332 }
333
334 fn evaluate(
339 &self,
340 config: &Self::Solution,
341 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
342 Ok({
343 crate::types::Or({
344 if config.len() != self.num_undistinguished {
345 return Err(crate::traits::EvaluationError::InvalidConfiguration(
346 "folding-map length does not match the query variables".into(),
347 ));
348 }
349 let range = self.domain_size + self.num_distinguished + self.num_undistinguished;
350 if config.iter().any(|&value| value >= range) {
351 return Err(crate::traits::EvaluationError::InvalidConfiguration(
352 "folding map contains an out-of-range value".into(),
353 ));
354 }
355 let substituted: HashSet<(usize, Vec<Term>)> = self
357 .query1_conjuncts
358 .iter()
359 .map(|(rel_idx, args)| {
360 let new_args = args
361 .iter()
362 .map(|term| self.apply_substitution(term, config))
363 .collect();
364 (*rel_idx, new_args)
365 })
366 .collect();
367
368 let q2_set: HashSet<(usize, Vec<Term>)> =
370 self.query2_conjuncts.iter().cloned().collect();
371
372 substituted == q2_set
373 })
374 })
375 }
376}
377
378impl crate::solvers::BruteForceProblem for ConjunctiveQueryFoldability {
379 fn dimensions(&self) -> Vec<usize> {
381 let range = self.domain_size + self.num_distinguished + self.num_undistinguished;
382 vec![range; self.num_undistinguished]
383 }
384}
385
386crate::declare_variants! {
387 default ConjunctiveQueryFoldability => "(num_distinguished + num_undistinguished + domain_size)^num_undistinguished * num_conjuncts_q1",
388}
389
390crate::register_brute_force! {
391 ConjunctiveQueryFoldability,
392}
393
394#[cfg(feature = "example-db")]
395pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
396 vec![crate::example_db::specs::ModelExampleSpec {
404 id: "conjunctive_query_foldability",
405 instance: Box::new(ConjunctiveQueryFoldability::new(
406 0, 1, 3, vec![2], vec![
411 (0, vec![Term::Distinguished(0), Term::Undistinguished(0)]), (0, vec![Term::Undistinguished(0), Term::Undistinguished(1)]), (0, vec![Term::Undistinguished(1), Term::Distinguished(0)]), (0, vec![Term::Undistinguished(0), Term::Undistinguished(0)]), ],
416 vec![
417 (0, vec![Term::Distinguished(0), Term::Undistinguished(2)]), (0, vec![Term::Undistinguished(2), Term::Undistinguished(2)]), (0, vec![Term::Undistinguished(2), Term::Distinguished(0)]), ],
421 )),
422 optimal_config: serde_json::json!(vec![3, 3, 3]),
423 optimal_value: serde_json::json!(true),
424 }]
425}
426
427#[cfg(test)]
428#[path = "../../unit_tests/models/misc/conjunctive_query_foldability.rs"]
429mod tests;