Skip to content
Merged
63 changes: 63 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
"SumOfSquaresPartition": [Sum of Squares Partition],
"SequencingWithinIntervals": [Sequencing Within Intervals],
"DirectedTwoCommodityIntegralFlow": [Directed Two-Commodity Integral Flow],
"ConjunctiveBooleanQuery": [Conjunctive Boolean Query],
"RectilinearPictureCompression": [Rectilinear Picture Compression],
"StringToStringCorrection": [String-to-String Correction],
)
Expand Down Expand Up @@ -3268,6 +3269,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.
]
]
}

#{
let x = load-model-example("ConsecutiveOnesSubmatrix")
let A = x.instance.matrix
Expand Down
29 changes: 29 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,35 @@ @article{even1976
doi = {10.1137/0205048}
}

@inproceedings{chandra1977,
author = {Ashok K. Chandra and Philip M. Merlin},
title = {Optimal Implementation of Conjunctive Queries in Relational Data Bases},
booktitle = {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}
}

@inproceedings{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}
}

@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 @@ -269,6 +269,7 @@ Flags by problem type:
SCS --strings, --bound [--alphabet-size]
StringToStringCorrection --source-string, --target-string, --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 @@ -518,6 +519,15 @@ pub struct CreateArgs {
/// Alphabet size for LCS, SCS, or StringToStringCorrection (optional; inferred from the input strings 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>,
/// Functional dependencies (semicolon-separated, each dep is lhs>rhs with comma-separated indices, e.g., "0,1>2,3;2,3>0,1")
#[arg(long)]
pub deps: Option<String>,
Expand Down
149 changes: 145 additions & 4 deletions problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ use problemreductions::models::graph::{
MinimumMultiwayCut, MultipleChoiceBranching, SteinerTree, StrongConnectivityAugmentation,
};
use problemreductions::models::misc::{
BinPacking, FlowShopScheduling, LongestCommonSubsequence, MinimumTardinessSequencing,
MultiprocessorScheduling, PaintShop, PartiallyOrderedKnapsack, RectilinearPictureCompression,
SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals,
ShortestCommonSupersequence, StringToStringCorrection, SubsetSum, SumOfSquaresPartition,
BinPacking, CbqRelation, ConjunctiveBooleanQuery, FlowShopScheduling, LongestCommonSubsequence,
MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop, PartiallyOrderedKnapsack,
QueryArg, RectilinearPictureCompression, SequencingWithReleaseTimesAndDeadlines,
SequencingWithinIntervals, ShortestCommonSupersequence, StringToStringCorrection, SubsetSum,
SumOfSquaresPartition,
};
use problemreductions::models::BiconnectivityAugmentation;
use problemreductions::prelude::*;
Expand Down Expand Up @@ -110,6 +111,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()
&& args.deps.is_none()
&& args.query.is_none()
}
Expand Down Expand Up @@ -356,6 +360,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
"--num-attributes 6 --dependencies \"0,1>2;0,2>3;1,3>4;2,4>5\" --k 2"
}
"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\""
}
"StringToStringCorrection" => {
"--source-string \"0,1,2,3,1,0\" --target-string \"0,1,3,2,1\" --bound 2"
}
Expand Down Expand Up @@ -2106,6 +2113,137 @@ 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"
// An empty tuple list (e.g., "2:") produces an empty relation.
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>> = if tuples_str.trim().is_empty() {
Vec::new()
} else {
tuples_str
.split('|')
.filter(|t| !t.trim().is_empty())
.map(|t| {
let tuple: Vec<usize> = t
.trim()
.split(',')
.map(|v| {
v.trim().parse::<usize>().map_err(|e| {
anyhow::anyhow!("Invalid tuple value: {e}")
})
})
.collect::<Result<Vec<_>>>()?;
if tuple.len() != arity {
bail!(
"Relation tuple has {} entries, expected arity {arity}",
tuple.len()
);
}
for &val in &tuple {
if val >= domain_size {
bail!("Tuple value {val} >= domain-size {domain_size}");
}
}
Ok(tuple)
})
.collect::<Result<Vec<_>>>()?
};
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}")
})?;
if rel_idx >= relations.len() {
bail!(
"Conjunct references relation {rel_idx}, but only {} relations exist",
relations.len()
);
}
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}")
})?;
if c >= domain_size {
bail!(
"Constant {c} >= domain-size {domain_size}"
);
}
Ok(QueryArg::Constant(c))
} else {
Err(anyhow::anyhow!(
"Invalid query arg '{a}': expected vN (variable) or cN (constant)"
))
}
})
.collect::<Result<Vec<_>>>()?;
let expected_arity = relations[rel_idx].arity;
if query_args.len() != expected_arity {
bail!(
"Conjunct has {} args, but relation {rel_idx} has arity {expected_arity}",
query_args.len()
);
}
Ok((rel_idx, query_args))
})
.collect::<Result<Vec<_>>>()?;
(
ser(ConjunctiveBooleanQuery::new(
domain_size,
relations,
num_vars_inferred,
conjuncts,
))?,
Comment on lines +2128 to +2242
resolved_variant.clone(),
)
}

// PartiallyOrderedKnapsack
"PartiallyOrderedKnapsack" => {
let sizes_str = args.sizes.as_deref().ok_or_else(|| {
Expand Down Expand Up @@ -3858,6 +3996,9 @@ mod tests {
requirements: None,
num_workers: None,
num_groups: None,
domain_size: None,
relations: None,
conjuncts_spec: None,
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ pub mod prelude {
UndirectedTwoCommodityIntegralFlow,
};
pub use crate::models::misc::{
BinPacking, Factoring, FlowShopScheduling, Knapsack, LongestCommonSubsequence,
MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop,
RectilinearPictureCompression, SequencingWithReleaseTimesAndDeadlines,
BinPacking, CbqRelation, ConjunctiveBooleanQuery, Factoring, FlowShopScheduling, Knapsack,
LongestCommonSubsequence, MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop,
QueryArg, RectilinearPictureCompression, SequencingWithReleaseTimesAndDeadlines,
SequencingWithinIntervals, ShortestCommonSupersequence, StaffScheduling,
StringToStringCorrection, SubsetSum, SumOfSquaresPartition,
};
Expand Down
Loading
Loading