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
23 changes: 23 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@

// Problem display names for theorem headers
#let display-name = (
"AdditionalKey": [Additional Key],
"MaximumIndependentSet": [Maximum Independent Set],
"MinimumVertexCover": [Minimum Vertex Cover],
"MaxCut": [Max-Cut],
Expand Down Expand Up @@ -2326,6 +2327,28 @@ NP-completeness was established by Garey, Johnson, and Stockmeyer @gareyJohnsonS
) <fig:d2cif>
]

#problem-def("AdditionalKey")[
Given a set $A$ of attribute names, a collection $F$ of functional dependencies on $A$,
a subset $R subset.eq A$, and a set $K$ of candidate keys for the relational scheme $chevron.l R, F chevron.r$,
determine whether there exists a subset $R' subset.eq R$ such that $R' in.not K$,
the closure $R'^+$ under $F$ equals $R$, and no proper subset of $R'$ also has this property.
][
A classical NP-complete problem from relational database theory @beeri1979.
Enumerating all candidate keys is necessary to verify Boyce-Codd Normal Form (BCNF),
and the NP-completeness of Additional Key implies that BCNF testing is intractable in general.
The best known exact algorithm is brute-force enumeration of all $2^(|R|)$ subsets,
checking each for the key property via closure computation under Armstrong's axioms.
#footnote[No algorithm improving on brute-force is known for the Additional Key problem.]

*Example.* Consider attribute set $A = {0, 1, 2, 3, 4, 5}$ with functional dependencies
$F = {{0,1} -> {2,3}, {2,3} -> {4,5}, {4,5} -> {0,1}, {0,2} -> {3}, {3,5} -> {1}}$,
relation $R = A$, and known keys $K = {{0,1}, {2,3}, {4,5}}$.
The subset ${0,2}$ is an additional key: starting from ${0,2}$, we apply ${0,2} -> {3}$
to get ${0,2,3}$, then ${2,3} -> {4,5}$ to get ${0,2,3,4,5}$, then ${4,5} -> {0,1}$
to reach $R^+ = A$. The set ${0,2}$ is minimal (neither ${0}$ nor ${2}$ alone determines $A$)
and ${0,2} in.not K$, so the answer is YES.
]

// Completeness check: warn about problem types in JSON but missing from paper
#{
let json-models = {
Expand Down
11 changes: 11 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ @article{lucas2014
year = {2014}
}

@article{beeri1979,
author = {Catriel Beeri and Philip A. Bernstein},
title = {Computational Problems Related to the Design of Normal Form Relational Schemas},
journal = {ACM Transactions on Database Systems},
volume = {4},
number = {1},
pages = {30--59},
year = {1979},
doi = {10.1145/320064.320066}
}

@article{barahona1982,
author = {Francisco Barahona},
title = {On the computational complexity of Ising spin glass models},
Expand Down
13 changes: 13 additions & 0 deletions problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ Flags by problem type:
OptimalLinearArrangement --graph, --bound
RuralPostman (RPP) --graph, --edge-weights, --required-edges, --bound
MultipleChoiceBranching --arcs [--weights] --partition --bound [--num-vertices]
AdditionalKey --num-attributes, --dependencies, --relation-attrs, --known-keys
SubgraphIsomorphism --graph (host), --pattern (pattern)
LCS --strings
FAS --arcs [--weights] [--num-vertices]
Expand Down Expand Up @@ -456,6 +457,18 @@ pub struct CreateArgs {
/// Alphabet size for SCS (optional; inferred from max symbol + 1 if omitted)
#[arg(long)]
pub alphabet_size: Option<usize>,
/// Number of attributes for AdditionalKey
#[arg(long)]
pub num_attributes: Option<usize>,
/// Functional dependencies for AdditionalKey (e.g., "0,1:2,3;2,3:4,5")
#[arg(long)]
pub dependencies: Option<String>,
/// Relation scheme attributes for AdditionalKey (comma-separated, e.g., "0,1,2,3,4,5")
#[arg(long)]
pub relation_attrs: Option<String>,
/// Known candidate keys for AdditionalKey (e.g., "0,1;2,3")
#[arg(long)]
pub known_keys: Option<String>,
}

#[derive(clap::Args)]
Expand Down
55 changes: 53 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,
AdditionalKey, BinPacking, FlowShopScheduling, LongestCommonSubsequence,
MinimumTardinessSequencing, PaintShop, SequencingWithinIntervals, ShortestCommonSupersequence,
SubsetSum,
};
use problemreductions::prelude::*;
use problemreductions::registry::collect_schemas;
Expand Down Expand Up @@ -90,6 +91,10 @@ fn all_data_flags_empty(args: &CreateArgs) -> bool {
&& args.sink_2.is_none()
&& args.requirement_1.is_none()
&& args.requirement_2.is_none()
&& args.num_attributes.is_none()
&& args.dependencies.is_none()
&& args.relation_attrs.is_none()
&& args.known_keys.is_none()
}

fn emit_problem_output(output: &ProblemJsonOutput, out: &OutputConfig) -> Result<()> {
Expand Down Expand Up @@ -292,6 +297,7 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
"MultipleChoiceBranching" => {
"--arcs \"0>1,0>2,1>3,2>3,1>4,3>5,4>5,2>4\" --weights 3,2,4,1,2,3,1,3 --partition \"0,1;2,3;4,7;5,6\" --bound 10"
}
"AdditionalKey" => "--num-attributes 6 --dependencies \"0,1:2,3;2,3:4,5;4,5:0,1\" --relation-attrs 0,1,2,3,4,5 --known-keys \"0,1;2,3;4,5\"",
"SubgraphIsomorphism" => "--graph 0-1,1-2,2-0 --pattern 0-1",
"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",
Expand Down Expand Up @@ -932,6 +938,51 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
}
}

// AdditionalKey
"AdditionalKey" => {
let usage = "Usage: pred create AdditionalKey --num-attributes 6 --dependencies \"0,1:2,3;2,3:4,5\" --relation-attrs \"0,1,2,3,4,5\" --known-keys \"0,1;2,3\"";
let num_attributes = args.num_attributes.ok_or_else(|| {
anyhow::anyhow!("AdditionalKey requires --num-attributes\n\n{usage}")
})?;
let deps_str = args.dependencies.as_deref().ok_or_else(|| {
anyhow::anyhow!("AdditionalKey requires --dependencies\n\n{usage}")
})?;
let ra_str = args.relation_attrs.as_deref().ok_or_else(|| {
anyhow::anyhow!("AdditionalKey requires --relation-attrs\n\n{usage}")
})?;
let dependencies: Vec<(Vec<usize>, Vec<usize>)> = deps_str
.split(';')
.map(|dep| {
let parts: Vec<&str> = dep.trim().split(':').collect();
anyhow::ensure!(
parts.len() == 2,
"Invalid dependency format '{}', expected 'lhs:rhs' (e.g., '0,1:2,3')",
dep.trim()
);
let lhs: Vec<usize> = util::parse_comma_list(parts[0].trim())?;
let rhs: Vec<usize> = util::parse_comma_list(parts[1].trim())?;
Ok((lhs, rhs))
})
.collect::<Result<Vec<_>>>()?;
let relation_attrs: Vec<usize> = util::parse_comma_list(ra_str)?;
let known_keys: Vec<Vec<usize>> = match args.known_keys.as_deref() {
Some(s) if !s.is_empty() => s
.split(';')
.map(|k| util::parse_comma_list(k.trim()))
.collect::<Result<Vec<_>>>()?,
_ => vec![],
};
(
ser(AdditionalKey::new(
num_attributes,
dependencies,
relation_attrs,
known_keys,
))?,
resolved_variant.clone(),
)
}

// SubsetSum
"SubsetSum" => {
let sizes_str = args.sizes.as_deref().ok_or_else(|| {
Expand Down
1 change: 1 addition & 0 deletions src/example_db/fixtures/examples.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"models": [
{"problem":"AdditionalKey","variant":{},"instance":{"dependencies":[[[0,1],[2,3]],[[2,3],[4,5]],[[4,5],[0,1]],[[0,2],[3]],[[3,5],[1]]],"known_keys":[[0,1],[2,3],[4,5]],"num_attributes":6,"relation_attrs":[0,1,2,3,4,5]},"samples":[{"config":[1,0,1,0,0,0],"metric":true}],"optimal":[{"config":[1,0,0,1,0,1],"metric":true},{"config":[1,0,1,0,0,0],"metric":true}]},
{"problem":"BMF","variant":{},"instance":{"k":2,"m":3,"matrix":[[true,true,false],[true,true,true],[false,true,true]],"n":3},"samples":[{"config":[1,0,1,1,0,1,1,1,0,0,1,1],"metric":{"Valid":0}}],"optimal":[{"config":[0,1,1,1,1,0,0,1,1,1,1,0],"metric":{"Valid":0}},{"config":[1,0,1,1,0,1,1,1,0,0,1,1],"metric":{"Valid":0}}]},
{"problem":"BicliqueCover","variant":{},"instance":{"graph":{"edges":[[0,0],[0,1],[1,1],[1,2]],"left_size":2,"right_size":3},"k":2},"samples":[{"config":[1,0,0,1,1,0,1,1,0,1],"metric":{"Valid":6}}],"optimal":[{"config":[0,1,0,1,0,1,0,1,0,1],"metric":{"Valid":5}},{"config":[1,0,1,0,1,0,1,0,1,0],"metric":{"Valid":5}}]},
{"problem":"BoundedComponentSpanningForest","variant":{"graph":"SimpleGraph","weight":"i32"},"instance":{"graph":{"inner":{"edge_property":"undirected","edges":[[0,1,null],[1,2,null],[2,3,null],[3,4,null],[4,5,null],[5,6,null],[6,7,null],[0,7,null],[1,5,null],[2,6,null]],"node_holes":[],"nodes":[null,null,null,null,null,null,null,null]}},"max_components":3,"max_weight":6,"weights":[2,3,1,2,3,1,2,1]},"samples":[{"config":[0,0,1,1,1,2,2,0],"metric":true}],"optimal":[{"config":[0,0,0,1,1,1,2,2],"metric":true},{"config":[0,0,0,1,1,2,2,2],"metric":true},{"config":[0,0,0,2,2,1,1,1],"metric":true},{"config":[0,0,0,2,2,2,1,1],"metric":true},{"config":[0,0,1,1,1,0,2,2],"metric":true},{"config":[0,0,1,1,1,2,2,0],"metric":true},{"config":[0,0,1,1,1,2,2,2],"metric":true},{"config":[0,0,1,1,2,0,1,1],"metric":true},{"config":[0,0,1,1,2,1,1,0],"metric":true},{"config":[0,0,1,1,2,2,1,0],"metric":true},{"config":[0,0,1,1,2,2,1,1],"metric":true},{"config":[0,0,1,1,2,2,2,0],"metric":true},{"config":[0,0,1,2,2,0,1,1],"metric":true},{"config":[0,0,1,2,2,1,1,0],"metric":true},{"config":[0,0,1,2,2,1,1,1],"metric":true},{"config":[0,0,1,2,2,2,1,0],"metric":true},{"config":[0,0,1,2,2,2,1,1],"metric":true},{"config":[0,0,2,1,1,0,2,2],"metric":true},{"config":[0,0,2,1,1,1,2,0],"metric":true},{"config":[0,0,2,1,1,1,2,2],"metric":true},{"config":[0,0,2,1,1,2,2,0],"metric":true},{"config":[0,0,2,1,1,2,2,2],"metric":true},{"config":[0,0,2,2,1,0,2,2],"metric":true},{"config":[0,0,2,2,1,1,1,0],"metric":true},{"config":[0,0,2,2,1,1,2,0],"metric":true},{"config":[0,0,2,2,1,1,2,2],"metric":true},{"config":[0,0,2,2,1,2,2,0],"metric":true},{"config":[0,0,2,2,2,0,1,1],"metric":true},{"config":[0,0,2,2,2,1,1,0],"metric":true},{"config":[0,0,2,2,2,1,1,1],"metric":true},{"config":[0,1,0,2,2,1,0,0],"metric":true},{"config":[0,1,0,2,2,2,0,0],"metric":true},{"config":[0,1,1,1,2,0,0,0],"metric":true},{"config":[0,1,1,1,2,2,0,0],"metric":true},{"config":[0,1,1,1,2,2,2,0],"metric":true},{"config":[0,1,1,2,2,0,0,0],"metric":true},{"config":[0,1,1,2,2,1,0,0],"metric":true},{"config":[0,1,1,2,2,2,0,0],"metric":true},{"config":[0,1,1,2,2,2,1,0],"metric":true},{"config":[0,1,2,2,2,0,0,0],"metric":true},{"config":[0,1,2,2,2,1,0,0],"metric":true},{"config":[0,1,2,2,2,1,1,0],"metric":true},{"config":[0,2,0,1,1,1,0,0],"metric":true},{"config":[0,2,0,1,1,2,0,0],"metric":true},{"config":[0,2,1,1,1,0,0,0],"metric":true},{"config":[0,2,1,1,1,2,0,0],"metric":true},{"config":[0,2,1,1,1,2,2,0],"metric":true},{"config":[0,2,2,1,1,0,0,0],"metric":true},{"config":[0,2,2,1,1,1,0,0],"metric":true},{"config":[0,2,2,1,1,1,2,0],"metric":true},{"config":[0,2,2,1,1,2,0,0],"metric":true},{"config":[0,2,2,2,1,0,0,0],"metric":true},{"config":[0,2,2,2,1,1,0,0],"metric":true},{"config":[0,2,2,2,1,1,1,0],"metric":true},{"config":[1,0,0,0,2,1,1,1],"metric":true},{"config":[1,0,0,0,2,2,1,1],"metric":true},{"config":[1,0,0,0,2,2,2,1],"metric":true},{"config":[1,0,0,2,2,0,1,1],"metric":true},{"config":[1,0,0,2,2,1,1,1],"metric":true},{"config":[1,0,0,2,2,2,0,1],"metric":true},{"config":[1,0,0,2,2,2,1,1],"metric":true},{"config":[1,0,1,2,2,0,1,1],"metric":true},{"config":[1,0,1,2,2,2,1,1],"metric":true},{"config":[1,0,2,2,2,0,0,1],"metric":true},{"config":[1,0,2,2,2,0,1,1],"metric":true},{"config":[1,0,2,2,2,1,1,1],"metric":true},{"config":[1,1,0,0,0,1,2,2],"metric":true},{"config":[1,1,0,0,0,2,2,1],"metric":true},{"config":[1,1,0,0,0,2,2,2],"metric":true},{"config":[1,1,0,0,2,0,0,1],"metric":true},{"config":[1,1,0,0,2,1,0,0],"metric":true},{"config":[1,1,0,0,2,2,0,0],"metric":true},{"config":[1,1,0,0,2,2,0,1],"metric":true},{"config":[1,1,0,0,2,2,2,1],"metric":true},{"config":[1,1,0,2,2,0,0,0],"metric":true},{"config":[1,1,0,2,2,0,0,1],"metric":true},{"config":[1,1,0,2,2,1,0,0],"metric":true},{"config":[1,1,0,2,2,2,0,0],"metric":true},{"config":[1,1,0,2,2,2,0,1],"metric":true},{"config":[1,1,1,0,0,0,2,2],"metric":true},{"config":[1,1,1,0,0,2,2,2],"metric":true},{"config":[1,1,1,2,2,0,0,0],"metric":true},{"config":[1,1,1,2,2,2,0,0],"metric":true},{"config":[1,1,2,0,0,0,2,1],"metric":true},{"config":[1,1,2,0,0,0,2,2],"metric":true},{"config":[1,1,2,0,0,1,2,2],"metric":true},{"config":[1,1,2,0,0,2,2,1],"metric":true},{"config":[1,1,2,0,0,2,2,2],"metric":true},{"config":[1,1,2,2,0,0,0,1],"metric":true},{"config":[1,1,2,2,0,0,2,1],"metric":true},{"config":[1,1,2,2,0,0,2,2],"metric":true},{"config":[1,1,2,2,0,1,2,2],"metric":true},{"config":[1,1,2,2,0,2,2,1],"metric":true},{"config":[1,1,2,2,2,0,0,0],"metric":true},{"config":[1,1,2,2,2,0,0,1],"metric":true},{"config":[1,1,2,2,2,1,0,0],"metric":true},{"config":[1,2,0,0,0,1,1,1],"metric":true},{"config":[1,2,0,0,0,2,1,1],"metric":true},{"config":[1,2,0,0,0,2,2,1],"metric":true},{"config":[1,2,1,0,0,0,1,1],"metric":true},{"config":[1,2,1,0,0,2,1,1],"metric":true},{"config":[1,2,2,0,0,0,1,1],"metric":true},{"config":[1,2,2,0,0,0,2,1],"metric":true},{"config":[1,2,2,0,0,1,1,1],"metric":true},{"config":[1,2,2,0,0,2,1,1],"metric":true},{"config":[1,2,2,2,0,0,0,1],"metric":true},{"config":[1,2,2,2,0,0,1,1],"metric":true},{"config":[1,2,2,2,0,1,1,1],"metric":true},{"config":[2,0,0,0,1,1,1,2],"metric":true},{"config":[2,0,0,0,1,1,2,2],"metric":true},{"config":[2,0,0,0,1,2,2,2],"metric":true},{"config":[2,0,0,1,1,0,2,2],"metric":true},{"config":[2,0,0,1,1,1,0,2],"metric":true},{"config":[2,0,0,1,1,1,2,2],"metric":true},{"config":[2,0,0,1,1,2,2,2],"metric":true},{"config":[2,0,1,1,1,0,0,2],"metric":true},{"config":[2,0,1,1,1,0,2,2],"metric":true},{"config":[2,0,1,1,1,2,2,2],"metric":true},{"config":[2,0,2,1,1,0,2,2],"metric":true},{"config":[2,0,2,1,1,1,2,2],"metric":true},{"config":[2,1,0,0,0,1,1,2],"metric":true},{"config":[2,1,0,0,0,1,2,2],"metric":true},{"config":[2,1,0,0,0,2,2,2],"metric":true},{"config":[2,1,1,0,0,0,1,2],"metric":true},{"config":[2,1,1,0,0,0,2,2],"metric":true},{"config":[2,1,1,0,0,1,2,2],"metric":true},{"config":[2,1,1,0,0,2,2,2],"metric":true},{"config":[2,1,1,1,0,0,0,2],"metric":true},{"config":[2,1,1,1,0,0,2,2],"metric":true},{"config":[2,1,1,1,0,2,2,2],"metric":true},{"config":[2,1,2,0,0,0,2,2],"metric":true},{"config":[2,1,2,0,0,1,2,2],"metric":true},{"config":[2,2,0,0,0,1,1,1],"metric":true},{"config":[2,2,0,0,0,1,1,2],"metric":true},{"config":[2,2,0,0,0,2,1,1],"metric":true},{"config":[2,2,0,0,1,0,0,2],"metric":true},{"config":[2,2,0,0,1,1,0,0],"metric":true},{"config":[2,2,0,0,1,1,0,2],"metric":true},{"config":[2,2,0,0,1,1,1,2],"metric":true},{"config":[2,2,0,0,1,2,0,0],"metric":true},{"config":[2,2,0,1,1,0,0,0],"metric":true},{"config":[2,2,0,1,1,0,0,2],"metric":true},{"config":[2,2,0,1,1,1,0,0],"metric":true},{"config":[2,2,0,1,1,1,0,2],"metric":true},{"config":[2,2,0,1,1,2,0,0],"metric":true},{"config":[2,2,1,0,0,0,1,1],"metric":true},{"config":[2,2,1,0,0,0,1,2],"metric":true},{"config":[2,2,1,0,0,1,1,1],"metric":true},{"config":[2,2,1,0,0,1,1,2],"metric":true},{"config":[2,2,1,0,0,2,1,1],"metric":true},{"config":[2,2,1,1,0,0,0,2],"metric":true},{"config":[2,2,1,1,0,0,1,1],"metric":true},{"config":[2,2,1,1,0,0,1,2],"metric":true},{"config":[2,2,1,1,0,1,1,2],"metric":true},{"config":[2,2,1,1,0,2,1,1],"metric":true},{"config":[2,2,1,1,1,0,0,0],"metric":true},{"config":[2,2,1,1,1,0,0,2],"metric":true},{"config":[2,2,1,1,1,2,0,0],"metric":true},{"config":[2,2,2,0,0,0,1,1],"metric":true},{"config":[2,2,2,0,0,1,1,1],"metric":true},{"config":[2,2,2,1,1,0,0,0],"metric":true},{"config":[2,2,2,1,1,1,0,0],"metric":true}]},
Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ pub mod prelude {
UndirectedTwoCommodityIntegralFlow,
};
pub use crate::models::misc::{
BinPacking, Factoring, FlowShopScheduling, Knapsack, LongestCommonSubsequence,
MinimumTardinessSequencing, PaintShop, SequencingWithinIntervals,
ShortestCommonSupersequence, SubsetSum,
AdditionalKey, BinPacking, Factoring, FlowShopScheduling, Knapsack,
LongestCommonSubsequence, MinimumTardinessSequencing, PaintShop,
SequencingWithinIntervals, ShortestCommonSupersequence, SubsetSum,
};
pub use crate::models::set::{
ExactCoverBy3Sets, MaximumSetPacking, MinimumSetCovering, SetBasis,
Expand Down
Loading
Loading