Skip to content

Commit 90f8733

Browse files
GiggleLiuisPANNclaude
authored
Fix #447: [Model] BoyceCoddNormalFormViolation (#685)
* Add plan for #447: [Model] BoyceCoddNormalFormViolation * Implement #447: [Model] BoyceCoddNormalFormViolation Add the Boyce-Codd Normal Form Violation satisfaction problem model (Garey & Johnson A4 SR29). Given attributes, functional dependencies, and a target subset, determines whether a BCNF violation exists. - Model: src/models/misc/boyce_codd_normal_form_violation.rs - Tests: 20 unit tests covering evaluation, solver, serialization, edge cases - CLI: create support with --n, --sets (lhs:rhs format), --target - Paper: problem-def entry in docs/paper/reductions.typ - Example-db: canonical example with violation witness X={2} * chore: remove plan file after implementation * fix: address PR #685 review comments * fix: update BCNF canonical example spec to new ModelExampleSpec API Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Xiwei Pan <xiwei.pan@connect.hkust-gz.edu.cn> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Xiwei Pan <90967972+isPANN@users.noreply.github.com>
1 parent 482c75a commit 90f8733

8 files changed

Lines changed: 644 additions & 10 deletions

File tree

docs/paper/reductions.typ

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
"BalancedCompleteBipartiteSubgraph": [Balanced Complete Bipartite Subgraph],
104104
"BoundedComponentSpanningForest": [Bounded Component Spanning Forest],
105105
"BinPacking": [Bin Packing],
106+
"BoyceCoddNormalFormViolation": [Boyce-Codd Normal Form Violation],
106107
"ClosestVectorProblem": [Closest Vector Problem],
107108
"ConsecutiveSets": [Consecutive Sets],
108109
"MinimumMultiwayCut": [Minimum Multiway Cut],
@@ -2884,6 +2885,14 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76],
28842885
*Example.* Let $n = 6$ tasks, $m = 3$ processors, $r = 1$ resource with $B_1 = 20$, and deadline $D = 2$. Resource requirements: $R_1(t_1) = 6$, $R_1(t_2) = 7$, $R_1(t_3) = 7$, $R_1(t_4) = 6$, $R_1(t_5) = 8$, $R_1(t_6) = 6$. Schedule: slot 0 $arrow.l {t_1, t_2, t_3}$ (3 tasks, resource $= 20$), slot 1 $arrow.l {t_4, t_5, t_6}$ (3 tasks, resource $= 20$). Both constraints satisfied; answer: YES.
28852886
]
28862887

2888+
#problem-def("BoyceCoddNormalFormViolation")[
2889+
*Instance:* A set $A$ of attribute names, a collection $F$ of functional dependencies on $A$, and a subset $A' subset.eq A$.
2890+
2891+
*Question:* Is there a subset $X subset.eq A'$ and two attributes $y, z in A' backslash X$ such that $y in X^+$ but $z in.not X^+$, where $X^+$ is the closure of $X$ under $F$?
2892+
][
2893+
A relation satisfies _Boyce-Codd Normal Form_ (BCNF) if every non-trivial functional dependency $X arrow.r Y$ has $X$ as a superkey --- that is, $X^+$ = $A'$. This classical NP-complete problem from database theory asks whether the given attribute subset $A'$ violates BCNF. The NP-completeness was established by Beeri and Bernstein (1979) via reduction from Hitting Set. It appears as problem SR29 in Garey and Johnson's compendium (category A4: Storage and Retrieval).
2894+
]
2895+
28872896
#problem-def("SumOfSquaresPartition")[
28882897
Given a finite set $A = {a_0, dots, a_(n-1)}$ with sizes $s(a_i) in ZZ^+$, a positive integer $K lt.eq |A|$ (number of groups), and a positive integer $J$ (bound), determine whether $A$ can be partitioned into $K$ disjoint sets $A_1, dots, A_K$ such that $sum_(i=1)^K (sum_(a in A_i) s(a))^2 lt.eq J$.
28892898
][

problemreductions-cli/src/commands/create.rs

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ use problemreductions::models::graph::{
1414
MultipleChoiceBranching, SteinerTree, StrongConnectivityAugmentation,
1515
};
1616
use problemreductions::models::misc::{
17-
BinPacking, CbqRelation, ConjunctiveBooleanQuery, FlowShopScheduling, LongestCommonSubsequence,
18-
MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop, PartiallyOrderedKnapsack,
19-
QueryArg, RectilinearPictureCompression, ResourceConstrainedScheduling,
17+
BinPacking, BoyceCoddNormalFormViolation, CbqRelation, ConjunctiveBooleanQuery,
18+
FlowShopScheduling, LongestCommonSubsequence, MinimumTardinessSequencing,
19+
MultiprocessorScheduling, PaintShop, PartiallyOrderedKnapsack, QueryArg,
20+
RectilinearPictureCompression, ResourceConstrainedScheduling,
2021
SequencingToMinimizeMaximumCumulativeCost, SequencingWithReleaseTimesAndDeadlines,
2122
SequencingWithinIntervals, ShortestCommonSupersequence, StringToStringCorrection, SubsetSum,
2223
SumOfSquaresPartition,
@@ -154,6 +155,20 @@ fn format_problem_ref(problem: &ProblemRef) -> String {
154155
format!("{}/{}", problem.name, values)
155156
}
156157

158+
fn ensure_attribute_indices_in_range(
159+
indices: &[usize],
160+
num_attributes: usize,
161+
context: &str,
162+
) -> Result<()> {
163+
for &attr in indices {
164+
anyhow::ensure!(
165+
attr < num_attributes,
166+
"{context} contains attribute index {attr}, which is out of range for --n {num_attributes}"
167+
);
168+
}
169+
Ok(())
170+
}
171+
157172
fn resolve_example_problem_ref(
158173
input: &str,
159174
rgraph: &problemreductions::rules::ReductionGraph,
@@ -402,6 +417,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
402417
"--matrix \"1,1,0,0;1,1,0,0;0,0,1,1;0,0,1,1\" --k 2"
403418
}
404419
"SubsetSum" => "--sizes 3,7,1,8,2,4 --target 11",
420+
"BoyceCoddNormalFormViolation" => {
421+
"--n 6 --sets \"0,1:2;2:3;3,4:5\" --target 0,1,2,3,4,5"
422+
}
405423
"SumOfSquaresPartition" => "--sizes 5,3,8,2,7,1 --num-groups 3 --bound 240",
406424
"ComparativeContainment" => {
407425
"--universe 4 --r-sets \"0,1,2,3;0,1\" --s-sets \"0,1,2,3;2,3\" --r-weights 2,5 --s-weights 3,6"
@@ -1301,6 +1319,58 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
13011319
create_vertex_weight_problem(args, canonical, graph_type, &resolved_variant)?
13021320
}
13031321

1322+
// BoyceCoddNormalFormViolation
1323+
"BoyceCoddNormalFormViolation" => {
1324+
let n = args.n.ok_or_else(|| {
1325+
anyhow::anyhow!(
1326+
"BoyceCoddNormalFormViolation requires --n, --sets, and --target\n\n\
1327+
Usage: pred create BoyceCoddNormalFormViolation --n 6 --sets \"0,1:2;2:3;3,4:5\" --target 0,1,2,3,4,5"
1328+
)
1329+
})?;
1330+
let sets_str = args.sets.as_deref().ok_or_else(|| {
1331+
anyhow::anyhow!(
1332+
"BoyceCoddNormalFormViolation requires --sets (functional deps as lhs:rhs;...)\n\n\
1333+
Usage: pred create BoyceCoddNormalFormViolation --n 6 --sets \"0,1:2;2:3;3,4:5\" --target 0,1,2,3,4,5"
1334+
)
1335+
})?;
1336+
let target_str = args.target.as_deref().ok_or_else(|| {
1337+
anyhow::anyhow!(
1338+
"BoyceCoddNormalFormViolation requires --target (comma-separated attribute indices)\n\n\
1339+
Usage: pred create BoyceCoddNormalFormViolation --n 6 --sets \"0,1:2;2:3;3,4:5\" --target 0,1,2,3,4,5"
1340+
)
1341+
})?;
1342+
let fds: Vec<(Vec<usize>, Vec<usize>)> = sets_str
1343+
.split(';')
1344+
.map(|fd_str| {
1345+
let parts: Vec<&str> = fd_str.split(':').collect();
1346+
anyhow::ensure!(
1347+
parts.len() == 2,
1348+
"Each FD must be lhs:rhs, got '{}'",
1349+
fd_str
1350+
);
1351+
let lhs: Vec<usize> = util::parse_comma_list(parts[0])?;
1352+
let rhs: Vec<usize> = util::parse_comma_list(parts[1])?;
1353+
ensure_attribute_indices_in_range(
1354+
&lhs,
1355+
n,
1356+
&format!("Functional dependency '{fd_str}' lhs"),
1357+
)?;
1358+
ensure_attribute_indices_in_range(
1359+
&rhs,
1360+
n,
1361+
&format!("Functional dependency '{fd_str}' rhs"),
1362+
)?;
1363+
Ok((lhs, rhs))
1364+
})
1365+
.collect::<Result<_>>()?;
1366+
let target: Vec<usize> = util::parse_comma_list(target_str)?;
1367+
ensure_attribute_indices_in_range(&target, n, "Target subset")?;
1368+
(
1369+
ser(BoyceCoddNormalFormViolation::new(n, fds, target))?,
1370+
resolved_variant.clone(),
1371+
)
1372+
}
1373+
13041374
// BinPacking
13051375
"BinPacking" => {
13061376
let sizes_str = args.sizes.as_deref().ok_or_else(|| {
@@ -4007,6 +4077,7 @@ mod tests {
40074077
use super::help_flag_name;
40084078
use super::parse_bool_rows;
40094079
use super::*;
4080+
use super::{ensure_attribute_indices_in_range, problem_help_flag_name};
40104081
use crate::cli::{Cli, Commands};
40114082
use crate::output::OutputConfig;
40124083

@@ -4039,6 +4110,16 @@ mod tests {
40394110
);
40404111
}
40414112

4113+
#[test]
4114+
fn test_ensure_attribute_indices_in_range_rejects_out_of_range_index() {
4115+
let err = ensure_attribute_indices_in_range(&[0, 4], 3, "Functional dependency '0:4' rhs")
4116+
.unwrap_err();
4117+
assert!(
4118+
err.to_string().contains("out of range"),
4119+
"unexpected error: {err}"
4120+
);
4121+
}
4122+
40424123
#[test]
40434124
fn test_problem_help_uses_prime_attribute_name_cli_overrides() {
40444125
assert_eq!(

problemreductions-cli/tests/cli_tests.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4988,6 +4988,88 @@ fn test_create_factoring_missing_bits() {
49884988
);
49894989
}
49904990

4991+
#[test]
4992+
fn test_create_bcnf_rejects_out_of_range_attribute_indices() {
4993+
let output = pred()
4994+
.args([
4995+
"create",
4996+
"BoyceCoddNormalFormViolation",
4997+
"--n",
4998+
"3",
4999+
"--sets",
5000+
"0:4",
5001+
"--target",
5002+
"0,1,2",
5003+
])
5004+
.output()
5005+
.unwrap();
5006+
assert!(
5007+
!output.status.success(),
5008+
"expected invalid indices to be rejected"
5009+
);
5010+
let stderr = String::from_utf8_lossy(&output.stderr);
5011+
assert!(
5012+
!stderr.contains("panicked at"),
5013+
"CLI should return a user-facing error, got: {stderr}"
5014+
);
5015+
assert!(
5016+
stderr.contains("out of range"),
5017+
"expected out-of-range error, got: {stderr}"
5018+
);
5019+
}
5020+
5021+
#[test]
5022+
fn test_create_bcnf_rejects_out_of_range_lhs_attribute_indices() {
5023+
let output = pred()
5024+
.args([
5025+
"create",
5026+
"BoyceCoddNormalFormViolation",
5027+
"--n",
5028+
"3",
5029+
"--sets",
5030+
"4:0",
5031+
"--target",
5032+
"0,1,2",
5033+
])
5034+
.output()
5035+
.unwrap();
5036+
assert!(
5037+
!output.status.success(),
5038+
"expected invalid lhs indices to be rejected"
5039+
);
5040+
let stderr = String::from_utf8_lossy(&output.stderr);
5041+
assert!(
5042+
stderr.contains("lhs contains attribute index 4"),
5043+
"expected lhs-specific out-of-range error, got: {stderr}"
5044+
);
5045+
}
5046+
5047+
#[test]
5048+
fn test_create_bcnf_rejects_out_of_range_target_attribute_indices() {
5049+
let output = pred()
5050+
.args([
5051+
"create",
5052+
"BoyceCoddNormalFormViolation",
5053+
"--n",
5054+
"3",
5055+
"--sets",
5056+
"0:1",
5057+
"--target",
5058+
"0,1,4",
5059+
])
5060+
.output()
5061+
.unwrap();
5062+
assert!(
5063+
!output.status.success(),
5064+
"expected invalid target indices to be rejected"
5065+
);
5066+
let stderr = String::from_utf8_lossy(&output.stderr);
5067+
assert!(
5068+
stderr.contains("Target subset contains attribute index 4"),
5069+
"expected target-specific out-of-range error, got: {stderr}"
5070+
);
5071+
}
5072+
49915073
#[test]
49925074
fn test_create_sequencing_to_minimize_maximum_cumulative_cost() {
49935075
let output = pred()

src/lib.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ pub mod prelude {
6060
UndirectedTwoCommodityIntegralFlow,
6161
};
6262
pub use crate::models::misc::{
63-
BinPacking, CbqRelation, ConjunctiveBooleanQuery, ConjunctiveQueryFoldability, Factoring,
64-
FlowShopScheduling, Knapsack, LongestCommonSubsequence, MinimumTardinessSequencing,
65-
MultiprocessorScheduling, PaintShop, Partition, QueryArg, RectilinearPictureCompression,
66-
ResourceConstrainedScheduling, SequencingToMinimizeMaximumCumulativeCost,
67-
SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals,
68-
ShortestCommonSupersequence, StaffScheduling, StringToStringCorrection, SubsetSum,
69-
SumOfSquaresPartition, Term,
63+
BinPacking, BoyceCoddNormalFormViolation, CbqRelation, ConjunctiveBooleanQuery,
64+
ConjunctiveQueryFoldability, Factoring, FlowShopScheduling, Knapsack,
65+
LongestCommonSubsequence, MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop,
66+
Partition, QueryArg, RectilinearPictureCompression, ResourceConstrainedScheduling,
67+
SequencingToMinimizeMaximumCumulativeCost, SequencingWithReleaseTimesAndDeadlines,
68+
SequencingWithinIntervals, ShortestCommonSupersequence, StaffScheduling,
69+
StringToStringCorrection, SubsetSum, SumOfSquaresPartition, Term,
7070
};
7171
pub use crate::models::set::{
7272
ComparativeContainment, ConsecutiveSets, ExactCoverBy3Sets, MaximumSetPacking,

0 commit comments

Comments
 (0)