Skip to content

Commit fbf7a76

Browse files
authored
Merge pull request #1209 from CodingThrust/fix/minimum-vertex-cover-unit-weight-cast
Complete numeric variant conversions and audit missing edges
2 parents 56ea1b1 + fbce255 commit fbf7a76

29 files changed

Lines changed: 933 additions & 81 deletions

‎.claude/CLAUDE.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ fields to issue templates. Changes to issue templates require user approval.
228228
### Paper (docs/paper/reductions.typ)
229229
- `problem-def(name)[def][body]` — defines a problem with auto-generated schema, reductions list, and label `<def:ProblemName>`. Title comes from `display-name` dict.
230230
- `reduction-rule(source, target, example: bool, ...)[rule][proof]` — generates a theorem with label `<thm:Source-to-Target>` and registers in `covered-rules` state. Overhead auto-derived from JSON edge data.
231-
- Every directed reduction needs its own `reduction-rule` entry (except trivial Decision↔Optimization pairs which are auto-filtered)
231+
- Every directed reduction needs its own `reduction-rule` entry (except trivial Decision↔Optimization pairs and pure `One → i64 → f64` numeric casts, which are auto-filtered)
232232
- Completeness warnings auto-check that all JSON graph nodes/edges are covered in the paper; `Decision<P> ↔ P` edges are excluded since they are trivial solve-and-compare reductions
233233
- `display-name` dict maps `ProblemName` to display text
234234

@@ -302,7 +302,7 @@ Also add to the `display-name` dictionary:
302302
]
303303
```
304304

305-
Every directed reduction in the graph needs its own `reduction-rule` entry. The paper auto-checks completeness against the generated `reduction_graph.json` export.
305+
Every nontrivial directed reduction in the graph needs its own `reduction-rule` entry. Do not add paper explanations for pure `One → i64 → f64` numeric casts. Other same-model variant reductions, such as graph transformations and bounded-integer encodings, still require entries. The paper auto-checks completeness against the generated `reduction_graph.json` export.
306306

307307
## Complexity Verification Requirements
308308

‎.claude/skills/how-to-code/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ Traits and helpers: `src/rules/traits.rs`, `src/rules/test_helpers.rs`, `src/rul
9595
and parameter counts vs the transform (reuse existing inputs; the shared executable-rule check lives in `src/unit_tests/parameter_formula_validation.rs`), and one test per malformed representation the decoder
9696
rejects (zero/multiple one-hot bits, duplicate permutation entries, ...). Aggregate edges: test
9797
`extract_value` against `BruteForce::solve` on both sides.
98-
5. Paper `reduction-rule` entry — how-to-write-manual (adapt the how-to-verify proof, don't rewrite).
98+
5. Paper `reduction-rule` entry — how-to-write-manual (adapt the how-to-verify proof, don't rewrite). Pure `One -> i64 -> f64` numeric casts are exempt; other same-model variant reductions still need entries.
9999

100100
## Traps
101101

‎.claude/skills/how-to-write-manual/SKILL.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ The paper `docs/paper/reductions.typ` is the mathematical manual: one `problem-d
2424
- `reduction-rule(source, target, example:, example-source-variant:, example-target-variant:,
2525
example-caption:, extra:)[statement][proof]` auto-derives overhead from the graph export and
2626
registers the edge for the completeness check; label `<thm:Source-to-Target>`.
27-
- Every directed reduction needs its own entry, including reverses. Only `Decision<P> <-> P` pairs are
28-
exempt. The completeness warning block at the end of the paper lists missing edges; your change
27+
- Every directed reduction needs its own entry, including reverses. `Decision<P> <-> P` pairs and pure `One -> i64 -> f64` numeric casts are
28+
exempt; do not explain numeric casts in the paper. Nontrivial same-model variant reductions still require entries. The completeness warning block at the end of the paper lists missing edges; your change
2929
must never increase it.
3030

3131
## Example data — never invent

‎docs/paper/reductions.typ‎

Lines changed: 25 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3287,7 +3287,7 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V|
32873287
]
32883288
}
32893289
#{
3290-
let x = load-model-example("SteinerTree")
3290+
let x = load-model-example("SteinerTree", variant: (graph: "SimpleGraph", weight: "i64"))
32913291
let nv = graph-num-vertices(x.instance)
32923292
let ne = graph-num-edges(x.instance)
32933293
let edges = x.instance.graph.edges
@@ -4451,7 +4451,7 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V|
44514451
}
44524452

44534453
#{
4454-
let x = load-model-example("ComparativeContainment")
4454+
let x = load-model-example("ComparativeContainment", variant: (weight: "i64"))
44554455
let n = x.instance.universe_size
44564456
let R = x.instance.r_sets
44574457
let S = x.instance.s_sets
@@ -4994,7 +4994,7 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V|
49944994
#problem-def("QUBO")[
49954995
Given $n$ binary variables $x_i in {0, 1}$ and an upper-triangular matrix $Q$, minimize $f(bold(x)) = sum_(i=1)^n Q_(i i) x_i + sum_(i < j) Q_(i j) x_i x_j$ (using $x_i^2 = x_i$ for binary variables). The registered variants use either exact integer coefficients $Q in ZZ^(n times n)$ (the default) or finite floating-point coefficients $Q in RR^(n times n)$.
49964996
][
4997-
Equivalent to the Ising model via the linear substitution $s_i = 2x_i - 1$. The native formulation for quantum annealing hardware (e.g., D-Wave) and a standard target for penalty-method reductions @glover2019. QUBO unifies many combinatorial problems into a single unconstrained binary framework, making it a universal intermediate representation for quantum and classical optimization. The integer variant stores exact coefficients, the floating-point variant stores finite real coefficients, and their explicit reduction converts exactly representable integers through `i64_to_exact_f64`. The best known general algorithm runs in $O^*(2^n)$ by brute-force enumeration#footnote[QUBO inherits the Ising model's complexity; no algorithm improving on brute-force is known for the general case.].
4997+
Equivalent to the Ising model via the linear substitution $s_i = 2x_i - 1$. The native formulation for quantum annealing hardware (e.g., D-Wave) and a standard target for penalty-method reductions @glover2019. QUBO unifies many combinatorial problems into a single unconstrained binary framework, making it a universal intermediate representation for quantum and classical optimization. The integer variant stores exact coefficients, the floating-point variant stores finite real coefficients. The best known general algorithm runs in $O^*(2^n)$ by brute-force enumeration#footnote[QUBO inherits the Ising model's complexity; no algorithm improving on brute-force is known for the general case.].
49984998

49994999
*Example.* Consider $n = #n$ with $Q = mat(#mat-rows)$. The objective is $f(bold(x)) = -x_1 - x_2 - x_3 + 2x_1 x_2 + 2x_2 x_3$. Evaluating all $2^#n$ assignments: $f(0,0,0) = 0$, $f(1,0,0) = -1$, $f(0,1,0) = -1$, $f(0,0,1) = -1$, $f(1,1,0) = 0$, $f(0,1,1) = 0$, $f(1,0,1) = -2$, $f(1,1,1) = 1$. The minimum is $f^* = #fstar$ at $bold(x)^* = (#fmt-values(xstar))$: selecting #selected.join(" and ") avoids the penalty terms #unselected-pairs.join(" and ").
50005000

@@ -16827,15 +16827,14 @@ See #link("https://github.com/CodingThrust/problem-reductions/blob/main/examples
1682716827
Problems parameterized by graph type, weight type, target type, or clause width ($k$) use explicit reductions between registered variants. Each rule constructs the target representation, registers an exact size map, and preserves the witness representation.
1682816828

1682916829
#reduction-rule("MaximumIndependentSet", "MaximumIndependentSet")[
16830-
Explicit MIS variant reductions convert King's and triangular lattice graphs to unit-disk graphs, unit-disk graphs to simple adjacency graphs, and unit weights to integer weights. Every rule preserves the vertex indices and has an exact identity size map.
16830+
Explicit MIS variant reductions convert King's and triangular lattice graphs to unit-disk graphs, and unit-disk graphs to simple adjacency graphs. Every rule preserves the vertex indices and has an exact identity size map.
1683116831
][
1683216832
_Construction._ The registered rules use these concrete maps:
1683316833
- _KingsSubgraph $arrow.r$ UnitDiskGraph:_ integer grid positions $(i, j)$ map to float coordinates with radius $r = 1.5$.
1683416834
- _TriangularSubgraph $arrow.r$ UnitDiskGraph:_ triangular lattice positions map to float coordinates with radius $r = 1.1$.
1683516835
- _UnitDiskGraph $arrow.r$ SimpleGraph:_ discard coordinates, retain only the adjacency edge list.
16836-
- _One $arrow.r$ i64:_ each unit weight maps to $1_ZZ$.
1683716836

16838-
_Correctness._ Each graph conversion preserves the adjacency relation, and the weight conversion preserves every unit objective contribution as the integer one. Since the MIS objective $max sum_(v in S) w(v)$ depends only on adjacency and weights, feasible sets and their objective values are preserved.
16837+
_Correctness._ Each graph conversion preserves the adjacency relation. Since the MIS objective $max sum_(v in S) w(v)$ depends only on adjacency and weights, feasible sets and their objective values are preserved.
1683916838

1684016839
_Solution extraction._ Return the target configuration unchanged (identity map on vertices).
1684116840
]
@@ -16860,44 +16859,27 @@ Problems parameterized by graph type, weight type, target type, or clause width
1686016859
_Solution extraction._ Return the target configuration unchanged.
1686116860
]
1686216861

16863-
#reduction-rule("SpinGlass", "SpinGlass")[
16864-
An Ising spin-glass instance with integer couplings and fields ($J_(i j), h_i in ZZ$) converts to the floating-point variant ($J_(i j), h_i in RR$) through exact `i64_to_exact_f64` embeddings. The graph topology is preserved.
16865-
][
16866-
_Construction._ Given $"SpinGlass"(G, bold(J), bold(h))$ with $J_(i j) in ZZ$ and $h_i in ZZ$, construct $"SpinGlass"(G, bold(J)', bold(h)')$ with $J'_(i j) = J_(i j) in RR$ and $h'_i = h_i in RR$.
16867-
16868-
_Correctness._ The spin-glass Hamiltonian $H(bold(s)) = sum_((i,j) in E) J_(i j) s_i s_j + sum_i h_i s_i$ is preserved exactly under the integer-to-float embedding (no rounding). Spin configurations and the objective value are unchanged.
16869-
16870-
_Solution extraction._ Return the target configuration unchanged.
16871-
]
16872-
16873-
#reduction-rule("MaximumSetPacking", "MaximumSetPacking")[
16874-
A Maximum Set Packing instance converts unit weights to integer weights ($"One" arrow.r ZZ$) and integer weights to exactly represented floating weights ($ZZ arrow.r RR$). The set family and universe are preserved.
16875-
][
16876-
_Construction._ Given $"MSP"(cal(S), bold(w))$, construct $"MSP"(cal(S), bold(w)')$ using:
16877-
- _One $arrow.r$ i64:_ each unit weight maps to $1_ZZ$.
16878-
- _i64 $arrow.r$ f64:_ each integer weight maps through `i64_to_exact_f64`.
16879-
16880-
_Correctness._ The packing constraint (no two selected sets share a universe element) depends only on set membership, not on weights. The objective $max sum_(i in P) w_i$ is preserved under the type embedding. Optimality is unchanged.
16881-
16882-
_Solution extraction._ Return the target configuration unchanged.
16883-
]
16884-
16885-
16886-
#reduction-rule("QUBO", "QUBO")[
16887-
An integer QUBO converts to the floating-coefficient variant by embedding every matrix coefficient with `i64_to_exact_f64`.
16888-
][
16889-
_Construction._ Given $Q in ZZ^(n times n)$, construct $Q' in RR^(n times n)$ with $Q'_(i j) = "f64"(Q_(i j))$ for every exactly representable coefficient $|Q_(i j)| lt.eq 2^53 - 1$.
16890-
16891-
_Correctness._ For every binary vector $bold(x)$, exact coefficient conversion gives $bold(x)^top Q' bold(x) = bold(x)^top Q bold(x)$. The objective ordering and minimizers are preserved.
16892-
16893-
_Solution extraction._ Return the binary vector unchanged.
16894-
]
1689516862

1689616863
// Completeness check: warn about reduction rules in JSON but missing from paper
1689716864
#context {
1689816865
let covered = covered-rules.final()
1689916866
let json-edges = {
16900-
let edges = graph-data.edges.map(e => (graph-data.nodes.at(e.source).name, graph-data.nodes.at(e.target).name))
16867+
let is-numeric-cast(src, tgt) = {
16868+
src.name == tgt.name and ("weight", "coefficient").any(field => {
16869+
let from = src.variant.at(field, default: none)
16870+
let to = tgt.variant.at(field, default: none)
16871+
let promotion = (from == "One" and to == "i64") or (from == "i64" and to == "f64")
16872+
let promoted = src.variant
16873+
promoted.insert(field, to)
16874+
promotion and promoted == tgt.variant
16875+
})
16876+
}
16877+
let source = (name: "MaximumIndependentSet", variant: (graph: "SimpleGraph", weight: "One"))
16878+
assert(is-numeric-cast(source, (name: source.name, variant: (graph: "SimpleGraph", weight: "i64"))))
16879+
assert(not is-numeric-cast(source, (name: source.name, variant: (graph: "TriangularSubgraph", weight: "i64"))))
16880+
let edges = graph-data.edges.filter(e => {
16881+
not is-numeric-cast(graph-data.nodes.at(e.source), graph-data.nodes.at(e.target))
16882+
}).map(e => (graph-data.nodes.at(e.source).name, graph-data.nodes.at(e.target).name))
1690116883
let unique = ()
1690216884
for e in edges {
1690316885
if unique.find(u => u.at(0) == e.at(0) and u.at(1) == e.at(1)) == none {
@@ -17149,13 +17131,13 @@ The following table shows concrete target-variable counts for example instances,
1714917131
// === Non-ILP reduction rules ===
1715017132

1715117133
#reduction-rule("ILP", "ILP")[
17152-
ILP variants convert between binary and bounded integer variable domains and between exact-integer and floating-point coefficients. The independent bounds dimension defaults to general; the bounded integer variant requires finite lower and upper endpoints for every variable. Binary variables embed into bounded integer ILP, which embeds into general integer ILP while retaining all stored bounds. A bounded integer variable is encoded by binary variables with truncated positional weights. Integer coefficients are embedded only when every stored coefficient and right-hand side has an exact `f64` representation.
17134+
ILP variants convert between binary and bounded integer variable domains. The independent bounds dimension defaults to general; the bounded integer variant requires finite lower and upper endpoints for every variable. Binary variables embed into bounded integer ILP, which embeds into general integer ILP while retaining all stored bounds. A bounded integer variable is encoded by binary variables with truncated positional weights.
1715317135
][
17154-
_Construction._ For the binary-to-bounded-integer and bounded-to-general edges, copy the variables, constraints, objective, and optimization direction unchanged. For an integer variable $x_i in [L_i, U_i]$, let $D_i = U_i - L_i$ and choose positive truncated binary weights $w_(i j)$ whose subset sums represent every integer from $0$ through $D_i$; substitute $x_i = L_i + sum_j w_(i j)y_(i j)$ into every constraint and objective term. This edge is registered only for the bounded integer variant; its constructor requires two finite bounds for every variable. For the coefficient edge, copy the variable bounds and optimization direction and convert each entry of the constraint matrix, right-hand side, and objective independently; reject the instance if any integer lies outside the exactly representable `f64` integer range.
17136+
_Construction._ For the binary-to-bounded-integer and bounded-to-general edges, copy the variables, constraints, objective, and optimization direction unchanged. For an integer variable $x_i in [L_i, U_i]$, let $D_i = U_i - L_i$ and choose positive truncated binary weights $w_(i j)$ whose subset sums represent every integer from $0$ through $D_i$; substitute $x_i = L_i + sum_j w_(i j)y_(i j)$ into every constraint and objective term. This edge is registered only for the bounded integer variant; its constructor requires two finite bounds for every variable.
1715517137

17156-
_Correctness._ The binary-to-bounded-integer and bounded-to-general embeddings change no mathematical expression. For bounded integer variables, every $x_i in [L_i,U_i]$ has a truncated binary representation, and every binary assignment decodes inside that interval; substitution preserves all constraints and changes the stored objective only by the constant lower-bound contribution, so optimal assignments are preserved. Exact conversion preserves every stored coefficient, so it constructs the same formal linear objective and constraints over the same integer variables.
17138+
_Correctness._ The binary-to-bounded-integer and bounded-to-general embeddings change no mathematical expression. For bounded integer variables, every $x_i in [L_i,U_i]$ has a truncated binary representation, and every binary assignment decodes inside that interval; substitution preserves all constraints and changes the stored objective only by the constant lower-bound contribution, so optimal assignments are preserved.
1715717139

17158-
_Solution extraction._ Binary-to-bounded-integer, bounded-to-general, and coefficient conversions preserve the assignment; coefficient conversion additionally checks the assignment against the source integer ILP. Binary encoding returns $x_i = L_i + sum_j w_(i j)y_(i j)$.
17140+
_Solution extraction._ Binary-to-bounded-integer and bounded-to-general conversions preserve the assignment. Binary encoding returns $x_i = L_i + sum_j w_(i j)y_(i j)$.
1715917141
]
1716017142

1716117143
#let hc_hp = load-example("HamiltonianCircuit", "HamiltonianPath")

‎src/models/graph/steiner_tree.rs‎

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -364,19 +364,30 @@ crate::register_brute_force! {
364364

365365
#[cfg(feature = "example-db")]
366366
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
367-
vec![crate::example_db::specs::ModelExampleSpec {
368-
id: "steiner_tree_simplegraph",
369-
instance: Box::new(SteinerTree::new(
370-
SimpleGraph::new(
371-
5,
372-
vec![(0, 1), (0, 3), (1, 2), (1, 3), (2, 3), (2, 4), (3, 4)],
373-
),
374-
vec![2, 5, 2, 1, 5, 6, 1],
375-
vec![0, 2, 4],
376-
)),
377-
optimal_config: serde_json::json!(vec![true, false, true, true, false, false, true]),
378-
optimal_value: serde_json::json!(6),
379-
}]
367+
vec![
368+
crate::example_db::specs::ModelExampleSpec {
369+
id: "steiner_tree_simplegraph",
370+
instance: Box::new(SteinerTree::new(
371+
SimpleGraph::new(
372+
5,
373+
vec![(0, 1), (0, 3), (1, 2), (1, 3), (2, 3), (2, 4), (3, 4)],
374+
),
375+
vec![2, 5, 2, 1, 5, 6, 1],
376+
vec![0, 2, 4],
377+
)),
378+
optimal_config: serde_json::json!(vec![true, false, true, true, false, false, true]),
379+
optimal_value: serde_json::json!(6),
380+
},
381+
crate::example_db::specs::ModelExampleSpec {
382+
id: "steiner_tree_one",
383+
instance: Box::new(SteinerTree::<SimpleGraph, One>::unit_weights(
384+
SimpleGraph::path(3),
385+
vec![0, 2],
386+
)),
387+
optimal_config: serde_json::json!([true, true]),
388+
optimal_value: serde_json::json!(2),
389+
},
390+
]
380391
}
381392

382393
#[cfg(test)]

‎src/models/set/comparative_containment.rs‎

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -366,21 +366,31 @@ fn contains_selected_subset_unchecked(config: &[bool], set: &[usize]) -> bool {
366366

367367
#[cfg(feature = "example-db")]
368368
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
369-
vec![crate::example_db::specs::ModelExampleSpec {
370-
id: "comparative_containment",
371-
instance: Box::new(
372-
ComparativeContainment::with_weights(
373-
4,
374-
vec![vec![0, 1, 2, 3], vec![0, 1]],
375-
vec![vec![0, 1, 2, 3], vec![2, 3]],
376-
vec![2, 5],
377-
vec![3, 6],
378-
)
379-
.expect("canonical comparative-containment instance must be valid"),
380-
),
381-
optimal_config: serde_json::json!(vec![false, true, false, false]),
382-
optimal_value: serde_json::json!(true),
383-
}]
369+
vec![
370+
crate::example_db::specs::ModelExampleSpec {
371+
id: "comparative_containment",
372+
instance: Box::new(
373+
ComparativeContainment::with_weights(
374+
4,
375+
vec![vec![0, 1, 2, 3], vec![0, 1]],
376+
vec![vec![0, 1, 2, 3], vec![2, 3]],
377+
vec![2, 5],
378+
vec![3, 6],
379+
)
380+
.expect("canonical comparative-containment instance must be valid"),
381+
),
382+
optimal_config: serde_json::json!(vec![false, true, false, false]),
383+
optimal_value: serde_json::json!(true),
384+
},
385+
crate::example_db::specs::ModelExampleSpec {
386+
id: "comparative_containment_one",
387+
instance: Box::new(
388+
ComparativeContainment::<One>::new(2, vec![vec![0]], vec![vec![1]]).unwrap(),
389+
),
390+
optimal_config: serde_json::json!([true, false]),
391+
optimal_value: serde_json::json!(true),
392+
},
393+
]
384394
}
385395

386396
#[cfg(test)]

0 commit comments

Comments
 (0)