problemreductions/models/misc/
conjunctive_boolean_query.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
14use crate::traits::Problem;
15use serde::{Deserialize, Serialize};
16
17inventory::submit! {
18 ProblemSchemaEntry {
19 name: "ConjunctiveBooleanQuery",
20 display_name: "Conjunctive Boolean Query",
21 aliases: &["CBQ"],
22 dimensions: &[],
23 category: crate::registry::ProblemCategory::Misc,
24 module_path: module_path!(),
25 description: "Evaluate a conjunctive Boolean query over a relational database",
26 fields: ConjunctiveBooleanQueryCreateSpec::FIELDS,
27 }
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct Relation {
33 pub arity: usize,
35 pub tuples: Vec<Vec<usize>>,
37}
38
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41pub enum QueryArg {
42 Variable(usize),
44 Constant(usize),
46}
47
48#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
79pub struct ConjunctiveBooleanQuery {
80 domain_size: usize,
81 relations: Vec<Relation>,
82 num_variables: usize,
83 conjuncts: Vec<(usize, Vec<QueryArg>)>,
84}
85
86#[derive(Debug, Deserialize, crate::CreateSpec)]
87struct ConjunctiveBooleanQueryCreateSpec {
88 domain_size: usize,
90 #[create(codec = "json")]
92 relations: Vec<Relation>,
93 #[create(codec = "json")]
95 conjuncts: Vec<(usize, Vec<QueryArg>)>,
96}
97
98impl TryFrom<ConjunctiveBooleanQueryCreateSpec> for ConjunctiveBooleanQuery {
99 type Error = crate::registry::ConstructionError;
100
101 fn try_from(spec: ConjunctiveBooleanQueryCreateSpec) -> Result<Self, Self::Error> {
102 let mut num_variables = 0_usize;
103 for (_, args) in &spec.conjuncts {
104 for arg in args {
105 if let QueryArg::Variable(variable) = arg {
106 let count = variable
107 .checked_add(1)
108 .ok_or_else(|| "number of query variables overflows usize".to_string())?;
109 num_variables = num_variables.max(count);
110 }
111 }
112 }
113
114 for (relation_index, relation) in spec.relations.iter().enumerate() {
115 for (tuple_index, tuple) in relation.tuples.iter().enumerate() {
116 if tuple.len() != relation.arity {
117 return Err(format!(
118 "relation {relation_index} tuple {tuple_index} has length {}, expected arity {}",
119 tuple.len(),
120 relation.arity
121 ).into());
122 }
123 for (entry_index, &value) in tuple.iter().enumerate() {
124 if value >= spec.domain_size {
125 return Err(format!(
126 "relation {relation_index} tuple {tuple_index} entry {entry_index} is {value}, must be less than domain size {}",
127 spec.domain_size
128 ).into());
129 }
130 }
131 }
132 }
133
134 for (conjunct_index, (relation_index, args)) in spec.conjuncts.iter().enumerate() {
135 let relation = spec.relations.get(*relation_index).ok_or_else(|| {
136 format!(
137 "conjunct {conjunct_index} relation index {relation_index} is out of range for {} relations",
138 spec.relations.len()
139 )
140 })?;
141 if args.len() != relation.arity {
142 return Err(format!(
143 "conjunct {conjunct_index} has {} arguments, expected arity {}",
144 args.len(),
145 relation.arity
146 )
147 .into());
148 }
149 for (argument_index, arg) in args.iter().enumerate() {
150 if let QueryArg::Constant(value) = arg {
151 if *value >= spec.domain_size {
152 return Err(format!(
153 "conjunct {conjunct_index} argument {argument_index} constant {value} must be less than domain size {}",
154 spec.domain_size
155 ).into());
156 }
157 }
158 }
159 }
160
161 Ok(Self {
162 domain_size: spec.domain_size,
163 relations: spec.relations,
164 num_variables,
165 conjuncts: spec.conjuncts,
166 })
167 }
168}
169
170impl ConjunctiveBooleanQuery {
171 pub fn new(
183 domain_size: usize,
184 relations: Vec<Relation>,
185 num_variables: usize,
186 conjuncts: Vec<(usize, Vec<QueryArg>)>,
187 ) -> Self {
188 for (i, rel) in relations.iter().enumerate() {
189 for (j, tuple) in rel.tuples.iter().enumerate() {
190 assert!(
191 tuple.len() == rel.arity,
192 "Relation {i}: tuple {j} has length {}, expected arity {}",
193 tuple.len(),
194 rel.arity
195 );
196 for (k, &val) in tuple.iter().enumerate() {
197 assert!(
198 val < domain_size,
199 "Relation {i}: tuple {j}, entry {k} is {val}, must be < {domain_size}"
200 );
201 }
202 }
203 }
204 for (i, (rel_idx, args)) in conjuncts.iter().enumerate() {
205 assert!(
206 *rel_idx < relations.len(),
207 "Conjunct {i}: relation index {rel_idx} out of range (have {} relations)",
208 relations.len()
209 );
210 assert!(
211 args.len() == relations[*rel_idx].arity,
212 "Conjunct {i}: has {} args, expected arity {}",
213 args.len(),
214 relations[*rel_idx].arity
215 );
216 for (k, arg) in args.iter().enumerate() {
217 match arg {
218 QueryArg::Variable(v) => {
219 assert!(
220 *v < num_variables,
221 "Conjunct {i}, arg {k}: Variable({v}) >= num_variables ({num_variables})"
222 );
223 }
224 QueryArg::Constant(c) => {
225 assert!(
226 *c < domain_size,
227 "Conjunct {i}, arg {k}: Constant({c}) >= domain_size ({domain_size})"
228 );
229 }
230 }
231 }
232 }
233 Self {
234 domain_size,
235 relations,
236 num_variables,
237 conjuncts,
238 }
239 }
240
241 pub fn domain_size(&self) -> usize {
243 self.domain_size
244 }
245
246 pub fn num_relations(&self) -> usize {
248 self.relations.len()
249 }
250
251 pub fn num_variables(&self) -> usize {
253 self.num_variables
254 }
255
256 pub fn num_conjuncts(&self) -> usize {
258 self.conjuncts.len()
259 }
260
261 pub fn relations(&self) -> &[Relation] {
263 &self.relations
264 }
265
266 pub fn conjuncts(&self) -> &[(usize, Vec<QueryArg>)] {
268 &self.conjuncts
269 }
270}
271
272impl Problem for ConjunctiveBooleanQuery {
273 const NAME: &'static str = "ConjunctiveBooleanQuery";
274 type Solution = Vec<usize>;
275 type Value = crate::types::Or;
276
277 crate::problem_parameters![
278 ("domain_size", domain_size),
279 ("num_conjuncts", num_conjuncts),
280 ("num_relations", num_relations),
281 ("num_variables", num_variables),
282 ];
283
284 fn variant() -> Vec<(&'static str, &'static str)> {
285 crate::variant_params![]
286 }
287
288 fn evaluate(
289 &self,
290 config: &Self::Solution,
291 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
292 Ok({
293 crate::types::Or({
294 if config.len() != self.num_variables {
295 return Err(crate::traits::EvaluationError::InvalidConfiguration(
296 "variable assignment length does not match the query".into(),
297 ));
298 }
299 if config.iter().any(|&v| v >= self.domain_size) {
300 return Err(crate::traits::EvaluationError::InvalidConfiguration(
301 "variable assignment contains an out-of-range domain value".into(),
302 ));
303 }
304 self.conjuncts.iter().all(|(rel_idx, args)| {
305 let tuple: Vec<usize> = args
306 .iter()
307 .map(|arg| match arg {
308 QueryArg::Variable(i) => config[*i],
309 QueryArg::Constant(c) => *c,
310 })
311 .collect();
312 self.relations[*rel_idx].tuples.contains(&tuple)
313 })
314 })
315 })
316 }
317}
318
319impl crate::solvers::BruteForceProblem for ConjunctiveBooleanQuery {
320 fn dimensions(&self) -> Vec<usize> {
321 vec![self.domain_size; self.num_variables]
322 }
323}
324
325crate::declare_variants! {
326 default ConjunctiveBooleanQuery => "domain_size ^ num_variables" create ConjunctiveBooleanQueryCreateSpec,
327}
328
329crate::register_brute_force! {
330 ConjunctiveBooleanQuery,
331}
332
333#[cfg(feature = "example-db")]
334pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
335 vec![crate::example_db::specs::ModelExampleSpec {
336 id: "conjunctive_boolean_query",
337 instance: Box::new(ConjunctiveBooleanQuery::new(
340 6,
341 vec![
342 Relation {
343 arity: 2,
344 tuples: vec![vec![0, 3], vec![1, 3], vec![2, 4], vec![3, 4], vec![4, 5]],
345 },
346 Relation {
347 arity: 3,
348 tuples: vec![vec![0, 1, 5], vec![1, 2, 5], vec![2, 3, 4], vec![0, 4, 3]],
349 },
350 ],
351 2,
352 vec![
353 (0, vec![QueryArg::Variable(0), QueryArg::Constant(3)]),
354 (0, vec![QueryArg::Variable(1), QueryArg::Constant(3)]),
355 (
356 1,
357 vec![
358 QueryArg::Variable(0),
359 QueryArg::Variable(1),
360 QueryArg::Constant(5),
361 ],
362 ),
363 ],
364 )),
365 optimal_config: serde_json::json!(vec![0, 1]),
366 optimal_value: serde_json::json!(true),
367 }]
368}
369
370#[cfg(test)]
371#[path = "../../unit_tests/models/misc/conjunctive_boolean_query.rs"]
372mod tests;