diff --git a/.claude/skills/auto-pipeline/SKILL.md b/.claude/skills/auto-pipeline/SKILL.md new file mode 100644 index 000000000..271e3f08e --- /dev/null +++ b/.claude/skills/auto-pipeline/SKILL.md @@ -0,0 +1,389 @@ +--- +name: auto-pipeline +description: Use when you want to take a Backlog issue all the way to Final review without manual orchestration — chains check-issue, fix-issue, add-model/add-rule, run-pipeline, and review-pipeline; substantive issue-quality problems are sent to a rewrite subagent; algorithmically unsalvageable issues are parked on OnHold +--- + +# Auto Pipeline + +Take **one** Backlog issue all the way from quality gate to **Final review** without human intervention. The merge step itself is still left to the human (see `/final-review`). + +This skill is an **orchestrator**: it never runs the heavy work itself. Each phase is delegated to a fresh-context subagent. Most phases invoke an existing skill (`check-issue`, `fix-issue`, `run-pipeline`, `review-pipeline`); Phase 3 is owned by the orchestrator and runs raw `cargo test --workspace` + `make paper` to catch breakage the per-item sub-skills cannot see. The only thing the main agent does directly is: + +1. pick the issue, +2. read structured reports from subagents, +3. decide whether to retry, dispatch a rewrite subagent for substantive issues, or park the issue on OnHold, +4. move the project board card forward. + +## Invocation + +- `/auto-pipeline` — pick the highest-priority Backlog issue (Good label first, then lowest issue number) +- `/auto-pipeline 123` — run on a specific Backlog issue number + +## Board states this skill writes + +Only three transitions happen here directly (the rest are owned by sub-skills): + +| Symbolic name passed to `pipeline_board.py move` | When | +|---|---| +| `ready` | Step 1d (quality gate passed) | +| `on-hold` | Step 1e (fundamental flaw or substantive retry cap hit) | + +The orchestrator reads the Backlog column in Step 0 and never writes to it. ID constants for all other columns live in [`run-pipeline`](../run-pipeline/SKILL.md) / [`review-pipeline`](../review-pipeline/SKILL.md) — sub-skills move the card through In Progress → Review pool → Final review. + +## Autonomous Mode + +Runs **fully autonomously** — no confirmation prompts, no clarifying questions. All sub-skills called from here must also auto-approve. The human only gets involved at `/final-review`, or when the issue is parked on OnHold with a diagnostic comment. + +## Subagent Contract + +Every subagent dispatched by this skill operates under the same contract. Each per-step prompt below references this contract by name and only adds the step-specific scope + JSON shape. + +**Output:** the subagent's LAST message must be a single fenced ```json``` block matching the shape given by the dispatching step. No prose before or after. The orchestrator parses only that block. + +**Don'ts:** +- Do NOT modify any source files unless the step's prompt explicitly says so. +- Do NOT move the project board card. The orchestrator owns all board transitions. +- Do NOT open pull requests or invoke `/issue-to-pr`, `gh pr create`, etc. unless the step is `run-pipeline` (which manages its own PR via the existing skill). +- Do NOT brainstorm with a human or wait for input. + +**Severity vocabulary** (used by every Phase-1 step that reports findings): +- `mechanical` — issue-body-fixable without changing the claim (typo, missing G&J number, wrong alias, malformed example, wrong heading). +- `substantive` — the claim is wrong or unsupported (incorrect complexity, broken overhead, mis-cited paper, flawed proof sketch) but a public reference probably exists. +- `fundamental` — algorithm/reduction is mathematically unsound AND your literature search found no public reference that would salvage it. Only assign after a genuine search. + +**Malformed JSON:** if the subagent's reply is missing the fenced JSON block, re-dispatch once with the prompt prefixed by "Your previous reply did not contain a parseable JSON block. Run the skill again from scratch and return ONLY the JSON block." If the second attempt also fails, park the issue on OnHold with reason `subagent contract violation in `. + +## Architecture + +```dot +digraph auto_pipeline { + rankdir=TB; + "Pick issue from Backlog" [shape=box]; + "Phase 1: check-issue (subagent)" [shape=box, style=filled, fillcolor="#cce0ff"]; + "Classify report" [shape=diamond]; + "Phase 1b: auto-fix (subagent)" [shape=box, style=filled, fillcolor="#cce0ff"]; + "Phase 1c: rewrite (subagent)" [shape=box, style=filled, fillcolor="#ffe0cc"]; + "Apply revised issue body" [shape=box]; + "Substantive loop counter" [shape=diamond]; + "Move to OnHold + comment" [shape=box, style=filled, fillcolor="#ffcccc"]; + "Move to Ready" [shape=box]; + "Phase 2: run-pipeline (subagent)" [shape=box, style=filled, fillcolor="#cce0ff"]; + "Phase 3: integration gate (subagent)" [shape=box, style=filled, fillcolor="#cce0ff"]; + "Phase 4: review-pipeline (subagent)" [shape=box, style=filled, fillcolor="#cce0ff"]; + "Final report" [shape=box, style=filled, fillcolor="#ccffcc"]; + + "Pick issue from Backlog" -> "Phase 1: check-issue (subagent)"; + "Phase 1: check-issue (subagent)" -> "Classify report"; + "Classify report" -> "Move to Ready" [label="pass"]; + "Classify report" -> "Phase 1b: auto-fix (subagent)" [label="mechanical only"]; + "Classify report" -> "Phase 1c: rewrite (subagent)" [label="substantive"]; + "Classify report" -> "Move to OnHold + comment" [label="fundamental + no reference"]; + "Phase 1b: auto-fix (subagent)" -> "Phase 1: check-issue (subagent)"; + "Phase 1c: rewrite (subagent)" -> "Apply revised issue body"; + "Apply revised issue body" -> "Substantive loop counter"; + "Substantive loop counter" -> "Phase 1: check-issue (subagent)" [label="< 2 retries"]; + "Substantive loop counter" -> "Move to OnHold + comment" [label=">= 2 retries"]; + "Move to Ready" -> "Phase 2: run-pipeline (subagent)"; + "Phase 2: run-pipeline (subagent)" -> "Phase 3: integration gate (subagent)" [label="success"]; + "Phase 2: run-pipeline (subagent)" -> "Final report" [label="fail (stop)"]; + "Phase 3: integration gate (subagent)" -> "Phase 4: review-pipeline (subagent)" [label="all pass"]; + "Phase 3: integration gate (subagent)" -> "Move to OnHold + comment" [label="any fail"]; + "Phase 4: review-pipeline (subagent)" -> "Final report"; +} +``` + +## Step 0: Pick the Issue + +`scripts/pipeline_board.py backlog` accepts only `model` or `rule` (NOT `all`), returns `{"issue_type": ..., "items": [{number, title, item_id, labels, has_good}, ...]}`, and **exits with code 1 when the queried kind is empty** even though it prints valid JSON — so the picker queries both kinds and ignores subprocess return codes. + +### 0a. Pick + +Set `ISSUE` to the requested number, or leave empty to auto-pick the top of Backlog (Good label first, then lowest number): + +```bash +ISSUE="${ISSUE:-}" # set this to a specific number, or leave empty to auto-pick + +PICK_JSON=$(ISSUE="$ISSUE" python3 <<'PY' +import json, os, subprocess +target = int(os.environ["ISSUE"]) if os.environ.get("ISSUE") else None +items = [] +for kind in ("model", "rule"): + out = subprocess.run( + ["uv", "run", "--project", "scripts", "scripts/pipeline_board.py", + "backlog", kind, "--format", "json"], + capture_output=True, text=True, + ) + try: + items.extend(json.loads(out.stdout)["items"]) + except Exception: + pass +if target is not None: + hit = next((i for i in items if i["number"] == target), None) + print(json.dumps(hit) if hit else "") +elif items: + items.sort(key=lambda i: (not i["has_good"], i["number"])) + print(json.dumps(items[0])) +else: + print("") +PY +) + +if [ -z "$PICK_JSON" ]; then + if [ -n "$ISSUE" ]; then + echo "Issue #$ISSUE is not in the Backlog column." + else + echo "Backlog is empty." + fi + exit 0 +fi +``` + +### 0b. Extract fields + +```bash +ISSUE=$(printf '%s' "$PICK_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin)['number'])") +ITEM_ID=$(printf '%s' "$PICK_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin)['item_id'])") +TITLE=$(printf '%s' "$PICK_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin)['title'])") +LABELS=$(printf '%s' "$PICK_JSON" | python3 -c "import sys,json; print(','.join(json.load(sys.stdin)['labels']))") + +echo "Auto-pipeline starting on issue #$ISSUE — $TITLE" +echo " item_id: $ITEM_ID" +echo " labels: $LABELS" +``` + +### 0c. Initialise loop counter + +```bash +SUBSTANTIVE_RETRIES=0 +MAX_SUBSTANTIVE_RETRIES=2 +``` + +## Step 1: Quality Gate (check-issue + fix loop) + +### 1a. Dispatch `check-issue` subagent + +Use the `Agent` tool with `subagent_type=general-purpose`. The subagent must run the existing `check-issue` skill (force re-check) and report back **structured JSON only**. + +**Prompt template** (subagent follows the Subagent Contract above for everything else): + +``` +Run /check-issue on issue # in CodingThrust/problem-reductions +(--force re-check). Follow .claude/skills/check-issue/SKILL.md exactly. + +For [Rule] issues, Rule Check 5 (Completeness) is the most important +check — find and quote the cited theorem, enumerate corner cases the +source model allows via `pred show --json` and existing +src/rules/ implementations, and hand-trace the algorithm on >= 2 +non-canonical corner cases. A cited precondition the issue ignores is +"substantive"; a cited reference that does not contain the reduction +at all is "fundamental" (set fundamental_no_reference: true). + +You may post the check-issue comment and apply failure/Good labels per +the skill. Do NOT close the issue. + +Return ONLY this JSON shape: +{ + "verdict": "pass" | "fail", + "errors": [{"check": "...", "label": "...", "summary": "...", "severity": "mechanical|substantive|fundamental"}], + "warnings": [{"check": "...", "summary": "...", "severity": "mechanical|substantive"}], + "fundamental_no_reference": true | false, + "comment_url": "" +} +``` + +### 1b. Classify the report + +Parse the JSON. Then branch: + +| Condition | Action | +|---|---| +| `verdict == "pass"` | → Step 1d (move to Ready) | +| `fundamental_no_reference == true` | → Step 1e (OnHold) | +| all `errors`/`warnings` have `severity == "mechanical"` | → Step 1c-mech | +| any `severity == "substantive"` | → Step 1c-sub | + +### 1c-mech. Dispatch auto-fix subagent (mechanical only) + +``` +Run /fix-issue on issue # in auto-fix-only mode: +- Apply only the mechanical auto-fixes from fix-issue's auto-fix step + (the one that runs before the human-brainstorm step). +- Edit the issue body via `gh issue edit` as the skill instructs. +- Skip the re-check and the project-card move (orchestrator handles + re-check by re-dispatching Phase 1). + +Return ONLY this JSON shape: +{ + "applied": [""], + "skipped_substantive": [""], + "errors": [""] +} +``` + +Loop back to **Step 1a** (re-check). Do not increment `SUBSTANTIVE_RETRIES` — mechanical fixes don't count toward the cap. + +### 1c-sub. Rewrite subagent (substantive) + +If `SUBSTANTIVE_RETRIES >= MAX_SUBSTANTIVE_RETRIES` → jump to Step 1e (OnHold) with reason `"substantive issues persist after $MAX_SUBSTANTIVE_RETRIES rewrites"`. + +Otherwise, fetch the current issue body and the latest check-issue comment: + +```bash +ISSUE_BODY=$(gh issue view "$ISSUE" --json body --jq .body) +CHECK_REPORT=$(gh issue view "$ISSUE" --json comments --jq '[.comments[] | select(.body | startswith("## Issue Quality Check"))] | last | .body') +``` + +Dispatch a subagent (`subagent_type=general-purpose`) to research and rewrite: + +``` +Issue # failed /check-issue with substantive findings. Read the +current issue body and the latest check-issue report (both pasted in +the prompt), research public literature with WebSearch / WebFetch, and +either rewrite the body grounded in citations or report that no public +reference can salvage the proposal. + +Issue body: +$ISSUE_BODY + +Latest check-issue report: +$CHECK_REPORT + +Return ONLY one of these JSON shapes: + {"outcome": "revised", "new_body": ""} + {"outcome": "fundamental_flaw", "reason": ""} +``` + +When the subagent returns: + +- **`outcome == "fundamental_flaw"`** → Step 1e (OnHold) with the reason. +- **`outcome == "revised"`** → orchestrator applies the new body (the subagent must NOT edit GitHub itself — keep all edits in the orchestrator for a clean audit trail): + + ```bash + printf '%s' "$NEW_BODY" > /tmp/auto-pipeline-issue-$ISSUE.md + gh issue edit "$ISSUE" --body-file /tmp/auto-pipeline-issue-$ISSUE.md + gh issue comment "$ISSUE" --body "auto-pipeline: issue body rewritten (substantive retry $((SUBSTANTIVE_RETRIES + 1)))" + rm /tmp/auto-pipeline-issue-$ISSUE.md + ``` + + Increment: `SUBSTANTIVE_RETRIES=$((SUBSTANTIVE_RETRIES + 1))` and loop back to **Step 1a**. + +### 1d. Move card to Ready + +```bash +uv run --project scripts scripts/pipeline_board.py move "$ITEM_ID" ready +gh issue comment "$ISSUE" --body "auto-pipeline: quality check passed — moving to Ready." +``` + +Continue to Step 2. + +### 1e. Park on OnHold + +```bash +REASON="" +gh issue comment "$ISSUE" --body "auto-pipeline: parked on OnHold — $REASON. Human triage needed." +uv run --project scripts scripts/pipeline_board.py move "$ITEM_ID" on-hold +``` + +Print the final report and STOP: + +``` +Auto-pipeline halted at quality gate: + Issue: # + Reason: + Board: Backlog -> OnHold +``` + +## Step 2: Implementation (`run-pipeline` subagent) + +Dispatch the existing `run-pipeline` skill against the same issue: + +**Prompt template** (this is the one step the Subagent Contract's no-board-moves rule does NOT apply to — run-pipeline owns its worktree, PR, and board transitions from Ready to Review pool): + +``` +Run /run-pipeline on issue # (already in Ready). Follow +.claude/skills/run-pipeline/SKILL.md exactly — it handles the +worktree, issue-to-pr invocation, and the Ready -> In Progress -> +Review pool transitions, including moving to OnHold on failure. + +Return ONLY this JSON shape: +{ + "outcome": "success" | "failure", + "pr_number": , + "board_status": "Review pool" | "OnHold" | "", + "summary": "" +} +``` + +When the subagent returns: + +- **`outcome == "success"`** → continue to Step 3. +- **`outcome == "failure"`** → STOP. The `run-pipeline` skill already moves the card to OnHold and posts a diagnostic comment, so we do not duplicate. Print: + + ``` + Auto-pipeline halted at implementation: + Issue: # + PR: # + Reason: + Board: + ``` + + Implementation failures need human eyes — `run-pipeline` already moves the card to OnHold and posts a diagnostic, so the orchestrator just stops here. + +## Step 3: Integration Gate (orchestrator-owned) + +The per-item sub-skills only test the new item in isolation, so cross-crate regressions (e.g. a relaxed model validator breaking pre-existing CLI tests) and paper-compile errors (orphan bib keys, math-mode typos like `intersect` vs Typst's `inter`) slip through Phase 2 and the per-item structural review. Running this gate after Phase 2 catches them locally instead of waiting for CI. + +Dispatch a fresh subagent (`subagent_type=general-purpose`, not invoking any existing skill): + +``` +Run the auto-pipeline integration gate on PR #. Check out the PR +branch in a fresh worktree, run `make check` then `make paper`, clean up. +Do not modify files. Return ONLY: + +{"tests": "pass" | "fail", "paper": "pass" | "fail", + "first_failure": ""} +``` + +- Both `pass` → continue to Step 4. +- Either `fail` → dispatch a fresh subagent (`subagent_type=general-purpose`) with the `first_failure` string and write access to the PR branch, asking it to fix the failure directly (CI-class problems are usually small: deleting a stale test, fixing a typo'd bib key, swapping `intersect` for `inter`). After it returns, re-run Step 3 once. If still failing, park on OnHold. + +## Step 4: Agentic Review (`review-pipeline` subagent) + +Dispatch the existing `review-pipeline` skill against the PR: + +**Prompt template** (board transitions to Final review are owned by review-pipeline; that's its contract): + +``` +Run /review-pipeline on PR #. Follow +.claude/skills/review-pipeline/SKILL.md exactly; it always moves the +PR to Final review at the end. + +Return ONLY this JSON shape: +{ + "outcome": "success" | "failure", + "board_status": "Final review" | "", + "review_verdicts": {"structural": "...", "quality": "...", "agentic": "..."}, + "summary": "" +} +``` + +Whatever the outcome, the PR is now either in Final review (success) or stuck somewhere the review skill left it (failure). Print the final report: + +``` +Auto-pipeline complete: + Issue: # + PR: # + Board: + Verdicts: structural=<...> quality=<...> agentic=<...> + Next: human runs /final-review +``` + +## Common Mistakes + +| Mistake | Fix | +|---------|-----| +| Calling sub-skills directly in the main agent | Always dispatch via `Agent` tool — keeps the orchestrator context clean | +| Letting the rewrite subagent edit GitHub | The orchestrator owns all `gh issue edit` calls — subagents only return text | +| Treating implementation failures as substantive issue problems | Step 2 failures go straight to a stop; the orchestrator does not attempt to auto-fix `run-pipeline` output | +| Picking from a non-Backlog column when no issue number is given | Auto-pick must read from Backlog only — never from OnHold, Ready, or elsewhere | +| Skipping Step 3 because Phase 2 reported `success` | Phase 2 success is scoped to the new item's own tests; workspace-wide regressions and paper-compile bugs are only visible from `make check` + `make paper`. | diff --git a/.claude/skills/check-issue/SKILL.md b/.claude/skills/check-issue/SKILL.md index cfa297aef..983e6fee0 100644 --- a/.claude/skills/check-issue/SKILL.md +++ b/.claude/skills/check-issue/SKILL.md @@ -199,6 +199,82 @@ If the algorithm is a high-level sketch rather than an implementable procedure --- +## Rule Check 5: Completeness (fail label: `Incomplete`) + +**Goal:** Does the proposed mapping work for *every* instance of the source problem, not just the canonical case in the example? + +A reduction that only handles a subset of source instances (e.g., "assumes connected graph", "assumes no duplicate elements", "works only when k is even") is **not a valid polynomial-time reduction** unless the issue explicitly: +- restricts the source to a sub-variant that the codebase actually exposes as a distinct model, AND +- the restriction is part of the algorithm statement, not a hidden assumption. + +This check is **mandatory** for every `[Rule]` issue and must be backed by **explicit literature and codebase research** — not vibes. + +### 5a: Literature research (mandatory) + +For the cited construction(s): + +1. Find the original paper or textbook section that defines the reduction. +2. Read the **statement of the theorem**: what does it claim the reduction handles? Look for phrases like: + - "for any instance of P" → covers all instances (good) + - "for a P-instance such that ..." → has a precondition (must be flagged) + - "in the special case where ..." → only a special case (must be flagged) +3. Read the **proof**: are there steps that silently assume something about the source (no isolated vertices, no zero weights, integer-valued capacities, ...)? + +Use the same fallback chain as Check 3c. + +If the cited paper is **not actually a reduction from the full source problem** but from a restricted variant → **Fail** with the precise restriction quoted from the paper. The fix is one of: (a) add a preprocessing step that reduces the full source to the restricted variant, (b) split into a `[Rule]` issue from the actual restricted source, or (c) drop the reduction. + +### 5b: Codebase corner-case research (mandatory) + +Check the actual codebase to see what shape the source problem can take: + +```bash +pred show --json +``` + +Read the `size_fields` and any variant getters, then enumerate corner cases the issue's algorithm must handle: + +| Class | Example corner cases the reduction must accept | +|---|---| +| Graph-input problems | empty graph, single vertex, isolated vertices, self-loops if the model allows them, parallel edges if allowed, disconnected components, complete graph | +| Weighted problems | all weights equal, all weights zero, mixed signs (if the weight type allows), one weight dominating the rest | +| Formula/circuit | empty clause set, single-literal clauses, tautological clauses, repeated variables in a clause | +| Set systems | empty universe, empty subsets, identical subsets, universe element appearing in no subset | +| Algebraic | zero matrix, identity, singular matrix | + +Then trace the **issue's** algorithm by hand against at least 2 corner cases that are not the worked example: + +1. Pick a corner case from the table above that the source model actually allows. +2. Simulate the issue's construction step by step. +3. Check: is the target problem well-defined? Does solution extraction still work? + +Also grep the codebase for any existing rule whose source has the same problem name — if it already handles certain corner cases, the new rule should at least match that coverage: + +```bash +grep -rl "impl.*ReduceTo.*for " src/rules/ +``` + +Read 1–2 of those existing rules for how they handle edge inputs. + +If the issue's algorithm **crashes, produces an invalid target instance, or loses information** on a legitimate corner case → **Fail** with the corner case spelled out. + +If the issue's algorithm appears to handle corner cases correctly but the issue body doesn't *state* this explicitly → **Warn** ("works on tested corner cases, but the algorithm description does not address edge inputs — please document"). + +### 5c: Verdict + +| Finding | Verdict | +|---|---| +| Literature explicitly covers all instances AND traced corner cases work | **Pass** | +| Literature explicitly covers all instances but issue is silent on corner cases | **Warn** | +| Literature has a precondition the issue ignores | **Fail** | +| Traced corner case breaks the algorithm | **Fail** | + +(If the cited reference doesn't actually contain the reduction at all, Check 3c already catches it — don't double-flag here.) + +Report the literature evidence and the corner cases you traced in the comment — this is the most expensive check and reviewers will want to see your work. + +--- + # Part B: Model Issue Checks Applies when the title contains `[Model]`. @@ -359,9 +435,10 @@ Post a single GitHub comment. The table adapts to the issue type: | Usefulness | ✅ Pass | No existing direct reduction Source → Target | | Non-trivial | ✅ Pass | Gadget construction with penalty terms | | Correctness | ❌ Fail | Paper "Smith 2020" not found on arxiv or Semantic Scholar | +| Completeness | ⚠️ Warn | Algorithm correct on traced corner cases but issue body silent on edge inputs | | Well-written | ⚠️ Warn | Symbol `m` used in overhead table but not defined in algorithm | -**Overall: 2 passed, 1 failed, 1 warning** +**Overall: 2 passed, 1 failed, 2 warnings** --- @@ -374,6 +451,9 @@ Post a single GitHub comment. The table adapts to the issue type: ### Correctness [Per-reference verification results, any better algorithms found] +### Completeness +[Literature passages cited (with quote + section/theorem number) showing whether the construction covers all source instances, and the corner cases you traced by hand with the algorithm — including any that broke or any preconditions you discovered] + ### Well-written [Specific items to fix] @@ -423,13 +503,14 @@ gh issue edit --add-label "Useless" # if Check 1 failed gh issue edit --add-label "Trivial" # if Check 2 failed gh issue edit --add-label "Wrong" # if Check 3 failed gh issue edit --add-label "PoorWritten" # if Check 4 failed +gh issue edit --add-label "Incomplete" # if Rule Check 5 failed -# "Good" label requires: zero failures AND zero warnings on Usefulness or Correctness. +# "Good" label requires: zero failures AND zero warnings on Usefulness, Correctness, or Completeness. # Warnings on Non-trivial or Well-written alone do NOT block "Good". gh issue edit --add-label "Good" # If re-checking after fixes, remove stale failure labels and add "Good" if now passing -gh issue edit --remove-label "Useless,Trivial,Wrong,PoorWritten" 2>/dev/null +gh issue edit --remove-label "Useless,Trivial,Wrong,PoorWritten,Incomplete" 2>/dev/null gh issue edit --add-label "Good" ``` diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index 449db0e57..f83c39a97 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -204,6 +204,13 @@ "BottleneckTravelingSalesman": [Bottleneck Traveling Salesman], "TravelingSalesman": [Traveling Salesman], "MaximumClique": [Maximum Clique], + "MaximumCoKPlex": [Maximum Co-$k$-Plex], + "MaximumCommonEdgeSubgraph": [Maximum Common Edge Subgraph], + "MaximumContactMapOverlap": [Maximum Contact Map Overlap], + "MaximumEdgeWeightedKClique": [Maximum Edge-Weighted $k$-Clique], + "HighlyConnectedDeletion": [Highly Connected Deletion], + "EulerianPath": [Eulerian Path], + "PrizeCollectingSteinerForest": [Prize-Collecting Steiner Forest], "MaximumSetPacking": [Maximum Set Packing], "MinimumHittingSet": [Minimum Hitting Set], "MinimumSetCovering": [Minimum Set Covering], @@ -251,6 +258,8 @@ "MixedChinesePostman": [Mixed Chinese Postman], "StackerCrane": [Stacker Crane], "LongestCommonSubsequence": [Longest Common Subsequence], + "ClosestString": [Closest String], + "ClosestSubstring": [Closest Substring], "ExactCoverBy3Sets": [Exact Cover by 3-Sets], "ThreeDimensionalMatching": [Three-Dimensional Matching], "ThreeMatroidIntersection": [Three-Matroid Intersection], @@ -277,6 +286,8 @@ "MinimumWeightSolutionToLinearEquations": [Minimum Weight Solution to Linear Equations], "DirectedTwoCommodityIntegralFlow": [Directed Two-Commodity Integral Flow], "MinimumEdgeCostFlow": [Minimum Edge-Cost Flow], + "MinimumCostMaximumFlow": [Minimum-Cost Maximum-Flow], + "MinimumCostCirculation": [Minimum-Cost Circulation], "IntegralFlowHomologousArcs": [Integral Flow with Homologous Arcs], "IntegralFlowWithMultipliers": [Integral Flow With Multipliers], "MinMaxMulticenter": [Min-Max Multicenter], @@ -293,6 +304,7 @@ "MultipleChoiceBranching": [Multiple Choice Branching], "MultipleCopyFileAllocation": [Multiple Copy File Allocation], "ExpectedRetrievalCost": [Expected Retrieval Cost], + "MinimumDiscretePlanarInverseKinematics": [Minimum Discrete Planar Inverse Kinematics], "MultiprocessorScheduling": [Multiprocessor Scheduling], "NonLivenessFreePetriNet": [Non-Liveness Free Petri Net], "ProductionPlanning": [Production Planning], @@ -779,6 +791,501 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V| ] } +#{ + let x = load-model-example("MaximumCoKPlex") + let nv = graph-num-vertices(x.instance) + let ne = graph-num-edges(x.instance) + let edges = x.instance.graph.edges + let weights = x.instance.weights + let k = x.instance.bound_k + let sol = (config: x.optimal_config, metric: x.optimal_value) + let S = sol.config.enumerate().filter(((i, v)) => v == 1).map(((i, _)) => i) + let wS = metric-value(sol.metric) + [ + #problem-def("MaximumCoKPlex")[ + Given $G = (V, E)$ with vertex weights $w: V -> RR$ and an integer $k >= 1$, find $S subset.eq V$ maximizing $sum_(v in S) w(v)$ such that the induced subgraph $G[S]$ has maximum degree at most $k - 1$: $forall v in S, deg_(G[S])(v) <= k - 1$. + ][ + The Maximum Co-$k$-Plex (also called the maximum $(k - 1)$-dependent set) is a clique-relaxation model that interpolates between the Maximum Independent Set ($k = 1$) and bounded-conflict variants used in molecular similarity scoring @Hernandez2016MolecularSimilarity and bipartite-side combinatorial optimization @HosseinianButenko2022KDependent. Its complement view is the maximum $k$-plex on $overline(G)$. The brute-force baseline enumerates all $2^n$ subsets in $O^*(2^n)$ time#footnote[No algorithm improving on brute-force enumeration is currently registered for the default `KN` variant.]. + + *Example.* Consider the 5-cycle $C_5$ with $n = #nv$ vertices, $|E| = #ne$ edges #edges.map(((u, v)) => [${#u, #v}$]).join(", "), vertex weights $w = #(weights)$, and $k = #k$. The set $S = {#S.map(i => $v_#i$).join(", ")}$ has weight $w(S) = #wS$. Its induced subgraph contains only the chord $(v_4, v_0)$, so the induced-degree sequence on $S$ is $(1, 0, 1)$ -- every selected vertex satisfies $deg_(G[S])(v) <= k - 1 = 1$. + + #pred-commands( + "pred create --example " + problem-spec(x) + " -o co-k-plex.json", + "pred solve co-k-plex.json", + "pred evaluate co-k-plex.json --config " + x.optimal_config.map(str).join(","), + ) + + #figure({ + let r = 1.1 + let verts = range(nv).map(i => { + let angle = calc.pi / 2 + 2 * calc.pi * i / nv + (r * calc.cos(angle), r * calc.sin(angle)) + }) + draw-node-highlight(verts, edges, S) + }, + caption: [The 5-cycle $C_5$ with $w = #(weights)$ and $k = #k$. Selected vertices $S = {#S.map(i => $v_#i$).join(", ")}$ (blue) have total weight $#wS$; in $G[S]$ every vertex has induced degree at most $k - 1 = 1$.], + ) + ] + ] +} + +#{ + // Hand-authored canonical example mirroring the in-repo example_db fixture + // (load-model-example is not used here because the corresponding example + // entry is shipped via the model file's canonical_model_example_specs rather + // than the docs/paper/data/examples.json bundle). + let g1 = ( + num_vertices: 5, + arcs: ( + (src: 0, label: 0, dst: 1), + (src: 1, label: 1, dst: 2), + (src: 0, label: 2, dst: 2), + (src: 2, label: 0, dst: 3), + (src: 1, label: 3, dst: 3), + (src: 3, label: 1, dst: 4), + ), + ) + let g2 = ( + num_vertices: 4, + arcs: ( + (src: 0, label: 0, dst: 1), + (src: 1, label: 1, dst: 2), + (src: 0, label: 2, dst: 2), + (src: 2, label: 0, dst: 3), + (src: 1, label: 3, dst: 3), + (src: 0, label: 1, dst: 3), + ), + ) + let n1 = g1.num_vertices + let n2 = g2.num_vertices + let label-name = ("a", "b", "c", "d") + let label-str(l) = label-name.at(l, default: str(l)) + let fmt-arcs(arcs) = arcs.map(a => $(#a.src, #label-str(a.label), #a.dst)$).join(", ") + let f = (0, 1, 2, 3, 4) // 4 encodes ⊥ since |V2| = 4 + let preserved = 5 + [ + #problem-def("MaximumCommonEdgeSubgraph")[ + Given two finite directed edge-labelled graphs $G_1 = (V_1, E_1)$ and $G_2 = (V_2, E_2)$ with $E_i subset.eq V_i times Sigma times V_i$, find a partial injective map $f: U_1 -> V_2$, where $U_1 subset.eq V_1$, maximizing the number of preserved labelled arcs + $ |{(u, lambda, v) in E_1 : u, v in U_1 "and" (f(u), lambda, f(v)) in E_2}|. $ + Edge labels must match exactly, vertex labels are ignored, and the model uses set semantics: each preserved labelled arc contributes $1$, independent of multiplicity. + ][ + The Maximum Common Edge Subgraph problem (MCES) was introduced by Bokhari as a model for the task-assignment / mapping problem on parallel architectures @Bokhari1981Mapping. Bahiense, Mani{\'c}, Piva, and de Souza later gave a thorough polyhedral investigation and exact branch-and-cut algorithms for general undirected MCES @Bahiense2012MCES. Soul{\'e}, Reinharz, Sarrazin-Gendron, Denise, and Waldisp{\"u}hl use a maximal (not maximum) common subgraph enumeration over edge-coloured graphs to detect recurrent RNA structural networks @Soule2021RNA; the edge-maximizing optimization surrogate registered here is the natural objective version of their setting. The decision form is NP-complete by direct reduction from Subgraph Isomorphism. The registered exact baseline enumerates every assignment $V_1 -> V_2 union {bot}$ in $O^*((|V_2| + 1)^(|V_1|))$ time and filters to injective maps#footnote[No algorithm improving on full enumeration is registered for the unlabelled-vertex variant. Refinements such as branch-and-bound on a product graph @Bahiense2012MCES improve on the worst case in practice but not in worst-case complexity.]. + + // Pretty-print the partial map: render f(u) as ⊥ when u is unmatched. + #let render-target(v) = if v == n2 { $bot$ } else { $#v$ } + #let map-tuple = f.map(v => render-target(v)).join($, $) + + *Example.* Encode the alphabet $Sigma = {a, b, c, d}$ as ${0, 1, 2, 3}$ (alphabetical). Let + $V_1 = {0, 1, 2, 3, 4}$ with $E_1 = {#fmt-arcs(g1.arcs)}$ and + $V_2 = {0, 1, 2, 3}$ with $E_2 = {#fmt-arcs(g2.arcs)}$. + The partial injective map $f = (#map-tuple)$ preserves $#preserved$ of the $#g1.arcs.len()$ source arcs; the only unmatched source arc is $(3, b, 4)$, since vertex $4 in V_1$ is left unmatched. No injective map can preserve all $#g1.arcs.len()$ source arcs, because matching every vertex of $V_1$ injectively into $V_2$ would require $|V_2| >= |V_1| = #n1 > #n2$. + + #pred-commands( + "pred create --example MaximumCommonEdgeSubgraph -o mces.json", + "pred solve mces.json --solver brute-force", + "pred evaluate mces.json --config " + f.map(str).join(","), + ) + + #figure({ + let r1 = 1.2 + let r2 = 1.0 + let dx = 4.0 + let verts1 = range(n1).map(i => { + let angle = calc.pi / 2 - 2 * calc.pi * i / n1 + (r1 * calc.cos(angle), r1 * calc.sin(angle)) + }) + let verts2 = range(n2).map(i => { + let angle = calc.pi / 2 - 2 * calc.pi * i / n2 + (dx + r2 * calc.cos(angle), r2 * calc.sin(angle)) + }) + canvas(length: 1cm, { + import draw: * + // G1 arcs + for arc in g1.arcs { + let p = verts1.at(arc.src) + let q = verts1.at(arc.dst) + let mid = ((p.at(0) + q.at(0)) / 2, (p.at(1) + q.at(1)) / 2) + line(p, q, mark: (end: "straight"), stroke: 0.7pt + luma(120)) + content(mid, text(7pt)[#label-str(arc.label)], frame: "rect", fill: white, stroke: none, padding: 0.04) + } + for (k, pos) in verts1.enumerate() { + let matched = f.at(k) != n2 + g-node(pos, name: "u" + str(k), + fill: if matched { graph-colors.at(0) } else { white }, + label: if matched { text(fill: white)[$#k$] } else { [$#k$] }) + } + // G2 arcs + for arc in g2.arcs { + let p = verts2.at(arc.src) + let q = verts2.at(arc.dst) + let mid = ((p.at(0) + q.at(0)) / 2, (p.at(1) + q.at(1)) / 2) + line(p, q, mark: (end: "straight"), stroke: 0.7pt + luma(120)) + content(mid, text(7pt)[#label-str(arc.label)], frame: "rect", fill: white, stroke: none, padding: 0.04) + } + for (k, pos) in verts2.enumerate() { + g-node(pos, name: "v" + str(k), fill: white, label: [$#k$]) + } + // Mapping arrows + for u in range(n1) { + let v = f.at(u) + if v != n2 { + line(verts1.at(u), verts2.at(v), + stroke: (paint: graph-colors.at(0), thickness: 0.6pt, dash: "dashed"), + mark: (end: "straight")) + } + } + content((verts1.at(0).at(0) - 0.6, r1 + 0.5), text(9pt, weight: "bold")[$G_1$]) + content((verts2.at(0).at(0) - 0.6, r2 + 0.5), text(9pt, weight: "bold")[$G_2$]) + }) + }, + caption: [Maximum Common Edge Subgraph instance from the issue. Left: source graph $G_1$ with $|V_1| = #n1$ and $|E_1| = #g1.arcs.len()$ labelled arcs; matched source vertices are highlighted. Right: target graph $G_2$ with $|V_2| = #n2$ and $|E_2| = #g2.arcs.len()$. Dashed arrows show the partial injective map $f$; the source arc $(3, b, 4)$ is the unique non-preserved arc because vertex $4 in V_1$ is unmatched.], + ) + ] + ] +} + +#{ + // Hand-authored canonical example mirroring the in-repo example_db fixture + // (the canonical example for MaximumContactMapOverlap ships via the model + // file's canonical_model_example_specs rather than docs/paper/data/examples.json). + let n1 = 4 + let n2 = 5 + let contacts1 = ((0, 2), (1, 3)) + let contacts2 = ((0, 3), (1, 4), (0, 2)) + // Encoded config: value 0 = unmatched, value j + 1 = matched to vertex j of G_2. + // The optimum [1, 2, 4, 5] aligns 0->0, 1->1, 2->3, 3->4. + let config = (1, 2, 4, 5) + // Decoded alignment as (i, f(i)) pairs for matched i; bot otherwise. + let alignment = config.enumerate().map(((i, v)) => (i, v)) + let preserved = 2 + let fmt-pair(p) = $\{#p.at(0), #p.at(1)\}$ + let fmt-edges(es) = es.map(fmt-pair).join(", ") + [ + #problem-def("MaximumContactMapOverlap")[ + Given two finite ordered contact maps $G_1 = (V_1, E_1)$ and $G_2 = (V_2, E_2)$ with $V_r = {0, 1, dots, n_r - 1}$ ordered by index and $E_r subset.eq binom(V_r, 2)$ a simple undirected contact set, find an order-preserving partial injective alignment $f: V_1 -> V_2 union {bot}$ maximizing the number of preserved contacts + $ |{{i, k} in E_1 : i, k "matched and" {f(i), f(k)} in E_2}|. $ + Feasibility requires injectivity on matched vertices and the order-preserving condition: if $i < k$ in $V_1$ and both are matched, then $f(i) < f(k)$ in $V_2$. + ][ + The Maximum Contact Map Overlap problem (CMO) is a standard combinatorial formulation of flexible protein-structure comparison: each protein is represented by an ordered residue contact graph, and the alignment quality is measured by the number of superimposed contacts. Xie and Sahinidis introduced a reduction-based exact algorithm that solves CMO via a sequence of smaller maximum-weight independent-set subproblems on a derived interaction graph @XieSahinidis2007CMO. Andonov, Malod-Dognin, and Yanev later strengthened the integer-programming bound and B&B search, producing one of the fastest known exact CMO solvers @AndonovMalodDogninYanev2011CMO. The order-preserving constraint distinguishes CMO from the general maximum common edge-subgraph problem and reflects the underlying sequence of residues along each protein backbone. The registered exact baseline enumerates every assignment $V_1 -> V_2 union {bot}$ in $O^*((|V_2| + 1)^(|V_1|))$ time and filters to order-preserving injective maps#footnote[No algorithm improving on full enumeration is registered for the unrestricted variant. The specialized exact algorithms of @XieSahinidis2007CMO and @AndonovMalodDogninYanev2011CMO improve on the worst case in practice but not in the registered worst-case complexity bound.]. + + *Example.* Let $V_1 = {0, 1, 2, 3\}$ with $E_1 = {#fmt-edges(contacts1)}$ and $V_2 = {0, 1, 2, 3, 4\}$ with $E_2 = {#fmt-edges(contacts2)}$. The alignment $f$ given by + + #align(center)[#table( + columns: (auto, auto, auto, auto, auto), + align: center, + stroke: 0.4pt, + [$i$], [$0$], [$1$], [$2$], [$3$], + [$f(i)$], [$0$], [$1$], [$3$], [$4$], + )] + + is order-preserving ($0 < 1 < 3 < 4$) and injective. Both contacts of $G_1$ are preserved: + - $\{0, 2\}$ maps to $\{f(0), f(2)\} = \{0, 3\} in E_2$, + - $\{1, 3\}$ maps to $\{f(1), f(3)\} = \{1, 4\} in E_2$. + Hence the alignment achieves the maximum possible objective $#preserved = |E_1|$. + + #pred-commands( + "pred create --example MaximumContactMapOverlap -o cmo.json", + "pred solve cmo.json --solver brute-force", + "pred evaluate cmo.json --config " + config.map(str).join(","), + ) + + #figure({ + let dx = 4.0 + let pos1 = range(n1).map(i => (i * 1.0, 0.0)) + let pos2 = range(n2).map(j => (dx + j * 1.0, 0.0)) + canvas(length: 1cm, { + import draw: * + // Backbone of G_1 (visualizes residue order). + for i in range(n1 - 1) { + line(pos1.at(i), pos1.at(i + 1), stroke: (paint: luma(180), thickness: 0.4pt)) + } + // Backbone of G_2. + for j in range(n2 - 1) { + line(pos2.at(j), pos2.at(j + 1), stroke: (paint: luma(180), thickness: 0.4pt)) + } + // Contacts of G_1 as arcs above the backbone. + for (u, v) in contacts1 { + let p = pos1.at(u) + let q = pos1.at(v) + let mid = ((p.at(0) + q.at(0)) / 2, (p.at(1) + q.at(1)) / 2 + 0.45 * (v - u)) + hobby(p, mid, q, stroke: 0.7pt + luma(90)) + } + // Contacts of G_2 above its backbone. + for (u, v) in contacts2 { + let p = pos2.at(u) + let q = pos2.at(v) + let mid = ((p.at(0) + q.at(0)) / 2, (p.at(1) + q.at(1)) / 2 + 0.45 * (v - u)) + hobby(p, mid, q, stroke: 0.7pt + luma(90)) + } + // Vertices of G_1: highlight matched ones. + for (i, pos) in pos1.enumerate() { + let matched = config.at(i) != 0 + g-node(pos, name: "u" + str(i), + fill: if matched { graph-colors.at(0) } else { white }, + label: if matched { text(fill: white)[$#i$] } else { [$#i$] }) + } + // Vertices of G_2. + for (j, pos) in pos2.enumerate() { + g-node(pos, name: "v" + str(j), fill: white, label: [$#j$]) + } + // Mapping arrows (drawn below the backbones). + for (i, v) in alignment { + if v != 0 { + let j = v - 1 + let p = pos1.at(i) + let q = pos2.at(j) + let mid = ((p.at(0) + q.at(0)) / 2, (p.at(1) + q.at(1)) / 2 - 1.0) + hobby(p, mid, q, + stroke: (paint: graph-colors.at(0), thickness: 0.6pt, dash: "dashed")) + } + } + content((pos1.at(0).at(0) - 0.7, 0.0), text(9pt, weight: "bold")[$G_1$]) + content((pos2.at(0).at(0) - 0.7, 0.0), text(9pt, weight: "bold")[$G_2$]) + }) + }, + caption: [Maximum Contact Map Overlap instance from the issue. Top: ordered contact maps $G_1$ (left, $|V_1| = #n1$, $|E_1| = #contacts1.len()$) and $G_2$ (right, $|V_2| = #n2$, $|E_2| = #contacts2.len()$); contacts are drawn as arcs above the backbone. Bottom: dashed curves show the order-preserving partial injective alignment $f$; both contacts of $G_1$ are preserved.], + ) + ] + ] +} + +#{ + let x = load-model-example("MaximumEdgeWeightedKClique") + let nv = graph-num-vertices(x.instance) + let ne = graph-num-edges(x.instance) + let edges = x.instance.graph.edges + let edge-weights = x.instance.edge_weights + let k = x.instance.k + let sol = (config: x.optimal_config, metric: x.optimal_value) + let S = sol.config.enumerate().filter(((i, v)) => v == 1).map(((i, _)) => i) + let wS = metric-value(sol.metric) + let edge-strs = edges.zip(edge-weights).map(((e, w)) => [$w_(#e.at(0)#e.at(1)) = #w$]).join(", ") + [ + #problem-def("MaximumEdgeWeightedKClique")[ + Given a simple undirected graph $G = (V, E)$, edge weights $w: E -> RR$, and an integer $k$ with $0 <= k <= |V|$, find $S subset.eq V$ with $|S| = k$ such that every two distinct vertices in $S$ are adjacent in $G$, maximizing the total weight of the induced clique edges: + $ sum_({u, v} subset.eq S, {u, v} in E) w_(u v). $ + Cliques of size $0$ and $1$ are allowed when $k$ takes those values, with objective $0$ since no edge is induced. + ][ + The Maximum Edge-Weighted $k$-Clique problem is the exact-cardinality, edge-weighted specialization of the Maximum Edge-Weight Clique family. The unrestricted version (no cardinality constraint, or only an upper bound $|S| <= b$) was studied by Hunting, Faigle, and Kern @HuntingFaigleKern2001 using Lagrangian relaxation, and by Gouveia and Martins @GouveiaMartins2015MEWC, who developed compact ILP formulations that perform well on sparse graphs. The model differs from Maximum Clique (vertex-weighted, free cardinality) and from $k$-Clique (decision form with threshold $|S| >= k$). The brute-force baseline enumerates all $binom(|V|, k)$ candidate $k$-subsets and tests each for clique-ness in $O(k^2)$ time, giving a conservative worst-case bound of $O^*(2^(|V|))$#footnote[No algorithm improving on subset enumeration is registered for this exact-$k$ edge-weighted specialization.]. + + *Example.* Consider the graph $G$ on $n = #nv$ vertices with $|E| = #ne$ edges #edges.map(((u, v)) => [${#u, #v}$]).join(", "), edge weights #edge-strs, and $k = #k$. The graph contains two triangles, ${0, 1, 2}$ and ${0, 1, 3}$, but no clique on ${0, 2, 3}$ or ${1, 2, 3}$ because edge ${2, 3} in.not E$. The optimal $k$-clique is $S = {#S.map(i => $v_#i$).join(", ")}$ with induced edge weights $5 + 4 + (-1) = #wS$; the alternative triangle ${v_0, v_1, v_3}$ scores only $5 + 1 + 0 = 6$. The negatively-weighted edge ${1, 2}$ does not prevent the clique ${0, 1, 2}$ from being optimal because the positive edges dominate. + + #pred-commands( + "pred create --example " + problem-spec(x) + " -o k-clique.json", + "pred solve k-clique.json", + "pred evaluate k-clique.json --config " + x.optimal_config.map(str).join(","), + ) + + #figure({ + let r = 1.1 + let verts = range(nv).map(i => { + let angle = calc.pi / 2 + 2 * calc.pi * i / nv + (r * calc.cos(angle), r * calc.sin(angle)) + }) + draw-node-highlight(verts, edges, S) + }, + caption: [The graph from issue \#1020 with edge weights $(5, 4, -1, 1, 0)$ in graph-edge order and $k = #k$. Selected vertices $S = {#S.map(i => $v_#i$).join(", ")}$ (blue) induce all three edges of the triangle ${v_0, v_1, v_2}$; the total induced weight is $#wS$.], + ) + ] + ] +} + +#{ + // Hand-authored canonical example mirroring the in-repo example_db fixture + // (load-model-example is not used here because the corresponding example + // entry is shipped via the model file's canonical_model_example_specs rather + // than the docs/paper/data/examples.json bundle). + let nv = 4 + let edges = ((0, 1), (0, 2), (1, 2), (2, 3)) + let ne = edges.len() + let config = (0, 0, 0, 1) + let deleted = edges.zip(config).filter(((e, b)) => b == 1).map(((e, _)) => e) + let surviving = edges.zip(config).filter(((e, b)) => b == 0).map(((e, _)) => e) + let cluster = (0, 1, 2) + let opt-val = deleted.len() + [ + #problem-def("HighlyConnectedDeletion")[ + Given a simple undirected graph $G = (V, E)$, find a minimum-cardinality edge set $F subset.eq E$ such that every connected component of $G - F$ is either an isolated vertex or a *highly connected* graph on at least $3$ vertices, where a graph $H$ is highly connected iff its edge connectivity satisfies + $ lambda(H) > |V(H)| / 2. $ + Components of size $2$ (isolated edges) are explicitly forbidden as clusters. The objective is $|F|$, the number of deleted edges. + ][ + Highly Connected Deletion is the edge-coverage maximizing form of the HCS (Highly Connected Subgraphs) clustering paradigm introduced by Hartuv and Shamir for biological networks, where every cluster must remain strictly more than $|V(H)|/2$-edge-connected to survive iterated minimum-cut splits @HartuvShamir2000. H{\"u}ffner, Komusiewicz, Liebtrau, and Niedermeier later turned this connectivity requirement into the exact optimization problem registered here — minimize the number of deleted edges so that every surviving component is either an isolated vertex or a highly connected subgraph on at least three vertices — and studied its parameterized and approximation complexity @HueffnerKomusiewiczLiebtrauNiedermeier2014. The decision form is NP-complete, and the registered exact baseline enumerates every edge-deletion subset in $O^*(2^(|E|))$ time and verifies feasibility via per-component edge-connectivity checks#footnote[No algorithm improving on subset enumeration is registered for general Highly Connected Deletion. The literature reports kernelization and FPT algorithms parameterized by the number of deletions @HueffnerKomusiewiczLiebtrauNiedermeier2014, but these are not currently part of the registry.]. Note that every clique on at least $3$ vertices is highly connected, but the converse fails, so Highly Connected Deletion is strictly weaker than clique-based clustering models such as Minimum Cluster Edge Deletion. + + *Example.* Consider the graph $G$ on $n = #nv$ vertices with $|E| = #ne$ edges #edges.map(((u, v)) => [${#u, #v}$]).join(", "). It is a triangle on ${0, 1, 2}$ with a leaf vertex $3$ attached to $2$ by the edge ${2, 3}$. Deleting only the leaf edge ${#deleted.at(0).at(0), #deleted.at(0).at(1)}$ — i.e. setting $x_e = 1$ for that one edge — yields two components: the triangle $G[{#cluster.map(i => $#i$).join(", ")}] = K_3$, which is highly connected because $lambda(K_3) = 2 > 3/2$, and the isolated vertex ${3}$, which is allowed as an unclustered leftover. Zero deletions are infeasible because the full graph has minimum degree $1$ at vertex $3$ and therefore edge connectivity $lambda(G) = 1$, which is not greater than $4/2 = 2$. Hence the optimum value is $|F| = #opt-val$. + + #pred-commands( + "pred create --example HighlyConnectedDeletion -o hcd.json", + "pred solve hcd.json", + "pred evaluate hcd.json --config " + config.map(str).join(","), + ) + + #figure({ + let verts = ((-1.0, 0.6), (1.0, 0.6), (0.0, -0.4), (1.6, -1.2)) + draw-edge-highlight(verts, edges, deleted, cluster) + }, + caption: [The triangle-with-leaf graph from the issue ($n = #nv$, $|E| = #ne$). The deleted edge ${#deleted.at(0).at(0), #deleted.at(0).at(1)}$ is shown in bold blue. The surviving triangle ${#cluster.map(i => $v_#i$).join(", ")}$ (blue nodes) is highly connected ($lambda = 2 > 3/2$); vertex $v_3$ remains as an allowed isolated leftover. Deletion budget $|F| = #opt-val$.], + ) + ] + ] +} + +#{ + let x = load-model-example("EulerianPath") + let nv = x.instance.graph.num_vertices + let arcs = x.instance.graph.arcs + let m = arcs.len() + // Witness ordering from the issue: a_0 -> a_2 -> a_3 -> a_1, tracing 0->1->2->0->1. + let pi = x.optimal_config + let trail = (arcs.at(pi.at(0)).at(0),) + pi.map(j => arcs.at(j).at(1)) + [ + #problem-def("EulerianPath")[ + Given a finite directed multigraph $D = (V, A)$ — with loops and parallel arcs permitted — decide whether there exists a directed trail $T = a_(i_1) a_(i_2) dots.h.c a_(i_m)$ that uses every arc occurrence in $A$ exactly once. The trail may be open or closed, and isolated vertices are allowed and ignored. Repeated arc occurrences are distinguished, so the empty-arc instance is accepted by convention with the empty trail. + ][ + An Eulerian trail is the classical "draw without lifting the pen" object: a walk that uses every arc exactly once. Euler's 1736 negative resolution of the Königsberg bridges problem launched graph theory by characterizing exactly when such walks exist on multigraphs. For directed multigraphs $D$ with weakly connected support, an Eulerian trail exists iff either every vertex has equal in-degree and out-degree (giving a closed trail / Eulerian circuit) or there exist two distinguished vertices $s != t$ with $"outdeg"(s) = "indeg"(s) + 1$, $"indeg"(t) = "outdeg"(t) + 1$, and all other vertices balanced @BangJensenGutin2009Digraphs. + + Unlike its sibling Hamiltonian Path, EulerianPath is polynomial-time decidable: Hierholzer's stitching algorithm constructs a witness — or certifies infeasibility — in $O(|V| + |A|)$ time, and Ebert's refinement provides an explicit linear-time algorithm tailored to directed multigraphs @Ebert1988ComputingEulerianTrails. Eulerian trails underpin DNA fragment assembly via de Bruijn graphs, optimal arc routing in postman problems, and printed-circuit drilling tours. The brute-force baseline used by the registry enumerates all $m^m$ position assignments and verifies the permutation-plus-trail invariants in linear time; the model is registered as a satisfaction problem and the literature linear-time algorithm is the worst-case best-known complexity. + + *Example (YES instance).* Consider the directed multigraph on $|V| = #nv$ vertices with $m = #m$ arcs $#arcs.map(((u, v)) => $(#u arrow.r #v)$).join(", ")$; the arcs $a_0 = (0 arrow.r 1)$ and $a_1 = (0 arrow.r 1)$ are parallel. The ordering $(a_(#pi.at(0)), a_(#pi.at(1)), a_(#pi.at(2)), a_(#pi.at(3)))$ traces the directed trail $#trail.map(v => $#v$).join($arrow.r$)$, using every arc exactly once. + + *Example (NO instance).* Let $V = {0, 1}$ and $A = {(0,1), (0,1), (0,1), (1,0)}$. The support is connected, but $"outdeg"(0) - "indeg"(0) = 2$ and $"indeg"(1) - "outdeg"(1) = 2$, so the directed balance criterion fails: any open trail can have at most one source-vertex of excess $+1$, hence no Eulerian trail exists. + + #pred-commands( + "pred create --example EulerianPath -o eulerian.json", + "pred solve eulerian.json", + "pred evaluate eulerian.json --config " + pi.map(str).join(","), + ) + + #figure( + canvas(length: 1cm, { + import draw: * + let blue = graph-colors.at(0) + let gray = luma(170) + let arc-mark-blue = (end: (symbol: ">", scale: 0.62, fill: blue)) + // Three vertices placed in a triangle layout so the parallel arc (0->1) + // and the back-arc (2->0) are easy to distinguish. + let verts = ( + (0, 0), + (2.4, 0), + (1.2, 1.6), + ) + for (k, pos) in verts.enumerate() { + g-node(pos, name: "ep-" + str(k), fill: blue, label: none) + } + // First copy of (0->1): straight thick blue segment carrying a_0. + line( + "ep-0", + "ep-1", + stroke: 2pt + blue, + mark: arc-mark-blue, + name: "ep-arc-0", + ) + // Second copy of (0->1) drawn as a downward bow so it does not overlap a_0. + bezier( + "ep-0.south", + "ep-1.south", + (1.2, -0.95), + stroke: 2pt + blue, + mark: arc-mark-blue, + name: "ep-arc-1", + ) + // (1->2) and (2->0) close the trail. + line( + "ep-1", + "ep-2", + stroke: 2pt + blue, + mark: arc-mark-blue, + name: "ep-arc-2", + ) + line( + "ep-2", + "ep-0", + stroke: 2pt + blue, + mark: arc-mark-blue, + name: "ep-arc-3", + ) + // Label vertices. + for (k, pos) in verts.enumerate() { + draw.content("ep-" + str(k), text(8pt, text(fill: white)[$v_#k$])) + } + // Annotate arc indices along the four trail edges. + content("ep-arc-0.mid", text(7pt, fill: gray)[$a_0$], frame: "rect", padding: 0.06, stroke: none, fill: white) + content((1.2, -0.95), text(7pt, fill: gray)[$a_1$], frame: "rect", padding: 0.06, stroke: none, fill: white) + content("ep-arc-2.mid", text(7pt, fill: gray)[$a_2$], frame: "rect", padding: 0.06, stroke: none, fill: white) + content("ep-arc-3.mid", text(7pt, fill: gray)[$a_3$], frame: "rect", padding: 0.06, stroke: none, fill: white) + }), + caption: [Canonical YES instance on $|V| = #nv$ vertices and $m = #m$ arcs (with parallel arcs $a_0, a_1$ between $v_0$ and $v_1$). The witness ordering $(a_0, a_2, a_3, a_1)$ traces the directed Eulerian trail $#trail.map(v => $v_#v$).join($arrow.r$)$.], + ) + ] + ] +} + +#{ + let x = load-model-example("PrizeCollectingSteinerForest") + let nv = x.instance.graph.num_vertices + let edges = x.instance.graph.edges + let ne = edges.len() + let vertex-prizes = x.instance.vertex_prizes + let edge-costs = x.instance.edge_costs + let beta = x.instance.beta + let omega = x.instance.omega + let config = x.optimal_config + // Configuration layout: first nv bits are vertex selectors x_v, next ne bits are y_e. + let selected-verts = range(nv).filter(v => config.at(v) == 1) + let omitted-verts = range(nv).filter(v => config.at(v) == 0) + let selected-edge-indices = range(ne).filter(i => config.at(nv + i) == 1) + let selected-edges = selected-edge-indices.map(i => edges.at(i)) + let omitted-prize-sum = omitted-verts.map(v => vertex-prizes.at(v)).fold(0, (a, b) => a + b) + let edge-cost-sum = selected-edge-indices.map(i => edge-costs.at(i)).fold(0, (a, b) => a + b) + let opt-val = x.optimal_value + [ + #problem-def("PrizeCollectingSteinerForest")[ + Given an undirected network $G = (V, E)$ with nonnegative vertex prizes $p: V -> RR_(>= 0)$, nonnegative edge costs $c: E -> RR_(>= 0)$, and parameters $beta >= 0$ and $omega >= 0$, find a forest $F = (V_F, E_F)$ — that is, a subgraph that is a disjoint union of trees, including singleton-vertex trees — minimizing + $ beta dot sum_(v in.not V_F) p(v) + sum_(e in E_F) c(e) + omega dot kappa(F), $ + where $kappa(F)$ is the number of (tree) components of $F$. Singleton selected vertices are allowed and count as one-vertex tree components; the empty forest is feasible. + ][ + The Prize-Collecting Steiner Forest (PCSF) model registered here is the biology-paper variant introduced by Tuncbag, Braunstein, Pagnani, Huang, Chayes, Borgs, Zecchina, and Fraenkel, who used it to jointly reconstruct multiple cellular signaling pathways from heterogeneous experimental evidence by trading off node prizes (importance of including a protein), edge costs (interaction reliability), and a per-component penalty $omega$ that discourages over-fragmentation of the recovered subnetwork @TuncbagEtAl2013PCSF @TuncbagEtAl2012RECOMB. The companion artificial-root reduction reformulates PCSF as a single rooted Steiner tree on an augmented graph and is registered separately as a reduction rule, so the model itself stays close to the original biological objective. The registered exact baseline enumerates the $2^(|V| + |E|)$ pairs of vertex- and edge-selectors and filters to feasible forests; we record the conservative bound $O^*(2^(|V| + |E|))$#footnote[No algorithm improving on full $(V, E)$-subset enumeration is registered for the biology-style PCSF variant with explicit component penalty $omega dot kappa(F)$.]. PCSF generalises the rooted prize-collecting Steiner tree ($omega = + infinity$, single component) and reduces to the maximum-prize independent-vertex selection when all edge costs dominate the prizes. + + *Example.* Take the undirected path $0 - 1 - 2$ on $n = #nv$ vertices with $|E| = #ne$ edges $#edges.map(((u, v)) => [${#u, #v}$]).join(", ")$, edge costs $c(0, 1) = #edge-costs.at(0)$ and $c(1, 2) = #edge-costs.at(1)$, vertex prizes $p = (#vertex-prizes.at(0), #vertex-prizes.at(1), #vertex-prizes.at(2))$, $beta = #beta$, and $omega = #omega$. The optimal forest selects $V_F = {#selected-verts.map(v => $v_#v$).join(", ")}$ and $E_F = {#selected-edges.map(((u, v)) => $(v_#u, v_#v)$).join(", ")}$, which decomposes into two tree components ${v_0, v_1}$ and ${v_2}$, so $kappa(F) = 2$. The objective decomposes as $beta dot #omitted-prize-sum + #edge-cost-sum + omega dot 2 = 0 + #edge-cost-sum + #(omega * 2) = #opt-val$. + Cheaper alternatives are dominated: the full path $E_F = {(v_0, v_1), (v_1, v_2)}$ costs $0 + (1 + 6) + omega = 9$, three singleton trees ${v_0}, {v_1}, {v_2}$ cost $0 + 0 + 3 omega = 6$, and the empty forest costs $beta dot (5 + 2 + 5) = 12$. + + #pred-commands( + "pred create --example PrizeCollectingSteinerForest -o pcsf.json", + "pred solve pcsf.json --solver brute-force", + "pred evaluate pcsf.json --config " + config.map(str).join(","), + ) + + #figure({ + let verts = ((0, 0), (1.6, 0), (3.2, 0)) + canvas(length: 1cm, { + import draw: * + let blue = graph-colors.at(0) + let gray = luma(160) + // Edges first so node circles overlay them. + for (idx, edge) in edges.enumerate() { + let (u, v) = edge + let on-forest = selected-edge-indices.contains(idx) + line(verts.at(u), verts.at(v), + stroke: if on-forest { 2pt + blue } else { (paint: gray, thickness: 1pt, dash: "dashed") }) + let mx = (verts.at(u).at(0) + verts.at(v).at(0)) / 2 + let my = (verts.at(u).at(1) + verts.at(v).at(1)) / 2 + content((mx, my + 0.28), text(7pt, fill: if on-forest { blue } else { gray })[$c = #edge-costs.at(idx)$], frame: "rect", padding: 0.04, stroke: none, fill: white) + } + for (k, pos) in verts.enumerate() { + let in-vf = selected-verts.contains(k) + g-node(pos, name: "v" + str(k), + fill: if in-vf { blue } else { white }, + stroke: if in-vf { none } else { 1pt + blue }, + label: text(fill: if in-vf { white } else { black })[$v_#k$]) + content((pos.at(0), pos.at(1) - 0.45), text(7pt, fill: luma(80))[$p = #vertex-prizes.at(k)$]) + } + }) + }, + caption: [Canonical PCSF instance from issue #1026 on the path $0 - 1 - 2$ with prizes $p$ and costs $c$ shown beside each vertex and edge. The selected forest $V_F = {v_0, v_1, v_2}$, $E_F = {(v_0, v_1)}$ (solid blue edge) decomposes into the tree on ${v_0, v_1}$ and the singleton tree ${v_2}$, so $kappa(F) = #selected-verts.len() - #selected-edges.len() = 2$. The dashed edge $(v_1, v_2)$ is omitted; objective value $= 0 + #edge-cost-sum + #(omega * 2) = #opt-val$.], + ) + ] + ] +} + #{ let x = load-model-example("MinimumVertexCover") let nv = graph-num-vertices(x.instance) @@ -5591,6 +6098,40 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V| ] } +#{ + let x = load-model-example("MinimumDiscretePlanarInverseKinematics") + [ + #problem-def("MinimumDiscretePlanarInverseKinematics")[ + Given positive link lengths $l_1, dots, l_n$, a target point $g = (g_x, g_y) in bb(R)^2$, finite sets of candidate absolute orientations $Phi_j = {phi_(j,0), dots, phi_(j,m_j-1)}$ for every link $j = 1, dots, n$, and admissible pair sets $A_j subset.eq {0, dots, m_(j-1) - 1} times {0, dots, m_j - 1}$ for $j = 2, dots, n$, choose indices $a_j in {0, dots, m_j - 1}$ such that $(a_(j-1), a_j) in A_j$ for every $j = 2, dots, n$, minimizing the squared end-effector error + $ norm(sum_(j=1)^n l_j (cos phi_(j,a_j), sin phi_(j,a_j)) - g)_2^2. $ + ][ + The Minimum Discrete Planar Inverse Kinematics problem is the discrete-sample reformulation of planar inverse kinematics studied by Salloum, Savin, Kholodov, Ryzhakov, Farina, and Oseledets @salloum2025ikqubo for quantum annealing pipelines. Each link is parameterized by its absolute orientation rather than a local joint angle, so the workspace position is linear in the per-link selector variables and one-hot encoding produces a genuinely quadratic objective when reducing to QUBO. The admissible pair sets $A_j$ model joint limits on the relative angle $phi_(j,a_j) - phi_(j-1,a_(j-1))$. The decision and exact optimization versions are NP-hard via Knapsack-style packing of discretized angle choices, mirroring the broader mixed-integer convex inverse-kinematics formulation of Dai, Izatt, and Tedrake @daiizatttedrake2019. The registered exact baseline enumerates the product domain $product_(j=1)^n m_j$#footnote[No algorithm improving on exhaustive enumeration over per-link discrete orientations is known for this discretized formulation in general.]. + + *Example.* Take $n = 2$ with link lengths $(l_1, l_2) = (2, 1)$, target $g = (2, 1)$, sampled orientations $Phi_1 = Phi_2 = {0, pi / 2}$, and admissible pair set $A_2 = {(0,0), (0,1), (1,1)}$. The configuration $(a_1, a_2) = (0, 1)$ lies in $A_2$ and places the end-effector at $(2 cos 0, 2 sin 0) + (cos(pi / 2), sin(pi / 2)) = (2, 1)$, giving optimal objective value $0$. The other two feasible configurations have squared errors $2$ (for $(0,0)$) and $8$ (for $(1,1)$); the configuration $(1, 0)$ is infeasible. + + #pred-commands( + "pred create --example MinimumDiscretePlanarInverseKinematics -o ik.json", + "pred solve ik.json --solver brute-force", + "pred evaluate ik.json --config " + x.optimal_config.map(str).join(","), + ) + + #figure( + table( + columns: 4, + inset: 6pt, + stroke: 0.5pt + luma(180), + [Config $(a_1, a_2)$], [Feasible?], [End-effector], [Squared error], + [$(0, 0)$], [yes], [$(3, 0)$], [$2$], + [$(0, 1)$], [yes (optimal)], [$(2, 1)$], [$0$], + [$(1, 0)$], [no], [---], [---], + [$(1, 1)$], [yes], [$(0, 3)$], [$8$], + ), + caption: [Minimum Discrete Planar Inverse Kinematics example with $n = 2$, link lengths $(2, 1)$, target $(2, 1)$, and per-link orientations ${0, pi / 2}$. Three of the four configurations are admissible under $A_2 = {(0,0), (0,1), (1,1)}$; the unique optimum is $(0, 1)$ with squared error $0$.], + ) + ] + ] +} + #{ let x = load-model-example("BMF") let mr = x.instance.m @@ -7050,6 +7591,93 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V| ] } +#{ + // Hand-authored canonical example mirroring the in-repo example_db fixture + // (load-model-example is not used here because the corresponding example + // entry is shipped via the model file's canonical_model_example_specs rather + // than the docs/paper/data/examples.json bundle). + let alphabet-size = 2 + let strings = ( + (0, 0, 0), + (0, 1, 1), + (1, 0, 1), + (1, 1, 0), + ) + let center = (0, 0, 0) + let m = center.len() + let n = strings.len() + let fmt-str(s) = s.map(c => str(c)).join("") + let hamming(a, b) = range(a.len()).filter(i => a.at(i) != b.at(i)).len() + let distances = strings.map(s => hamming(center, s)) + let radius = distances.fold(0, (a, b) => calc.max(a, b)) + [ + #problem-def("ClosestString")[ + Given a finite alphabet $Sigma = {0, dots, q - 1}$ and a list of input strings $s_1, dots, s_n in Sigma^m$ all of common length $m$, find a center string $c in Sigma^m$ minimizing the maximum Hamming distance from $c$ to any input: + $ min_(c in Sigma^m) max_(1 lt.eq i lt.eq n) d_H (c, s_i), $ + where $d_H (x, y) = |{j : x[j] != y[j]}|$ is the Hamming distance. + ][ + A central problem in computational biology, coding theory, and consensus-pattern discovery. #cite(, form: "prose") showed that the decision version is NP-complete and gave the first polynomial-time approximation scheme; the problem remains NP-hard even over the binary alphabet $|Sigma| = 2$. Closest String is fixed-parameter tractable when parameterized by either the radius or the number of input strings, but the registered exact baseline simply enumerates every center string in $|Sigma|^m$ time and reports the smallest worst-case Hamming distance. + + *Example.* Let $Sigma = {0, 1}$ ($q = #alphabet-size$) and consider the $n = #n$ binary strings of length $m = #m$: + $s_1 = #fmt-str(strings.at(0))$, $s_2 = #fmt-str(strings.at(1))$, $s_3 = #fmt-str(strings.at(2))$, $s_4 = #fmt-str(strings.at(3))$. + The center $c = #fmt-str(center)$ achieves Hamming distances + $d_H (c, s_1) = #distances.at(0)$, $d_H (c, s_2) = #distances.at(1)$, $d_H (c, s_3) = #distances.at(2)$, $d_H (c, s_4) = #distances.at(3)$, + so its worst-case distance is $#radius$. No center attains radius $1$: any binary length-$3$ string differs from at least one of $s_1, dots, s_4$ in at least two positions, so the optimum radius is exactly $#radius$. + + #pred-commands( + "pred create --example ClosestString -o closest-string.json", + "pred solve closest-string.json --solver brute-force", + "pred evaluate closest-string.json --config " + center.map(str).join(","), + ) + ] + ] +} + +#{ + // Hand-authored canonical example mirroring the in-repo example_db fixture + // for ClosestSubstring (q = 2, ell = 3, three length-5 binary strings). + let alphabet-size = 2 + let ell = 3 + let strings = ( + (0, 0, 0, 1, 1), + (1, 0, 1, 0, 0), + (1, 1, 0, 0, 1), + ) + let center = (0, 1, 0) + let window-starts = (0, 1, 0) + let n = strings.len() + let fmt-str(s) = s.map(c => str(c)).join("") + let window-of(i) = range(ell).map(j => strings.at(i).at(window-starts.at(i) + j)) + let hamming(a, b) = range(a.len()).filter(j => a.at(j) != b.at(j)).len() + let windows = range(n).map(i => window-of(i)) + let distances = windows.map(w => hamming(center, w)) + let radius = distances.fold(0, (a, b) => calc.max(a, b)) + let config = center + window-starts + [ + #problem-def("ClosestSubstring")[ + Given a finite alphabet $Sigma = {0, dots, q - 1}$, a list of input strings $s_1, dots, s_n$ over $Sigma$ (not necessarily of equal length), and a window length $ell$ with $ell lt.eq |s_i|$ for every $i$, find a center $c in Sigma^ell$ and per-string window starts $p_i in {0, dots, |s_i| - ell}$ minimizing + $ min_(c, p_1, dots, p_n) max_(1 lt.eq i lt.eq n) d_H (c, s_i [p_i .. p_i + ell)), $ + where $d_H$ is the Hamming distance and $s_i [p_i .. p_i + ell)$ is the length-$ell$ substring of $s_i$ starting at position $p_i$. + ][ + Introduced by #cite(, form: "prose"), who showed that the decision version is NP-complete (even over the binary alphabet) and gave the first polynomial-time approximation scheme. Closest Substring strictly generalizes Closest String: the special case $ell = |s_i|$ for all $i$ forces a unique window in each string and recovers Closest String. The registered exact baseline enumerates every center in $Sigma^ell$ together with every tuple of window starts, giving $O(q^ell dot product_i (|s_i| - ell + 1))$ configurations. + + *Example.* Let $Sigma = {0, 1}$ ($q = #alphabet-size$), $ell = #ell$, and consider the $n = #n$ binary strings + $s_1 = #fmt-str(strings.at(0))$, $s_2 = #fmt-str(strings.at(1))$, $s_3 = #fmt-str(strings.at(2))$. + The center $c = #fmt-str(center)$ with window starts $(p_1, p_2, p_3) = (#window-starts.at(0), #window-starts.at(1), #window-starts.at(2))$ selects the substrings + $s_1 [#window-starts.at(0) .. #(window-starts.at(0) + ell)) = #fmt-str(windows.at(0))$, + $s_2 [#window-starts.at(1) .. #(window-starts.at(1) + ell)) = #fmt-str(windows.at(1))$, + $s_3 [#window-starts.at(2) .. #(window-starts.at(2) + ell)) = #fmt-str(windows.at(2))$, + with Hamming distances $#distances.at(0), #distances.at(1), #distances.at(2)$ and worst-case distance $#radius$. The three sets of length-$ell$ windows have empty intersection, so no center attains radius $0$ and the optimum is exactly $#radius$. + + #pred-commands( + "pred create --example ClosestSubstring -o closest-substring.json", + "pred solve closest-substring.json --solver brute-force", + "pred evaluate closest-substring.json --config " + config.map(str).join(","), + ) + ] + ] +} + #{ let x = load-model-example("SubsetSum") let sizes = x.instance.sizes @@ -9761,37 +10389,95 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V| } #{ - let x = load-model-example("IntegralFlowBundles") - let source = x.instance.source - let sink = x.instance.sink + let x = load-model-example("MinimumCostMaximumFlow") + let arcs-j = x.instance.graph.arcs.map(a => (a.at(0), a.at(1))) + let caps = x.instance.capacities + let costs-j = x.instance.costs + let src = x.instance.source + let snk = x.instance.sink + let flow = x.optimal_config [ - #problem-def("IntegralFlowBundles")[ - Given a directed graph $G = (V, A)$, specified vertices $s, t in V$, a family of arc bundles $I_1, dots, I_k subset.eq A$ whose union covers $A$, positive bundle capacities $c_1, dots, c_k$, and a requirement $R in ZZ^+$, determine whether there exists an integral flow $f: A -> ZZ_(>= 0)$ such that (1) $sum_(a in I_j) f(a) <= c_j$ for every bundle $j$, (2) flow is conserved at every vertex in $V backslash {s, t}$, and (3) the net flow into $t$ is at least $R$. + #problem-def("MinimumCostMaximumFlow")[ + Given a directed graph $G = (V, A)$ with non-negative arc capacities $c: A -> ZZ_(>= 0)$, non-negative arc costs $"cost": A -> ZZ_(>= 0)$, a source vertex $s in V$, and a sink vertex $t in V$ with $s != t$, find an integral flow $f: A -> ZZ_(>= 0)$ with $0 <= f(a) <= c(a)$ that conserves flow at every $v in V backslash {s, t}$ and that lexicographically (1) maximizes the flow value $|f| = sum_(a in delta^+(s)) f(a) - sum_(a in delta^-(s)) f(a)$, and (2) among all maximum-value flows, minimizes the total arc cost $sum_(a in A) "cost"(a) dot f(a)$. ][ - Integral Flow with Bundles is the shared-capacity single-commodity flow problem listed as ND36 in Garey \& Johnson @garey1979. Sahni introduced it as one of a family of computationally related network problems and showed that the bundled-capacity variant is NP-complete even in a very sparse unit-capacity regime @sahni1974. + Minimum-Cost Maximum-Flow is the standard lexicographic combination underlying network design and routing applications. The continuous version is polynomial-time solvable via successive-shortest-paths or network-simplex algorithms (see, e.g., the MIT 6.854 lecture notes @mit6854MinCostFlow); when capacities and costs are integral, the totally-unimodular constraint matrix guarantees an integral optimum, so restricting $f$ to integers does not change the optimal value. This formulation is the optimization target adopted by the CellRouter pipeline for single-cell trajectory reconstruction @LummertzDaRocha2018CellRouter, where the lexicographic objective first commits maximum biological throughput between progenitor and target cell states and then chooses the cheapest realization of that throughput. - The implementation keeps one non-negative integer variable per directed arc. Unlike ordinary max-flow, the usable range of an arc is not determined by an intrinsic per-arc capacity; it is bounded instead by the smallest bundle capacity among the bundles that contain that arc. The registered $O(2^m)$ catalog bound therefore reflects the unit-capacity case with $m = |A|$, which is exactly the regime highlighted by Garey \& Johnson and Sahni.#footnote[No exact worst-case algorithm improving on brute-force is claimed here for the bundled-capacity formulation.] + *Lexicographic scalarization.* The catalog encodes the lex objective as the single Min-objective + $ "score"(f) = M dot (B - |f|) + sum_(a in A) "cost"(a) f(a), $ + where $B = sum_(a in A) c(a)$ upper-bounds any feasible flow value and $M = sum_(a in A) c(a) dot "cost"(a) + 1$ strictly exceeds any feasible cost. Minimizing $"score"(f)$ therefore picks a maximum-value flow first and breaks ties by minimum cost; on infeasible configurations the value is $bot$. - *Example.* The canonical YES instance has source $s = v_#source$, sink $t = v_#sink$, and arcs $(0,1)$, $(0,2)$, $(1,3)$, $(2,3)$, $(1,2)$, $(2,1)$. The three bundles are $I_1 = {(0,1), (0,2)}$, $I_2 = {(1,3), (2,1)}$, and $I_3 = {(2,3), (1,2)}$, each with capacity 1. Sending one unit along the path $0 -> 1 -> 3$ yields the flow vector $(1, 0, 1, 0, 0, 0)$: bundle $I_1$ contributes $1 + 0 = 1$, bundle $I_2$ contributes $1 + 0 = 1$, bundle $I_3$ contributes $0 + 0 = 0$, and the only nonterminal vertices $v_1, v_2$ satisfy conservation. If the requirement is raised from $R = 1$ to $R = 2$, the same gadget becomes infeasible because $I_1$ caps the total outflow leaving the source at one unit. + The registered polynomial bound $(|V| + |A|)^6$ is a conservative placeholder honoring the polynomial-time solvability via the linear-programming formulation; sharper strongly-polynomial bounds are available for specific algorithms (e.g., Orlin's enhanced capacity-scaling minimum-cost flow algorithm). + + *Example.* On the canonical instance ($n = 4$ vertices, source $s = v_#src$, sink $t = v_#snk$, $|A| = #{arcs-j.len()}$ arcs with capacities $(#caps.map(str).join(", "))$ and costs $(#costs-j.map(str).join(", "))$), the source out-capacity bounds the flow value at $|f| <= 2 + 1 = 3$, and this is achievable: routing 2 units along $v_0 -> v_1$ and 1 unit along $v_0 -> v_2$, balanced by 1 unit through the lateral $v_1 -> v_2$ and 1 unit on $v_1 -> v_3$, gives $f = (#flow.map(str).join(", "))$ with $|f| = 3$ and total cost $2 dot 1 + 1 dot 0 + 1 dot 0 + 1 dot 1 + 2 dot 2 = 7$. The scalar score is $M dot (B - 3) + 7 = 8 dot 4 + 7 = 39$ where $B = 7$ and $M = 8$. #pred-commands( - "pred create --example IntegralFlowBundles -o integral-flow-bundles.json", - "pred solve integral-flow-bundles.json", - "pred evaluate integral-flow-bundles.json --config " + x.optimal_config.map(str).join(","), + "pred create --example MinimumCostMaximumFlow -o mcmf.json", + "pred solve mcmf.json", + "pred evaluate mcmf.json --config " + flow.map(str).join(","), ) + ] + ] +} - #figure( - canvas(length: 1cm, { - import draw: * - let blue = graph-colors.at(0) - let orange = rgb("#f28e2b") - let teal = rgb("#76b7b2") - let positions = ( - (0, 0), // v0 = source - (2.5, 1.5), // v1 - (2.5, -1.5), // v2 - (5.0, 0), // v3 = sink - ) +#{ + let x = load-model-example("MinimumCostCirculation") + let arcs-j = x.instance.graph.arcs.map(a => (a.at(0), a.at(1))) + let caps = x.instance.capacities + let costs-j = x.instance.costs + let circ = x.optimal_config + [ + #problem-def("MinimumCostCirculation")[ + Given a finite directed multigraph $G = (V, A)$ (loops and parallel arcs allowed) with non-negative integral arc capacities $c: A -> ZZ_(>= 0)$ and *signed* integral arc costs $a: A -> ZZ$, find an integral circulation $g: A -> ZZ_(>= 0)$ that minimizes the total cost $sum_(a in A) a(a) dot g(a)$ subject to $0 <= g(a) <= c(a)$ for every arc $a$ and to flow conservation at *every* vertex $v in V$, i.e. inflow equals outflow at $v$ (there is no distinguished source or sink). + ][ + Minimum-Cost Circulation is the natural graph-flow target of Minimum-Cost Maximum-Flow: the standard reduction adds a single sufficiently negative return arc from the sink to the source, turning a max-value/min-cost flow problem into pure cost minimization over circulations. Because every vertex must balance, signed costs do not cause the objective to diverge when capacities are finite — negative-cost cycles are bounded by the smallest arc capacity along them and are exactly what drives the reduction from min-cost max-flow. The continuous problem is polynomial-time solvable as a standard minimum-cost flow @mit6854MinCostFlow. + + *Integral-circulation restriction.* The mathematical model uses continuous flows $g: A -> RR_(>= 0)$, but the framework requires a discrete configuration space. Following the same precedent as Minimum Edge-Cost Flow and Minimum-Cost Maximum-Flow, the catalog entry restricts $g$ to integer values $g(a) in {0, 1, dots, c(a)}$. For integral capacities and costs the total-unimodularity of the network constraint matrix guarantees an integral optimum exists, so the restriction does not change the optimal value. + + The registered polynomial bound $(|V| + |A|)^6$ is a conservative placeholder honoring polynomial-time solvability via the linear-programming formulation; sharper strongly-polynomial bounds are available for specific min-cost-flow algorithms. + + *Example.* On the canonical instance ($n = #{arcs-j.fold(0, (acc, a) => calc.max(acc, a.at(0), a.at(1))) + 1}$ vertices, $|A| = #{arcs-j.len()}$ arcs with capacities $(#caps.map(str).join(", "))$ and signed costs $(#costs-j.map(str).join(", "))$) there are two competing cycles through vertex $v_0$: cycle $A = (v_0 -> v_1 -> v_0)$ has per-unit cost $2 + (-3) = -1$ and capacity $2$, while cycle $B = (v_0 -> v_2 -> v_0)$ has per-unit cost $1 + (-4) = -3$ and capacity $1$. Cycle $B$ is cheaper per unit but smaller; cycle $A$ is more expensive per unit but larger. Both reduce cost, so the optimum pushes each cycle to capacity, giving $g = (#circ.map(str).join(", "))$ with total cost $2 dot 2 + 2 dot (-3) + 1 dot 1 + 1 dot (-4) = -5$. By comparison, running only cycle $A$ gives cost $-2$, only cycle $B$ gives $-3$, and the zero circulation gives $0$, so the optimum $-5$ strictly beats every alternative. + + #pred-commands( + "pred create --example MinimumCostCirculation -o mcc.json", + "pred solve mcc.json", + "pred evaluate mcc.json --config " + circ.map(str).join(","), + ) + ] + ] +} + +#{ + let x = load-model-example("IntegralFlowBundles") + let source = x.instance.source + let sink = x.instance.sink + [ + #problem-def("IntegralFlowBundles")[ + Given a directed graph $G = (V, A)$, specified vertices $s, t in V$, a family of arc bundles $I_1, dots, I_k subset.eq A$ whose union covers $A$, positive bundle capacities $c_1, dots, c_k$, and a requirement $R in ZZ^+$, determine whether there exists an integral flow $f: A -> ZZ_(>= 0)$ such that (1) $sum_(a in I_j) f(a) <= c_j$ for every bundle $j$, (2) flow is conserved at every vertex in $V backslash {s, t}$, and (3) the net flow into $t$ is at least $R$. + ][ + Integral Flow with Bundles is the shared-capacity single-commodity flow problem listed as ND36 in Garey \& Johnson @garey1979. Sahni introduced it as one of a family of computationally related network problems and showed that the bundled-capacity variant is NP-complete even in a very sparse unit-capacity regime @sahni1974. + + The implementation keeps one non-negative integer variable per directed arc. Unlike ordinary max-flow, the usable range of an arc is not determined by an intrinsic per-arc capacity; it is bounded instead by the smallest bundle capacity among the bundles that contain that arc. The registered $O(2^m)$ catalog bound therefore reflects the unit-capacity case with $m = |A|$, which is exactly the regime highlighted by Garey \& Johnson and Sahni.#footnote[No exact worst-case algorithm improving on brute-force is claimed here for the bundled-capacity formulation.] + + *Example.* The canonical YES instance has source $s = v_#source$, sink $t = v_#sink$, and arcs $(0,1)$, $(0,2)$, $(1,3)$, $(2,3)$, $(1,2)$, $(2,1)$. The three bundles are $I_1 = {(0,1), (0,2)}$, $I_2 = {(1,3), (2,1)}$, and $I_3 = {(2,3), (1,2)}$, each with capacity 1. Sending one unit along the path $0 -> 1 -> 3$ yields the flow vector $(1, 0, 1, 0, 0, 0)$: bundle $I_1$ contributes $1 + 0 = 1$, bundle $I_2$ contributes $1 + 0 = 1$, bundle $I_3$ contributes $0 + 0 = 0$, and the only nonterminal vertices $v_1, v_2$ satisfy conservation. If the requirement is raised from $R = 1$ to $R = 2$, the same gadget becomes infeasible because $I_1$ caps the total outflow leaving the source at one unit. + + #pred-commands( + "pred create --example IntegralFlowBundles -o integral-flow-bundles.json", + "pred solve integral-flow-bundles.json", + "pred evaluate integral-flow-bundles.json --config " + x.optimal_config.map(str).join(","), + ) + + #figure( + canvas(length: 1cm, { + import draw: * + let blue = graph-colors.at(0) + let orange = rgb("#f28e2b") + let teal = rgb("#76b7b2") + let positions = ( + (0, 0), // v0 = source + (2.5, 1.5), // v1 + (2.5, -1.5), // v2 + (5.0, 0), // v3 = sink + ) // Draw nodes for (i, pos) in positions.enumerate() { @@ -11161,6 +11847,36 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead _Solution extraction._ For covering ${S_v : v in C}$, return VC $= C$ (same variable assignment). ] +#let dmvc_cc = load-example("DecisionMinimumVertexCover", "ComparativeContainment") +#let dmvc_cc_sol = dmvc_cc.solutions.at(0) +#reduction-rule("DecisionMinimumVertexCover", "ComparativeContainment", + example: true, + example-caption: [Path $P_4$: $n = 4$ vertices, $K = 2$ bound], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(dmvc_cc.source) + " -o source.json", + "pred reduce source.json --to " + target-spec(dmvc_cc) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate source.json --config " + dmvc_cc_sol.source_config.map(str).join(","), + ) + Source VC witness $(#dmvc_cc_sol.source_config.map(str).join(", "))$, target containment indicator $(#dmvc_cc_sol.target_config.map(str).join(", "))$. + ], +)[ + Plaisted's reduction @plaisted1976 encodes a unit-weight Decision Vertex Cover instance $(G = (V, E), K)$ as a Comparative Containment instance on universe $X = V$. Each vertex contributes a complement set with unit reward; each edge contributes a complement-of-edge penalty set with weight $|V| + 1$ that dominates the total reward whenever the edge is uncovered; and a single budget set with weight $|V| - K$ enforces the cardinality bound. +][ + _Construction._ Given a unit-weight VC instance $(G = (V, E), K)$ with $n = |V|$, set the universe $X = V$ and define: + - For each vertex $v in V$, the reward set $R_v = V without {v}$ with weight $w(R_v) = 1$. Then $Y subset.eq R_v$ iff $v in.not Y$, so $sum_(Y subset.eq R_v) w(R_v) = n - |Y|$. + - For each edge $e = {u, v} in E$, the edge-penalty set $S_e = V without {u, v}$ with weight $w(S_e) = n + 1$. Then $Y subset.eq S_e$ iff neither $u$ nor $v$ lies in $Y$, i.e.\ iff $e$ is uncovered. + - A budget set $S_0 = V$ with weight $w(S_0) = n - K$. Since $Y subset.eq V$ always holds, this set contributes the constant penalty $n - K$. + + The containment inequality becomes $n - |Y| >= (n + 1) dot (\#"uncovered edges") + (n - K)$, which simplifies to + $ K - |Y| >= (n + 1) dot (\#"uncovered edges"). $ + + _Correctness._ ($arrow.r.double$) If $Y$ is a vertex cover with $|Y| <= K$, the right-hand side equals $0$ and the inequality $K - |Y| >= 0$ holds. ($arrow.l.double$) Suppose the inequality holds for some $Y$. If $Y$ leaves an edge uncovered, the right-hand side is at least $n + 1 > n >= K - |Y|$, a contradiction. Hence $Y$ is a vertex cover and $K - |Y| >= 0$, i.e.\ $|Y| <= K$. + + _Solution extraction._ The indicator vector of $Y subset.eq X$ over the universe $X = V$ is read off as the source vertex-cover indicator. Two corner cases are emitted as trivial instances: when $K >= n$ every cover satisfies the bound, so the target is the empty Comparative Containment instance whose unique configuration is trivially feasible; when $K < 0$ the bound is unattainable, and the target is a fixed unsatisfiable instance with a single penalty set. +] + #reduction-rule("MinimumVertexCover", "EnsembleComputation")[ This $O(|V| + |E|)$ reduction @garey1979 encodes the unit-weight vertex-cover problem as an ensemble-computation minimization over disjoint unions. A fresh element $a_0$ is introduced, and each edge becomes a 3-element target subset. The minimum sequence length equals $K^* + |E|$, where $K^*$ is the minimum vertex cover size. ][ @@ -11690,6 +12406,50 @@ where $P$ is a penalty weight large enough that any constraint violation costs m _Solution extraction._ Return the same binary selection vector: element $i$ is in the partition subset if and only if it is selected in the Subset Sum witness. ] +#let part_ifwm = load-example("Partition", "IntegralFlowWithMultipliers") +#let part_ifwm_sol = part_ifwm.solutions.at(0) +#let part_ifwm_sizes = part_ifwm.source.instance.sizes +#let part_ifwm_n = part_ifwm_sizes.len() +#let part_ifwm_total = part_ifwm_sizes.fold(0, (a, b) => a + b) +#let part_ifwm_half = part_ifwm_total / 2 +#let part_ifwm_selected = part_ifwm_sol.source_config.enumerate().filter(((i, x)) => x == 1).map(((i, x)) => i) +#let part_ifwm_selected_sizes = part_ifwm_selected.map(i => part_ifwm_sizes.at(i)) +#let part_ifwm_source_arcs = part_ifwm_sol.target_config.slice(0, part_ifwm_n) +#let part_ifwm_relay_arcs = part_ifwm_sol.target_config.slice(part_ifwm_n, 2 * part_ifwm_n) +#let part_ifwm_bottleneck = part_ifwm_sol.target_config.at(2 * part_ifwm_n) +#reduction-rule("Partition", "IntegralFlowWithMultipliers", + example: true, + example-caption: [#part_ifwm_n elements, total sum $S = #part_ifwm_total$, bottleneck $R = #part_ifwm_half$], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(part_ifwm.source) + " -o partition.json", + "pred reduce partition.json --to " + target-spec(part_ifwm) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate partition.json --config " + part_ifwm_sol.source_config.map(str).join(","), + ) + + *Step 1 -- Source instance.* The canonical Partition multiset is $(#part_ifwm_sizes.map(str).join(", "))$, so the total is $S = #part_ifwm_total$ and any balanced witness must sum to $S / 2 = #part_ifwm_half$. + + *Step 2 -- Build the relay network.* The reduction creates vertices $s$, one item vertex $v_i$ per element, a relay vertex $w$, and sink $t$. It adds unit-capacity arcs $(s, v_i)$, item arcs $(v_i, w)$ with capacities $(#part_ifwm_sizes.map(str).join(", "))$, and one bottleneck arc $(w, t)$ with capacity $#part_ifwm_half$. The target witness therefore has $#part_ifwm_sol.target_config.len()$ arc-flow coordinates ordered as source arcs, relay arcs, then the bottleneck arc. + + *Step 3 -- Verify the canonical witness.* The source witness $bold(x) = (#part_ifwm_sol.source_config.map(str).join(", "))$ selects item indices $\{#part_ifwm_selected.map(str).join(", ")\}$ with sizes $(#part_ifwm_selected_sizes.map(str).join(", "))$, summing to $#part_ifwm_half$. On the target side, the source arcs carry $(#part_ifwm_source_arcs.map(str).join(", "))$, the relay arcs carry $(#part_ifwm_relay_arcs.map(str).join(", "))$, and the bottleneck arc carries $#part_ifwm_bottleneck$. Thus the relay receives $#part_ifwm_selected_sizes.map(str).join(" + ") = #part_ifwm_half$ units and the sink inflow equals the requirement #sym.checkmark. + + *Witness semantics.* The fixture stores one canonical balanced subset. Other balanced subsets may exist, but every feasible target witness still extracts by reading the first $n$ unit-capacity source arcs. + ], +)[ + This $O(n)$ reduction @sahni1974 @garey1979[ND33] implements Sahni's multiplier-flow gadget for subset selection. Each Partition element becomes an item vertex whose multiplier amplifies a binary source choice into either $0$ or $a_i$ units entering a relay. A single bottleneck arc of capacity $S / 2$ then converts the target model's sink condition "net inflow at least $R$" into the exact equality needed by Partition. +][ + _Construction._ Let the source multiset be $A = {a_1, dots, a_n}$ with total sum $S = sum_(i=1)^n a_i$. If $S$ is odd, return a fixed infeasible Integral Flow With Multipliers instance with vertices $(s, u, t)$, arcs $(s, u)$ and $(u, t)$ both of capacity $1$, multiplier $h(u) = 2$, and requirement $R = 1$. Otherwise set $M = S / 2$ and build a directed graph with vertices $s, v_1, dots, v_n, w, t$. Add arcs $(s, v_i)$ of capacity $1$ and arcs $(v_i, w)$ of capacity $a_i$ for each $i in {1, dots, n}$, plus one bottleneck arc $(w, t)$ of capacity $M$. Assign multipliers $h(v_i) = a_i$ and $h(w) = 1$, and set the sink requirement to $R = M$. + + _Correctness._ ($arrow.r.double$) If the Partition instance is satisfiable, choose a subset $I subset.eq {1, dots, n}$ with $sum_(i in I) a_i = S / 2 = M$. Send one unit on $(s, v_i)$ for each $i in I$ and zero otherwise. Multiplier conservation at each item vertex forces $f(v_i, w) = a_i$ when $i in I$ and $0$ otherwise. The relay multiplier is $1$, so the total flow on $(w, t)$ becomes $sum_(i in I) a_i = M$, which respects the bottleneck capacity and meets the sink requirement $R = M$. When $S$ is odd, the source instance is unsatisfiable and the fixed target is also infeasible because conservation at $u$ would require $f(u, t) = 2 f(s, u)$ while the arc capacity is only $1$. + + ($arrow.l.double$) Suppose the target instance is feasible. In the odd branch the fixed target is infeasible, so only the even branch can yield a witness. Every arc $(s, v_i)$ has capacity $1$, hence integrality forces $f(s, v_i) in {0, 1}$. Conservation at $v_i$ gives $f(v_i, w) = a_i f(s, v_i)$, so each item contributes either $0$ or exactly $a_i$ units to the relay. Conservation at $w$ with multiplier $1$ gives + $ f(w, t) = sum_(i=1)^n a_i f(s, v_i). $ + The bottleneck capacity enforces $f(w, t) <= M$, while the sink requirement enforces $f(w, t) >= R = M$. Therefore $f(w, t) = M = S / 2$, and the indices with $f(s, v_i) = 1$ form a balanced partition subset. + + _Solution extraction._ Read the first $n$ arc-flow coordinates, corresponding to the unit-capacity arcs $(s, v_1), dots, (s, v_n)$. Output bit $x_i = f(s, v_i)$. In the odd branch, return the all-zero source vector. +] + // Removed: Partition → ShortestWeightConstrainedPath (unsound reduction, #1006) #let ks_qubo = load-example("Knapsack", "QUBO") @@ -11739,6 +12499,50 @@ where $P$ is a penalty weight large enough that any constraint violation costs m _Solution extraction._ Discard slack variables: return $bold(z)[0..n]$. ] +#let mdpik_qubo = load-example("MinimumDiscretePlanarInverseKinematics", "QUBO") +#let mdpik_qubo_sol = mdpik_qubo.solutions.at(0) +#reduction-rule("MinimumDiscretePlanarInverseKinematics", "QUBO", + example: true, + example-caption: [2-link worked example with 4 QUBO variables], + extra: [ + #pred-commands( + "pred create --example MinimumDiscretePlanarInverseKinematics -o ik.json", + "pred reduce ik.json --to " + target-spec(mdpik_qubo) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate ik.json --config " + mdpik_qubo_sol.source_config.map(str).join(","), + ) + *Step 1 -- Source instance.* The canonical instance has link lengths $(2, 1)$, target $g = (2, 1)$, sampled orientations $Phi_1 = Phi_2 = {0, pi / 2}$, and admissible pair set $A_2 = {(0,0), (0,1), (1,1)}$. + + *Step 2 -- One-hot variables.* Introduce one binary selector per sampled orientation: + $ underbrace(y_(1,0) y_(1,1), "link 1") #h(6pt) underbrace(y_(2,0) y_(2,1), "link 2") $ + The QUBO therefore has $2 + 2 = #mdpik_qubo.target.instance.num_vars$ variables. + + *Step 3 -- Quadratic energy.* The geometric coefficients are $c = (2, 0, 1, 0)$ for the $x$-coordinate and $s = (0, 2, 0, 1)$ for the $y$-coordinate, so the position term is + $ (2 y_(1,0) + y_(2,0) - 2)^2 + (2 y_(1,1) + y_(2,1) - 1)^2. $ + The implementation adds the same safe penalty to every one-hot violation and every forbidden pair, here penalizing the single forbidden adjacency $(1, 0)$ between the two blocks. + + *Step 4 -- Verify a solution.* The QUBO ground state $bold(y) = (#mdpik_qubo_sol.target_config.map(str).join(", "))$ decodes to source configuration $(#mdpik_qubo_sol.source_config.map(str).join(", "))$, i.e. link 1 uses angle $0$ and link 2 uses angle $pi / 2$. The end-effector reaches $(2, 1)$ exactly, so the squared distance is $0$ #sym.checkmark. + ], +)[ + Discrete planar inverse kinematics is already close to QUBO form: once each sampled orientation is lifted to a binary selector, both end-effector coordinates become linear functions of those selectors, and the squared Euclidean error expands to a quadratic polynomial. Adding quadratic one-hot penalties and quadratic penalties for forbidden consecutive orientation pairs yields a QUBO with $sum_(j=1)^n m_j$ variables @salloum2025ikqubo. +][ + _Construction._ For each link $j in {1, dots, n}$ and sample index $a in {0, dots, m_j - 1}$, introduce a binary variable $y_(j,a) in {0,1}$ with the intended meaning "$y_(j,a) = 1$ iff link $j$ chooses orientation $phi_(j,a)$." Define + $ c_(j,a) = l_j cos phi_(j,a), quad s_(j,a) = l_j sin phi_(j,a). $ + Let + $ P = 1 + (sum_(j,a) |c_(j,a)| + |g_x|)^2 + (sum_(j,a) |s_(j,a)| + |g_y|)^2. $ + The QUBO objective is the sum of three terms: + $ + H = underbrace((sum_(j,a) c_(j,a) y_(j,a) - g_x)^2 + (sum_(j,a) s_(j,a) y_(j,a) - g_y)^2)_"position error" + + underbrace(P sum_(j=1)^n (sum_(a=0)^(m_j - 1) y_(j,a) - 1)^2)_"one-hot" + + underbrace(P sum_(j=2)^n sum_((a,b) in.not A_j) y_(j-1,a) y_(j,b))_"forbidden pairs". + $ + Expanding with $y_(j,a)^2 = y_(j,a)$ gives the upper-triangular QUBO matrix. As usual, the additive constant $g_x^2 + g_y^2$ is dropped. + + _Correctness._ ($arrow.r.double$) Any feasible inverse-kinematics configuration $a_1, dots, a_n$ maps to the one-hot assignment with $y_(j,a_j) = 1$ and all other selectors $0$. Every one-hot penalty vanishes, every consecutive pair lies in the relevant admissible set, and the remaining QUBO objective equals the squared end-effector distance up to the dropped additive constant. ($arrow.l.double$) If some link is not one-hot, then $(sum_a y_(j,a) - 1)^2 >= 1$, so the assignment pays at least $P$. If every link is one-hot but some consecutive pair is forbidden, then exactly one forbidden-pair monomial is active at that junction, again contributing at least $P$. By definition of $P$, every decoded source configuration has squared distance at most $P - 1$, while the dropped-constant geometric term is bounded below by $-(g_x^2 + g_y^2)$. Therefore every violating assignment has strictly larger energy than every feasible source assignment. Among the penalty-zero assignments, minimizing $H$ is exactly minimizing the source squared distance. + + _Solution extraction._ For each link block $j$, read the unique active selector $y_(j,a) = 1$ and output its sample index $a$. If the decoded index vector violates an admissible-pair constraint, the source evaluator rejects it with `Min(None)`. +] + #let mwc_qubo = load-example("MinimumMultiwayCut", "QUBO") #let mwc_qubo_sol = mwc_qubo.solutions.at(0) #let mwc_qubo_edges = mwc_qubo.source.instance.graph.edges.map(e => (e.at(0), e.at(1))) @@ -12594,6 +13398,119 @@ The following reductions to Integer Linear Programming are straightforward formu _Solution extraction._ $K = {v : x_v = 1}$. ] +#let mckp_ilp = load-example( + "MaximumCoKPlex", + "ILP", + source-variant: (graph: "SimpleGraph", k: "KN", weight: "i32"), + target-variant: (variable: "bool"), +) +#let mckp_ilp_sol = mckp_ilp.solutions.at(0) +#reduction-rule("MaximumCoKPlex", "ILP", + example: true, + example-source-variant: (graph: "SimpleGraph", k: "KN", weight: "i32"), + example-target-variant: (variable: "bool"), + example-caption: [Weighted 5-cycle ($n = 5$), $k = 2$], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(mckp_ilp.source) + " -o source.json", + "pred reduce source.json --to " + target-spec(mckp_ilp) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate source.json --config " + mckp_ilp_sol.source_config.map(str).join(","), + ) + Source co-$k$-plex witness $(#mckp_ilp_sol.source_config.map(str).join(", "))$, target ILP witness $(#mckp_ilp_sol.target_config.map(str).join(", "))$. + ], +)[ + This direct binary ILP formulation introduces one variable per source vertex and one induced-degree cap per source vertex @Hernandez2016MolecularSimilarity. +][ + _Construction._ Let the source instance be a weighted graph $G = (V, E)$ with vertex weights $w_v$ and parameter $k >= 1$. Introduce binary variables $x_v in {0,1}$ for each $v in V$, where $x_v = 1$ iff $v$ is selected. Maximize + $ sum_(v in V) w_v x_v. $ + For each vertex $v$, let $N(v)$ be its neighbourhood and $d(v) = |N(v)|$. Add the constraint + $ sum_(u in N(v)) x_u <= (k - 1) + d(v) (1 - x_v). $ + Equivalently, write it as + $ sum_(u in N(v)) x_u + d(v) x_v <= d(v) + k - 1. $ + + _Correctness._ ($arrow.r.double$) If $S subset.eq V$ is a feasible co-$k$-plex and we set $x_v = 1$ exactly for $v in S$, then every selected vertex $v$ has at most $k - 1$ selected neighbours in $G[S]$, so the constraint for $v$ is satisfied. If $x_v = 0$, the right-hand side becomes $d(v) + k - 1$, which is at least $d(v)$, so the constraint is automatically satisfied. Thus every feasible co-$k$-plex yields a feasible ILP solution with the same objective value. ($arrow.l.double$) Conversely, let $x$ be any feasible ILP solution and define $S = {v in V : x_v = 1}$. For each $v in S$, feasibility gives $sum_(u in N(v)) x_u <= k - 1$, so $v$ has induced degree at most $k - 1$ inside $G[S]$. Therefore $S$ is a feasible co-$k$-plex, and the linear objective is exactly its total weight. + + _Solution extraction._ Output the same binary selection vector: $S = {v in V : x_v = 1}$. +] + +#let mces_ilp = load-example( + "MaximumCommonEdgeSubgraph", + "ILP", + target-variant: (variable: "bool"), +) +#let mces_ilp_sol = mces_ilp.solutions.at(0) +#reduction-rule("MaximumCommonEdgeSubgraph", "ILP", + example: true, + example-target-variant: (variable: "bool"), + example-caption: [Two labelled 3-vertex digraphs with 2 arcs each], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(mces_ilp.source) + " -o source.json", + "pred reduce source.json --to " + target-spec(mces_ilp) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate source.json --config " + mces_ilp_sol.source_config.map(str).join(","), + ) + Source mapping witness $(#mces_ilp_sol.source_config.map(str).join(", "))$, target ILP witness $(#mces_ilp_sol.target_config.map(str).join(", "))$. + ], +)[ + Encode a partial injective vertex map with row and column inequalities and linearize each label-compatible source/target arc pair with a McCormick product variable @Bahiense2012MCES. +][ + _Construction._ Let the source instance be the pair of directed edge-labelled graphs $G_1 = (V_1, E_1)$ and $G_2 = (V_2, E_2)$ with labels in a finite alphabet $Sigma$. Introduce binary variables $x_(u,p) in {0, 1}$ for every $u in V_1, p in V_2$, where $x_(u,p) = 1$ iff source vertex $u$ is mapped to target vertex $p$. For every label-compatible source/target arc pair $a = (u, lambda, v) in E_1$ and $b = (p, lambda, q) in E_2$ with the same label $lambda$, introduce a binary variable $y_(a,b) in {0, 1}$. The ILP is: + $ + max quad & sum_{a, b "label-compatible"} y_(a,b) \ + "subject to" quad & sum_(p in V_2) x_(u,p) <= 1 quad forall u in V_1 \ + & sum_(u in V_1) x_(u,p) <= 1 quad forall p in V_2 \ + & y_(a,b) <= x_(u,p) quad forall (a, b) "label-compatible" \ + & y_(a,b) <= x_(v,q) quad forall (a, b) "label-compatible" \ + & y_(a,b) >= x_(u,p) + x_(v,q) - 1 quad forall (a, b) "label-compatible" \ + & x_(u,p), y_(a,b) in {0, 1}. + $ + + _Correctness._ ($arrow.r.double$) Any partial injective map $f : U_1 -> V_2$ with $U_1 subset.eq V_1$ produces a feasible ILP solution by setting $x_(u,p) = 1$ iff $u in U_1$ and $f(u) = p$, and $y_(a,b) = 1$ exactly when $a = (u, lambda, v)$ is preserved by $f$, i.e. $b = (f(u), lambda, f(v)) in E_2$. The row and column inequalities encode that $f$ is a (partial) function and injective; the McCormick triple forces $y_(a,b) = x_(u,p) and x_(v,q)$. The ILP objective equals the number of preserved labelled arcs. ($arrow.l.double$) Any feasible ILP solution selects an injective partial map via $f(u) = p$ when $x_(u,p) = 1$ (well-defined by the row constraints, injective by the column constraints), and the McCormick triple forces $y_(a,b) = 1$ iff both endpoint mappings are realized, i.e. iff $a$ is preserved. Therefore the ILP optimum equals the MCES optimum. + + _Solution extraction._ For each source vertex $u in V_1$, output the unique $p in V_2$ with $x_(u,p) = 1$ if such a $p$ exists, otherwise the sentinel $|V_2|$ encoding "unmatched". +] + +#let mewkc_ilp = load-example( + "MaximumEdgeWeightedKClique", + "ILP", + source-variant: (weight: "i32"), + target-variant: (variable: "bool"), +) +#let mewkc_ilp_sol = mewkc_ilp.solutions.at(0) +#reduction-rule("MaximumEdgeWeightedKClique", "ILP", + example: true, + example-source-variant: (weight: "i32"), + example-target-variant: (variable: "bool"), + example-caption: [$n = 4$ vertices, $m = 5$ edges, $k = 3$], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(mewkc_ilp.source) + " -o source.json", + "pred reduce source.json --to " + target-spec(mewkc_ilp) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate source.json --config " + mewkc_ilp_sol.source_config.map(str).join(","), + ) + Source $k$-clique witness $(#mewkc_ilp_sol.source_config.map(str).join(", "))$, target ILP witness $(#mewkc_ilp_sol.target_config.map(str).join(", "))$. + ], +)[ + Binary vertex selectors with an exact-cardinality constraint, non-edge clique constraints, and McCormick edge-product variables linearize the induced edge-weight sum @ParkLeePark1996EWClique @GouveiaMartins2015EWClique. +][ + _Construction._ Let the source instance be $G = (V, E)$ with edge weights $w : E -> RR$ and size bound $k$. Introduce binary variables $x_v in {0, 1}$ for every $v in V$ and $y_(u v) in {0, 1}$ for every edge ${u, v} in E$. The ILP is: + $ + max quad & sum_({u, v} in E) w_(u v) y_(u v) \ + "subject to" quad & sum_(v in V) x_v = k \ + & x_u + x_v <= 1 quad forall {u, v} in.not E \ + & y_(u v) <= x_u, quad y_(u v) <= x_v quad forall {u, v} in E \ + & y_(u v) >= x_u + x_v - 1 quad forall {u, v} in E \ + & x_v, y_(u v) in {0, 1}. + $ + + _Correctness._ ($arrow.r.double$) Any $k$-clique $S subset.eq V$ yields a feasible solution by setting $x_v = 1$ iff $v in S$ and $y_(u v) = 1$ iff $u, v in S$; the non-edge constraints are satisfied because $G[S]$ is a clique, and the McCormick triple enforces $y_(u v) = x_u and x_v$. The objective equals $sum_({u, v} in E(S)) w_(u v)$. ($arrow.l.double$) Any feasible solution with cardinality $k$ selects $k$ vertices forming a clique (the non-edge constraints rule out non-adjacent pairs), and the McCormick lower bound $y_(u v) >= x_u + x_v - 1$ forces $y_(u v) = 1$ whenever both endpoints are selected, even when $w_(u v) < 0$. + + _Solution extraction._ Take the first $|V|$ entries of the ILP solution as the source selection vector. +] + #let ks_ilp = load-example("Knapsack", "ILP") #let ks_ilp_sol = ks_ilp.solutions.at(0) @@ -12780,6 +13697,39 @@ The following reductions to Integer Linear Programming are straightforward formu _Solution extraction._ Identity: read the ILP vector $(x_0, dots, x_(m-1))$ directly as the arc-flow vector of the source problem. ] +#let ola_seqmwct = load-example("OptimalLinearArrangement", "SequencingToMinimizeWeightedCompletionTime") +#let ola_seqmwct_sol = ola_seqmwct.solutions.at(0) +#reduction-rule("OptimalLinearArrangement", "SequencingToMinimizeWeightedCompletionTime", + example: true, + example-caption: [Path $P_4$: $n = 4$ vertices, $m = 3$ edges], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(ola_seqmwct.source) + " -o source.json", + "pred reduce source.json --to " + target-spec(ola_seqmwct) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate source.json --config " + ola_seqmwct_sol.source_config.map(str).join(","), + ) + Source arrangement $pi = (#ola_seqmwct_sol.source_config.map(str).join(", "))$, target schedule $(#ola_seqmwct_sol.target_config.map(str).join(", "))$. + ], +)[ + @lawler1978 This $O(n + m)$ reduction turns each vertex into a unit-length job, each edge into a zero-length job, and uses precedences so that every edge job completes exactly when its later endpoint does. The weighted completion-time objective then equals the linear-arrangement objective plus the fixed shift $d_"max" n (n + 1) / 2$. +][ + _Construction._ Let the source instance be an undirected graph $G = (V, E)$ with $n = |V|$, $m = |E|$, and maximum degree $d_"max" = max_(v in V) deg(v)$. For each vertex $v in V$, create a job $J_v$ of length 1 and weight $d_"max" - deg(v)$. For each edge $e = {u, v} in E$, create a job $J_e$ of length 0 and weight 2. Add the precedence constraints $J_u prec.eq J_e$ and $J_v prec.eq J_e$ for every edge job $J_e$. There are no other precedences, so the target has $n + m$ jobs and $2m$ precedence arcs. + + _Correctness._ Write the source arrangement as a bijection $pi : V -> {0, dots, n - 1}$. Schedule the vertex jobs in increasing $pi$-order, so $J_v$ completes at time $C_v = pi(v) + 1$. Because $J_e$ has length 0 and must follow both endpoints, edge job $J_{ {u, v} }$ completes at time $max(pi(u), pi(v)) + 1$. The total weighted completion time is + $ + sum_(v in V) (d_"max" - deg(v)) (pi(v) + 1) + sum_({u,v} in E) 2 (max(pi(u), pi(v)) + 1). + $ + Using $sum_v deg(v) (pi(v) + 1) = sum_({u,v} in E) (pi(u) + pi(v) + 2)$, this becomes + $ + d_"max" sum_(v in V) (pi(v) + 1) + sum_({u,v} in E) (2 max(pi(u), pi(v)) - pi(u) - pi(v)) + = d_"max" n (n + 1) / 2 + sum_({u,v} in E) |pi(u) - pi(v)|. + $ + Therefore minimizing the target objective is exactly minimizing the Optimal Linear Arrangement objective, up to the additive constant $d_"max" n (n + 1) / 2$. The source uses 0-indexed positions, but the completion-time shift by 1 is already absorbed into that constant. + + _Solution extraction._ Read the target schedule order, delete all edge jobs, and assign source positions $0, 1, dots, n - 1$ to the remaining vertex jobs in the order they appear. +] + #reduction-rule("SequencingToMinimizeWeightedCompletionTime", "ILP")[ Completion times are natural integer variables, precedence constraints compare those completion times directly, and one binary order variable per task pair enforces that a single machine cannot overlap two jobs. ][ @@ -13008,6 +13958,93 @@ The following reductions to Integer Linear Programming are straightforward formu _Solution extraction._ Sort the selected vertices by position in $s_1$. Read off the characters to obtain the common subsequence, then pad to `max_length` with the padding symbol. ] +#let cs_ilp_str = load-example( + "ClosestString", + "ILP", + target-variant: (variable: "i32"), +) +#let cs_ilp_str_sol = cs_ilp_str.solutions.at(0) +#reduction-rule("ClosestString", "ILP", + example: true, + example-target-variant: (variable: "i32"), + example-caption: [Binary alphabet, 4 length-3 strings], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(cs_ilp_str.source) + " -o source.json", + "pred reduce source.json --to " + target-spec(cs_ilp_str) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate source.json --config " + cs_ilp_str_sol.source_config.map(str).join(","), + ) + Source center witness $(#cs_ilp_str_sol.source_config.map(str).join(", "))$, target ILP witness $(#cs_ilp_str_sol.target_config.map(str).join(", "))$. + ], +)[ + Binary variables select one alphabet symbol at each center position. An auxiliary radius variable upper-bounds the Hamming distance from the chosen center to every input string and is minimized. +][ + _Construction._ Given alphabet $Sigma$ of size $q$, $n$ input strings $s_1, dots, s_n in Sigma^m$ of common length $m$: + + _Variables:_ (1) $x_(j, a) in {0, 1}$ for $j in {0, dots, m - 1}$ and $a in {0, dots, q - 1}$: $x_(j, a) = 1$ iff the center has symbol $a$ at position $j$. (2) Nonnegative integer $R$: an upper bound on the worst-case Hamming distance. + + _Constraints:_ (1) Assignment: $sum_(a = 0)^(q - 1) x_(j, a) = 1$ for every position $j$. Combined with the nonnegativity built into the ILP, this also forces every $x_(j, a) in {0, 1}$. (2) Radius: $R + sum_(j = 0)^(m - 1) x_(j, s_i [j]) >= m$ for every input string $s_i$, which is equivalent to $R >= m - sum_j x_(j, s_i [j]) = d_H (c, s_i)$. + + _Objective:_ Minimize $R$. + + The ILP is: + $ + "minimize" quad & R \ + "subject to" quad & sum_(a = 0)^(q - 1) x_(j, a) = 1 quad forall j in {0, dots, m - 1} \ + & R + sum_(j = 0)^(m - 1) x_(j, s_i [j]) >= m quad forall i in {1, dots, n} \ + & x_(j, a) in {0, 1}, quad R in ZZ_(>= 0). + $ + + _Correctness._ ($arrow.r.double$) Given an optimal center $c^* in Sigma^m$, set $x_(j, c^*[j]) = 1$ for every $j$ and let $R = max_i d_H (c^*, s_i)$. Each assignment constraint is satisfied, and each radius constraint reduces to $R >= d_H (c^*, s_i)$, which holds with equality at the worst case. ($arrow.l.double$) The assignment constraints force each $x_(j, *)$ block to be a one-hot vector, hence encode a unique center string $c$. The radius constraint then gives $R >= d_H (c, s_i)$ for every $i$, so $R >= max_i d_H (c, s_i)$. Minimizing $R$ therefore minimizes the worst-case Hamming distance. + + _Solution extraction._ For each position $j$, read the unique symbol $a$ with $x_(j, a) = 1$; the resulting length-$m$ vector is the source center. +] + +#let css_ilp = load-example( + "ClosestSubstring", + "ILP", + target-variant: (variable: "i32"), +) +#let css_ilp_sol = css_ilp.solutions.at(0) +#reduction-rule("ClosestSubstring", "ILP", + example: true, + example-target-variant: (variable: "i32"), + example-caption: [Binary alphabet, 3 length-5 strings, length-3 windows], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(css_ilp.source) + " -o source.json", + "pred reduce source.json --to " + target-spec(css_ilp) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate source.json --config " + css_ilp_sol.source_config.map(str).join(","), + ) + Source center+windows witness $(#css_ilp_sol.source_config.map(str).join(", "))$, target ILP witness $(#css_ilp_sol.target_config.map(str).join(", "))$. + ], +)[ + Integer variables select one alphabet symbol at each center position and one window start per input string. A conditional radius constraint is activated by the window-choice indicator and upper-bounds the Hamming distance between the center and the selected window of each string. +][ + _Construction._ Given alphabet $Sigma$ of size $q$, $n$ input strings $s_1, dots, s_n$ over $Sigma$, and window length $ell$ with $W_i = |s_i| - ell + 1$: + + _Variables:_ (1) $x_(r, a) in {0, 1}$ for $r in {0, dots, ell - 1}$ and $a in {0, dots, q - 1}$: $x_(r, a) = 1$ iff the center has symbol $a$ at position $r$. (2) $y_(i, p) in {0, 1}$ for input string $s_i$ and window start $p in {0, dots, W_i - 1}$: $y_(i, p) = 1$ iff window $p$ is selected from $s_i$. (3) Nonnegative integer $R$: an upper bound on the worst-case Hamming distance. + + _Constraints:_ (1) Center assignment: $sum_(a = 0)^(q - 1) x_(r, a) = 1$ for every position $r$. (2) Window choice: $sum_(p = 0)^(W_i - 1) y_(i, p) = 1$ for every input string $s_i$. (3) Conditional radius: $R + sum_(r = 0)^(ell - 1) x_(r, s_i [p + r]) - ell dot y_(i, p) >= 0$ for every $(i, p)$. When $y_(i, p) = 1$, this is equivalent to $R >= ell - sum_r x_(r, s_i [p + r]) = d_H (c, s_i [p .. p + ell))$; when $y_(i, p) = 0$, the constraint reduces to $R + (text("nonneg match count")) >= 0$, which holds automatically. + + _Objective:_ Minimize $R$. + + The ILP is: + $ + "minimize" quad & R \ + "subject to" quad & sum_(a = 0)^(q - 1) x_(r, a) = 1 quad forall r in {0, dots, ell - 1} \ + & sum_(p = 0)^(W_i - 1) y_(i, p) = 1 quad forall i in {1, dots, n} \ + & R + sum_(r = 0)^(ell - 1) x_(r, s_i [p + r]) - ell dot y_(i, p) >= 0 quad forall i, p \ + & x_(r, a), y_(i, p) in {0, 1}, quad R in ZZ_(>= 0). + $ + + _Correctness._ ($arrow.r.double$) Given an optimal center $c^* in Sigma^ell$ and optimal window starts $p_1^*, dots, p_n^*$, set $x_(r, c^*[r]) = 1$, $y_(i, p_i^*) = 1$, and $R = max_i d_H (c^*, s_i [p_i^* .. p_i^* + ell))$. The assignment and window-choice constraints hold by construction. For each pair $(i, p_i^*)$ the radius constraint becomes $R >= d_H (c^*, s_i [p_i^* .. p_i^* + ell))$, which holds with equality at the worst case; for every other $(i, p)$ with $y_(i, p) = 0$ the constraint is redundant. ($arrow.l.double$) The assignment and window-choice constraints force each block of $x$ and each block of $y$ to be one-hot, encoding a center $c$ and one window per input string. The conditional radius constraint is active exactly on the selected windows and forces $R >= d_H (c, s_i [p_i .. p_i + ell))$ for every $i$, so $R$ is at least the worst-case selected Hamming distance. Minimizing $R$ therefore minimizes the maximum Hamming distance over chosen windows. + + _Solution extraction._ For each position $r$, read the unique symbol $a$ with $x_(r, a) = 1$ as the center symbol; for each input string $s_i$, read the unique $p$ with $y_(i, p) = 1$ as the selected window start. +] + #reduction-rule("LongestCommonSubsequence", "ILP")[ An optimization ILP formulation maximizes the length of a common subsequence. Binary variables choose a symbol (or padding) at each witness position. Match variables link active positions to source string indices, and the objective maximizes the number of non-padding positions. ][ @@ -13386,6 +14423,131 @@ The following reductions to Integer Linear Programming are straightforward formu _Solution extraction._ $I = {v : x_v = 1}$. ] +#let mmm_ach = load-example("MinimumMaximalMatching", "MaximumAchromaticNumber") +#let mmm_ach_sol = mmm_ach.solutions.at(0) +#reduction-rule("MinimumMaximalMatching", "MaximumAchromaticNumber", + example: true, + example-source-variant: (graph: "BipartiteGraph"), + example-target-variant: (graph: "SimpleGraph"), + example-caption: [Path $P_4$ as a bipartite graph with $A = {v_0, v_2}$, $B = {v_1, v_3}$.], + extra: [ + #{ + let source-edges = mmm_ach.target.instance.graph.edges + let n-source = mmm_ach.source.instance.graph.left_size + mmm_ach.source.instance.graph.right_size + let m-source = mmm_ach.source.instance.graph.edges.len() + let m-target = mmm_ach.target.instance.graph.edges.len() + let color-of = mmm_ach_sol.target_config + let matched = mmm_ach_sol.source_config.enumerate() + .filter(((i, x)) => x == 1) + .map(((i, _)) => i) + [ + #pred-commands( + "pred create --example " + problem-spec(mmm_ach.source) + " -o mmm.json", + "pred reduce mmm.json --to " + target-spec(mmm_ach) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate mmm.json --config " + mmm_ach_sol.source_config.map(str).join(","), + ) + + *Step 1 -- Source instance.* Path $P_4$ encoded as a bipartite graph with bipartition $A = {v_0, v_2}$ and $B = {v_1, v_3}$. In unified indices the vertex set is ${0, 1, 2, 3}$ (left vertices first), $n = #n-source$, and the $m = #m-source$ edges are #source-edges.map(e => $(#e.at(0), #e.at(1))$).join(", "). + + *Step 2 -- Complement graph $H = overline(G)$.* The non-edges of $G$ in $K_4$ give the target edge set, with $|E(H)| = #m-target$ edges (#mmm_ach.target.instance.graph.edges.map(e => $(#e.at(0), #e.at(1))$).join(", ")). The decision threshold transforms as $K' = n - K$. + + *Step 3 -- Source optimum.* The minimum maximal matching uses the middle edge, so $"mm"(G) = #matched.len() = 1$ (source index $#matched.at(0)$). + + *Step 4 -- Target optimum.* The achromatic coloring stored in the fixture is $#color-of.map(str).join(", ")$. The size-$2$ color class corresponds to the source edge selected in Step 3, and the singletons contribute the remaining $n - 2$ classes, so the achromatic number is $psi(H) = n - "mm"(G) = #n-source - 1 = #(n-source - 1) #sym.checkmark$. + + *Multiplicity:* The fixture stores one canonical witness; other valid achromatic $3$-colorings exist and would extract to other minimum maximal matchings. + ] + } + ], +)[ + This $O(n^2)$ reduction @yannakakis1980 takes a bipartite source $G = (V, E)$ with $n = |V|$, builds the complement $H = overline(G)$ on the same vertex set, and sets the achromatic threshold to $K' = |V| - K$. For bipartite $G$, every color class of an achromatic coloring of $H$ has size at most two, and the size-two classes are exactly the edges of a maximal matching of $G$. The construction yields $|E(H)| = binom(n, 2) - |E|$ target edges. +][ + _Construction._ Given a Minimum Maximal Matching instance $(G = (V, E), K)$ with $G$ bipartite, build a Maximum Achromatic Number instance $(H, K')$ where $H = (V, overline(E))$ with $overline(E) = {(u, v) : u != v, (u, v) in.not E}$ and $K' = |V| - K$. + + _Correctness._ The reduction proves the identity $psi(H) = |V| - "mm"(G)$. + + ($arrow.r.double$) Let $M$ be a maximal matching of $G$ with $|M| <= K$. Assign one color to each edge $\{u, v\} in M$ (placing $u$ and $v$ in the same $2$-vertex class) and a distinct color to each unmatched vertex. The number of colors used is $|V| - |M| >= |V| - K = K'$. + + _Proper._ Each $2$-vertex class $\{u, v\}$ is an edge of $G$, hence a clique of size $2$ in $G$, hence an independent set in $H$. Singletons are trivially independent in $H$. + + _Complete._ Let $A$ and $B$ be the bipartition of $G$ and consider any two distinct classes $C_i, C_j$. Each class lies in $A$, in $B$, or is a $G$-edge with one endpoint on each side. In every case, $C_i union C_j$ contains two vertices on the same side of the bipartition. These two vertices are non-adjacent in $G$ (the bipartite property), so they are adjacent in $H$. Hence the coloring is complete and uses $|V| - |M| >= K'$ colors. + + ($arrow.l.double$) Let $cal(C)$ be a complete proper coloring of $H$ using $k >= K'$ colors. Because $H$ is the complement of a bipartite graph, every independent set of $H$ has size at most two, so each color class is a singleton or a pair. Let $M$ be the set of source edges $\{u, v\}$ such that $\{u, v\}$ is a $2$-vertex class. The classes are pairwise disjoint, so $M$ is a matching. The number of colors equals $k = |M| + (|V| - 2|M|) = |V| - |M|$, hence $|M| = |V| - k <= |V| - K' = K$. + + _Maximality._ Suppose for contradiction that $M$ is not maximal and let $\{u, v\} in E$ have both endpoints unmatched. Then $\{u\}$ and $\{v\}$ are singleton classes in $cal(C)$. Because $\{u, v\} in E$, the pair is _not_ an edge of $H$, contradicting completeness of $cal(C)$ on the class pair $({u}, {v})$. Hence $M$ is a maximal matching with $|M| <= K$. + + _Solution extraction._ Group target vertices by color. Every class of size $2$ identifies a source edge in $E$; mark those edges as selected (and leave all others unselected) to obtain a maximal matching $M$ of $G$ with $|M| = |V| - k$. +] + +#let mmm_mmd = load-example("MinimumMaximalMatching", "MinimumMatrixDomination") +#let mmm_mmd_sol = mmm_mmd.solutions.at(0) +#reduction-rule("MinimumMaximalMatching", "MinimumMatrixDomination", + example: true, + example-source-variant: (graph: "BipartiteGraph"), + example-caption: [Bipartite graph $B$ with $L = {l_0, l_1}$, $R = {r_0, r_1, r_2}$, and $|F| = 5$ edges.], + extra: [ + #{ + let m-left = mmm_mmd.source.instance.graph.left_size + let n-right = mmm_mmd.source.instance.graph.right_size + let local-edges = mmm_mmd.source.instance.graph.edges + let ones = mmm_mmd.target.instance.ones + let big-n = mmm_mmd.target.instance.matrix.len() + let s-cfg = mmm_mmd_sol.source_config + let t-cfg = mmm_mmd_sol.target_config + let matched-source = s-cfg.enumerate() + .filter(((i, x)) => x == 1) + .map(((i, _)) => i) + let selected-target = t-cfg.enumerate() + .filter(((i, x)) => x == 1) + .map(((i, _)) => i) + [ + #pred-commands( + "pred create --example " + problem-spec(mmm_mmd.source) + " -o mmm.json", + "pred reduce mmm.json --to " + target-spec(mmm_mmd) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate mmm.json --config " + s-cfg.map(str).join(","), + ) + + *Step 1 -- Source instance.* Bipartite graph $B$ with $|L| = #m-left$, $|R| = #n-right$ and $#local-edges.len()$ bipartite-local edges #local-edges.map(e => $(#e.at(0), #e.at(1))$).join(", "). The decision threshold is $K = 2$. + + *Step 2 -- Embedded matrix.* The constructed instance is the $#big-n times #big-n$ binary matrix $M$ whose upper-right $#m-left times #n-right$ block holds the biadjacency matrix $B^*$. Its $#ones.len()$ 1-entries lie at positions #ones.map(p => $(#p.at(0), #p.at(1))$).join(", "), and $M$ is upper triangular (all 1-entries above the row-block boundary). + + *Step 3 -- Source optimum.* A minimum maximal matching $M^* = {(l_0, r_0), (l_1, r_1)}$ uses source edge indices #matched-source.map(i => str(i)).join(", "), giving $"mm"(B) = #matched-source.len() = 2$. + + *Step 4 -- Target optimum.* The fixture selects the matrix-domination set $C = {(0, 2), (1, 3)}$ at 1-entry indices #selected-target.map(i => str(i)).join(", "). Every other 1-entry of $M$ shares row $0$ or row $1$ with one of the chosen entries, so $C$ is dominating with $|C| = #selected-target.len() = 2 = "mm"(B) #sym.checkmark$. + + *Extraction.* The selected 1-entries here happen to be pairwise independent (no shared row or column), so they already correspond to a matching of $B$. In general a matrix-domination witness only yields an edge dominating set, and the Yannakakis-Gavril transformation @yannakakis1980 converts it to a maximal matching of equal size. + ] + } + ], +)[ + This $O(n^2)$ reduction @yannakakis1980 takes a bipartite source $B = (L, R, F)$ with $|L| = m$ and $|R| = n$, builds the $N times N$ binary matrix $M$ on $N = m + n$ rows and columns whose upper-right $m times n$ block is the biadjacency matrix $B^*$ and whose other entries are zero, and leaves the decision threshold unchanged. The resulting matrix has exactly $|F|$ 1-entries and is upper triangular. +][ + _Construction._ Given a Minimum Maximal Matching instance $(B = (L, R, F), K)$ with $B$ bipartite, label the vertices so that $L = {l_0, dots, l_(m-1)}$ corresponds to rows $0, dots, m-1$ of $M$ and $R = {r_0, dots, r_(n-1)}$ corresponds to columns $m, dots, m+n-1$. Define + $ + M_(i,j) = cases( + 1 quad &"if " i < m " and " j >= m " and " (l_i, r_(j-m)) in F, + 0 quad &"otherwise", + ) + $ + and set $K' = K$. Output the Minimum Matrix Domination instance $(M, K')$. + + _Correctness._ The reduction proves the identity $"md"(M) = "mm"(B)$, where $"md"$ denotes the minimum matrix-domination size and $"mm"$ the minimum maximal-matching size. + + Each 1-entry of $M$ lies in the upper-right block and therefore corresponds bijectively to a source edge: $(i, m + j) <-> (l_i, r_j) in F$. Two 1-entries share a row iff their source edges share a left endpoint; they share a column iff their source edges share a right endpoint. Hence a set $C$ of 1-entries dominates $M$ iff the corresponding edges $F_C subset.eq F$ form an edge dominating set (EDS) of $B$. + + Yannakakis and Gavril @yannakakis1980 prove that for every graph the minimum EDS size equals the minimum independent EDS size, and an independent EDS is exactly a maximal matching. Combining these facts: + + ($arrow.r.double$) A maximal matching $M^*$ of $B$ with $|M^*| <= K$ is in particular an EDS, so its image in $M$ is a dominating set of size $<= K = K'$. + + ($arrow.l.double$) A dominating set $C$ of $M$ with $|C| <= K'$ corresponds to an EDS $F_C$ of size $|C| <= K' = K$. Applying the polynomial-time Yannakakis-Gavril transformation to $F_C$ yields a maximal matching of $B$ of the same size, so $"mm"(B) <= K$. + + _Solution extraction._ Read the selected 1-entries of the matrix-domination witness, map each $(i, m + j)$ back to the bipartite edge $(l_i, r_j)$ to obtain an EDS $F_C$ of $B$, and apply the Yannakakis-Gavril EDS-to-maximal-matching conversion to recover a maximal matching $M^*$ with $|M^*| = |F_C|$. + + _Note on source variant._ The reduction crucially requires the source graph to be bipartite. The biadjacency matrix faithfully represents the edge structure of $B$ (each edge contributes exactly one 1-entry). The adjacency matrix of a general undirected graph would produce two symmetric 1-entries per edge that do not preserve the row/column sharing pattern. +] + #reduction-rule("MinimumMaximalMatching", "ILP")[ Each edge is either selected or not; matching and maximality constraints are both directly linear in binary edge indicators. ][ @@ -14257,6 +15419,81 @@ The following reductions to Integer Linear Programming are straightforward formu _Solution extraction._ For each position $k$, decode the unique vertex $v$ with $x_(v,k) = 1$ to recover the permutation; convert to Lehmer code for the source configuration. ] +#let hcd_ilp = load-example( + "HighlyConnectedDeletion", + "ILP", + target-variant: (variable: "bool"), +) +#let hcd_ilp_sol = hcd_ilp.solutions.at(0) +#reduction-rule("HighlyConnectedDeletion", "ILP", + example: true, + example-target-variant: (variable: "bool"), + example-caption: [Triangle plus pendant: $n = 4$ vertices, $m = 4$ edges], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(hcd_ilp.source) + " -o source.json", + "pred reduce source.json --to " + target-spec(hcd_ilp) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate source.json --config " + hcd_ilp_sol.source_config.map(str).join(","), + ) + Source deletion witness $(#hcd_ilp_sol.source_config.map(str).join(", "))$, target ILP witness $(#hcd_ilp_sol.target_config.map(str).join(", "))$. + ], +)[ + Enumerate the family of feasible clusters of $G$ and pick a partition of $V$ into feasible clusters maximizing the kept internal edge count; since $|E|$ is fixed, this is equivalent to minimizing deleted edges @HueffnerKomusiewiczLiebtrauNiedermeier2014. +][ + _Construction._ Let the source instance be a simple undirected graph $G = (V, E)$. Call a vertex set $S subset.eq V$ a _feasible cluster_ when either $|S| = 1$, or $|S| >= 3$ and the induced subgraph $G[S]$ is _highly connected_, i.e. its edge connectivity satisfies $lambda(G[S]) > |S| / 2$ (strict). Let $cal(C)(G)$ be the family of all feasible clusters. Introduce binary variables $x_S in {0, 1}$ for each $S in cal(C)(G)$, where $x_S = 1$ iff $S$ is chosen as one block of the final partition. The ILP is: + $ + max quad & sum_(S in cal(C)(G)) |E(G[S])| x_S \ + "subject to" quad & sum_(S in cal(C)(G), v in S) x_S = 1 quad forall v in V \ + & x_S in {0, 1}. + $ + + _Correctness._ ($arrow.r.double$) Any feasible source partition $cal(P) = {B_1, dots, B_k}$ -- where every block $B_i$ is a singleton or a highly connected component on $>= 3$ vertices -- yields the feasible ILP assignment $x_(B_i) = 1$ for $i = 1, dots, k$ and $0$ elsewhere; the partition constraints hold because each vertex belongs to exactly one block, and the objective value is the number of edges kept by the partition. ($arrow.l.double$) Any feasible ILP solution selects a sub-family of $cal(C)(G)$ that, by the equality constraints, partitions $V$ into feasible clusters; the objective equals the number of intra-cluster edges. Since $|E|$ is constant, maximizing intra-cluster edges is equivalent to minimizing $|E| - sum_S |E(G[S])| x_S$, the number of deleted edges. + + _Solution extraction._ Decode the chosen clusters $C subset.eq cal(C)(G)$ from $x$. The source configuration is the binary edge-deletion vector: edge $e = {u, v}$ is kept (config bit $0$) iff some chosen cluster $S in C$ contains both $u$ and $v$, otherwise deleted (config bit $1$). +] + +#let ep_ilp = load-example( + "EulerianPath", + "ILP", + target-variant: (variable: "i32"), +) +#let ep_ilp_sol = ep_ilp.solutions.at(0) +#reduction-rule("EulerianPath", "ILP", + example: true, + example-target-variant: (variable: "i32"), + example-caption: [3-vertex digraph with 4 arcs (parallel edges)], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(ep_ilp.source) + " -o source.json", + "pred reduce source.json --to " + target-spec(ep_ilp) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate source.json --config " + ep_ilp_sol.source_config.map(str).join(","), + ) + Source trail witness $(#ep_ilp_sol.source_config.map(str).join(", "))$, target ILP witness $(#ep_ilp_sol.target_config.map(str).join(", "))$. + ], +)[ + Encode the directed Eulerian-trail witness structure as an integer feasibility program: successor variables on compatible arc pairs, start / end indicators, and Miller--Tucker--Zemlin-style position variables eliminate spurious sub-cycles @Ebert1988ComputingEulerianTrails @BangJensenGutin2009Digraphs. +][ + _Construction._ Let the source instance be a directed multigraph $D = (V, A)$ with arc occurrences $A = {a_1, dots, a_m}$ and let + $ + P = { (a, b) in A times A : a != b "and" "head"(a) = "tail"(b) } + $ + be the set of compatible ordered arc pairs. When $m = 0$ we map to the empty ILP, which is vacuously feasible (the empty trail). Otherwise introduce integer variables $y_(a,b) in {0, 1}$ for $(a, b) in P$, $s_a, e_a in {0, 1}$ and a position variable $u_a in {0, 1, dots, m - 1}$ for every $a in A$. The ILP is: + $ + "find" quad & bold(x) \ + "subject to" quad & s_a + sum_((b, a) in P) y_(b,a) = 1 quad forall a in A \ + & e_a + sum_((a, b) in P) y_(a,b) = 1 quad forall a in A \ + & u_b >= u_a + 1 - m (1 - y_(a,b)) quad forall (a, b) in P \ + & sum_(a in A) s_a = 1, quad sum_(a in A) e_a = 1 \ + & y_(a,b), s_a, e_a in {0, 1}, quad u_a in {0, 1, dots, m - 1}. + $ + + _Correctness._ ($arrow.r.double$) Any Eulerian trail $a_(pi(1)), dots, a_(pi(m))$ supplies an immediate ILP witness: set $s_(a_(pi(1))) = e_(a_(pi(m))) = 1$, set $y_(a_(pi(t)), a_(pi(t+1))) = 1$ for every consecutive pair, and set $u_(a_(pi(t))) = t - 1$. Every constraint holds by construction. ($arrow.l.double$) In a feasible ILP solution, the predecessor / successor equalities force each arc to appear exactly once on a directed path decomposition; the unique start and unique end constraints leave only one path; the order constraints rule out any disjoint directed cycle because they force strict position increases along every active successor edge. + + _Solution extraction._ Read off the unique start arc with $s_a = 1$ and repeatedly follow the unique active successor $(a, b)$ with $y_(a,b) = 1$ to recover the trail; output the resulting arc permutation as the source configuration. +] + #reduction-rule("BottleneckTravelingSalesman", "ILP")[ Use a cyclic position assignment for the tour and a bottleneck variable that dominates the weight of every chosen tour edge. ][ @@ -15081,6 +16318,39 @@ The following reductions to Integer Linear Programming are straightforward formu _Solution extraction._ For each vertex $v$, output its unique parent $u$ with $p_(v,u) = 1$. ] +#let mcmf_mcc = load-example("MinimumCostMaximumFlow", "MinimumCostCirculation") +#let mcmf_mcc_sol = mcmf_mcc.solutions.at(0) +#reduction-rule("MinimumCostMaximumFlow", "MinimumCostCirculation", + example: true, + example-caption: [Diamond network: $n = 4$ vertices, $m = 5$ arcs, max flow $= 3$], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(mcmf_mcc.source) + " -o source.json", + "pred reduce source.json --to " + target-spec(mcmf_mcc) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate source.json --config " + mcmf_mcc_sol.source_config.map(str).join(","), + ) + Source flow $(#mcmf_mcc_sol.source_config.map(str).join(", "))$; target circulation $(#mcmf_mcc_sol.target_config.map(str).join(", "))$ appends the return arc. + ], +)[ + Augment the flow network with a single return arc from the sink to the source. Give it capacity equal to a feasible-flow upper bound and a sufficiently negative cost so that the resulting min-cost circulation lex-orders the original (max value, min cost) objective. Recover the source flow by deleting the return arc. +][ + _Construction._ Let the source instance be $(G = (V, A), s, t, u, c)$ with $n = |V|$ vertices, $m = |A|$ arcs, nonnegative capacities $u_a$, and nonnegative costs $c_a$. Build $G' = (V, A')$ where $A' = A union {e^*}$ and $e^* = (t, s)$ is a new return arc. Define: + - $U = sum_(a in delta^+(s)) u_a$ — capacity of $e^*$ (a trivial upper bound on $|f|$), + - $B = 1 + sum_(a in A) c_a$ — strict upper bound on any simple $s$-$t$ path cost, + - $u'_a = u_a$, $c'_a = c_a$ for $a in A$, and $u'_(e^*) = U$, $c'_(e^*) = -B$. + + The target instance is the min-cost integral circulation on $G'$ with capacities $u'$ and signed costs $c'$. Size: $n$ vertices and $m + 1$ arcs. + + _Correctness._ ($arrow.r.double$) Any feasible $s$-$t$ flow $f$ of value $F$ in the source lifts to a circulation $g$ in $G'$ by $g_a = f_a$ for $a in A$ and $g_(e^*) = F$. Conservation is restored at $s$ and $t$ because the return arc carries the net flow back. The circulation cost is $sum_(a in A) c_a f_a + (-B) F = "cost"(f) - B F$. + + ($arrow.l.double$) Any feasible circulation $g$ in $G'$ projects to a feasible $s$-$t$ flow $f_a = g_a$ on $A$ with value $|f| = g_(e^*)$, since conservation at $s$ and $t$ in $G'$ together with the return arc forces net outflow at $s$ to equal $g_(e^*)$. + + Because $B$ strictly exceeds $sum_(a in A) c_a$, each unit of return-arc flow paired with a feasible $s$-$t$ path has strictly negative net cost. Thus the optimal circulation pushes $g_(e^*)$ to the maximum feasible flow value $F^*$; once $F^*$ is fixed, $g_(e^*) dot (-B)$ is constant and the remaining objective is exactly $sum_(a in A) c_a f_a$. Minimizing the circulation cost therefore lex-orders $(max |f|, min "cost"(f))$. + + _Solution extraction._ Discard the last circulation variable (the return arc) and read the first $m$ variables as the source flow. +] + #reduction-rule("MinimumEdgeCostFlow", "ILP")[ Introduce integer flow variables and binary arc-activation indicators, link them so that an indicator is forced to 1 whenever the corresponding arc carries positive flow, and minimize the total price of activated arcs. ][ @@ -17113,6 +18383,32 @@ The following table shows concrete variable overhead for example instances, take _Solution extraction._ Inspect the label assigned to each matching edge $x_i y_i$. Compress the distinct matching-edge labels to $0, dots, k-1$ and assign source vertex $v_i$ to the compressed label of its matching edge. The previous paragraph proves that these label classes are source cliques, and the forced gadget/side cliques guarantee $k <= K$ whenever the target cover has size at most $K + 2m + 2$. ] +#let mcbc_migb = load-example("MinimumCoveringByCliques", "MinimumIntersectionGraphBasis") +#let mcbc_migb_sol = mcbc_migb.solutions.at(0) +#reduction-rule("MinimumCoveringByCliques", "MinimumIntersectionGraphBasis", + example: true, + example-caption: [Triangle plus pendant: $n = 4$ vertices, $m = 4$ edges], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(mcbc_migb.source) + " -o source.json", + "pred reduce source.json --to " + target-spec(mcbc_migb) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate source.json --config " + mcbc_migb_sol.source_config.map(str).join(","), + ) + Source clique labels $(#mcbc_migb_sol.source_config.map(str).join(", "))$, target intersection witness $(#mcbc_migb_sol.target_config.map(str).join(", "))$. + ], +)[ + This $O(n + m)$ identity reduction @garey1979[GT59] @erdosgoodmanposa1966 @kouStockmeyerWong1978 keeps the graph unchanged and reinterprets the objective. The minimum number of cliques covering all edges of $G$ equals the minimum universe size of an intersection representation of $G$, so the two optimization problems are equivalent reformulations. +][ + _Construction._ Given a Minimum Covering by Cliques instance on graph $G = (V, E)$, output the Minimum Intersection Graph Basis instance on the same graph $G$. No vertices or edges are added, deleted, or relabeled. + + _Correctness._ ($arrow.r.double$) Suppose $C_1, dots, C_k$ is an edge-clique cover of $G$. Let the target universe be $U = {1, dots, k}$, and for each vertex $v in V$ define $S[v] = {i in U : v in C_i}$. If $\{u, v\} in E$, then some cover clique $C_i$ contains both endpoints, so $i in S[u] inter S[v]$ and the two sets intersect. Conversely, if $S[u] inter S[v] != emptyset$, then some $i$ satisfies $u, v in C_i$, and because $C_i$ is a clique, $\{u, v\} in E$. Thus the family $(S[v])_(v in V)$ is an intersection representation using $k$ universe elements. + + ($arrow.l.double$) Suppose $G$ has an intersection representation $(S[v])_(v in V)$ over a universe $U$ of size $k$. For each element $s in U$, define $C_s = {v in V : s in S[v]}$. If $u, v in C_s$, then $s in S[u] inter S[v]$, so $\{u, v\} in E$; hence every $C_s$ is a clique. Every edge $\{u, v\} in E$ must satisfy $S[u] inter S[v] != emptyset$, so choosing any shared element $s$ places both endpoints in $C_s$. Therefore the cliques $(C_s)_(s in U)$ cover all edges of $G$, using at most $k$ cliques. + + _Solution extraction._ The implementation reads the target witness as subsets $S[v]$ over the built-in $|E|$ universe slots. For each source edge $\{u, v\}$, choose any slot in $S[u] inter S[v]$ and use that slot as the source clique label for the edge. All edges receiving the same label lie inside the clique induced by that universe element, so the extracted labeling is a valid edge-clique cover. +] + // 6. KSatisfiability → Kernel (#882) #let ksat_ker = load-example("KSatisfiability", "Kernel") #let ksat_ker_sol = ksat_ker.solutions.at(0) @@ -17312,6 +18608,54 @@ The following table shows concrete variable overhead for example instances, take _Solution extraction._ The X3C configuration equals the Subset Product configuration: select subset $j$ iff $x_j = 1$. ] +// ExactCoverBy3Sets → BoundedDiameterSpanningTree (#913) +#let x3c_bdst = load-example("ExactCoverBy3Sets", "BoundedDiameterSpanningTree") +#let x3c_bdst_sol = x3c_bdst.solutions.at(0) +#let x3c_bdst_nv = graph-num-vertices(x3c_bdst.target.instance) +#let x3c_bdst_ne = graph-num-edges(x3c_bdst.target.instance) +#reduction-rule("ExactCoverBy3Sets", "BoundedDiameterSpanningTree", + example: true, + example-caption: [$|U| = #x3c_bdst.source.instance.universe_size$, $|cal(C)| = #x3c_bdst.source.instance.subsets.len()$ subsets; target $D = #x3c_bdst.target.instance.diameter_bound$, $B = #x3c_bdst.target.instance.weight_bound$], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(x3c_bdst.source) + " -o x3c.json", + "pred reduce x3c.json --to " + target-spec(x3c_bdst) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate x3c.json --config " + x3c_bdst_sol.source_config.map(str).join(","), + ) + + #let q = x3c_bdst.source.instance.universe_size / 3 + #let m = x3c_bdst.source.instance.subsets.len() + *Step 1 -- Source instance.* The X3C fixture has universe $U = {0, dots, #(x3c_bdst.source.instance.universe_size - 1)}$ with $q = #q$ and candidate triples + #for (i, s) in x3c_bdst.source.instance.subsets.enumerate() [ + $C_#i = {#s.map(str).join(", ")}$#if i < m - 1 [, ] else [.] + ] + + *Step 2 -- Build the spanning-tree gadget.* Create a root $r$, two forced-path vertices $v_1, v_2$, one set vertex $s_i$ per triple, and one element vertex $e_j$ per universe element. The target therefore has $#x3c_bdst_nv = 3 + #m + #(x3c_bdst.source.instance.universe_size)$ vertices and $#x3c_bdst_ne$ weighted edges: the forced path $(r, v_1), (v_1, v_2)$ at weight $1$, the root-to-set edges $(r, s_i)$ at weight $2$, the set-to-element edges $(s_i, e_j)$ for $j in C_i$ at weight $1$, and the set clique $(s_i, s_(i'))$ at weight $1$. The bounds are $D = #x3c_bdst.target.instance.diameter_bound$ and $B = 4q + m + 2 = #x3c_bdst.target.instance.weight_bound$. + + *Step 3 -- Verify the canonical witness.* The stored source configuration $(#x3c_bdst_sol.source_config.map(str).join(", "))$ selects subsets ${#x3c_bdst_sol.source_config.enumerate().filter(((i, x)) => x == 1).map(((i, x)) => "C_" + str(i)).join(", ")}$. The corresponding tree keeps the forced path, every root-to-set edge for a selected $s_i$, every $(s_i, e_j)$ for $j in C_i$, and one clique edge to attach each remaining set vertex. With $q = #q$ selected sets it has total weight $2 + 2q + 3q + (m - q) = #(2 + 2 * q + 3 * q + m - q) = B$ and every vertex sits within distance $2$ of $r$, so the diameter is at most $4$ #sym.checkmark. + + *Multiplicity:* The fixture stores one canonical witness. Any feasible spanning tree of weight $B$ and diameter $D = 4$ corresponds to an exact cover via the same extractor, so additional witnesses, when they exist, just enumerate other exact covers. + ], +)[ + This $O(m^2 + q)$ reduction @garey1979[ND4] embeds X3C into the spanning-tree gadget of Bounded Diameter Spanning Tree. The constructed graph has $3 + m + 3q$ vertices and $2 + 4m + binom(m, 2)$ edges with weights in ${1, 2}$. Setting $D = 4$ and $B = 4q + m + 2$, the BDST instance is feasible if and only if the X3C instance has an exact cover. +][ + _Construction._ Let the X3C instance be $(U, cal(C))$ with $|U| = 3q$ and $cal(C) = {C_0, dots, C_(m-1)}$. Introduce a root vertex $r$, two forced-path vertices $v_1, v_2$, set vertices $s_0, dots, s_(m-1)$, and element vertices $e_0, dots, e_(3q-1)$. Add the edges + $ + (r, v_1) " and " (v_1, v_2) " of weight " 1, quad (r, s_i) " of weight " 2 " for every " i, + $ + $ + (s_i, e_j) " of weight " 1 " whenever " j in C_i, quad (s_i, s_(i')) " of weight " 1 " for all " 0 <= i < i' < m. + $ + Set the diameter bound $D = 4$ and the weight bound $B = 4q + m + 2$. + + _Correctness._ ($arrow.r.double$) Let $cal(C)' = {C_(i_1), dots, C_(i_q)}$ be an exact cover. Pick the forced-path edges, the $q$ root-to-set edges for the chosen indices, the $3q$ set-to-element edges that match the cover, and for every unselected set $C_i$ a single clique edge to some chosen set. This is a spanning tree of weight $2 + 2q + 3q + (m - q) = 4q + m + 2 = B$, and every vertex lies within distance $2$ of $r$, so the diameter is at most $4 = D$. + + ($arrow.l.double$) Suppose $T$ is a spanning tree of weight at most $B$ and diameter at most $4$. Because $"dist"_T(r, v_2) = 2$ in any tree containing the forced edges, every other vertex must sit within distance $2$ of $r$; otherwise its distance to $v_2$ would exceed $4$. Element vertices $e_j$ have neighbors only among set vertices, so each $e_j$ is at depth $2$ and connects through some $s_i$ that is directly attached to $r$. Let $k$ be the number of root-to-set edges in $T$. The cheapest way to spawn the remaining $m - k$ set vertices uses clique edges of weight $1$, so the minimum tree weight is $k dot 2 + (m - k) dot 1 + 3q dot 1 + 2 dot 1 = k + m + 3q + 2$. Feasibility forces $k <= q$. Each chosen set covers at most three element vertices, so covering all $3q$ elements requires $k >= q$, hence $k = q$. The $q$ chosen sets contribute exactly $3q$ element attachments, so they must be pairwise disjoint and form an exact cover. + + _Solution extraction._ The target configuration has one coordinate per edge in the order produced by the construction. The $m$ coordinates indexing the root-to-set edges $(r, s_i)$ are the X3C selection vector: $x_i = 1$ iff $(r, s_i) in T$. +] + // 8. SubsetSum → IntegerExpressionMembership (#569) #let ss_iem = load-example("SubsetSum", "IntegerExpressionMembership") #let ss_iem_sol = ss_iem.solutions.at(0) @@ -17393,6 +18737,30 @@ The following table shows concrete variable overhead for example instances, take _Solution extraction._ Set $tau(x_i) = "TRUE"$ if $x mod p_i = 1$, FALSE if $x mod p_i = 2$. ] +#let n3dm_nmts = load-example("Numerical3DimensionalMatching", "NumericalMatchingWithTargetSums") +#let n3dm_nmts_sol = n3dm_nmts.solutions.at(0) +#reduction-rule("Numerical3DimensionalMatching", "NumericalMatchingWithTargetSums", + example: true, + example-caption: [$m = 2$ triples, target sum $B = 15$], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(n3dm_nmts.source) + " -o source.json", + "pred reduce source.json --to " + target-spec(n3dm_nmts) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate source.json --config " + n3dm_nmts_sol.source_config.map(str).join(","), + ) + Source N3DM witness $(#n3dm_nmts_sol.source_config.map(str).join(", "))$, target NMTS pairing $(#n3dm_nmts_sol.target_config.map(str).join(", "))$. + ], +)[ + This linear-time reduction @garey1979 keeps the $X$ and $Y$ sets unchanged and replaces each $w_i in W$ by a target complement $B_i = B - s(w_i)$. For an instance with $m$ triples, the target has $m$ pairs. +][ + _Construction._ Let the source instance be $(W, X, Y, s, B)$ with $W = {w_1, dots, w_m}$, $X = {x_1, dots, x_m}$, and $Y = {y_1, dots, y_m}$. Construct the Numerical Matching with Target Sums instance with the same ordered size lists for $X$ and $Y$, and target vector $(B - s(w_1), dots, B - s(w_m))$. + + _Correctness._ ($arrow.r.double$) Suppose the N3DM instance has triples $(w_(rho(i)), x_(sigma(i)), y_(tau(i)))$ summing to $B$. Relabel the triples so that the $i$-th triple contains $w_i$. Then $s(x_(sigma(i))) + s(y_(tau(i))) = B - s(w_i)$ for every $i$, so the same $X/Y$ pairings satisfy the NMTS target multiset. ($arrow.l.double$) Suppose the NMTS instance has a perfect pairing of $X$ and $Y$ whose pair-sum multiset equals $(B - s(w_1), dots, B - s(w_m))$. Match each realized pair sum to one unused complement of the same value, and attach that pair to the corresponding $w_i$. Every resulting triple has sum $s(w_i) + (B - s(w_i)) = B$, so the triples form a valid N3DM solution. + + _Solution extraction._ Given a target pairing, compute each realized pair sum $s(x_j) + s(y_(pi(j)))$ and match these sums back to the complement multiset $(B - s(w_i))$. This reconstructs the $X$- and $Y$-permutations indexed by $W$. Because the implemented NMTS model stores `i64` sizes, the reduction additionally assumes that every copied $X/Y$ size and every complement $B - s(w_i)$ fits in `i64`. +] + // 12. Partition → SequencingToMinimizeTardyTaskWeight (#471) #let part_stw = load-example("Partition", "SequencingToMinimizeTardyTaskWeight") #let part_stw_sol = part_stw.solutions.at(0) @@ -17545,6 +18913,36 @@ The following table shows concrete variable overhead for example instances, take #let tdm_tp = load-example("ThreeDimensionalMatching", "ThreePartition") #let tdm_tp_sol = tdm_tp.solutions.at(0) +#let tdm_tmi = load-example("ThreeDimensionalMatching", "ThreeMatroidIntersection") +#let tdm_tmi_sol = tdm_tmi.solutions.at(0) +#reduction-rule("ThreeDimensionalMatching", "ThreeMatroidIntersection", + example: true, + example-caption: [$q = 3$, $t = 5$ triples], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(tdm_tmi.source) + " -o source.json", + "pred reduce source.json --to " + target-spec(tdm_tmi) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate source.json --config " + tdm_tmi_sol.source_config.map(str).join(","), + ) + Source 3DM witness $(#tdm_tmi_sol.source_config.map(str).join(", "))$, target common-independent witness $(#tdm_tmi_sol.target_config.map(str).join(", "))$. + ], +)[ + This $O(t + q)$ direct embedding @garey1979[SP11] takes the triple set itself as the common ground set and builds three partition matroids, one per coordinate family. The target has $t = |T|$ ground-set elements, $3 q$ groups in total, and bound $K = q$. +][ + _Construction._ Let the 3DM instance have universe size $q$ and triples $T = {t_0, dots, t_(t - 1)} subset.eq W times X times Y$, where $t_l = (w_(a_l), x_(b_l), y_(c_l))$. Create a Three-Matroid Intersection instance whose ground set is $E = {0, dots, t - 1}$, with element $l$ representing triple $t_l$. + + Build three partition matroids on $E$. For each $i in {0, dots, q - 1}$, let + $ G_i^W = {l in E : a_l = i}, quad G_i^X = {l in E : b_l = i}, quad G_i^Y = {l in E : c_l = i}. $ + A subset $S subset.eq E$ is independent in the first matroid iff $|S inter G_i^W| <= 1$ for every $i$; define the second and third matroids analogously using the $X$- and $Y$-coordinate groups. Set the target bound to $K = q$. + + _Correctness._ ($arrow.r.double$) If $M subset.eq T$ is a perfect 3-dimensional matching, let $S subset.eq E$ contain exactly the indices of the triples in $M$. Because no two triples in $M$ share a $W$-, $X$-, or $Y$-coordinate, $S$ meets every group $G_i^W$, $G_i^X$, and $G_i^Y$ in at most one element, so $S$ is independent in all three matroids. Also $|S| = |M| = q$, hence $S$ is a feasible Three-Matroid Intersection solution. + + ($arrow.l.double$) Let $S subset.eq E$ be a common independent set of size $q$. Independence in the first matroid implies that the selected triples use $q$ distinct $W$-coordinates; since only $q$ such coordinates exist, they use each element of $W$ exactly once. The same argument applies to $X$ and $Y$. Therefore the triples indexed by $S$ form a perfect 3-dimensional matching. + + _Solution extraction._ Return the same binary indicator vector: target element $l$ is selected iff source triple $t_l$ is selected. +] + #reduction-rule("ThreeDimensionalMatching", "ThreePartition", example: true, example-caption: [$q = #tdm_tp.source.instance.universe_size$, $t = #tdm_tp.source.instance.triples.len()$, target has #tdm_tp.target.instance.sizes.len() numbers], diff --git a/docs/paper/references.bib b/docs/paper/references.bib index bb99fc9c0..a5d99206a 100644 --- a/docs/paper/references.bib +++ b/docs/paper/references.bib @@ -1071,6 +1071,17 @@ @article{wagnerfischer1974 doi = {10.1145/321796.321811} } +@article{lima2002closeststring, + author = {Ming Li and Bin Ma and Lusheng Wang}, + title = {On the Closest String and Substring Problems}, + journal = {Journal of the ACM}, + volume = {49}, + number = {2}, + pages = {157--171}, + year = {2002}, + doi = {10.1145/506147.506150} +} + @article{blum2021, author = {Christian Blum and Maria J. Blesa and Borja Calvo}, title = {{ILP}-based reduced variable neighborhood search for the longest common subsequence problem}, @@ -1845,3 +1856,229 @@ @inproceedings{lovasz1973 pages = {3--12}, year = {1973} } + +@article{salloum2025ikqubo, + author = {Hadi Salloum and Sergei Savin and Yaroslav Kholodov and Gleb Ryzhakov and Mirko Farina and Ivan Oseledets}, + title = {Quantum annealing for inverse kinematics in robotics}, + journal = {Scientific Reports}, + volume = {16}, + number = {1}, + pages = {4244}, + year = {2025}, + doi = {10.1038/s41598-025-34346-z} +} + +@article{daiizatttedrake2019, + author = {Hongkai Dai and Gregory Izatt and Russ Tedrake}, + title = {Global inverse kinematics via mixed-integer convex optimization}, + journal = {The International Journal of Robotics Research}, + volume = {38}, + number = {12--13}, + pages = {1420--1441}, + year = {2019}, + doi = {10.1177/0278364919846512} +} + + +@article{Hernandez2016MolecularSimilarity, + author = {Maritza Hernandez and Arman Zaribafiyan and Maliheh Aramon and Mohammad Naghibi}, + title = {A Novel Graph-based Approach for Determining Molecular Similarity}, + journal = {arXiv preprint arXiv:1601.06693}, + year = {2016}, + url = {https://arxiv.org/abs/1601.06693} +} + +@article{HosseinianButenko2022KDependent, + author = {Seyedmohammadhossein Hosseinian and Sergiy Butenko}, + title = {An improved approximation for Maximum k-dependent Set on bipartite graphs}, + journal = {Discrete Applied Mathematics}, + volume = {307}, + pages = {95--101}, + year = {2022}, + doi = {10.1016/j.dam.2021.10.015} +} + +@article{Bokhari1981Mapping, + author = {Shahid H. Bokhari}, + title = {On the Mapping Problem}, + journal = {IEEE Transactions on Computers}, + volume = {C-30}, + number = {3}, + pages = {207--214}, + year = {1981}, + doi = {10.1109/TC.1981.1675756} +} + +@article{Bahiense2012MCES, + author = {Laura Bahiense and Gordana Mani{\'c} and Breno Piva and Cid C. de Souza}, + title = {The maximum common edge subgraph problem: A polyhedral investigation}, + journal = {Discrete Applied Mathematics}, + volume = {160}, + number = {18}, + pages = {2523--2541}, + year = {2012}, + doi = {10.1016/j.dam.2012.01.026} +} + +@article{Soule2021RNA, + author = {Antoine Soul{\'e} and Vladimir Reinharz and Roman Sarrazin-Gendron and Alain Denise and J{\'e}r{\^o}me Waldisp{\"u}hl}, + title = {Finding recurrent RNA structural networks with fast maximal common subgraphs of edge-colored graphs}, + journal = {PLOS Computational Biology}, + volume = {17}, + number = {5}, + pages = {e1008990}, + year = {2021}, + doi = {10.1371/journal.pcbi.1008990} +} + +@article{AndonovMalodDogninYanev2011CMO, + author = {Rumen Andonov and No{\"e}l Malod-Dognin and Nicola Yanev}, + title = {Maximum Contact Map Overlap Revisited}, + journal = {Journal of Computational Biology}, + volume = {18}, + number = {1}, + pages = {27--41}, + year = {2011}, + doi = {10.1089/cmb.2009.0196} +} + +@article{XieSahinidis2007CMO, + author = {Wei Xie and Nikolaos V. Sahinidis}, + title = {A Reduction-Based Exact Algorithm for the Contact Map Overlap Problem}, + journal = {Journal of Computational Biology}, + volume = {14}, + number = {5}, + pages = {637--654}, + year = {2007}, + doi = {10.1089/cmb.2007.R007} +} + +@article{GouveiaMartins2015MEWC, + author = {Luis Gouveia and Pedro Martins}, + title = {Solving the maximum edge-weight clique problem in sparse graphs with compact formulations}, + journal = {EURO Journal on Computational Optimization}, + volume = {3}, + number = {1}, + pages = {1--30}, + year = {2015}, + doi = {10.1007/s13675-014-0028-1} +} + +@article{ParkLeePark1996EWClique, + author = {Kyungchul Park and Kyungsik Lee and Sungsoo Park}, + title = {An extended formulation approach to the edge-weighted maximal clique problem}, + journal = {European Journal of Operational Research}, + volume = {95}, + number = {3}, + pages = {671--682}, + year = {1996}, + doi = {10.1016/0377-2217(95)00299-5} +} + +@article{GouveiaMartins2015EWClique, + author = {Luis Gouveia and Pedro Martins}, + title = {Solving the maximum edge-weight clique problem in sparse graphs with compact formulations}, + journal = {EURO Journal on Computational Optimization}, + volume = {3}, + number = {1}, + pages = {1--30}, + year = {2015}, + doi = {10.1007/s13675-014-0028-1} +} + +@article{HuntingFaigleKern2001, + author = {Marcel Hunting and Ulrich Faigle and Walter Kern}, + title = {A {Lagrangian} relaxation approach to the edge-weighted clique problem}, + journal = {European Journal of Operational Research}, + volume = {131}, + number = {1}, + pages = {119--131}, + year = {2001}, + doi = {10.1016/S0377-2217(99)00449-X} +} + +@article{HueffnerKomusiewiczLiebtrauNiedermeier2014, + author = {Falk H{\"u}ffner and Christian Komusiewicz and Adrian Liebtrau and Rolf Niedermeier}, + title = {Partitioning Biological Networks into Highly Connected Clusters with Maximum Edge Coverage}, + journal = {IEEE/ACM Transactions on Computational Biology and Bioinformatics}, + volume = {11}, + number = {3}, + pages = {455--467}, + year = {2014}, + doi = {10.1109/TCBB.2013.177} +} + +@article{HartuvShamir2000, + author = {Erez Hartuv and Ron Shamir}, + title = {A clustering algorithm based on graph connectivity}, + journal = {Information Processing Letters}, + volume = {76}, + number = {4--6}, + pages = {175--181}, + year = {2000}, + doi = {10.1016/S0020-0190(00)00142-3} +} + +@book{BangJensenGutin2009Digraphs, + author = {J{\o}rgen Bang-Jensen and Gregory Z. Gutin}, + title = {Digraphs: Theory, Algorithms and Applications}, + edition = {2}, + series = {Springer Monographs in Mathematics}, + publisher = {Springer London}, + year = {2009}, + doi = {10.1007/978-1-84800-998-1} +} + +@article{Ebert1988ComputingEulerianTrails, + author = {J{\"u}rgen Ebert}, + title = {Computing {E}ulerian trails}, + journal = {Information Processing Letters}, + volume = {28}, + number = {2}, + pages = {93--97}, + year = {1988}, + doi = {10.1016/0020-0190(88)90170-6} +} + +@article{TuncbagEtAl2013PCSF, + author = {Nurcan Tuncbag and Alfredo Braunstein and Andrea Pagnani and Shao-Shan Carol Huang and Jennifer Chayes and Christian Borgs and Riccardo Zecchina and Ernest Fraenkel}, + title = {Simultaneous Reconstruction of Multiple Signaling Pathways via the Prize-Collecting {S}teiner Forest Problem}, + journal = {Journal of Computational Biology}, + volume = {20}, + number = {2}, + pages = {124--136}, + year = {2013}, + doi = {10.1089/cmb.2012.0092} +} + +@inproceedings{TuncbagEtAl2012RECOMB, + author = {Nurcan Tuncbag and Alfredo Braunstein and Andrea Pagnani and Shao-Shan Carol Huang and Jennifer Chayes and Christian Borgs and Riccardo Zecchina and Ernest Fraenkel}, + title = {Simultaneous Reconstruction of Multiple Signaling Pathways via the Prize-Collecting {S}teiner Forest Problem}, + booktitle = {Research in Computational Molecular Biology (RECOMB 2012)}, + series = {Lecture Notes in Computer Science}, + volume = {7262}, + pages = {287--301}, + year = {2012}, + publisher = {Springer Berlin Heidelberg}, + doi = {10.1007/978-3-642-29627-7_31} +} + +@article{LummertzDaRocha2018CellRouter, + author = {Edroaldo Lummertz da Rocha and R. Grant Rowe and Vanessa Lundin and Mohan Malleshaiah and Deepak Kumar Jha and Carlos R. Rambo and Hu Li and Trista E. North and James J. Collins and George Q. Daley}, + title = {Reconstruction of complex single-cell trajectories using {CellRouter}}, + journal = {Nature Communications}, + volume = {9}, + number = {1}, + pages = {892}, + year = {2018}, + doi = {10.1038/s41467-018-03214-y} +} + +@misc{mit6854MinCostFlow, + title = {Min-Cost Flow Algorithms (Lecture Scribe Notes)}, + author = {{MIT 6.854 Advanced Algorithms}}, + year = {2021}, + howpublished = {Course notes}, + note = {Section 10, scribe notes}, + url = {https://courses.csail.mit.edu/6.854/21/Scribe/s10-minCostFlowAlg/s10-minCostFlowAlg.html} +} diff --git a/problemreductions-cli/src/cli.rs b/problemreductions-cli/src/cli.rs index f401759b1..70fa1e5af 100644 --- a/problemreductions-cli/src/cli.rs +++ b/problemreductions-cli/src/cli.rs @@ -252,6 +252,8 @@ Flags by problem type: GeneralizedHex --graph, --source, --sink IntegralFlowWithMultipliers --arcs, --capacities, --source, --sink, --multipliers, --requirement MinimumEdgeCostFlow --arcs, --edge-weights (prices), --capacities, --source, --sink, --requirement + MinimumCostMaximumFlow --arcs, --capacities, --costs, --source, --sink + MinimumCostCirculation, MCC --arcs, --capacities, --costs MinimumCutIntoBoundedSets --graph, --edge-weights, --source, --sink, --size-bound HamiltonianCircuit, HC --graph MaximumLeafSpanningTree --graph @@ -331,6 +333,8 @@ Flags by problem type: SubgraphIsomorphism --graph (host), --pattern (pattern) GroupingBySwapping --string, --bound [--alphabet-size] LCS --strings [--alphabet-size] + ClosestString --alphabet-size, --strings + ClosestSubstring --alphabet-size, --strings, --substring-length FAS --arcs [--weights] [--num-vertices] FVS --arcs [--weights] [--num-vertices] QBF --num-vars, --clauses, --quantifiers @@ -566,6 +570,36 @@ pub struct CreateArgs { /// Record access probabilities for ExpectedRetrievalCost (comma-separated, e.g., "0.2,0.15,0.15,0.2,0.1,0.2") #[arg(long)] pub probabilities: Option, + /// Link lengths for MinimumDiscretePlanarInverseKinematics (comma-separated positive reals, e.g., "2.0,1.0") + #[arg(long)] + pub link_lengths: Option, + /// Target point (x,y) for MinimumDiscretePlanarInverseKinematics (e.g., "2.0,1.0") + #[arg(long)] + pub target_point: Option, + /// Sampled absolute orientations per link for MinimumDiscretePlanarInverseKinematics (semicolon-separated angle lists, e.g., "0.0,1.5707963267948966;0.0,1.5707963267948966") + #[arg(long)] + pub orientation_samples: Option, + /// Admissible (a_{j-1}, a_j) pair sets per junction for MinimumDiscretePlanarInverseKinematics (pipe-separated junctions, each comma-separated "i-j" pairs, e.g., "0-0,0-1,1-1") + #[arg(long)] + pub allowed_pairs: Option, + /// Source labelled digraph G1 for MaximumCommonEdgeSubgraph. Format: ":,,..." with each arc "-