Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"MinimumTardinessSequencing": [Minimum Tardiness Sequencing],
"SequencingWithinIntervals": [Sequencing Within Intervals],
"DirectedTwoCommodityIntegralFlow": [Directed Two-Commodity Integral Flow],
"ConjunctiveBooleanQuery": [Conjunctive Boolean Query],
)

// Definition label: "def:<ProblemName>" — each definition block must have a matching label
Expand Down Expand Up @@ -2326,6 +2327,68 @@ NP-completeness was established by Garey, Johnson, and Stockmeyer @gareyJohnsonS
) <fig:d2cif>
]

#{
let x = load-model-example("ConjunctiveBooleanQuery")
let d = x.instance.domain_size
let nv = x.instance.num_variables
let rels = x.instance.relations
let conj = x.instance.conjuncts
let nr = rels.len()
let nc = conj.len()
let sol = x.optimal.at(0)
let assignment = sol.config
[
#problem-def("ConjunctiveBooleanQuery")[
Given a finite domain $D = {0, dots, d - 1}$, a collection of relations $R_0, R_1, dots, R_(m-1)$ where each $R_i$ is a set of $a_i$-tuples with entries from $D$, and a conjunctive Boolean query
$ Q = (exists y_0, y_1, dots, y_(l-1))(A_0 and A_1 and dots.c and A_(r-1)) $
where each _atom_ $A_j$ has the form $R_(i_j)(u_0, u_1, dots)$ with every $u$ in ${y_0, dots, y_(l-1)} union D$, determine whether there exists an assignment to the variables that makes $Q$ true --- i.e., the resolved tuple of every atom belongs to its relation.
][
The Conjunctive Boolean Query (CBQ) problem is one of the most fundamental problems in database theory and finite model theory. #cite(<chandra1977>, form: "prose") showed that evaluating conjunctive queries is NP-complete by reduction from the Clique problem. CBQ is equivalent to the Constraint Satisfaction Problem (CSP) and to the homomorphism problem for relational structures; this equivalence connects database query evaluation, constraint programming, and graph theory under a single computational framework @kolaitis1998.

For queries of bounded _hypertree-width_, evaluation becomes polynomial-time @gottlob2002. The general brute-force algorithm enumerates all $d^l$ variable assignments and checks every atom, running in $O(d^l dot r dot max_i a_i)$ time.#footnote[No substantially faster general algorithm is known for arbitrary conjunctive Boolean queries.]

*Example.* Let $D = {0, dots, #(d - 1)}$ ($d = #d$), with #nr relations:

#align(center, grid(
columns: nr,
gutter: 1.5em,
..range(nr).map(ri => {
let rel = rels.at(ri)
let arity = rel.arity
let header = range(arity).map(j => [$c_#j$])
table(
columns: arity + 1,
align: center,
inset: (x: 4pt, y: 3pt),
table.header([$R_#ri$], ..header),
table.hline(stroke: 0.3pt),
..rel.tuples.enumerate().map(((ti, tup)) => {
let cells = tup.map(v => [#v])
([$tau_#ti$], ..cells)
}).flatten()
)
})
))

The query has #nv variables $(y_0, y_1)$ and #nc atoms:
#{
let fmt-arg(a) = {
if "Variable" in a { $y_#(a.Variable)$ }
else { $#(a.Constant)$ }
}
let atoms = conj.enumerate().map(((j, c)) => {
let ri = c.at(0)
let args = c.at(1)
[$A_#j = R_#ri (#args.map(fmt-arg).join($, $))$]
})
[$ Q = (exists y_0, y_1)(#atoms.join($ and $)) $]
}

Under the assignment $y_0 = #assignment.at(0)$, $y_1 = #assignment.at(1)$: atom $A_0$ resolves to $(#assignment.at(0), 3) in R_0$ (row $tau_0$), atom $A_1$ resolves to $(#assignment.at(1), 3) in R_0$ (row $tau_1$), and atom $A_2$ resolves to $(#assignment.at(0), #assignment.at(1), 5) in R_1$ (row $tau_0$). All three atoms are satisfied, so $Q$ is true.
]
]
}

// Completeness check: warn about problem types in JSON but missing from paper
#{
let json-models = {
Expand Down
29 changes: 29 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,35 @@ @article{even1976
doi = {10.1137/0205048}
}

@article{chandra1977,
author = {Ashok K. Chandra and Philip M. Merlin},
title = {Optimal Implementation of Conjunctive Queries in Relational Data Bases},
journal = {Proceedings of the 9th Annual ACM Symposium on Theory of Computing (STOC)},
pages = {77--90},
year = {1977},
doi = {10.1145/800105.803397}
}

@article{gottlob2002,
author = {Georg Gottlob and Nicola Leone and Francesco Scarcello},
title = {Hypertree Decompositions and Tractable Queries},
journal = {Journal of Computer and System Sciences},
volume = {64},
number = {3},
pages = {579--627},
year = {2002},
doi = {10.1006/jcss.2001.1809}
}

@incollection{kolaitis1998,
author = {Phokion G. Kolaitis and Moshe Y. Vardi},
title = {Conjunctive-Query Containment and Constraint Satisfaction},
booktitle = {Proceedings of the 17th ACM SIGACT-SIGMOD-SIGART Symposium on Principles of Database Systems (PODS)},
pages = {205--213},
year = {1998},
doi = {10.1145/275487.275511}
}
Comment on lines +696 to +723

@article{papadimitriou1982,
author = {Christos H. Papadimitriou and Mihalis Yannakakis},
title = {The Complexity of Restricted Spanning Tree Problems},
Expand Down
10 changes: 10 additions & 0 deletions problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ Flags by problem type:
MinimumTardinessSequencing --n, --deadlines [--precedence-pairs]
SCS --strings, --bound [--alphabet-size]
D2CIF --arcs, --capacities, --source-1, --sink-1, --source-2, --sink-2, --requirement-1, --requirement-2
CBQ --domain-size, --relations, --conjuncts-spec
ILP, CircuitSAT (via reduction only)

Geometry graph variants (use slash notation, e.g., MIS/KingsSubgraph):
Expand Down Expand Up @@ -456,6 +457,15 @@ pub struct CreateArgs {
/// Alphabet size for SCS (optional; inferred from max symbol + 1 if omitted)
#[arg(long)]
pub alphabet_size: Option<usize>,
/// Domain size for ConjunctiveBooleanQuery
#[arg(long)]
pub domain_size: Option<usize>,
/// Relations for ConjunctiveBooleanQuery (format: "arity:tuple1|tuple2;arity:tuple1|tuple2")
#[arg(long)]
pub relations: Option<String>,
/// Conjuncts for ConjunctiveBooleanQuery (format: "rel:args;rel:args" where args use v0,v1 for variables, c0,c1 for constants)
#[arg(long)]
pub conjuncts_spec: Option<String>,
}

#[derive(clap::Args)]
Expand Down
105 changes: 103 additions & 2 deletions problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ use problemreductions::models::graph::{
SteinerTree,
};
use problemreductions::models::misc::{
BinPacking, FlowShopScheduling, LongestCommonSubsequence, MinimumTardinessSequencing,
PaintShop, SequencingWithinIntervals, ShortestCommonSupersequence, SubsetSum,
BinPacking, CbqRelation, ConjunctiveBooleanQuery, FlowShopScheduling, LongestCommonSubsequence,
MinimumTardinessSequencing, PaintShop, QueryArg, SequencingWithinIntervals,
ShortestCommonSupersequence, SubsetSum,
};
use problemreductions::prelude::*;
use problemreductions::registry::collect_schemas;
Expand Down Expand Up @@ -90,6 +91,9 @@ fn all_data_flags_empty(args: &CreateArgs) -> bool {
&& args.sink_2.is_none()
&& args.requirement_1.is_none()
&& args.requirement_2.is_none()
&& args.domain_size.is_none()
&& args.relations.is_none()
&& args.conjuncts_spec.is_none()
}

fn emit_problem_output(output: &ProblemJsonOutput, out: &OutputConfig) -> Result<()> {
Expand Down Expand Up @@ -296,6 +300,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
"SubsetSum" => "--sizes 3,7,1,8,2,4 --target 11",
"SetBasis" => "--universe 4 --sets \"0,1;1,2;0,2;0,1,2\" --k 3",
"ShortestCommonSupersequence" => "--strings \"0,1,2;1,2,0\" --bound 4",
"ConjunctiveBooleanQuery" => {
"--domain-size 6 --relations \"2:0,3|1,3|2,4;3:0,1,5|1,2,5\" --conjuncts-spec \"0:v0,c3;0:v1,c3;1:v0,v1,c5\""
}
_ => "",
}
}
Expand Down Expand Up @@ -1542,6 +1549,100 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
)
}

// ConjunctiveBooleanQuery
"ConjunctiveBooleanQuery" => {
let usage = "Usage: pred create CBQ --domain-size 6 --relations \"2:0,3|1,3;3:0,1,5|1,2,5\" --conjuncts-spec \"0:v0,c3;0:v1,c3;1:v0,v1,c5\"";
let domain_size = args.domain_size.ok_or_else(|| {
anyhow::anyhow!("ConjunctiveBooleanQuery requires --domain-size\n\n{usage}")
})?;
let relations_str = args.relations.as_deref().ok_or_else(|| {
anyhow::anyhow!("ConjunctiveBooleanQuery requires --relations\n\n{usage}")
})?;
let conjuncts_str = args.conjuncts_spec.as_deref().ok_or_else(|| {
anyhow::anyhow!("ConjunctiveBooleanQuery requires --conjuncts-spec\n\n{usage}")
})?;
// Parse relations: "arity:t1,t2|t3,t4;arity:t5,t6,t7|t8,t9,t10"
let relations: Vec<CbqRelation> = relations_str
.split(';')
.map(|rel_str| {
let rel_str = rel_str.trim();
let (arity_str, tuples_str) = rel_str.split_once(':').ok_or_else(|| {
anyhow::anyhow!(
"Invalid relation format: expected 'arity:tuples', got '{rel_str}'"
)
})?;
let arity: usize = arity_str
.trim()
.parse()
.map_err(|e| anyhow::anyhow!("Invalid arity '{arity_str}': {e}"))?;
let tuples: Vec<Vec<usize>> = tuples_str
.split('|')
.map(|t| {
t.trim()
.split(',')
.map(|v| {
v.trim()
.parse::<usize>()
.map_err(|e| anyhow::anyhow!("Invalid tuple value: {e}"))
})
.collect::<Result<Vec<_>>>()
})
.collect::<Result<Vec<_>>>()?;
Comment on lines +1578 to +1590
Ok(CbqRelation { arity, tuples })
})
.collect::<Result<Vec<_>>>()?;
// Parse conjuncts: "rel_idx:arg1,arg2;rel_idx:arg1,arg2,arg3"
let mut num_vars_inferred: usize = 0;
let conjuncts: Vec<(usize, Vec<QueryArg>)> = conjuncts_str
.split(';')
.map(|conj_str| {
let conj_str = conj_str.trim();
let (idx_str, args_str) = conj_str.split_once(':').ok_or_else(|| {
anyhow::anyhow!(
"Invalid conjunct format: expected 'rel_idx:args', got '{conj_str}'"
)
})?;
let rel_idx: usize = idx_str.trim().parse().map_err(|e| {
anyhow::anyhow!("Invalid relation index '{idx_str}': {e}")
})?;
let query_args: Vec<QueryArg> = args_str
.split(',')
.map(|a| {
let a = a.trim();
if let Some(rest) = a.strip_prefix('v') {
let v: usize = rest.parse().map_err(|e| {
anyhow::anyhow!("Invalid variable index '{rest}': {e}")
})?;
if v + 1 > num_vars_inferred {
num_vars_inferred = v + 1;
}
Ok(QueryArg::Variable(v))
} else if let Some(rest) = a.strip_prefix('c') {
let c: usize = rest.parse().map_err(|e| {
anyhow::anyhow!("Invalid constant value '{rest}': {e}")
})?;
Ok(QueryArg::Constant(c))
} else {
Err(anyhow::anyhow!(
"Invalid query arg '{a}': expected vN (variable) or cN (constant)"
))
}
})
.collect::<Result<Vec<_>>>()?;
Ok((rel_idx, query_args))
})
.collect::<Result<Vec<_>>>()?;
(
ser(ConjunctiveBooleanQuery::new(
domain_size,
relations,
num_vars_inferred,
conjuncts,
))?,
Comment on lines +1564 to +1641
resolved_variant.clone(),
)
}

_ => bail!("{}", crate::problem_name::unknown_problem_error(canonical)),
};

Expand Down
Loading
Loading