Skip to content

Commit 057025f

Browse files
GiggleLiuisPANNclaude
authored
Fix #444: [Model] MinimumCardinalityKey (#679)
* Add plan for #444: MinimumCardinalityKey model * Implement #444: MinimumCardinalityKey model - Add MinimumCardinalityKey satisfaction problem (set/ category) - Functional dependency closure under Armstrong's axioms - CLI create support with --num-attributes, --dependencies, --k flags - Paper entry with problem definition and worked example - 11 unit tests covering creation, evaluation, solver, serialization - Regenerate example-db fixtures * chore: remove plan file after implementation * Fix MinimumCardinalityKey review feedback * Fix formatting after merge conflict resolution Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add missing fields to empty_args() test helper after merge 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>
1 parent 08d5574 commit 057025f

11 files changed

Lines changed: 542 additions & 3 deletions

File tree

docs/paper/reductions.typ

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"MinimumSetCovering": [Minimum Set Covering],
8181
"ComparativeContainment": [Comparative Containment],
8282
"SetBasis": [Set Basis],
83+
"MinimumCardinalityKey": [Minimum Cardinality Key],
8384
"SpinGlass": [Spin Glass],
8485
"QUBO": [QUBO],
8586
"ILP": [Integer Linear Programming],
@@ -1518,6 +1519,29 @@ NP-completeness was established by Garey, Johnson, and Stockmeyer @gareyJohnsonS
15181519
]
15191520
}
15201521

1522+
#{
1523+
let x = load-model-example("MinimumCardinalityKey")
1524+
let n = x.instance.num_attributes
1525+
let deps = x.instance.dependencies
1526+
let m = deps.len()
1527+
let bound = x.instance.bound_k
1528+
let sample = x.samples.at(0)
1529+
let key-attrs = range(n).filter(i => sample.config.at(i) == 1)
1530+
let sat-count = x.optimal.len()
1531+
let fmt-set(s) = "${" + s.map(e => str(e)).join(", ") + "}$"
1532+
let fmt-fd(d) = fmt-set(d.at(0)) + " $arrow.r$ " + fmt-set(d.at(1))
1533+
[
1534+
#problem-def("MinimumCardinalityKey")[
1535+
Given a set $A$ of attribute names, a collection $F$ of functional dependencies (ordered pairs of subsets of $A$), and a positive integer $M$, does there exist a candidate key $K subset.eq A$ with $|K| <= M$, i.e., a minimal subset $K$ such that the closure of $K$ under $F^*$ equals $A$?
1536+
][
1537+
The Minimum Cardinality Key problem arises in relational database theory, where identifying the smallest candidate key determines the most efficient way to uniquely identify rows in a relation. It was shown NP-complete by Lucchesi and Osborn (1978) @lucchesi1978keys via transformation from Vertex Cover. The problem appears as SR26 in Garey & Johnson (A4) @garey1979. The closure $F^*$ is defined by Armstrong's axioms: reflexivity ($B subset.eq C$ implies $C arrow.r B$), transitivity, and union. The best known exact algorithm is brute-force enumeration of all subsets of $A$, giving $O^*(2^(|A|))$ time#footnote[Lucchesi and Osborn give an output-polynomial algorithm for enumerating all candidate keys, but the number of keys can be exponential.].
1538+
1539+
*Example.* Let $A = {0, 1, ..., #(n - 1)}$ ($|A| = #n$) with $M = #bound$ and functional dependencies $F = {#deps.enumerate().map(((i, d)) => fmt-fd(d)).join(", ")}$.
1540+
The candidate key $K = #fmt-set(key-attrs)$ has $|K| = #key-attrs.len() <= #bound$. Its closure: start with ${0, 1}$; apply ${0, 1} arrow.r {2}$ to get ${0, 1, 2}$; apply ${0, 2} arrow.r {3}$ to get ${0, 1, 2, 3}$; apply ${1, 3} arrow.r {4}$ to get ${0, 1, 2, 3, 4}$; apply ${2, 4} arrow.r {5}$ to get $A$. Neither ${0}$ nor ${1}$ alone determines $A$, so $K$ is minimal. There are #sat-count satisfying encodings in total.
1541+
]
1542+
]
1543+
}
1544+
15211545
== Optimization Problems
15221546

15231547
#{

docs/paper/references.bib

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,17 @@ @article{lucchesi1978
710710
doi = {10.1112/jlms/s2-17.3.369}
711711
}
712712

713+
@article{lucchesi1978keys,
714+
author = {Cl\'audio L. Lucchesi and Sylvia L. Osborn},
715+
title = {Candidate Keys for Relations},
716+
journal = {Journal of Computer and System Sciences},
717+
volume = {17},
718+
number = {2},
719+
pages = {270--279},
720+
year = {1978},
721+
doi = {10.1016/0022-0000(78)90009-0}
722+
}
723+
713724
@article{lenstra1976,
714725
author = {Jan Karel Lenstra and Alexander H. G. Rinnooy Kan},
715726
title = {On General Routing Problems},

docs/src/cli.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ pred create LengthBoundedDisjointPaths --graph 0-1,1-6,0-2,2-3,3-6,0-4,4-5,5-6 -
352352
pred create Factoring --target 15 --bits-m 4 --bits-n 4 -o factoring.json
353353
pred create Factoring --target 21 --bits-m 3 --bits-n 3 -o factoring2.json
354354
pred create X3C --universe 9 --sets "0,1,2;0,2,4;3,4,5;3,5,7;6,7,8;1,4,6;2,5,8" -o x3c.json
355+
pred create MinimumCardinalityKey --num-attributes 6 --dependencies "0,1>2;0,2>3;1,3>4;2,4>5" --k 2 -o mck.json
355356
pred create MinimumTardinessSequencing --n 5 --deadlines 5,5,5,3,3 --precedence-pairs "0>3,1>3,1>4,2>4" -o mts.json
356357
pred create StringToStringCorrection --source-string "0,1,2,3,1,0" --target-string "0,1,3,2,1" --bound 2 | pred solve - --solver brute-force
357358
pred create StrongConnectivityAugmentation --arcs "0>1,1>2,2>0,3>4,4>3,2>3,4>5,5>3" --candidate-arcs "3>0:5,3>1:3,3>2:4,4>0:6,4>1:2,4>2:7,5>0:4,5>1:3,5>2:1,0>3:8,0>4:3,0>5:2,1>3:6,1>4:4,1>5:5,2>4:3,2>5:7,1>0:2" --bound 1 -o sca.json
@@ -519,10 +520,17 @@ Source evaluation: Valid(2)
519520
520521
> **Note:** The ILP solver requires a reduction path from the target problem to ILP.
521522
> Some problems do not currently have one. Examples include BoundedComponentSpanningForest,
522-
> LengthBoundedDisjointPaths, QUBO, SpinGlass, MaxCut, CircuitSAT, and MultiprocessorScheduling.
523+
> LengthBoundedDisjointPaths, MinimumCardinalityKey, QUBO, SpinGlass, MaxCut, CircuitSAT, and MultiprocessorScheduling.
523524
> Use `pred solve <file> --solver brute-force` for these, or reduce to a problem that supports ILP first.
524525
> For other problems, use `pred path <PROBLEM> ILP` to check whether an ILP reduction path exists.
525526
527+
For example, the canonical Minimum Cardinality Key instance can be created and solved with:
528+
529+
```bash
530+
pred create MinimumCardinalityKey --num-attributes 6 --dependencies "0,1>2;0,2>3;1,3>4;2,4>5" --k 2 -o mck.json
531+
pred solve mck.json --solver brute-force
532+
```
533+
526534
## Shell Completions
527535
528536
Enable tab completion by adding one line to your shell config:

problemreductions-cli/src/cli.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ Flags by problem type:
238238
ComparativeContainment --universe, --r-sets, --s-sets [--r-weights] [--s-weights]
239239
X3C (ExactCoverBy3Sets) --universe, --sets (3 elements each)
240240
SetBasis --universe, --sets, --k
241+
MinimumCardinalityKey --num-attributes, --dependencies, --k
241242
BicliqueCover --left, --right, --biedges, --k
242243
BalancedCompleteBipartiteSubgraph --left, --right, --biedges, --k
243244
BiconnectivityAugmentation --graph, --potential-edges, --budget [--num-vertices]
@@ -286,7 +287,8 @@ Examples:
286287
pred create FVS --arcs \"0>1,1>2,2>0\" --weights 1,1,1
287288
pred create UndirectedTwoCommodityIntegralFlow --graph 0-2,1-2,2-3 --capacities 1,1,2 --source-1 0 --sink-1 3 --source-2 1 --sink-2 3 --requirement-1 1 --requirement-2 1
288289
pred create X3C --universe 9 --sets \"0,1,2;0,2,4;3,4,5;3,5,7;6,7,8;1,4,6;2,5,8\"
289-
pred create SetBasis --universe 4 --sets \"0,1;1,2;0,2;0,1,2\" --k 3")]
290+
pred create SetBasis --universe 4 --sets \"0,1;1,2;0,2;0,1,2\" --k 3
291+
pred create MinimumCardinalityKey --num-attributes 6 --dependencies \"0,1>2;0,2>3;1,3>4;2,4>5\" --k 2")]
290292
pub struct CreateArgs {
291293
/// Problem type (e.g., MIS, QUBO, SAT). Omit when using --example.
292294
#[arg(value_parser = crate::problem_name::ProblemNameParser)]
@@ -498,6 +500,12 @@ pub struct CreateArgs {
498500
/// Alphabet size for SCS or StringToStringCorrection (optional; inferred from max symbol + 1 if omitted)
499501
#[arg(long)]
500502
pub alphabet_size: Option<usize>,
503+
/// Functional dependencies for MinimumCardinalityKey (semicolon-separated "lhs>rhs" pairs, e.g., "0,1>2;0,2>3")
504+
#[arg(long)]
505+
pub dependencies: Option<String>,
506+
/// Number of attributes for MinimumCardinalityKey
507+
#[arg(long)]
508+
pub num_attributes: Option<usize>,
501509
/// Source string for StringToStringCorrection (comma-separated symbol indices, e.g., "0,1,2,3")
502510
#[arg(long)]
503511
pub source_string: Option<String>,

problemreductions-cli/src/commands/create.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ fn all_data_flags_empty(args: &CreateArgs) -> bool {
9494
&& args.requirements.is_none()
9595
&& args.num_workers.is_none()
9696
&& args.alphabet_size.is_none()
97+
&& args.dependencies.is_none()
98+
&& args.num_attributes.is_none()
9799
&& args.source_string.is_none()
98100
&& args.target_string.is_none()
99101
&& args.capacities.is_none()
@@ -239,6 +241,7 @@ fn type_format_hint(type_name: &str, graph_type: Option<&str>) -> &'static str {
239241
Some("UnitDiskGraph") => "float positions: \"0.0,0.0;1.0,0.0\"",
240242
_ => "edge list: 0-1,1-2,2-3",
241243
},
244+
"Vec<(Vec<usize>, Vec<usize>)>" => "semicolon-separated dependencies: \"0,1>2;0,2>3\"",
242245
"Vec<u64>" => "comma-separated integers: 4,5,3,2,6",
243246
"Vec<W>" => "comma-separated: 1,2,3",
244247
"Vec<usize>" => "comma-separated indices: 0,2,4",
@@ -329,6 +332,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
329332
"--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"
330333
}
331334
"SetBasis" => "--universe 4 --sets \"0,1;1,2;0,2;0,1,2\" --k 3",
335+
"MinimumCardinalityKey" => {
336+
"--num-attributes 6 --dependencies \"0,1>2;0,2>3;1,3>4;2,4>5\" --k 2"
337+
}
332338
"ShortestCommonSupersequence" => "--strings \"0,1,2;1,2,0\" --bound 4",
333339
"StringToStringCorrection" => {
334340
"--source-string \"0,1,2,3,1,0\" --target-string \"0,1,3,2,1\" --bound 2"
@@ -342,6 +348,7 @@ fn help_flag_name(canonical: &str, field_name: &str) -> String {
342348
match (canonical, field_name) {
343349
("BoundedComponentSpanningForest", "max_components") => return "k".to_string(),
344350
("BoundedComponentSpanningForest", "max_weight") => return "bound".to_string(),
351+
("MinimumCardinalityKey", "bound_k") => return "k".to_string(),
345352
("StaffScheduling", "shifts_per_schedule") => return "k".to_string(),
346353
_ => {}
347354
}
@@ -1223,6 +1230,33 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
12231230
)
12241231
}
12251232

1233+
// MinimumCardinalityKey
1234+
"MinimumCardinalityKey" => {
1235+
let num_attributes = args.num_attributes.ok_or_else(|| {
1236+
anyhow::anyhow!(
1237+
"MinimumCardinalityKey requires --num-attributes, --dependencies, and --k\n\n\
1238+
Usage: pred create MinimumCardinalityKey --num-attributes 6 --dependencies \"0,1>2;0,2>3;1,3>4;2,4>5\" --k 2"
1239+
)
1240+
})?;
1241+
let k = args.k.ok_or_else(|| {
1242+
anyhow::anyhow!("MinimumCardinalityKey requires --k (bound on key cardinality)")
1243+
})?;
1244+
let deps_str = args.dependencies.as_deref().ok_or_else(|| {
1245+
anyhow::anyhow!(
1246+
"MinimumCardinalityKey requires --dependencies (e.g., \"0,1>2;0,2>3\")"
1247+
)
1248+
})?;
1249+
let dependencies = parse_dependencies(deps_str)?;
1250+
(
1251+
ser(problemreductions::models::set::MinimumCardinalityKey::new(
1252+
num_attributes,
1253+
dependencies,
1254+
k,
1255+
))?,
1256+
resolved_variant.clone(),
1257+
)
1258+
}
1259+
12261260
// BicliqueCover
12271261
"BicliqueCover" => {
12281262
let usage = "pred create BicliqueCover --left 2 --right 2 --biedges 0-0,0-1,1-1 --k 2";
@@ -2328,6 +2362,39 @@ fn parse_named_sets(sets_str: Option<&str>, flag: &str) -> Result<Vec<Vec<usize>
23282362
.collect()
23292363
}
23302364

2365+
/// Parse `--dependencies` as semicolon-separated "lhs>rhs" pairs.
2366+
/// E.g., "0,1>2;0,2>3;1,3>4;2,4>5" means {0,1}->{2}, {0,2}->{3}, etc.
2367+
fn parse_dependencies(input: &str) -> Result<Vec<(Vec<usize>, Vec<usize>)>> {
2368+
fn parse_dependency_side(side: &str) -> Result<Vec<usize>> {
2369+
if side.trim().is_empty() {
2370+
return Ok(vec![]);
2371+
}
2372+
side.split(',')
2373+
.map(|s| {
2374+
s.trim()
2375+
.parse::<usize>()
2376+
.map_err(|e| anyhow::anyhow!("Invalid attribute index: {}", e))
2377+
})
2378+
.collect()
2379+
}
2380+
2381+
input
2382+
.split(';')
2383+
.map(|dep| {
2384+
let parts: Vec<&str> = dep.trim().split('>').collect();
2385+
if parts.len() != 2 {
2386+
bail!(
2387+
"Invalid dependency format: expected 'lhs>rhs', got '{}'",
2388+
dep.trim()
2389+
);
2390+
}
2391+
let lhs = parse_dependency_side(parts[0])?;
2392+
let rhs = parse_dependency_side(parts[1])?;
2393+
Ok((lhs, rhs))
2394+
})
2395+
.collect()
2396+
}
2397+
23312398
fn validate_comparative_containment_sets(
23322399
family_name: &str,
23332400
flag: &str,
@@ -3240,6 +3307,8 @@ mod tests {
32403307
deadline: None,
32413308
num_processors: None,
32423309
alphabet_size: None,
3310+
dependencies: None,
3311+
num_attributes: None,
32433312
source_string: None,
32443313
target_string: None,
32453314
schedules: None,

problemreductions-cli/tests/cli_tests.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,6 +1361,73 @@ fn test_create_set_basis_rejects_out_of_range_elements() {
13611361
assert!(!stderr.contains("panicked at"), "stderr: {stderr}");
13621362
}
13631363

1364+
#[test]
1365+
fn test_create_minimum_cardinality_key_problem_help_uses_supported_flags() {
1366+
let output = pred()
1367+
.args(["create", "MinimumCardinalityKey"])
1368+
.output()
1369+
.unwrap();
1370+
assert!(!output.status.success());
1371+
let stderr = String::from_utf8_lossy(&output.stderr);
1372+
assert!(stderr.contains("--num-attributes"), "stderr: {stderr}");
1373+
assert!(stderr.contains("--dependencies"), "stderr: {stderr}");
1374+
assert!(stderr.contains("--k"), "stderr: {stderr}");
1375+
assert!(
1376+
stderr.contains("semicolon-separated dependencies"),
1377+
"stderr: {stderr}"
1378+
);
1379+
assert!(!stderr.contains("--bound-k"), "stderr: {stderr}");
1380+
}
1381+
1382+
#[test]
1383+
fn test_create_minimum_cardinality_key_allows_empty_lhs_dependency() {
1384+
let output = pred()
1385+
.args([
1386+
"create",
1387+
"MinimumCardinalityKey",
1388+
"--num-attributes",
1389+
"1",
1390+
"--dependencies",
1391+
">0",
1392+
"--k",
1393+
"1",
1394+
])
1395+
.output()
1396+
.unwrap();
1397+
assert!(
1398+
output.status.success(),
1399+
"stderr: {}",
1400+
String::from_utf8_lossy(&output.stderr)
1401+
);
1402+
1403+
let stdout = String::from_utf8(output.stdout).unwrap();
1404+
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
1405+
assert_eq!(json["type"], "MinimumCardinalityKey");
1406+
assert_eq!(json["data"]["num_attributes"], 1);
1407+
assert_eq!(json["data"]["bound_k"], 1);
1408+
assert_eq!(json["data"]["dependencies"][0][0], serde_json::json!([]));
1409+
assert_eq!(json["data"]["dependencies"][0][1], serde_json::json!([0]));
1410+
}
1411+
1412+
#[test]
1413+
fn test_create_minimum_cardinality_key_missing_num_attributes_message() {
1414+
let output = pred()
1415+
.args([
1416+
"create",
1417+
"MinimumCardinalityKey",
1418+
"--dependencies",
1419+
"0>0",
1420+
"--k",
1421+
"1",
1422+
])
1423+
.output()
1424+
.unwrap();
1425+
assert!(!output.status.success());
1426+
let stderr = String::from_utf8_lossy(&output.stderr);
1427+
assert!(stderr.contains("MinimumCardinalityKey requires --num-attributes"));
1428+
assert!(!stderr.contains("--num-vertices"), "stderr: {stderr}");
1429+
}
1430+
13641431
#[test]
13651432
fn test_create_then_evaluate() {
13661433
// Create a problem

src/example_db/fixtures/examples.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
{"problem":"MaximumIndependentSet","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,0,null],[5,7,null],[7,9,null],[9,6,null],[6,8,null],[8,5,null],[0,5,null],[1,6,null],[2,7,null],[3,8,null],[4,9,null]],"node_holes":[],"nodes":[null,null,null,null,null,null,null,null,null,null]}},"weights":[5,1,1,1,1,3,1,1,1,3]},"samples":[{"config":[1,0,1,0,0,0,0,0,1,1],"metric":{"Valid":10}}],"optimal":[{"config":[1,0,1,0,0,0,0,0,1,1],"metric":{"Valid":10}}]},
2525
{"problem":"MaximumMatching","variant":{"graph":"SimpleGraph","weight":"i32"},"instance":{"edge_weights":[1,1,1,1,1,1],"graph":{"inner":{"edge_property":"undirected","edges":[[0,1,null],[0,2,null],[1,3,null],[2,3,null],[2,4,null],[3,4,null]],"node_holes":[],"nodes":[null,null,null,null,null]}}},"samples":[{"config":[1,0,0,0,1,0],"metric":{"Valid":2}}],"optimal":[{"config":[0,0,1,0,1,0],"metric":{"Valid":2}},{"config":[0,1,0,0,0,1],"metric":{"Valid":2}},{"config":[0,1,1,0,0,0],"metric":{"Valid":2}},{"config":[1,0,0,0,0,1],"metric":{"Valid":2}},{"config":[1,0,0,0,1,0],"metric":{"Valid":2}},{"config":[1,0,0,1,0,0],"metric":{"Valid":2}}]},
2626
{"problem":"MaximumSetPacking","variant":{"weight":"i32"},"instance":{"sets":[[0,1],[1,2],[2,3],[3,4]],"weights":[1,1,1,1]},"samples":[{"config":[1,0,1,0],"metric":{"Valid":2}}],"optimal":[{"config":[0,1,0,1],"metric":{"Valid":2}},{"config":[1,0,0,1],"metric":{"Valid":2}},{"config":[1,0,1,0],"metric":{"Valid":2}}]},
27+
{"problem":"MinimumCardinalityKey","variant":{},"instance":{"bound_k":2,"dependencies":[[[0,1],[2]],[[0,2],[3]],[[1,3],[4]],[[2,4],[5]]],"num_attributes":6},"samples":[{"config":[1,1,0,0,0,0],"metric":true}],"optimal":[{"config":[1,1,0,0,0,0],"metric":true}]},
2728
{"problem":"MinimumDominatingSet","variant":{"graph":"SimpleGraph","weight":"i32"},"instance":{"graph":{"inner":{"edge_property":"undirected","edges":[[0,1,null],[0,2,null],[1,3,null],[2,3,null],[2,4,null],[3,4,null]],"node_holes":[],"nodes":[null,null,null,null,null]}},"weights":[1,1,1,1,1]},"samples":[{"config":[0,0,1,1,0],"metric":{"Valid":2}}],"optimal":[{"config":[0,0,1,1,0],"metric":{"Valid":2}},{"config":[0,1,0,0,1],"metric":{"Valid":2}},{"config":[0,1,0,1,0],"metric":{"Valid":2}},{"config":[0,1,1,0,0],"metric":{"Valid":2}},{"config":[1,0,0,0,1],"metric":{"Valid":2}},{"config":[1,0,0,1,0],"metric":{"Valid":2}},{"config":[1,0,1,0,0],"metric":{"Valid":2}}]},
2829
{"problem":"MinimumFeedbackVertexSet","variant":{"weight":"i32"},"instance":{"graph":{"inner":{"edge_property":"directed","edges":[[0,1,null],[1,2,null],[2,0,null],[0,3,null],[3,4,null],[4,1,null],[4,2,null]],"node_holes":[],"nodes":[null,null,null,null,null]}},"weights":[1,1,1,1,1]},"samples":[{"config":[1,0,0,0,0],"metric":{"Valid":1}}],"optimal":[{"config":[0,0,1,0,0],"metric":{"Valid":1}},{"config":[1,0,0,0,0],"metric":{"Valid":1}}]},
2930
{"problem":"MinimumMultiwayCut","variant":{"graph":"SimpleGraph","weight":"i32"},"instance":{"edge_weights":[2,3,1,2,4,5],"graph":{"inner":{"edge_property":"undirected","edges":[[0,1,null],[1,2,null],[2,3,null],[3,4,null],[0,4,null],[1,3,null]],"node_holes":[],"nodes":[null,null,null,null,null]}},"terminals":[0,2,4]},"samples":[{"config":[1,0,0,1,1,0],"metric":{"Valid":8}}],"optimal":[{"config":[1,0,0,1,1,0],"metric":{"Valid":8}}]},

src/models/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,6 @@ pub use misc::{
2727
ShortestCommonSupersequence, StaffScheduling, StringToStringCorrection, SubsetSum,
2828
};
2929
pub use set::{
30-
ComparativeContainment, ExactCoverBy3Sets, MaximumSetPacking, MinimumSetCovering, SetBasis,
30+
ComparativeContainment, ExactCoverBy3Sets, MaximumSetPacking, MinimumCardinalityKey,
31+
MinimumSetCovering, SetBasis,
3132
};

0 commit comments

Comments
 (0)