Skip to content

Commit 172815a

Browse files
authored
Replace reduction size contracts and add path size reporting (#1130)
* feat: add exact symbolic size maps * feat: add certified symbolic size bounds * feat: replace reduction size metadata with explicit contracts * fix: separate symbolic path discovery from size contracts * refactor: simplify symbolic path reporting * feat: execute path analysis on complete instances * docs: simplify reduction prompt language * docs: simplify user-facing size analysis language * refactor: remove undefined coefficient size field * fix: unify symbolic path reporting * test: align LongestCircuit MCP expectations
1 parent 595e12d commit 172815a

312 files changed

Lines changed: 5175 additions & 7013 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Makefile

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -295,17 +295,12 @@ cli-demo: cli
295295
$$PRED from QUBO --hops 1; \
296296
\
297297
echo ""; \
298-
echo "--- 5. path: asymptotic Pareto front ---"; \
298+
echo "--- 5. path: symbolic path enumeration ---"; \
299299
$$PRED path MIS QUBO; \
300300
$$PRED path Factoring SpinGlass; \
301-
echo "--- 5b. explicitly choose one semantic route from the Pareto front ---"; \
302-
$$PRED path MIS QUBO -o $(CLI_DEMO_DIR)/front_mis_qubo.json; \
303-
jq -e 'first(.front[] | select(([.path[0].from.name] + [.path[].to.name]) == ["MaximumIndependentSet", "MaximumIndependentSet", "MaximumSetPacking", "MaximumSetPacking", "QUBO"]))' $(CLI_DEMO_DIR)/front_mis_qubo.json > $(CLI_DEMO_DIR)/path_mis_qubo.json; \
304-
\
305-
echo ""; \
306-
echo "--- 6. path --all: enumerate all paths ---"; \
307-
$$PRED path MIS QUBO --all; \
308-
$$PRED path MIS QUBO --all -o $(CLI_DEMO_DIR)/all_paths/; \
301+
echo "--- 5b. explicitly choose one route from the path set ---"; \
302+
$$PRED path MIS QUBO -o $(CLI_DEMO_DIR)/paths_mis_qubo.json; \
303+
jq -e 'first(.paths[] | select(([.path[0].from.name] + [.path[].to.name]) == ["MaximumIndependentSet", "MaximumIndependentSet", "MaximumSetPacking", "MaximumSetPacking", "QUBO"]))' $(CLI_DEMO_DIR)/paths_mis_qubo.json > $(CLI_DEMO_DIR)/path_mis_qubo.json; \
309304
\
310305
echo ""; \
311306
echo "--- 7. export-graph: full reduction graph ---"; \
@@ -314,7 +309,7 @@ cli-demo: cli
314309
echo ""; \
315310
echo "--- 8. create: build problem instances ---"; \
316311
$$PRED create MIS --graph 0-1,1-2,2-3,3-4,4-0 -o $(CLI_DEMO_DIR)/mis.json; \
317-
$$PRED create MIS --graph 0-1,1-2,2-3 --weights 2,1,3,1 -o $(CLI_DEMO_DIR)/mis_weighted.json; \
312+
$$PRED create MaximumIndependentSet/SimpleGraph/i32 --graph 0-1,1-2,2-3 --weights 2,1,3,1 -o $(CLI_DEMO_DIR)/mis_weighted.json; \
318313
$$PRED create SAT --num-vars 3 --clauses "1,2;-1,3;2,-3" -o $(CLI_DEMO_DIR)/sat.json; \
319314
$$PRED create 3SAT --num-vars 4 --clauses "1,2,3;-1,2,-3;1,-2,3" -o $(CLI_DEMO_DIR)/3sat.json; \
320315
$$PRED create QUBO --matrix "1,-0.5;-0.5,2" -o $(CLI_DEMO_DIR)/qubo.json; \
@@ -347,7 +342,7 @@ cli-demo: cli
347342
$$PRED solve $(CLI_DEMO_DIR)/mis_weighted.json; \
348343
\
349344
echo ""; \
350-
echo "--- 13. reduce: MIS → QUBO along the chosen Pareto route ---"; \
345+
echo "--- 13. reduce: MIS → QUBO along the explicitly chosen route ---"; \
351346
$$PRED reduce $(CLI_DEMO_DIR)/mis.json --via $(CLI_DEMO_DIR)/path_mis_qubo.json -o $(CLI_DEMO_DIR)/bundle_qubo.json; \
352347
\
353348
echo ""; \
@@ -360,8 +355,8 @@ cli-demo: cli
360355
\
361356
echo ""; \
362357
echo "--- 16. solve bundle with ILP: MIS → MVC → ILP ---"; \
363-
$$PRED path MIS MVC -o $(CLI_DEMO_DIR)/front_mis_mvc.json; \
364-
jq -e 'first(.front[] | select(([.path[0].from.name] + [.path[].to.name]) == ["MaximumIndependentSet", "MaximumIndependentSet", "MinimumVertexCover"]))' $(CLI_DEMO_DIR)/front_mis_mvc.json > $(CLI_DEMO_DIR)/path_mis_mvc.json; \
358+
$$PRED path MIS MVC -o $(CLI_DEMO_DIR)/paths_mis_mvc.json; \
359+
jq -e 'first(.paths[] | select(([.path[0].from.name] + [.path[].to.name]) == ["MaximumIndependentSet", "MaximumIndependentSet", "MinimumVertexCover"]))' $(CLI_DEMO_DIR)/paths_mis_mvc.json > $(CLI_DEMO_DIR)/path_mis_mvc.json; \
365360
$$PRED reduce $(CLI_DEMO_DIR)/mis.json --via $(CLI_DEMO_DIR)/path_mis_mvc.json -o $(CLI_DEMO_DIR)/bundle_mvc.json; \
366361
$$PRED solve $(CLI_DEMO_DIR)/bundle_mvc.json --solver ilp; \
367362
\

docs/agent-profiles/FEATURES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
- [Reduction Graph] — Automatic shortest-path search through registered reductions between problem types
77
- [BruteForce Solver] — Enumerate all configurations to find optimal or satisfying solutions
88
- [Variant System] — Graph/weight type parameterization with compile-time complexity registration
9-
- [Overhead System]Symbolic expressions describing how target problem size relates to source after reduction
9+
- [Size Analysis]Explain how problem size changes along a path and measure complete instances
1010
- [Serialization] — JSON schema export and serde-based serialization for all problem types

docs/agent-profiles/pred-sym-prof-yuki-tanaka.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pred-sym (symbolic expression CLI)
66
## Use Case
77
Three combined scenarios:
88
1. **Complexity comparison** — Compare algorithm complexity expressions to determine asymptotic equivalence (e.g., O(n^2 + n) == O(n^2), O(n log n) != O(n^2)).
9-
2. **Reduction overhead audit** — Parse and simplify overhead expressions from reduction rules to verify they match expected growth (e.g., '3*num_vertices + num_edges^2').
9+
2. **Reduction size-contract audit** — Parse and simplify exact maps and certified bounds from reduction rules, and verify them against constructed examples.
1010
3. **Teaching complexity notation** — Use pred-sym as a learning/demonstration tool to explore how expressions simplify, evaluate at concrete sizes, and compare growth rates.
1111

1212
## Expected Outcome

docs/paper/reductions.typ

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
target: e.target,
99
source-name: graph-data.nodes.at(e.source).name,
1010
target-name: graph-data.nodes.at(e.target).name,
11-
overhead: e.overhead,
11+
size-fields: e.size_fields,
12+
size-contract-error: e.size_contract_error,
1213
))
1314

1415
#let _edges-by-source-name = {
@@ -552,10 +553,14 @@
552553
if parts.len() > 0 { [#base (#parts.join(", "))] } else { base }
553554
}
554555

555-
// Format overhead fields as inline text
556-
#let format-overhead(overhead) = {
557-
let parts = overhead.map(o => raw(o.field + " = " + o.formula))
558-
[_Overhead:_ #parts.join(", ").]
556+
// Format explicitly classified size fields as inline text.
557+
#let format-size-contract(fields) = {
558+
let parts = fields.map(o => {
559+
if o.contract == "exact" { raw(o.field + " = " + o.formula) }
560+
else if o.contract == "bound-only" { raw(o.field + " <= " + o.formula) }
561+
else { raw(o.field + " unavailable: " + o.reason) }
562+
})
563+
[_Size contract:_ #parts.join(", ").]
559564
}
560565

561566
// Unified function for reduction rules: theorem + proof + optional example
@@ -576,15 +581,15 @@
576581
else { display-name.at(target) }
577582
let src-lbl = label("def:" + source)
578583
let tgt-lbl = label("def:" + target)
579-
let overhead = if edge != none and edge.overhead.len() > 0 { edge.overhead } else { none }
584+
let size-fields = if edge != none and edge.size-fields.len() > 0 { edge.size-fields } else { none }
580585
let thm-lbl = label("thm:" + source + "-to-" + target)
581586
covered-rules.update(old => old + ((source, target),))
582587

583588
[
584589
#v(1em)
585590
#theorem[
586591
*(*#context { if query(src-lbl).len() > 0 { link(src-lbl)[#src-disp] } else [#src-disp] }* #arrow *#context { if query(tgt-lbl).len() > 0 { link(tgt-lbl)[#tgt-disp] } else [#tgt-disp] }*)* #theorem-body
587-
#if overhead != none { linebreak(); format-overhead(overhead) }
592+
#if size-fields != none { linebreak(); format-size-contract(size-fields) }
588593
] #thm-lbl]
589594

590595
proof[#proof-body]
@@ -11426,10 +11431,10 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V|
1142611431

1142711432
= Reductions <sec:reductions>
1142811433

11429-
Each reduction is presented as a *Rule* (with linked problem names and overhead from the graph data), followed by a *Proof* (construction, correctness, variable mapping, solution extraction), and optionally a *Concrete Example* (a small instance with verified solution). Problem names in the rule title link back to their definitions in @sec:problems.
11434+
Each reduction is presented as a *Rule* (with linked problem names and explicit size contracts from the graph data), followed by a *Proof* (construction, correctness, variable mapping, solution extraction), and optionally a *Concrete Example* (a small instance with verified solution). Problem names in the rule title link back to their definitions in @sec:problems.
1143011435

1143111436
The command blocks assume `route.json` contains the explicitly chosen direct route for
11432-
the displayed rule, extracted from the corresponding `pred path` Pareto-front item.
11437+
the displayed rule, extracted from the corresponding `pred path` entry.
1143311438

1143411439

1143511440
#let max2sat_mc = load-example("Maximum2Satisfiability", "MaxCut")
@@ -12110,7 +12115,7 @@ the displayed rule, extracted from the corresponding `pred path` Pareto-front it
1211012115
$ w_(i,p) = 2^p quad (0 <= p < L_i - 1), quad w_(i,L_i-1) = r_i + 1 - 2^(L_i - 1) $
1211112116
so that every bit vector represents an offset in ${0, dots, r_i}$. Then
1211212117
$ x_i = ell_i + sum_(p=0)^(L_i-1) w_(i,p) z_(i,p) $
12113-
and the total number of QUBO variables is $N = sum_i L_i$, exactly the exported overhead `num_vars = num_encoding_bits`.
12118+
and the total number of QUBO variables is $N = sum_i L_i$, exactly the exported size map `num_vars = num_encoding_bits`.
1211412119

1211512120
Let $G = A^top A$ and $h = A^top bold(t)$. Writing $bold(x) = bold(ell) + B bold(z)$ for the encoding matrix $B in RR^(n times N)$ gives
1211612121
$ norm(A bold(x) - bold(t))_2^2 = bold(z)^top (B^top G B) bold(z) + 2 bold(z)^top B^top (G bold(ell) - h) + "const" $
@@ -16757,10 +16762,10 @@ See #link("https://github.com/CodingThrust/problem-reductions/blob/main/examples
1675716762

1675816763
== Variant Cast Reductions
1675916764

16760-
Problems parameterized by graph type, weight type, or clause-width ($k$) admit identity reductions between specialised and general variants. Each cast preserves the problem structure exactly (same number of vertices/variables, same constraints), converting only the type parameter to a more general one. These are registered as self-edges in the reduction graph with identity overhead.
16765+
Problems parameterized by graph type, weight type, or clause-width ($k$) admit identity reductions between specialised and general variants. Each cast preserves the problem structure exactly (same number of vertices/variables, same constraints), converting only the type parameter to a more general one. These are registered as self-edges in the reduction graph with exact identity size maps.
1676116766

1676216767
#reduction-rule("MaximumIndependentSet", "MaximumIndependentSet")[
16763-
The graph hierarchy $"KingsSubgraph" subset "UnitDiskGraph" subset "SimpleGraph"$ and weight hierarchy $"One" subset ZZ subset RR$ induce identity-overhead casts between MIS variants. Graph casts discard geometric information (grid coordinates $arrow.r$ Euclidean coordinates $arrow.r$ adjacency list); weight casts embed unit weights into integers ($1 arrow.r 1_ZZ$) or integers into floats ($w arrow.r w_RR$). All edges and weights are preserved verbatim.
16768+
The graph hierarchy $"KingsSubgraph" subset "UnitDiskGraph" subset "SimpleGraph"$ and weight hierarchy $"One" subset ZZ subset RR$ induce exact identity size maps between MIS variants. Graph casts discard geometric information (grid coordinates $arrow.r$ Euclidean coordinates $arrow.r$ adjacency list); weight casts embed unit weights into integers ($1 arrow.r 1_ZZ$) or integers into floats ($w arrow.r w_RR$). All edges and weights are preserved verbatim.
1676416769
][
1676516770
_Construction._ Given $"MIS"(G, bold(w))$ with graph type $G_"sub"$ and weight type $W_"sub"$, construct $"MIS"(G', bold(w)')$ where $G' = "cast"(G_"sub")$ lifts the graph to its parent type and $bold(w)' = "cast"(bold(w))$ lifts each weight. The `CastToParent` trait defines the concrete maps:
1676616771
- _KingsSubgraph $arrow.r$ UnitDiskGraph:_ integer grid positions $(i, j)$ map to float coordinates with radius $r = 1.5$.
@@ -16849,7 +16854,7 @@ Problems parameterized by graph type, weight type, or clause-width ($k$) admit i
1684916854

1685016855
== Resource Estimation from Examples
1685116856

16852-
The following table shows concrete variable overhead for example instances, taken directly from the canonical fixture examples.
16857+
The following table shows concrete target-variable counts for example instances, taken directly from the canonical fixture examples.
1685316858

1685416859
#let example-files = (
1685516860
(source: "MaximumIndependentSet", target: "MinimumVertexCover"),
@@ -19727,7 +19732,7 @@ The following table shows concrete variable overhead for example instances, take
1972719732
"pred reduce pcsf.json --via route.json -o bundle.json",
1972819733
"pred solve bundle.json",
1972919734
)
19730-
The canonical PCSF source has $beta = #pcsf_st.source.instance.beta$, $omega = #pcsf_st.source.instance.omega$, and prizes $p = (#pcsf_st_prizes.at(0), #pcsf_st_prizes.at(1), #pcsf_st_prizes.at(2))$. The target SteinerTree has $|V_H| = n + k + 1 = #(pcsf_st_n + pcsf_st_k + 1)$ vertices, $|E_H| = m + n + 2 k = #(pcsf_st_m + pcsf_st_n + 2 * pcsf_st_k)$ edges, and $|T_H| = k + 1 = #(pcsf_st_k + 1)$ terminals, matching the registered overhead formulas.
19735+
The canonical PCSF source has $beta = #pcsf_st.source.instance.beta$, $omega = #pcsf_st.source.instance.omega$, and prizes $p = (#pcsf_st_prizes.at(0), #pcsf_st_prizes.at(1), #pcsf_st_prizes.at(2))$. The target SteinerTree has $|V_H| = n + k + 1 = #(pcsf_st_n + pcsf_st_k + 1)$ vertices, $|E_H| = m + n + 2 k = #(pcsf_st_m + pcsf_st_n + 2 * pcsf_st_k)$ edges, and $|T_H| = k + 1 = #(pcsf_st_k + 1)$ terminals, matching the registered exact size formulas.
1973119736
],
1973219737
)[
1973319738
Bienstock, Goemans, Simchi-Levi, Williamson @BienstockGoemansSimchiLeviWilliamson1993 introduced the prize/penalty framework for prize-collecting network design; Tuncbag and coauthors @TuncbagEtAl2013PCSF @TuncbagEtAl2012RECOMB used the same artificial-root idea to translate PCSF into a rooted prize-collecting Steiner tree on biological networks. The combined construction recorded here adds a per-vertex auxiliary-terminal gadget that compiles the remaining omitted-prize term `beta * p(v)` into ordinary Steiner-tree edge costs, so the target is a plain (unweighted-prize) Steiner Tree instance.

docs/src/cli.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ pred solve lbdp.json --solver brute-force
8080
# Evaluate a specific configuration (shows the aggregate value, e.g. Max(2) or Min(None))
8181
pred evaluate problem.json --config 1,0,1,0
8282

83-
# Reduce along an explicitly chosen Pareto-front route and solve via brute-force
83+
# Reduce along an explicitly chosen route and solve via brute-force
8484
pred reduce problem.json --via route.json -o reduced.json
8585
pred solve reduced.json --solver brute-force
8686

@@ -138,7 +138,7 @@ Explore which problems the given problem can reduce to, starting **from** it:
138138

139139
### `pred path` — Find reduction paths
140140

141-
Find the symbolic Pareto front between two problems:
141+
Enumerate paths between two problems:
142142

143143
```text
144144
{{#include generated/pred-path-mis-qubo.txt}}
@@ -150,21 +150,21 @@ Multi-step paths are discovered automatically:
150150
{{#include generated/pred-path-factoring-spinglass.txt}}
151151
```
152152

153-
Show all paths or save for later use with `pred reduce --via`:
153+
Inspect reduction paths or save the path set for later route selection:
154154

155155
```bash
156-
pred path MIS QUBO --all # all paths (up to 20)
157-
pred path MIS QUBO --all --max-paths 50 # increase limit
158-
pred path MIS QUBO -o front.json # save the Pareto front
159-
pred path MIS QUBO --all -o paths/ # save all paths to a folder
156+
pred path MIS QUBO # paths (up to 20)
157+
pred path MIS QUBO --max-paths 50 # increase the cap
158+
pred path MIS MaximumClique mis.json # execute paths on a complete instance
159+
pred path MIS QUBO -o paths.json # save the path set
160160
```
161161

162-
When using `--all`, the output is capped at `--max-paths` (default: 20). If more paths exist, the output indicates truncation.
163-
164-
Every front item contains its complete route. The envelope does not select a
165-
winner; extract the route you want before passing it to `pred reduce --via`.
166-
Paths with unknown symbolic growth are excluded from the front and listed with
167-
their analysis-failure reason.
162+
Without an instance file, each route explains how problem size changes. With a
163+
problem JSON file, every returned path is executed on the complete source instance
164+
and the actual size of each constructed intermediate is reported. Discovery never
165+
ranks or Pareto-prunes routes. Output is capped by `--max-paths` (default: 20);
166+
extract one route from the path-set envelope before passing it to
167+
`pred reduce --via`.
168168

169169
### `pred export-graph` — Export the reduction graph
170170

@@ -411,7 +411,7 @@ This is useful for scripting and piping:
411411

412412
```bash
413413
pred list --json | jq '.variants[].name'
414-
pred path MIS QUBO --json | jq '.front[] | {growth, path}'
414+
pred path MIS QUBO --json | jq '.paths[] | {overall_size, path}'
415415
```
416416

417417
## Problem Name Aliases

0 commit comments

Comments
 (0)