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)]
79#[serde(try_from = "ConjunctiveBooleanQueryData")]
80pub struct ConjunctiveBooleanQuery {
81 domain_size: usize,
82 relations: Vec<Relation>,
83 num_variables: usize,
84 conjuncts: Vec<(usize, Vec<QueryArg>)>,
85}
86
87#[derive(Deserialize)]
88struct ConjunctiveBooleanQueryData {
89 domain_size: usize,
90 relations: Vec<Relation>,
91 num_variables: usize,
92 conjuncts: Vec<(usize, Vec<QueryArg>)>,
93}
94
95impl TryFrom<ConjunctiveBooleanQueryData> for ConjunctiveBooleanQuery {
96 type Error = crate::registry::ConstructionError;
97 fn try_from(data: ConjunctiveBooleanQueryData) -> Result<Self, Self::Error> {
98 Self::try_new(
99 data.domain_size,
100 data.relations,
101 data.num_variables,
102 data.conjuncts,
103 )
104 }
105}
106
107#[derive(Debug, Deserialize, crate::CreateSpec)]
108struct ConjunctiveBooleanQueryCreateSpec {
109 domain_size: usize,
111 #[create(codec = "json")]
113 relations: Vec<Relation>,
114 #[create(codec = "json")]
116 conjuncts: Vec<(usize, Vec<QueryArg>)>,
117}
118
119impl TryFrom<ConjunctiveBooleanQueryCreateSpec> for ConjunctiveBooleanQuery {
120 type Error = crate::registry::ConstructionError;
121
122 fn try_from(spec: ConjunctiveBooleanQueryCreateSpec) -> Result<Self, Self::Error> {
123 let mut num_variables = 0_usize;
124 for (_, args) in &spec.conjuncts {
125 for arg in args {
126 if let QueryArg::Variable(variable) = arg {
127 let count = variable
128 .checked_add(1)
129 .ok_or_else(|| "number of query variables overflows usize".to_string())?;
130 num_variables = num_variables.max(count);
131 }
132 }
133 }
134
135 Self::try_new(
136 spec.domain_size,
137 spec.relations,
138 num_variables,
139 spec.conjuncts,
140 )
141 }
142}
143
144impl ConjunctiveBooleanQuery {
145 pub fn new(
157 domain_size: usize,
158 relations: Vec<Relation>,
159 num_variables: usize,
160 conjuncts: Vec<(usize, Vec<QueryArg>)>,
161 ) -> Self {
162 Self::try_new(domain_size, relations, num_variables, conjuncts)
163 .unwrap_or_else(|error| panic!("{error}"))
164 }
165
166 fn try_new(
167 domain_size: usize,
168 relations: Vec<Relation>,
169 num_variables: usize,
170 conjuncts: Vec<(usize, Vec<QueryArg>)>,
171 ) -> Result<Self, crate::registry::ConstructionError> {
172 for (i, rel) in relations.iter().enumerate() {
173 for (j, tuple) in rel.tuples.iter().enumerate() {
174 if tuple.len() != rel.arity {
175 return Err(format!(
176 "Relation {i}: tuple {j} has length {}, expected arity {}",
177 tuple.len(),
178 rel.arity
179 )
180 .into());
181 }
182 for (k, &val) in tuple.iter().enumerate() {
183 if val >= domain_size {
184 return Err(format!(
185 "Relation {i}: tuple {j}, entry {k} is {val}, must be < {domain_size}"
186 )
187 .into());
188 }
189 }
190 }
191 }
192 for (i, (rel_idx, args)) in conjuncts.iter().enumerate() {
193 if *rel_idx >= relations.len() {
194 return Err(format!(
195 "Conjunct {i}: relation index {rel_idx} out of range (have {} relations)",
196 relations.len()
197 )
198 .into());
199 }
200 if args.len() != relations[*rel_idx].arity {
201 return Err(format!(
202 "Conjunct {i}: has {} args, expected arity {}",
203 args.len(),
204 relations[*rel_idx].arity
205 )
206 .into());
207 }
208 for (k, arg) in args.iter().enumerate() {
209 match arg {
210 QueryArg::Variable(v) => {
211 if *v >= num_variables {
212 return Err(format!("Conjunct {i}, arg {k}: Variable({v}) >= num_variables ({num_variables})").into());
213 }
214 }
215 QueryArg::Constant(c) => {
216 if *c >= domain_size {
217 return Err(format!("Conjunct {i}, arg {k}: Constant({c}) >= domain_size ({domain_size})").into());
218 }
219 }
220 }
221 }
222 }
223 Ok(Self {
224 domain_size,
225 relations,
226 num_variables,
227 conjuncts,
228 })
229 }
230
231 pub fn domain_size(&self) -> usize {
233 self.domain_size
234 }
235
236 pub fn num_relations(&self) -> usize {
238 self.relations.len()
239 }
240
241 pub fn num_variables(&self) -> usize {
243 self.num_variables
244 }
245
246 pub fn num_conjuncts(&self) -> usize {
248 self.conjuncts.len()
249 }
250
251 pub fn relations(&self) -> &[Relation] {
253 &self.relations
254 }
255
256 pub fn conjuncts(&self) -> &[(usize, Vec<QueryArg>)] {
258 &self.conjuncts
259 }
260}
261
262impl Problem for ConjunctiveBooleanQuery {
263 const NAME: &'static str = "ConjunctiveBooleanQuery";
264 type Solution = Vec<usize>;
265 type Value = crate::types::Or;
266
267 crate::problem_parameters![
268 ("domain_size", domain_size),
269 ("num_conjuncts", num_conjuncts),
270 ("num_relations", num_relations),
271 ("num_variables", num_variables),
272 ];
273
274 fn variant() -> Vec<(&'static str, &'static str)> {
275 crate::variant_params![]
276 }
277
278 fn evaluate(
279 &self,
280 config: &Self::Solution,
281 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
282 Ok({
283 crate::types::Or({
284 if config.len() != self.num_variables {
285 return Err(crate::traits::EvaluationError::InvalidConfiguration(
286 "variable assignment length does not match the query".into(),
287 ));
288 }
289 if config.iter().any(|&v| v >= self.domain_size) {
290 return Err(crate::traits::EvaluationError::InvalidConfiguration(
291 "variable assignment contains an out-of-range domain value".into(),
292 ));
293 }
294 self.conjuncts.iter().all(|(rel_idx, args)| {
295 let tuple: Vec<usize> = args
296 .iter()
297 .map(|arg| match arg {
298 QueryArg::Variable(i) => config[*i],
299 QueryArg::Constant(c) => *c,
300 })
301 .collect();
302 self.relations[*rel_idx].tuples.contains(&tuple)
303 })
304 })
305 })
306 }
307}
308
309impl crate::solvers::BruteForceProblem for ConjunctiveBooleanQuery {
310 fn dimensions(&self) -> Vec<usize> {
311 vec![self.domain_size; self.num_variables]
312 }
313}
314
315crate::declare_variants! {
316 default ConjunctiveBooleanQuery => "domain_size ^ num_variables" create ConjunctiveBooleanQueryCreateSpec,
317}
318
319crate::register_brute_force! {
320 ConjunctiveBooleanQuery,
321}
322
323#[cfg(feature = "example-db")]
324pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
325 vec![crate::example_db::specs::ModelExampleSpec {
326 id: "conjunctive_boolean_query",
327 instance: Box::new(ConjunctiveBooleanQuery::new(
330 6,
331 vec![
332 Relation {
333 arity: 2,
334 tuples: vec![vec![0, 3], vec![1, 3], vec![2, 4], vec![3, 4], vec![4, 5]],
335 },
336 Relation {
337 arity: 3,
338 tuples: vec![vec![0, 1, 5], vec![1, 2, 5], vec![2, 3, 4], vec![0, 4, 3]],
339 },
340 ],
341 2,
342 vec![
343 (0, vec![QueryArg::Variable(0), QueryArg::Constant(3)]),
344 (0, vec![QueryArg::Variable(1), QueryArg::Constant(3)]),
345 (
346 1,
347 vec![
348 QueryArg::Variable(0),
349 QueryArg::Variable(1),
350 QueryArg::Constant(5),
351 ],
352 ),
353 ],
354 )),
355 optimal_config: serde_json::json!(vec![0, 1]),
356 optimal_value: serde_json::json!(true),
357 }]
358}
359
360#[cfg(test)]
361#[path = "../../unit_tests/models/misc/conjunctive_boolean_query.rs"]
362mod tests;