From f375575e6c76b5a22a43009cbbf52e9b95250c41 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 13 Oct 2026 06:48:07 +0200 Subject: [PATCH 01/24] Record native search branch split and preserve research Zdev-Change-Id: Z3a84f10967609b95b8d767472cd476f6d65d3b852ac05f06ffbd7ee533e2dff6 --- .zdev/config.toml | 6 + .zdev/open-cp/TASKS.md | 11 + .zdev/open-cp/area.toml | 7 + .zdev/open-cp/brief.md | 17 + .zdev/open-search/TASKS.md | 41 ++ .zdev/open-search/area.toml | 8 + .../background/beam-stack-regeneration.md | 186 ++++++++ .../background/cadds-retained-fragments.md | 108 +++++ .../open-search/background/didp-and-tsptw.md | 151 ++++++ .zdev/open-search/background/integration.md | 214 +++++++++ .../background/next-search-and-benchmarks.md | 179 +++++++ .../background/suffix-compatible-repair.md | 68 +++ .zdev/open-search/brief.md | 439 ++++++++++++++++++ ...sequential-a-with-incumbent-publication.md | 50 ++ .../002-deliver-standalone-widening-cabs.md | 50 ++ ...-engines-through-optional-model-guidanc.md | 50 ++ ...tic-engines-in-ordinary-space-portfolio.md | 47 ++ ...act-tsptw-data-and-independent-checking.md | 48 ++ ...er-a-strong-conventional-cp-tsptw-model.md | 48 ++ ...-tsptw-transitions-and-an-eager-control.md | 48 ++ ...-state-and-pareto-pruning-to-a-and-cabs.md | 49 ++ ...bounds-and-compare-residual-propagation.md | 48 ++ ...rogressive-column-search-with-cbfs-mode.md | 49 ++ ...and-column-search-through-optional-inte.md | 47 ++ ...small-resumable-tsptw-comparison-runner.md | 47 ++ ...freeze-the-conventional-gecode-baseline.md | 45 ++ ...width-beam-stack-regeneration-in-gecode.md | 45 ++ ...ompatible-beam-repair-with-existing-lns.md | 45 ++ ...mulations-and-engines-on-held-out-tsptw.md | 47 ++ ...column-frontier-selection-interruptible.md | 41 ++ ...hmark-cleanup-and-proof-outcomes-honest.md | 43 ++ ...d-incumbent-obligations-of-heuristic-se.md | 42 ++ ...and-ranking-with-independent-dependenci.md | 43 ++ ...l-search-facade-accept-a-native-builder.md | 45 ++ ...l-portfolio-recipes-with-native-heurist.md | 46 ++ ...-expose-cabs-limits-in-the-model-driver.md | 42 ++ ...escriptor-retention-from-search-schedul.md | 45 ++ ...ice-expansion-and-avoid-the-final-clone.md | 44 ++ ...move-cabs-full-beam-descriptor-recounts.md | 45 ++ ...sses-when-discarded-work-is-bounded-out.md | 46 ++ ...-a-cheap-tsptw-residual-deadline-filter.md | 46 ++ ...configurable-native-best-first-ordering.md | 47 ++ ...quential-cadds-retained-fragment-search.md | 49 ++ 43 files changed, 2822 insertions(+) create mode 100644 .zdev/config.toml create mode 100644 .zdev/open-cp/TASKS.md create mode 100644 .zdev/open-cp/area.toml create mode 100644 .zdev/open-cp/brief.md create mode 100644 .zdev/open-search/TASKS.md create mode 100644 .zdev/open-search/area.toml create mode 100644 .zdev/open-search/background/beam-stack-regeneration.md create mode 100644 .zdev/open-search/background/cadds-retained-fragments.md create mode 100644 .zdev/open-search/background/didp-and-tsptw.md create mode 100644 .zdev/open-search/background/integration.md create mode 100644 .zdev/open-search/background/next-search-and-benchmarks.md create mode 100644 .zdev/open-search/background/suffix-compatible-repair.md create mode 100644 .zdev/open-search/brief.md create mode 100644 .zdev/open-search/tasks/001-deliver-sequential-a-with-incumbent-publication.md create mode 100644 .zdev/open-search/tasks/002-deliver-standalone-widening-cabs.md create mode 100644 .zdev/open-search/tasks/003-configure-both-native-engines-through-optional-model-guidanc.md create mode 100644 .zdev/open-search/tasks/004-compose-native-heuristic-engines-in-ordinary-space-portfolio.md create mode 100644 .zdev/open-search/tasks/005-establish-exact-tsptw-data-and-independent-checking.md create mode 100644 .zdev/open-search/tasks/006-deliver-a-strong-conventional-cp-tsptw-model.md create mode 100644 .zdev/open-search/tasks/007-deliver-open-list-tsptw-transitions-and-an-eager-control.md create mode 100644 .zdev/open-search/tasks/008-add-optional-exact-state-and-pareto-pruning-to-a-and-cabs.md create mode 100644 .zdev/open-search/tasks/009-add-cheap-tsptw-bounds-and-compare-residual-propagation.md create mode 100644 .zdev/open-search/tasks/010-deliver-native-progressive-column-search-with-cbfs-mode.md create mode 100644 .zdev/open-search/tasks/011-expose-state-pruning-and-column-search-through-optional-inte.md create mode 100644 .zdev/open-search/tasks/012-build-a-small-resumable-tsptw-comparison-runner.md create mode 100644 .zdev/open-search/tasks/013-tune-and-freeze-the-conventional-gecode-baseline.md create mode 100644 .zdev/open-search/tasks/014-investigate-fixed-width-beam-stack-regeneration-in-gecode.md create mode 100644 .zdev/open-search/tasks/015-investigate-suffix-compatible-beam-repair-with-existing-lns.md create mode 100644 .zdev/open-search/tasks/016-evaluate-frozen-formulations-and-engines-on-held-out-tsptw.md create mode 100644 .zdev/open-search/tasks/017-keep-column-frontier-selection-interruptible.md create mode 100644 .zdev/open-search/tasks/018-keep-benchmark-cleanup-and-proof-outcomes-honest.md create mode 100644 .zdev/open-search/tasks/019-state-the-progress-and-incumbent-obligations-of-heuristic-se.md create mode 100644 .zdev/open-search/tasks/020-compose-model-bounds-and-ranking-with-independent-dependenci.md create mode 100644 .zdev/open-search/tasks/021-let-the-owning-model-search-facade-accept-a-native-builder.md create mode 100644 .zdev/open-search/tasks/022-compose-explicit-model-portfolio-recipes-with-native-heurist.md create mode 100644 .zdev/open-search/tasks/023-expose-cabs-limits-in-the-model-driver.md create mode 100644 .zdev/open-search/tasks/024-separate-persistent-descriptor-retention-from-search-schedul.md create mode 100644 .zdev/open-search/tasks/025-share-resumable-choice-expansion-and-avoid-the-final-clone.md create mode 100644 .zdev/open-search/tasks/026-remove-cabs-full-beam-descriptor-recounts.md create mode 100644 .zdev/open-search/tasks/027-close-cabs-passes-when-discarded-work-is-bounded-out.md create mode 100644 .zdev/open-search/tasks/028-evaluate-a-cheap-tsptw-residual-deadline-filter.md create mode 100644 .zdev/open-search/tasks/029-add-configurable-native-best-first-ordering.md create mode 100644 .zdev/open-search/tasks/030-evaluate-sequential-cadds-retained-fragment-search.md diff --git a/.zdev/config.toml b/.zdev/config.toml new file mode 100644 index 0000000000..4b1cd6b487 --- /dev/null +++ b/.zdev/config.toml @@ -0,0 +1,6 @@ +schema_version = 1 + +[project] +name = "gecode" +record = "pull-request" +trunk = "main" diff --git a/.zdev/open-cp/TASKS.md b/.zdev/open-cp/TASKS.md new file mode 100644 index 0000000000..37708065ce --- /dev/null +++ b/.zdev/open-cp/TASKS.md @@ -0,0 +1,11 @@ + + +# Tasks: open-cp + +- Total: 0 +- Ready: 0 +- Blocked: 0 +- Done: 0 + +| ID | Task | State | Blocked by | +| --- | --- | --- | --- | diff --git a/.zdev/open-cp/area.toml b/.zdev/open-cp/area.toml new file mode 100644 index 0000000000..b36d1aafb8 --- /dev/null +++ b/.zdev/open-cp/area.toml @@ -0,0 +1,7 @@ +schema_version = 1 +tag = "open-cp" +title = "Native open constraint programming" +objective = "Provide the native open-list runtime used by standalone heuristic search." +branch = "feature/open-cp" +lifecycle = "open" +base_commit = "6b7de57b0414fe2f8af3513743c9835840a3c019" diff --git a/.zdev/open-cp/brief.md b/.zdev/open-cp/brief.md new file mode 100644 index 0000000000..5315dba8e3 --- /dev/null +++ b/.zdev/open-cp/brief.md @@ -0,0 +1,17 @@ +# Native open constraint programming + +## Objective + +Record the existing `feature/open-cp` parent used by standalone native search. + +## Boundaries + +The branch split does not change this parent branch. Its recorded base is main. + +## Testing + +No implementation or new tests belong to this parent registration. + +## Validation + +Check branch identity and ancestry when admitting dependent native search work. diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md new file mode 100644 index 0000000000..dc4ece8b33 --- /dev/null +++ b/.zdev/open-search/TASKS.md @@ -0,0 +1,41 @@ + + +# Tasks: open-search + +- Total: 30 +- Ready: 0 +- Blocked: 0 +- Done: 30 + +| ID | Task | State | Blocked by | +| --- | --- | --- | --- | +| [open-search-001](tasks/001-deliver-sequential-a-with-incumbent-publication.md) | Deliver sequential A* with incumbent publication | done | — | +| [open-search-002](tasks/002-deliver-standalone-widening-cabs.md) | Deliver standalone widening CABS | done | open-search-001 | +| [open-search-003](tasks/003-configure-both-native-engines-through-optional-model-guidanc.md) | Configure both native engines through optional Model guidance | done | open-search-002 | +| [open-search-004](tasks/004-compose-native-heuristic-engines-in-ordinary-space-portfolio.md) | Compose native heuristic engines in ordinary-Space portfolios | done | open-search-002 | +| [open-search-005](tasks/005-establish-exact-tsptw-data-and-independent-checking.md) | Establish exact TSPTW data and independent checking | done | — | +| [open-search-006](tasks/006-deliver-a-strong-conventional-cp-tsptw-model.md) | Deliver a strong conventional CP TSPTW model | done | open-search-005 | +| [open-search-007](tasks/007-deliver-open-list-tsptw-transitions-and-an-eager-control.md) | Deliver open-list TSPTW transitions and an eager control | done | open-search-005 | +| [open-search-008](tasks/008-add-optional-exact-state-and-pareto-pruning-to-a-and-cabs.md) | Add optional exact-state and Pareto pruning to A* and CABS | done | open-search-007 | +| [open-search-009](tasks/009-add-cheap-tsptw-bounds-and-compare-residual-propagation.md) | Add cheap TSPTW bounds and compare residual propagation | done | open-search-006, open-search-007 | +| [open-search-010](tasks/010-deliver-native-progressive-column-search-with-cbfs-mode.md) | Deliver native progressive column search with CBFS mode | done | open-search-006, open-search-008 | +| [open-search-011](tasks/011-expose-state-pruning-and-column-search-through-optional-inte.md) | Expose state pruning and column search through optional integrations | done | open-search-008, open-search-010 | +| [open-search-012](tasks/012-build-a-small-resumable-tsptw-comparison-runner.md) | Build a small resumable TSPTW comparison runner | done | open-search-006, open-search-007 | +| [open-search-013](tasks/013-tune-and-freeze-the-conventional-gecode-baseline.md) | Tune and freeze the conventional Gecode baseline | done | open-search-006, open-search-012 | +| [open-search-014](tasks/014-investigate-fixed-width-beam-stack-regeneration-in-gecode.md) | Investigate fixed-width beam-stack regeneration in Gecode | done | open-search-007, open-search-012 | +| [open-search-015](tasks/015-investigate-suffix-compatible-beam-repair-with-existing-lns.md) | Investigate suffix-compatible beam repair with existing LNS | done | open-search-007, open-search-012, open-search-013 | +| [open-search-016](tasks/016-evaluate-frozen-formulations-and-engines-on-held-out-tsptw.md) | Evaluate frozen formulations and engines on held-out TSPTW | done | open-search-009, open-search-010, open-search-012, open-search-013, open-search-014, open-search-015 | +| [open-search-017](tasks/017-keep-column-frontier-selection-interruptible.md) | Keep column frontier selection interruptible | done | — | +| [open-search-018](tasks/018-keep-benchmark-cleanup-and-proof-outcomes-honest.md) | Keep benchmark cleanup and proof outcomes honest | done | — | +| [open-search-019](tasks/019-state-the-progress-and-incumbent-obligations-of-heuristic-se.md) | State the progress and incumbent obligations of heuristic search | done | — | +| [open-search-020](tasks/020-compose-model-bounds-and-ranking-with-independent-dependenci.md) | Compose Model bounds and ranking with independent dependencies | done | — | +| [open-search-021](tasks/021-let-the-owning-model-search-facade-accept-a-native-builder.md) | Let the owning Model search facade accept a native builder | done | — | +| [open-search-022](tasks/022-compose-explicit-model-portfolio-recipes-with-native-heurist.md) | Compose explicit Model portfolio recipes with native heuristic builders | done | open-search-021 | +| [open-search-023](tasks/023-expose-cabs-limits-in-the-model-driver.md) | Expose CABS limits in the Model driver | done | — | +| [open-search-024](tasks/024-separate-persistent-descriptor-retention-from-search-schedul.md) | Separate persistent descriptor retention from search scheduling | done | — | +| [open-search-025](tasks/025-share-resumable-choice-expansion-and-avoid-the-final-clone.md) | Share resumable Choice expansion and avoid the final clone | done | — | +| [open-search-026](tasks/026-remove-cabs-full-beam-descriptor-recounts.md) | Remove CABS full-beam descriptor recounts | done | — | +| [open-search-027](tasks/027-close-cabs-passes-when-discarded-work-is-bounded-out.md) | Close CABS passes when discarded work is bounded out | done | — | +| [open-search-028](tasks/028-evaluate-a-cheap-tsptw-residual-deadline-filter.md) | Evaluate a cheap TSPTW residual deadline filter | done | open-search-018 | +| [open-search-029](tasks/029-add-configurable-native-best-first-ordering.md) | Add configurable native best-first ordering | done | open-search-024, open-search-025 | +| [open-search-030](tasks/030-evaluate-sequential-cadds-retained-fragment-search.md) | Evaluate sequential CADDS retained-fragment search | done | open-search-018, open-search-025 | diff --git a/.zdev/open-search/area.toml b/.zdev/open-search/area.toml new file mode 100644 index 0000000000..747216d3ba --- /dev/null +++ b/.zdev/open-search/area.toml @@ -0,0 +1,8 @@ +schema_version = 1 +tag = "open-search" +title = "Heuristic search and open transition models" +objective = "Add A* and complete anytime beam search to Gecode for ordinary and open models, demonstrate open TSPTW transitions, and validate correctness and practical search/memory trade-offs." +branch = "feature/open-search" +lifecycle = "open" +parent = "open-cp" +base_commit = "55dc729c80ed5d60a742177612c870bdf366c532" diff --git a/.zdev/open-search/background/beam-stack-regeneration.md b/.zdev/open-search/background/beam-stack-regeneration.md new file mode 100644 index 0000000000..befa7b09d1 --- /dev/null +++ b/.zdev/open-search/background/beam-stack-regeneration.md @@ -0,0 +1,186 @@ +# Fixed-width beam-stack regeneration study + +Fixed-width continuation can preserve coverage on the worked deterministic +TSPTW transitions and reduce retained Spaces when CABS must widen substantially. +The simpler original-Choice cursor achieved the same storage reduction with +less repetition in this study, while using a different search order. These +results support a separately reviewed, restricted follow-up; they do not justify +generic Space border replay or a production engine here. + +The study used clean HEAD `b2bae0b94038ee0c2f723660d709315b114b7008` on +2026-10-07. All prototypes, inputs and records remain under +`/tmp/open-search-014-study`; production source was unchanged. + +## Two continuation contracts + +For original decision coverage, keep the prepared source Space unchanged and +retain its original Choice and cursor over alternatives `0..n-1`. Clone the +source, commit that recorded alternative, invalidate retained branchers, and +apply a newer incumbent to the child before propagation. Never mutate the +source, obtain a fresh Choice, and reuse an old index. Keep preparation state, +partial page selection, the next alternative and pending goals across pauses +and solution returns. Advance the cursor only after the child is retained, +processed or soundly pruned. Distinguish failure, branching, solution and +preparation suspension; inspect goals at every committed depth. + +The native n-ary witness demonstrates the index problem: a cut removes action +0 from original actions `[0,1,2]`. Fresh alternative 1 now commits action 2; +original alternative 1 commits action 1, whose deeper branch contains a better +goal. The retained cursor covers goals `[0]`, `[1,11]`, `[2]`, encounters a failed +child `[1,10]`, and survives seven preparation/expansion pauses and solution +returns. Adopting a ceiling of 6 after the first cost-9 goal preserves both later +paths. This is a finite continuation witness, not a general engine. + +Historical replay must preserve original Choice payloads in their original +commit order. It need not recover identical fixpoints, subsequent branchers or +heuristic values. Schulte and Tack distinguish solution-preserving path replay +from replaying newly chosen branch indices: non-monotonic propagation can change +the latter and lose solutions. Their completeness result allows extra or less +search after replay. [Weakly Monotonic Propagators, recomputation discussion and +Theorem 10](https://doi.org/10.1007/978-3-642-04244-7_56), local fulltext lines +195–252. The native Choice/prepare contracts are in `gecode/kernel/core.hpp`; +current CABS keeps its original parent/Choice cursor in `gecode/search/cabs.cpp`. + +Border pagination requires more: the same successor identities and an immutable +strict total order on every regeneration. Chen and Beck retain the first omitted +successor and regenerate successors greater than or equal to that border. Their +implementation orders concrete states by bound and lexicographic state values, +with duplicate-state removal. Their frontier theorem uses the least admissible +bound of all unexpanded states with expanded parents; it does not require bounds +to increase along a path. [Linear-Memory Beam Search Algorithms in DIDP, +border definition, Theorem 3 and BSS appendix](https://doi.org/10.4230/LIPIcs.CP.2026.12), +local fulltext lines 244–279, 382–415 and 868–921. + +The concrete experiment uses `examples/tsptw-transition.hh`, residual propagation +OFF, MIN_ARCS guidance, explicit depot return, and no state pruning. Its pre-cut +key is `(rank, bound, location, time, prefix_cost, returned, unvisited, complete +alternative path)`; here rank equals bound. Equal residual values remain separate +paths. New cuts affect committed children after key generation and never redefine +a stored page boundary. Older inherited cuts only remove permanently non-improving +paths. This restriction avoids changed residual inference or choice enumeration +during regeneration. A rank-first border with an unrelated rank cannot alone +certify the least frontier bound. + +Original Choices preserve original decisions on arbitrary clone-related Spaces; +they do not freeze regenerated child bounds or ranks. The mutable-rank witness +shows why: after selecting A with rank 1 and retaining B's rank-2 border, changing +A to rank 3 and B to rank 0 makes the old boundary repeat A and skip B. Freezing +all edge keys could avoid that problem but adds storage. The cursor control +instead uses fixed original source/alternative order; it does not reproduce the +border experiment's bound ordering. + +## Checked bounded results + +Coverage runs disable incumbent pruning and compare every feasible tour identity +with independent rational-arithmetic enumeration in `misc/tsptw-check.py`. Both +representations and widths 1 and 2 agree on waiting (one feasible tour), decimal +(one), folded-service (two), and late-return (none). The only generated case has +six customers, unit off-diagonal arcs, customer windows `[0,100]`, and depot +return deadline 6. Every tour requires seven units. Both methods visit all 720 +distinct failed path identities, checked against the permutation-derived +original alternative indices. This also exercises equal bounds and different +histories with equal residual values; the full path tie prevents their collapse. + +Separate optimization runs reach checked optimum 9 on folded-service and prove +the generated case infeasible. External runs adopt a checked cost-13 tour during +a partially expanded prototype page, then publish cost 9 and exhaust. Native +CABS receives the same external tour at its safe entry boundary. Every reported +tour and improvement is checked. The native n-ary witness tests the cut-induced +choice shift explicitly. The prototype uses one-commit work slices, preserving +partial candidate scans; its native preparation-suspension witness is separate +from the TSPTW brancher, whose preparation completes immediately. + +Generated-case optimization counts are identical for lazy and eager: + +| Continuation | Commit attempts | Bound evaluations | Extra page scans / CABS restarts | Peak Spaces | Peak original Choices | Peak border keys | Retained path / observed key capacity, ints | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| Border, B=1 | 3906 | 3186 | 719 scans | 7 | 6 | 5 | 21 / 36 | +| Border, B=2 | 3888 | 3168 | 359 scans | 12 | 11 | 5 | 42 / 48 | +| Cursor, B=1 | 1956 | 1236 | 0 | 7 | 6 | 0 | 21 / 0 | +| Cursor, B=2 | 1956 | 1236 | 0 | 12 | 11 | 0 | 42 / 0 | +| Native CABS, widths 1 through 1024 | 7211 | 5479 | 10 restarts | 722 | at most 1* | 0 | unavailable* | + +Border commitment counts exceed cursor counts by 1950 and 1932, respectively. +CABS makes 5255 more commitments than the cursor and additionally clones its +restart root. CABS commitments are derived from native `node - (restart + 1)`; +all roots in these cases are initially branching. Its saved Choice ceiling is +source-derived from its single `choice` field, not direct Choice instrumentation. +CABS path/vector capacity is not exposed. Both prototypes keep only the selected +B children and the first omitted key, plus one in-flight child; they never keep +every generated child Space. The table includes metadata from partial scans. + +The feasible control limits the recommendation: on folded-service, native CABS +peaks at 4 Spaces, border at 6/9 and cursor at 6/9 for B=1/2. CABS makes 4 +commitments and one restart; border makes 6, cursor 4/6. All find cost 9. The +memory advantage depends on the search tree and how soon valid cuts close it. + +## Memory and measurement limits + +The counting subclass includes the seed/root, stored sources, incumbent, +in-flight children and caller-owned returned solutions in live Space counts. +Its allocated-position totals are sampled, not exact byte measurements. +Original Choice counts do not measure their action-vector payloads. Path/key +capacity counts exclude allocator/container overhead; retained path counts +exclude transient local path copies, while observed key counts include the +in-flight key. Full paths and clone-local visit lists grow with depth, so an +O(Bd) count of Spaces does not imply O(Bd) scalar storage or bytes. + +| Generated-case process peak RSS | Lazy, bytes | Eager, bytes | +| --- | ---: | ---: | +| Border B=1 | 2260992 | 2228224 | +| Border B=2 | 2244608 | 2244608 | +| Cursor B=1 | 2260992 | 2228224 | +| Cursor B=2 | 2228224 | 2228224 | +| Native CABS | 4521984 | 4472832 | + +A separate probe warms 16 clones, then retains 2048 root clones. Resident memory +increases by 5488640 bytes (lazy) and 5439488 (eager). Each lazy clone has one +materialized position; eager has seven. `sizeof(State)` is 488 in both. These +process deltas include native regions, vectors, the counting registry and +allocator effects; the instance is shared. They are bounded retention samples, +not exact Space sizes. The slight lazy/eager difference is noise at this scale. + +There is one observation per configuration, on arm64 macOS with Apple clang 21 +and the existing Release build. Startup and subprocess wait polling dominate +many tiny times. No speedup or workload-wide memory claim follows. The final +suite has 73 launches; preliminary runs and the added failed-path check bring +the cumulative implementation ledger to 148 launches, zero failed/timeout +launches, and **3.062307 seconds** actual launch-to-reap solver wall. Four +successful compilation attempts total **8.87 seconds**, counted separately. +Each solver launch has a five-second cap and the runner refuses launches that +could exceed its 300-second cumulative allowance. Independent replay has a +separate allowance of at most 300 seconds; study plus replay must stay below +600. `wall-ledger.json` and saved per-launch samples retain the totals. + +## Reproduction and decision + +The following commands require the temporary artifacts; they deliberately do +not install a production engine or commit prototype source. Run from the stated +checkout and use a fresh results directory for independent replay. Repeating +the same run resumes completed samples without launching them again. The +checker analyzes saved output without invoking any solver. + +```sh +cd /Users/zayenz/.codex/worktrees/2d06/gecode +clang++ -std=c++17 -O2 -I build/open-search -I . /tmp/open-search-014-study/study.cpp -L build/open-search -Wl,-rpath,/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search -lgecodesearch -lgecodeint -lgecodekernel -lgecodesupport -o /tmp/open-search-014-study/study +/tmp/open-search-014-study/run-study.sh /tmp/open-search-014-study/replay-results +env UV_CACHE_DIR=/tmp/open-search-014-study/uv-cache uv run --script /tmp/open-search-014-study/check.py /tmp/open-search-014-study/replay-results --repository /Users/zayenz/.codex/worktrees/2d06/gecode > /tmp/open-search-014-study/replay-checked.json +env UV_CACHE_DIR=/tmp/open-search-014-study/uv-cache uv run --script /tmp/open-search-014-study/benchmark.py analyze --name return-limit-7-border-opt-lazy-b1-v4 --results-root /tmp/open-search-014-study/replay-results +python3 misc/tsptw-check.py enumerate examples/data/tsptw/tiny/folded-service.txt +python3 misc/tsptw-check.py check examples/data/tsptw/tiny/folded-service.txt /tmp/open-search-014-study/folded-optimum.tour --cost 9 +``` + +The final raw records are the names ending `-v4` in `results/`; the earlier +records remain only to preserve the cumulative wall ledger. `final-results.json` +contains the final checked rows, `checked-results.json` includes every attempt, +and `compile-1.log` through `compile-4.log` retain compiler timing. Compiler +deployment-target warnings were present; all recorded launches succeeded. + +Defer arbitrary-Space heuristic border replay. A separately reviewed bounded +follow-up could implement fixed-width continuation for deterministic transitions +with a declared immutable path-distinguishing key and the existing prepare, +incumbent and ownership contracts. Compare it with the fixed-alternative cursor +before choosing border regeneration: the cursor needs fewer keys and fewer +commitments here, but sacrifices heuristic beam ordering. The study supplies +enough evidence for that restricted proposal; it does not select a public API, +authorize production adoption, or create a follow-up task. diff --git a/.zdev/open-search/background/cadds-retained-fragments.md b/.zdev/open-search/background/cadds-retained-fragments.md new file mode 100644 index 0000000000..c886232c9f --- /dev/null +++ b/.zdev/open-search/background/cadds-retained-fragments.md @@ -0,0 +1,108 @@ +# Sequential CADDS retained-fragment study + +Defer a production CADDS engine. The sequential schedule preserves finite-tree coverage and uses far fewer live Spaces than native CABS on these three TSPTW cases. The bounded comparison also exposes a quality tradeoff: CABS finds the better Gendreau incumbent, and the cursor proves the Dumas optimum while retaining fewer Spaces. Two short repetitions do not establish a general production advantage. + +The study uses clean baseline `a40dd5f238f93aeb7d4516cac430dc6cf3b5a23f`, Apple clang 21.0.0, arm64 macOS 26.6.2 and the existing Release build. Durable source is `misc/tsptw-cadds-study.cpp` and `misc/tsptw-cadds-study.py`; `examples/data/tsptw/cadds-comparison.json` retains the frozen configuration and all 30 checked development rows. The report describes implementation evidence; independent verification appends its own charges to the same ledger. + +## Schedule and continuation + +Tardivo, Michel and van Hoeve select the deepest nonempty layer, freeze its best `min(B, size)` entries, expand the entire fragment, retain the unselected entries and install surviving successors at the next depth. Algorithm 2 separates sound successor pruning from fragment ranking. This study follows that sequential schedule, with optional state pruning disabled. [Complete Anytime Decision Diagram Search with GPU-Accelerated State Expansion, Algorithms 1–2 and Proposition 1](https://doi.org/10.1007/978-3-032-27242-3_34), CPKB paper `2026-tardivo-gpu-accelerated-state-expansion-for-anytime-exac`, original fulltext lines 279–403. + +The local `advance(unsigned commit_quota)` returns an owned solution, a suspension or exhaustion. Selection moves stable entries into one frozen fragment, ordered by lower rank and unique insertion order. Expansion uses `gecode/search/seq/choice-expansion.hh`: the source retains its original Choice and alternative cursor; new incumbent cuts apply to children with older source revisions. The final alternative transfers the source to the child while keeping its Choice alive through commitment, propagation and feedback. Classification handles failed, solved and branching states, publishes strict improvements in optimization mode and retains every surviving branching child. Coverage mode applies no incumbent cuts and publishes every tour. + +An incumbent return, preparation suspension or ordinary stop leaves the active fragment and staged successors intact. CADDS selects another layer only after every original alternative of every frozen source has finished. Empty pending layers and no active fragment establish exhaustion. Goals are checked at the root and at each committed depth. Rank selects work; only the admissible bound and the compatible Space cut prune it. + +The cursor control retains ancestor source/alternative continuations and descends after the next page of `B` surviving children. It handles goals when generated; a goal counts toward that page even though it needs no retained branching entry. Its pages follow original source/alternative order and do not use the supplied rank to reorder work. It is the minimal cursor described in the [014 continuation study](beam-stack-regeneration.md), adapted to native ChoiceExpansion without its border experiment, descriptor keys or instrumentation path copies. Native CABS uses its actual width-one/doubling passes with no maximum-width cap. No production engine, Model dependency or public API was added. + +## Frozen comparison + +Before the first solver launch, the runner validated these durable published copies against the existing development manifest: + +| Development case | SHA-256 | +| --- | --- | +| Dumas/n20w20.001.txt | `83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748` | +| GendreauDumasExtended/n20w120.001.txt | `633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba` | +| SolomonPotvinBengio/rc_201.1.txt | `e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f` | + +The fixed configurations are CADDS B=1/B=4, cursor B=1/B=4 and native CABS. Each case has two repetitions; the second reverses configuration order. Every method uses the same lazy Transition model, MIN_ARCS bound, rank equal to bound, residual propagation OFF, state pruning OFF, ascending native visit branching, no seed and one thread. An ordinary 1,500 ms TimeStop and a 2-second outer cap give 30 rows and 60 nominal outer-cap seconds. No held-out input was inspected or run, and no configuration changed after results arrived. + +All 30 rows completed capture successfully: 26 ordinary stops and four Dumas cursor optimality proofs. There were no outer timeouts, RSS stops, execution errors or invalid-output rows in this matrix. The independent Fraction checker accepted all 48 incumbent publications, which strictly improve within each run. Costs in the table are exact original units. Both repetitions agree on each final cost and peak live Space count. Times are the arithmetic mean of the two native publication clocks; wall time is mean launch-through-exit time. RSS gives the two-sample range in MiB. A dash means no incumbent, not infeasibility. + +| Case | Method | Final cost / outcome | First / best publication, ms | Wall, s | Peak live Spaces | Sampled RSS, MiB | +| --- | --- | --- | ---: | ---: | ---: | ---: | +| Dumas | cadds-b1 | 378 / stopped | 55.25 / 55.25 | 1.5563 | 153 | 2.73–2.75 | +| Dumas | cadds-b4 | 378 / stopped | 4.39 / 4.39 | 1.5617 | 394 | 3.38–3.48 | +| Dumas | cursor-b1 | 378 / optimal | 549.41 / 596.51 | 1.5622 | 21 | 2.34–2.38 | +| Dumas | cursor-b4 | 378 / optimal | 539.19 / 585.39 | 1.4716 | 44 | 2.44–2.44 | +| Dumas | cabs | 378 / stopped | 15.69 / 15.69 | 1.5748 | 55,660 | 167.97–174.55 | +| Gendreau | cadds-b1 | 272 / stopped | 1349.76 / 1430.57 | 1.5695 | 180 | 2.80–2.91 | +| Gendreau | cadds-b4 | 274 / stopped | 7.53 / 60.18 | 1.5487 | 545 | 3.70–3.73 | +| Gendreau | cursor-b1 | — / stopped | — / — | 1.5394 | 19 | 2.25–2.31 | +| Gendreau | cursor-b4 | — / stopped | — / — | 1.5727 | 50 | 2.44–2.45 | +| Gendreau | cabs | 267 / stopped | 1433.72 / 1433.72 | 1.5450 | 31,537 | 56.56–63.50 | +| Solomon | cadds-b1 | 444.5425 / stopped | 265.44 / 271.31 | 1.5692 | 149 | 2.80–2.83 | +| Solomon | cadds-b4 | 444.5425 / stopped | 748.20 / 748.20 | 1.5706 | 492 | 3.69–3.75 | +| Solomon | cursor-b1 | — / stopped | — / — | 1.5542 | 15 | 2.31–2.38 | +| Solomon | cursor-b4 | — / stopped | — / — | 1.5716 | 40 | 2.39–2.44 | +| Solomon | cabs | 444.5425 / stopped | 261.43 / 261.43 | 1.5771 | 58,335 | 177.27–178.41 | + +CADDS B=4 finds the first Dumas and Gendreau incumbents sooner than B=1, but finds the Solomon incumbent later. CABS obtains Gendreau cost 267; CADDS ends at 272 or 274 without a proof. The Dumas cursor runs prove cost 378; all other methods stop with that same feasible cost. CADDS and CABS find Solomon cost 444.5425 without proving it optimal. The cursor finds no incumbent on Gendreau or Solomon in this allowance. These differences constrain the adoption decision more than a single memory ratio would. The compact JSON retains each row's native node, failure, depth, propagation and restart counts, clone count and measured bound calls/time. OFF performs no residual inference; the reported inference counts/time are zero. CABS frontier subdivisions and exact retained bytes remain unavailable. + +## Retained states and memory + +The paper's `O(d * B * delta)` argument counts retained states. Selecting the deepest nonempty layer leaves its successor layer empty at fragment start. Expanding a whole fragment creates at most `B * delta` children. Search exhausts a deeper layer before a shallower fragment can replenish it. For finite maximum depth `d` and bounded original branching factor `delta`, each pending layer therefore contains at most one fragment's successor batch, apart from the root. Current-fragment parents, staged successors, an in-flight child, incumbent and caller-owned publication add bounded ownership terms. Deferred work is never width-truncated. + +Measured CADDS peak pending counts were 151/390 for Dumas B=1/B=4, 178/543 for Gendreau and 148/491 for Solomon. Peak staged counts were 19/62, 20/64 and 19/60, respectively; peak active source counts were 1/4. These separate maxima need not occur together. Pending layers can contain more than `B` entries. The live Space counter includes pending states, active parents, in-flight children, retained incumbent and caller-owned publications, including CABS restart roots. It uses constant-time construction/destruction counters and no live-Space registry. + +These counts do not measure arbitrary Space bytes. Native Choice action vectors, clone-local visit lists, depth metadata, vectors and allocator overhead can grow independently. Choice payload and container/Space byte costs are unavailable here. CABS does not expose its separate pending/source/staged counts. The cursor's retained source maxima ranged from 15 to 49; its pending-layer counter is zero because it retains ancestor pages instead. This study does not establish BSS's `O(B*d)` regeneration claim. + +The existing capture policy samples process-group RSS every requested 50 ms with a 1 GiB watchdog. Actual probe intervals differ: the largest development sample gap was 177.49 ms. All development rows had measured RSS; some tiny processes exited before a nonzero RSS observation, which remains null. RSS includes native model regions, libraries and allocator retention and is separate from Space counts. All outer-cap overshoots and RSS overshoots were zero. Native TimeStop checks do not interrupt propagation or destruction: observed native summaries reached 1533.51 ms despite the equal 1500 ms setting. Startup, ps probing, waiting and teardown remain in end-to-end wall time. + +## Correctness and budget evidence + +The ordinary-Space witness covers n-ary alternatives, one failed child, shallow and deeper goals, failed and solved roots, strict improvement, an intentionally misleading rank with a sound bound, preparation suspension, one-commit slices, ordinary stop/resume during an active fragment and exact finite-tree exhaustion. All four CADDS/cursor width combinations cover the expected three goal identities. Final measured optimization sequences are `[9,5,3]` for both CADDS widths and `[9,3]` for both cursor widths. The CADDS B=4 expansion trace `[0,2,1,4,3]` shows that node 2 generates node 4, then selected parent 1 expands before successor 4. A direct cut witness changes fresh root actions from `[9,1,2]` to `[1,2]`: fresh alternative 1 selects node 2, while original alternative 1 still selects node 1. + +Twenty-five tiny TSPTW rows cover all five existing inputs: CADDS/cursor widths 1 and 4 emit every independently enumerated feasible tour exactly once with cuts disabled; native CABS optima/infeasibility agree. All 19 tiny publications pass the Fraction checker. Saved streams are rechecked whenever analysis runs. The first tiny row was collected alone, then resume collected only the other 24. A completed rerun left all raw/sample hashes unchanged and launched nothing. Reusing the name with the ASan binary was rejected before launch because its binary hash changed. + +The new frontier ownership was also checked with the standalone AddressSanitizer semantic witness, including a final rerun that emits each configuration's actual cost sequence. It passed without diagnostics. The executable was instrumented; the Release Gecode libraries were not, so this is not a full-library sanitizer result. Seven compilation attempts, including three failed enum-name builds, took 12.985523 seconds in total, separately from solver time. Linker deployment-target warnings were present; both final normal and ASan builds succeeded. + +The first sandboxed capture could not execute ps. Existing capture killed and reaped the owned child and charged 0.006755 seconds as interrupted. Its empty raw streams remain under `results/witness-v1`; the successful witness uses a separate name. Subsequent captures used sandbox escalation for the required RSS probe. No unresolved process remains. + +The fresh shared ledger is `/private/tmp/open-search-030-budget.jsonl`: task `open-search-030`, ceiling 300 seconds, independent verification reserve 120 seconds and cleanup reserve 20 seconds. Implementation has 60 solver launches charged from launch through confirmed exit: 2.730918 seconds correctness and 46.689475 seconds development, **49.420392 seconds total**. This is an implementation-only snapshot. The verifier appends its own charges; the final completion result owns the final shared total. Admission includes remaining nominal frozen work and both reserves, and any pending launch blocks later launches. Builds remain separate. + +The historical `/private/tmp/open-search-016-budget.jsonl` (7200-second campaign) and finished `/private/tmp/open-search-028-budget.jsonl` were read and hashed only; they were not appended to or reset. Task 029's immediately preceding independently verified full check, six focused native selections, inherited open-list/regular/Model/portfolio selections and native/inherited examples provide the unchanged-production baseline. Task 030 did not rerun those checks. Its attributed source delta contains only this standalone study and compact data artifact. After measurement, the witness output was corrected to emit the collected per-configuration cost sequences, and its depth comment was corrected. The scheduling, model, measurement and development-output code remained identical. The original measured source is retained at `/private/tmp/open-search-030-study/measured-study.cpp`; original binary/configuration hashes and raw records were preserved. Final witness evidence uses `study-final` and `study-final-asan`. Reusing an old collection name with the final source is correctly rejected as a changed source hash; offline analysis of its retained records remains available. The [completed TSPTW measurements](../../../examples/data/tsptw/cheap-deadline-comparison.json) and [measurement documentation](../../../examples/data/tsptw/README.md) retain the distinctions between incumbent feasibility, proof, native clocks, launch clocks and missing post-kill summaries; their earlier observations are not new task-030 passes. + +## Replay + +Run from the checkout root with the existing Release libraries. Source and the three inputs are durable; the old 014 temporary prototype is unnecessary. The collector imports the existing `misc/tsptw-benchmark.py` capture, task-budget and recovery functions unchanged. One writer owns each named run. Configuration, binary/source hashes, inputs, exact arguments, caps and schedule are saved before collection. A new name is required when material configuration changes; repeating a matching name skips terminal rows, including terminal failures. Interrupted attempts retain separate raw streams. `analyze` never launches a solver. + +```sh +mkdir -p /private/tmp/open-search-030-replay +clang++ -std=c++17 -O2 -I build/open-search -I . misc/tsptw-cadds-study.cpp -L build/open-search -Wl,-rpath,/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search -lgecodesearch -lgecodeint -lgecodekernel -lgecodesupport -o /private/tmp/open-search-030-replay/study +python3 misc/tsptw-cadds-study.py run --name replay-development --binary /private/tmp/open-search-030-replay/study --dry-run +python3 misc/tsptw-cadds-study.py run --name replay-witness --suite witness --phase correctness --binary /private/tmp/open-search-030-replay/study +python3 misc/tsptw-cadds-study.py run --name replay-tiny --suite tiny --phase correctness --binary /private/tmp/open-search-030-replay/study +python3 misc/tsptw-cadds-study.py run --name replay-development --suite development --phase development --binary /private/tmp/open-search-030-replay/study +python3 misc/tsptw-cadds-study.py analyze --name replay-development +``` + +These commands append to the authorized 030 ledger and refuse admission if its remaining allowance is insufficient. If the temporary ledger no longer exists, the runner creates this task's fixed header only with exclusive creation. Captures require a usable ps probe. Use `uv run --script` in place of `python3` if desired; the script has no external Python dependencies. + +The exact implementation runs used `/private/tmp/open-search-030-study/study`, names `witness-v2`, `tiny` and `development`, and the same run/suite/phase arguments above. The development capture was split with `--limit 15`, then resumed without a limit. Raw config, samples and streams are retained under `/private/tmp/open-search-030-study/results`. Offline reproduction of the committed comparison is: + +```sh +python3 misc/tsptw-cadds-study.py analyze --name development --output examples/data/tsptw/cadds-comparison.json +python3 misc/tsptw-cadds-study.py analyze --name tiny +python3 misc/tsptw-cadds-study.py analyze --name witness-final +``` + +The exact sanitizer build/run was: + +```sh +clang++ -std=c++17 -O1 -g -fsanitize=address -fno-omit-frame-pointer -I build/open-search -I . misc/tsptw-cadds-study.cpp -L build/open-search -Wl,-rpath,/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search -lgecodesearch -lgecodeint -lgecodekernel -lgecodesupport -o /private/tmp/open-search-030-study/study-final-asan +python3 misc/tsptw-cadds-study.py run --name witness-final-asan --suite witness --phase correctness --binary /private/tmp/open-search-030-study/study-final-asan +``` + +Independent verification can compile to its own binary, run the semantic witness and selected tiny/development rows with `--phase verification --only row000,row004` (row IDs come from that suite's dry-run), and recheck every saved publication offline. `recover --name NAME` delegates conservative absence confirmation to the existing recovery routine; it never kills an unidentified process or erases its budget charge. + +A separately reviewed production follow-up would need a clear ordinary-Space API and broader checked evidence that the heuristic scheduling benefits justify another engine. This task creates neither that API nor a follow-up task. The bounded result supports retaining this study as evidence and deferring adoption. diff --git a/.zdev/open-search/background/didp-and-tsptw.md b/.zdev/open-search/background/didp-and-tsptw.md new file mode 100644 index 0000000000..dd3c50918b --- /dev/null +++ b/.zdev/open-search/background/didp-and-tsptw.md @@ -0,0 +1,151 @@ +# DIDP search and a TSPTW starting model + +Read this before settling search semantics, state pruning, or evaluation. This +is a planning synthesis, not a new benchmark result. CPKB's complete concepts, +sources, and fulltext index was checked fresh on 2026-10-07. The principal topic +and source pages were reviewed in the KB on that date. Primary paper passages +and the didp-rs code ranges below were also read for this plan. + +## Search semantics that transfer to Gecode + +DIDP exposes a residual state, transitions, terminal cases, state constraints, +resource preferences, and admissible remaining-cost bounds. A* can order by a +bound on total solution cost. Resource dominance must also account for the cost +of reaching the state. A good ranking score alone cannot justify deleting a +candidate. These are mathematical model obligations, not properties inferred +from a variable or callback name. + +Kuroiwa and Beck's original formulation supplies the modeling starting point: +[ICAPS 2023 foundational paper](https://doi.org/10.1609/icaps.v33i1.27200). +Its original cost-algebraic A* returns an optimal first solution under its stated +assumptions. That guarantee should not be transferred automatically to arbitrary +CP ranking callbacks, custom objectives, or broader state models. + +The [2023 anytime paper](https://doi.org/10.1609/icaps.v33i1.27201), section +"Complete Anytime Beam Search" and Algorithm 2, restarts a layered beam with +widths 1, 2, 4, ... and retains the incumbent. It makes beam loss explicit. +A narrow run can miss an optimum or all solutions. Early return after an +improvement also leaves work to recover, even if no width truncation occurred. +This suggests explicit tracking of unsearched improving alternatives in the +engine, rather than deriving proof from frontier emptiness alone. + +The [2026 formal treatment](https://arxiv.org/abs/2401.13883v5), Algorithms 2–3 +and Theorems 16–20, establishes termination for finite, acyclic models under +its cost assumptions and separates a complete iteration from early solution +return. Its global dual bound includes the bounds of discarded beam states. +A bound over only surviving states can be too high. Increasing beam width alone +does not make an unbounded cyclic model terminate; cross-layer handling requires +additional assumptions. Use finite bounded examples for the first Gecode slice. + +For ordinary CP, queued nodes represent domains and propagator state, rather +than necessarily concrete DP states. One engine should support tree search +without requiring an equivalence key. Optional graph pruning requires a +sufficient residual-state contract. Do not use a hash value as state identity +without equality handling, or merge states merely because selected output +variables match. Whether inconsistent admissible estimates require reopening +must be settled alongside the precise graph-pruning contract. + +## Inspected implementation reference + +The KB registers didp-rs v0.11.1 at +`59a5330537962bcc2f93c3914cf5fd9ee361b23d`. The following exact ranges were read +from the local checkout at that commit: + +- [`Cabs::search_inner`, cabs.rs, 178–273](https://github.com/domain-independent-dp/didp-rs/blob/59a5330537962bcc2f93c3914cf5fd9ee361b23d/dypdl-heuristic-search/src/search_algorithm/cabs.rs#L178-L273): incumbent/bound updates, width growth, and separate width/time termination. +- [`beam_search`, beam_search.rs, 237–336](https://github.com/domain-independent-dp/didp-rs/blob/59a5330537962bcc2f93c3914cf5fd9ee361b23d/dypdl-heuristic-search/src/search_algorithm/beam_search.rs#L237-L336): tracks truncated/replaced beam nodes, their bounds, and proof separately. The registry is cleared between layers unless all-layer retention is selected. + +These are design references for mechanisms, not timings or proof that a Gecode +port would have identical behavior. The papers use separate historical versions. +RPID's programmable model interface is another useful API comparison; its +registered v0.4.0 source reading path is in the KB system page below. RPID code +was not independently reviewed during this setup. + +## TSPTW transition model + +Use a state `(U, i, t)`: unvisited customers, current location, and current +service/arrival time after permitted waiting. Keep prefix travel cost `g` +separate from `t`; waiting increases time but not the travel objective. + +A visit to `j in U` is applicable when `t + travel[i,j] <= due[j]`, with valid +input windows. It removes `j`, sets the location to `j`, advances time to +`max(t + travel[i,j], ready[j])`, and adds `travel[i,j]` to cost. At `U = {}`, +include the return-to-depot travel cost, either as a terminal cost or explicit +return transition. Decide depot-window and service-duration conventions from +the chosen data before implementing; keep the checker and all baselines equal. +The fundamental [2023 paper](https://doi.org/10.1609/icaps.v33i1.27200), +"Example: TSPTW", gives this residual formulation. + +For equal `(U,i)`, label `(t1,g1)` can dominate `(t2,g2)` when both +`t1 <= t2` and `g1 <= g2`, provided the formulation permits waiting and earlier +arrival cannot harm future choices. Earlier arrival reached at higher cost is +not sufficient: retain the Pareto-incomparable labels. Path output belongs to +reconstruction, while all history that affects future constraints belongs in +the state/equivalence contract. + +Start with a zero remaining-cost bound to check semantics. Then compare cheap +minimum incoming/outgoing travel bounds: every completion must enter every +remaining customer and the depot, and leave the current location and remaining +customers. Each sum is a lower bound when its candidate arcs include every +possible completion; their maximum can be stronger. Account for the root and +terminal cases explicitly. For reachability pruning on nonmetric data, use +shortest-path travel lower bounds; direct travel can overestimate reachability +when intermediate visits are possible. Do not assume symmetry or a triangle +inequality from the name TSPTW. + +The open formulation should grow a visit/action list lazily and keep the +residual state clone-local. The current Model open-list transition callback is +called when a position is materialized; this is not automatically one committed +visit. Determine where assigned visit effects occur and how they relate to +CABS layers. TSPTW has a finite visit count, so using open construction here +means avoiding eager decision/state allocation, not inventing an unbounded tour. +An automaton for every subset would defeat the intended state representation. + +## Propagation and measurement + +[Marijnissen et al., DIDP with constraint propagation](https://arxiv.org/abs/2603.16648v2) +adds residual CP inference inside model functions while retaining A* and CABS. +It tests failures, filters next actions, and combines DP/CP lower bounds by +maximum. Its TSPTW propagation model is a scheduling relaxation. It reports +benefits for tightly constrained cases and overhead on looser cases. The +publisher text uses one pass per state and reports that fixpoint propagation +was too costly for that setup; Gecode's ordinary `status()` fixpoint behavior +makes this a policy question to measure, not a setting to copy. + +The [RPID paper](https://doi.org/10.4230/LIPIcs.CP.2025.23) is a lead for +compiled state/transition functions and stronger native bounds. +The KB source page reports that a stronger 1-tree bound reduces expansions but +hurts TSPTW runtime/coverage in that experiment. Begin with inexpensive bounds, +then measure evaluation time and state storage before adding stronger ones. + +Compare engines on one formulation first. Compare open transition and closed +CP formulations separately. Include loose, tight, and infeasible windows, +several sizes, and at least one existing planning and one closed-model example. +Use tiny exhaustive references for correctness; use development and held-out +benchmark subsets for tuning. Report wall time, quality over time, proof status, +peak memory, expansions, and relevant propagation/cloning costs. Paper results +motivate the experiments; they do not establish a Gecode speedup. + +## CPKB reading paths + +Resolve these paths under `/Users/zayenz/lab/knowledge/cpkb`. Start with the +concept pages, then the relevant source and fulltext passages. Keep the KB +read-only for this work. + +- `wiki/topics/domain-independent-dynamic-programming.md` +- `wiki/topics/domain-independent-dynamic-programming/modelling-and-dominance.md` +- `wiki/topics/domain-independent-dynamic-programming/search-algorithms.md` +- `wiki/topics/domain-independent-dynamic-programming/results-and-comparisons.md` +- `wiki/topics/search/complete-anytime-beam-search.md` +- `wiki/systems/didp-rs.md` and `wiki/systems/rpid.md` +- `wiki/sources/2023-kuroiwa-domain-independent-dynamic-programming-generic-s.md` +- `wiki/sources/2023-kuroiwa-solving-domain-independent-dynamic-programming-p.md` +- `wiki/sources/2026-kuroiwa-domain-independent-dynamic-programming.md` +- `wiki/sources/2025-kuroiwa-rpid-rust-programmable-interface-for-domain-inde.md` +- `wiki/sources/2026-marijnissen-domain-independent-dynamic-programming-with-cons.md` + +Canonical bibliographic fields and original extracted passages live at +`sources/derived/papers//metadata.yaml` and `fulltext.md`. +The didp-rs YAML TSPTW example is a useful formulation comparison at +`sources/code/didp-rs/didp-yaml/examples/tsptw/tsptw-domain.yaml`; it has an +explicit return action. Check its travel-matrix assumptions before reusing its +reachability condition. diff --git a/.zdev/open-search/background/integration.md b/.zdev/open-search/background/integration.md new file mode 100644 index 0000000000..f8bd084d10 --- /dev/null +++ b/.zdev/open-search/background/integration.md @@ -0,0 +1,214 @@ +# Combined base and integration points + +Read before implementing heuristic search or refreshing the parent branch. +The first combined base is `0a07191db190745af9a4acdbf0457cd9f2d6db65`. + +## Starting branches + +- `feature/portfolio`: `ed2f9d0e2562dcf47c07176151e11ccd82947d34`. +- `feature/open-model`: `e105fa6efb690a8b04febdd12fa23400de0707fc`. +- Shared `feature/model` base: `6028fb38eedf7e5467cec1e78ebc02f4d8b424c7`. + +Both inputs use the current named-pass Model layer. Open-model's visible Model +addition is open-list support and four planning examples; it does not import +the older A* helper. Portfolio also includes non-failing and other search work; +those inherited features are part of the starting tree, not separate work for +this area. + +The integration was resolved as a merge, built and checked, then recorded as a +single commit on portfolio because zdev rejects merge history for task work. +The recorded tree is identical to the validated merge tree. The input branches +were left in place. Zdev uses `portfolio` as the area's single parent. + +## Resolutions + +The ModelSpace clone constructor retains both portfolio restart/FDS fields and +open integer/Boolean list updates. Its header retains the soft-model declarations, +open-list declarations/accessors, and portfolio `slave()` override. The regular +test file retains the non-failing runtime-retirement test and the open/NFA test +classes and registrations. Both changelog additions remain. + +CI and `vcpkg.json` retain portfolio's matching vcpkg baseline +`45f9f39362a4c52e2b1fbe57b7e649db7f3d96d4`. Other files merged automatically. +No heuristic search engine has been introduced during setup. + +## Sources to inspect when designing the interface + +- `gecode/search.hh`, `gecode/search/engine.hpp`, `gecode/search/seq/`: native + engine/builders, options, stops, statistics, ownership, and expansion. +- `gecode/search/portfolio.hpp`: `SpacePortfolioSearchFactory`, complete recipes, + objective capabilities, asset lifecycle, and status. Natural exhaustion is a + coverage claim: a beam that dropped alternatives must not masquerade as an + exhausted complete asset. Inspect these contracts before adding an engine. +- `gecode/model/runtime.hh` and `.cpp`: retained variables, root/clone creation, + custom objective contexts, comparison, and improvement restrictions. Decide + guidance dependencies and remapping here before adding a public callback API. +- `gecode/model/driver.hh`, `gecode/model/portfolio.cpp`: public model journeys + and driver dispatch. Add only the route needed by the vertical slice. +- `gecode/model/open-list.hh` and `.cpp`, `gecode/int/open-var-list.hpp` and + `.cpp`, `gecode/int.hh`: open-list factories, transitions, native resolution, + horizon/value branching, length and prefix lifetime. +- `models/open-miconic.cpp`, `models/open-puzzle.cpp`, `models/open-valves.cpp`, + `models/open-maintenance.cpp`: inherited planning examples and open Model use. +- `test/model/open-list.cpp`, `test/int/open-var-list.cpp`, `test/search.cpp`, + `test/model/runtime.cpp`: reuse these test seams rather than adding a harness. + +Factories/transitions must remain deterministic and must not capture space-local +state. A materialized list position is not necessarily an assigned transition. +Check this distinction when representing residual state or defining beam layers. +Model handles can be remapped or removed by compilation; declare every guidance +or output dependency through the appropriate retention mechanism. + +## Older implementation reference + +`feature/open-cp-model:models/astar.hh` contains a sequential template over a +Space with `cost()`, `estimate()`, and `key()`. It queues propagated clones, +saves their choices, skips stale entries, checks stops, and deletes its frontier. +Its concrete unsigned cost and 64-bit key types, additive comparison, and +statistics conventions are prototype choices to reconsider for the new contract. +Do not import the older Model implementation to obtain this helper. + +`feature/open-cp-model:models/open-puzzle.cpp` has explicit residual puzzle state, +a custom move brancher, open-plan variants, and a native A* comparison. Consult +it if the current automaton example leaves transition-state design unclear. +The current `models/open-puzzle.cpp` is a smaller open Model example using the +current layer; the older file is not its current interface specification. + +## Completed setup validation + +On 2026-10-07 the combined code passed a Release build with AppleClang 21, +CMake 3.27.6, and Ninja 1.11.1. The configuration enables the normal Model, +search, integer, set, float, FlatZinc, and thread modules; MPFR and GUI support +were disabled for this local check. This does not establish Windows, MPFR, or +sanitizer validation. + +```sh +cmake -S . -B build/open-search -G Ninja \ + -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON \ + -DGECODE_ENABLE_QT=OFF -DGECODE_ENABLE_GIST=OFF -DGECODE_ENABLE_MPFR=OFF +cmake --build build/open-search --target gecode-test \ + model-open-maintenance model-open-miconic model-open-valves model-open-puzzle -j 8 +cmake --build build/open-search --target check -j 8 +build/open-search/bin/gecode-test -iter 1 -threads 0 \ + -test Model::OpenLists -test Int::Extensional::Reg \ + -test Model::Portfolio::Restrictions -test Model::Portfolio::Sequential \ + -test Model::Portfolio::ActivationRoots -test Search::Portfolio::PublicContract +build/open-search/bin/gecode-test -iter 1 -threads 0 -test Int::OpenList +build/open-search/bin/model-open-maintenance +build/open-search/bin/model-open-miconic +build/open-search/bin/model-open-valves +build/open-search/bin/model-open-puzzle +``` + +The native tests are named `Int::OpenList`, despite the source filename +`open-var-list.cpp`. The existing `check` selection includes Model open-list +lifecycle and sparse NFA checks. All four default example runs found a solution. +The integration added no new tests. For later code changes, choose focused checks +from the actual touched contracts and follow the testing policy in `brief.md`. + +## Native CABS validation and fixed comparison + +On 2026-10-07, the native CABS implementation passed the following commands in +the existing Release build. The focused search selection is separate from the +repository check target. All four inherited open examples also passed their +default runs listed above. + +```sh +cmake --build build/open-search --target gecode-test astar \ + model-open-maintenance model-open-miconic model-open-valves model-open-puzzle -j 8 +build/open-search/bin/gecode-test -iter 1 -threads 0 \ + -test Search::CABS -test Search::AStar \ + -test Search::Completion::Native -test Search::WorkerControl::Pause +cmake --build build/open-search --target check -j 8 +build/open-search/bin/gecode-test -iter 1 -threads 0 \ + -test Int::OpenList -test Model::OpenLists -test Int::Extensional::Reg \ + -test Model::Portfolio::Restrictions -test Model::Portfolio::Sequential \ + -test Model::Portfolio::ActivationRoots -test Search::Portfolio::PublicContract +build/open-search/bin/astar +otool -L build/open-search/bin/astar +``` + +The same four focused search selections and native example passed in the +existing Debug AddressSanitizer build, with no sanitizer diagnostics. That +configuration disables Model, set, float, and FlatZinc. + +```sh +cmake --build build/open-search-asan --target gecode-test astar -j 8 +build/open-search-asan/bin/gecode-test -iter 1 -threads 0 \ + -test Search::CABS -test Search::AStar \ + -test Search::Completion::Native -test Search::WorkerControl::Pause +build/open-search-asan/bin/astar +``` + +The native example uses one bounded ordinary Space formulation and one thread, +with no ordinary time/node stop. Zero width/frontier limits mean unlimited. +The following is one Release run; elapsed times are illustrative and establish +neither speedups nor suitable defaults. The binary links search and integer +libraries plus their kernel/support dependencies, without Model or driver. + +| Engine | Maximum width | Frontier quota | Objective | Result | Milliseconds | Nodes | Restarts | +| --- | ---: | ---: | ---: | --- | ---: | ---: | ---: | +| A* | 0 | 0 | -7 | proven | 0.133 | 7 | 0 | +| BAB | 0 | 0 | -7 | proven | 0.009 | 7 | 0 | +| CABS | 1 | 0 | -2 | stopped | 0.005 | 7 | 0 | +| CABS | 2 | 0 | -7 | proven | 0.006 | 14 | 1 | +| CABS | 4 | 0 | -7 | proven | 0.006 | 14 | 1 | +| CABS | 0 | 0 | -7 | proven | 0.005 | 14 | 1 | +| CABS | 0 | 1 | 4 | stopped | 0.001 | 3 | 0 | +| CABS | 0 | 4 | -7 | proven | 0.005 | 14 | 1 | + +A* published 4 then -7; BAB and complete CABS runs published 4, -2, then -7. +Width one loses the optimum to the deliberately misleading rank. Widening +retains the -2 incumbent, whose valid cuts reduce the next pass enough for +width two to recover and prove -7. The frontier quota is the retained pending +node count described in `docs/cabs.md`, not a process-memory limit. + +## Configurable best-first ordering (task 029) + +The existing AStar worker supports fixed bound-first or rank-first ordering. +Both use admissible bounds for pruning. On the standalone additive example, +greedy `h` publishes 10 then 5; fixed rank `g+2*h` publishes 5. Both exhaust +and prove optimum 5. Native linkage requires search, integer, kernel and support, +with no Model or driver dependency. + +The following commands passed in the macOS Release configuration. The baseline +AStar selection also passed before the change. Ninja exposes the concrete +`gecodesearch_shared` target; `gecodesearch` is an INTERFACE target. + +```sh +cmake -S . -B build/open-search -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DGECODE_ENABLE_QT=OFF -DGECODE_ENABLE_GIST=OFF -DGECODE_ENABLE_MPFR=OFF +cmake --build build/open-search --target gecodesearch_shared gecode-test astar best-first model-open-maintenance model-open-miconic model-open-valves model-open-puzzle -j 8 +build/open-search/bin/gecode-test -iter 1 -threads 0 -test Search::AStar -test Search::CABS -test Search::Column -test Search::Completion::Native -test Search::WorkerControl::Pause -test Search::Portfolio::PublicContract +cmake --build build/open-search --target check -j 8 +build/open-search/bin/gecode-test -iter 1 -threads 0 -test Int::OpenList -test Model::OpenLists -test Int::Extensional::Reg -test Model::Portfolio::Restrictions -test Model::Portfolio::Sequential -test Model::Portfolio::ActivationRoots -test Search::Portfolio::PublicContract +build/open-search/bin/best-first +build/open-search/bin/astar +build/open-search/bin/model-open-maintenance +build/open-search/bin/model-open-miconic +build/open-search/bin/model-open-valves +build/open-search/bin/model-open-puzzle +otool -L build/open-search/bin/best-first +git diff --check +``` + +The ordering cases use nonterminal root alternatives, both objective senses, +missing-rank fallback and optional sound descriptor pruning. Existing lifecycle +and portfolio checks also passed. No new frontier ownership was introduced, +so the plan's conditional sanitizer run did not apply. Autotools, Windows and +sanitizer builds were not run for this task. No performance study was launched. + +## Sequential CADDS study (task 030) + +Only standalone study source and data were added; production libraries and +tests remain as verified in task 029. Its full check, native/open-feature +selections and examples provide the unchanged-production baseline, and were +not rerun for task 030. The [study report](cadds-retained-fragments.md) records +exact build/capture/analyze commands and the shared 300-second solver budget. + +Normal and standalone ASan semantic witnesses passed, including original +Choice continuation, full-fragment scheduling and stop/resume. The ASan +executable linked uninstrumented Release Gecode libraries. All 25 tiny rows +matched independently checked coverage or optima, and all 30 frozen development +rows completed capture with checked publications. The source and replay commands +are retained under `misc`; no production CADDS engine was added. The bounded +quality/memory comparison supports deferring adoption. diff --git a/.zdev/open-search/background/next-search-and-benchmarks.md b/.zdev/open-search/background/next-search-and-benchmarks.md new file mode 100644 index 0000000000..1e92b41460 --- /dev/null +++ b/.zdev/open-search/background/next-search-and-benchmarks.md @@ -0,0 +1,179 @@ +# Search extensions and the next TSPTW study + +Read before planning additional engines, state pruning, or TSPTW measurements. +This is a design recommendation, not a Gecode performance result. On +2026-10-07 CPKB's concepts, sources, and fulltext index was fresh. Its DIDP +algorithm, modelling/dominance, CABS, didp-rs, and RPID pages informed this +comparison; original implementation and paper passages were checked below. + +## Which engines to add + +[Kuroiwa and Beck's anytime study](https://doi.org/10.1609/icaps.v33i1.27201) +compares DFBnB, CBFS, ACPS, APPS, DBDFS, and CABS. Their different storage and +expansion policies are useful choices, rather than six mandatory ports. + +| Candidate | Fit for Gecode | Recommendation for this bundle | +| --- | --- | --- | +| Guided DFBnB | BAB already supplies exact depth-first optimization and recomputation. Model-specific value ordering can provide a useful baseline. DIDP DFBnB additionally retains a dominance registry, so its memory claim differs. | Use BAB with deliberate branching; defer a separate bound-ordered successor engine until it adds measured value. | +| CBFS / ACPS | Depth queues retain deferred alternatives. A progressive quota changes the balance between diving and revisiting shallow choices, without beam restarts. Cloned Spaces make the retained frontier expensive. | Implement one column-search engine: quota one with no growth is CBFS; the default grows quota by one each cycle. Keep the admissible bound separate from ranking. | +| APPS | Packs and suspend lists offer another anytime order, with much of the same retained-Space cost as column search. | Defer until column-search results reveal a distinct unmet need. | +| DBDFS / discrepancy search | Gecode already has LDS and brancher preferences. A discrepancy band over complete transition alternatives is not automatically the same as its binary decision discrepancy. | Use existing LDS only where its semantics fit; defer a new engine. | +| Weighted A* | Current A* uses rank only for equal bounds; this cannot implement weighted expansion. A total CP bound does not expose a unique prefix/remaining decomposition. | Defer. Any later weighted/greedy expansion must use a separate rank and retain admissible pruning; it must specify whether it returns bounded-suboptimal or eventually exact results. | +| BSS, BULB, BUDDS | Fixed-width stacks avoid CABS's widening frontier, at the cost of regeneration. This matters when every node is a Space. | Investigate BSS first; do not implement all three before demonstrating sound regeneration and useful storage savings. | +| LNBS | Path-segment repair is particularly relevant to TSPTW and existing portfolio LNS recipes. | Investigate a small suffix-compatible repair journey; defer a new universal LNBS engine or bandit policy. | +| Parallel, GPU, learned ranking, relaxed DD search | Require additional concurrency, representation, inference, or relaxation contracts. | Outside this bundle. | + +The [DIDPPy solver-selection guide](https://didppy.readthedocs.io/en/stable/solver-selection.html) +recommends CABS generally and identifies ACPS/APPS and LNBS as useful alternatives +with different proof, quality, and memory tradeoffs. This is guidance for its +models, not evidence of a Gecode speedup. Its +[ACPS API](https://didppy.readthedocs.io/en/stable/_autosummary/didppy.ACPS.html) +exposes initial quota, growth, maximum quota, and reset. We need only initial +quota and checked growth, with a retained-frontier resource cap; do not copy +the entire options surface. A finite quota never discards pending alternatives. + +The [CP 2026 linear-memory paper](https://doi.org/10.4230/LIPIcs.CP.2026.12) +uses a border state and total bound/lexicographic order to regenerate omitted +successors (local fulltext lines 244–280). Its O(Bd) bound counts stored nodes, +with fixed width B and depth d, not bytes. Generic CP domains, weakly monotonic +propagation, new incumbent cuts, and changed choice enumerations need their own +coverage argument. A retained original Choice cursor and stable source clone +may offer a Gecode alternative to the paper's concrete-state border. The study +must include ties, n-ary alternatives, variable-depth goals, and resumption. + +## LNBS repair study + +[Large Neighborhood Beam Search](https://doi.org/10.4230/LIPIcs.CP.2023.23) +retains a prefix and suffix while repairing the intervening path. Successful +suffix rollout requires executable suffix transitions and a valid complete +solution, rather than the original boundary resource values. A changed arrival +time or prefix cost can therefore be allowed when the suffix remains feasible. +The complete adaptive algorithm also searches the full path eventually; a +single local repair does not supply that global proof. + +Read CPKB's +`wiki/sources/2023-kuroiwa-large-neighborhood-beam-search-for-domain-indepe.md` +and its original +`sources/derived/papers/2023-kuroiwa-large-neighborhood-beam-search-for-domain-indepe/fulltext.md`, +especially suffix rollout, before the study. Start from +`SpacePortfolioLnsRecipe` in `gecode/search/portfolio.hpp` for the existing +neighborhood construction and inner-search lifecycle. The proposed study uses +one fixed segment rule and checks the whole repaired tour; adaptive neighborhood +selection and production LNBS integration require a later decision. + +## Guidance, equality, dominance, and merging + +Keep `Search::HeuristicGuidance` as the read-only total-bound/rank contract. +For a closed CP model use its propagated objective minimum/maximum. For an +additive transition model explicitly calculate g+h; do not add g to a bound +that already includes it. Cache expensive estimates once per admitted stable +node where useful, without imposing state functions on all models. + +Optional state pruning has a separate declaration. Extract a small owned +descriptor from the stable Space: an exactly comparable grouping key and the +labels needed for a cost-aware relation. Hashing only selects a bucket; key +equality must resolve collisions. The relation distinguishes equivalence, +dominance in either direction, and incomparability. Missing declaration means +ordinary tree search, with no registry or descriptor allocation. The model +author is responsible for sufficient state and a sound relation; an integer +resource name or a common output prefix proves neither. + +For TSPTW group by unvisited set U and current vertex i. Retain Pareto labels +(t,g): t1<=t2 AND g1<=g2 can justify pruning label 2 when waiting is allowed. +Earlier time at greater cost remains incomparable. The path is retained for +output, but history affecting future constraints must also enter the descriptor. +An ordinary CP model with partially assigned successors does not acquire this +state key merely because it is also a TSPTW formulation. + +The registry owns descriptors, not extra Space clones. A* and column search +must admit a new dominating representative before invalidating old pending +work, and reopen when a better label is reached after expansion; admissible +inconsistent bounds remain legal. Initially CABS compares only live next-layer +candidates, with registry entries removed when their representative leaves +that candidate beam and cleared between layers and passes. A representative +discarded by a beam cannot justify deleting the surviving route to the same +residual problem. Record beam loss independently of sound label pruning. +Keep one representative for equivalent labels; never delete both. Forgetting +registry information may cause repeated work but cannot discard search work. + +This is exact representative retention and Pareto pruning. It does not merge +two CP stores into a relaxed state. Relaxed decision-diagram merging requires +a separate relaxation operator and bound argument and is deferred. Likewise, +[transition dominance](https://doi.org/10.4230/LIPIcs.CP.2025.5) concerns choices +at one state, including immediate cost, rather than registry labels. Keep any +TSPTW-specific valid rule in its transition brancher; do not add a generic +SCC/rule language in this increment. + +Model may declare descriptor dependencies using its existing guidance retention +and read-context conventions. It resolves them in each evaluated clone and +exports the same native callbacks; no Model prerequisite, Space virtual +method, general analyzer, or portfolio registry is needed. Existing objective +and functional-dependency metadata can support explicit branching suggestions, +but cannot automatically infer an admissible estimate or a sufficient state key. + +## Inspected implementation references + +The registered didp-rs checkout is v0.11.1, +`59a5330537962bcc2f93c3914cf5fd9ee361b23d`. This research read: + +- [`StateRegistry::remove_dominated`, lines 171–235](https://github.com/domain-independent-dp/didp-rs/blob/59a5330537962bcc2f93c3914cf5fd9ee361b23d/dypdl-heuristic-search/src/search_algorithm/data_structure/state_registry.rs#L171-L235): residual preference and prefix cost are both compared. +- [`Acps::search_next`, lines 270–420](https://github.com/domain-independent-dp/didp-rs/blob/59a5330537962bcc2f93c3914cf5fd9ee361b23d/dypdl-heuristic-search/src/search_algorithm/acps.rs#L270-L420): depth queues, retained successors, resumable solution return, and progressive cycles. +- [`beam_search`, lines 136–230 and 310–332](https://github.com/domain-independent-dp/didp-rs/blob/59a5330537962bcc2f93c3914cf5fd9ee361b23d/dypdl-heuristic-search/src/search_algorithm/beam_search.rs#L136-L230): layer registry handling and loss/proof distinction. + +RPID v0.4.0's +[`Dominance` interface, lines 205–233](https://github.com/domain-independent-dp/rpid/blob/81e14e055cc231705fe988f58c52b55386e424f9/src/dp.rs#L205-L233) +separates a hashable key from a partial comparison. Its default equal-key +equivalence is an assumption we should make explicit rather than silently +apply to resource labels. + +## TSPTW data and comparison + +Use the [López-Ibáñez/Blum canonical matrices](https://lopez-ibanez.eu/tsptw-instances), +starting with Dumas, GendreauDumasExtended, SolomonPotvinBengio, and AFG. Preserve +matrix decimals by exact integer scaling. Do not regenerate distances from +coordinates or add service twice when it is already included in an arc. +Minimize the sum of the supplied arc values, including return; waiting affects +feasibility but not this objective. Use fixed departure at time zero, waiting +to customer ready times, and the depot window on return, matching the collection's +checker. Nonzero start-depot ready times are rejected in this initial adapter. +The publisher's `check_solution.cpp` lines 256–287 were read to establish these +conventions. Nonnegative asymmetric and nonmetric matrices remain supported. +Checked scaling and Gecode integer limits are input constraints, not silent +rounding or saturation. + +Keep a small checked-in correctness set and a deterministic acquisition manifest +for a larger development/held-out selection. Independently check published +tours and distinguish a best-known cost from a proven optimum. Include tight, +loose, and infeasible cases. Tiny nonmetric, service-folded, waiting, return-window, +and decimal cases expose semantic errors; a few deterministic window variants +extend workload diversity and are reported separately from canonical data. + +[Soulignac's 2026 benchmark analysis](https://arxiv.org/abs/2512.01064v2) +warns that many large classical instances are easy for makespan/duration +objectives and identifies harder loose-window cases. Its conclusion does not +prove the same result for travel-cost optimization. It does justify avoiding +size alone as a difficulty measure and keeping objective conventions explicit. +Do not compare travel-cost runs with makespan best-known values. + +The closed CP baseline starts from `examples/tsp.cpp`'s costed circuit, adds +customer times and separate start/return depot handling, and uses strong +existing propagation and deliberate branching. Its reversal symmetry break is +not valid for general asymmetric/windowed tours. BAB, then a bounded comparison +of propagation/branching and existing LNS facilities, supplies a serious Gecode +baseline. Do not hold it artificially to the transition model's brancher. + +The transition Space uses finite clone-local (U,i,t,g), a lazily grown native +open visit list, and a problem-specific n-ary brancher. A commit means one +assigned visit, with an explicit final return. Materialization is not a visit. +Use the existing prepare protocol when adding variables before a choice. An +eager path-variable control with the same transition decisions isolates list +storage; the conventional CP model is a separate formulation comparison. + +Measure native Gecode against a pinned DIDPPy transition formulation using the +same exact inputs and checker. Start with cheap incoming/outgoing bounds; +stronger scheduling relaxations must repay their propagation/clone costs. +Record elapsed time including startup/preprocessing, quality over time, proof +and stop status, peak process RSS, frontier/descriptor counts, and sampled +clone footprint. Space count is not DP-state byte cost. Use a small named, +resumable runner following existing `misc/` benchmark conventions, without +importing the unrelated FlatZinc collection pipeline wholesale. diff --git a/.zdev/open-search/background/suffix-compatible-repair.md b/.zdev/open-search/background/suffix-compatible-repair.md new file mode 100644 index 0000000000..9db36172e2 --- /dev/null +++ b/.zdev/open-search/background/suffix-compatible-repair.md @@ -0,0 +1,68 @@ +# Suffix-compatible TSPTW repair + +The existing transition model and LNS recipe can repair a fixed TSPTW segment while retaining its prefix and suffix. The repaired boundary can have different location, time and cost. Both native BAB and widening CABS found the checked neighborhood optimum on the tiny cases. The one-attempt recipe publishes its inner engine's first candidate; draining that engine can find a better repair. The two selected development neighborhoods produced no improvement. These observations support feasibility, with no production LNBS integration or performance claim. + +Kuroiwa and Beck's [LNBS paper](https://doi.org/10.4230/LIPIcs.CP.2023.23), section 3.1 and Algorithm 2, checks the retained suffix from each repaired state through transition preconditions, state constraints and a base case. It does not require the original endpoint state. Algorithm 2 can return an improving rollout before completing its neighborhood. Algorithm 3's accompanying prose requires the original full search space for global proof. I read the original [derived full text](/Users/zayenz/lab/knowledge/cpkb/sources/derived/papers/2023-kuroiwa-large-neighborhood-beam-search-for-domain-indepe/fulltext.md), lines 320–438; CPKB reported a fresh, complete generation. This study uses a fixed-length permutation neighborhood, while the paper permits variable-length repairs. + +The temporary prototype keeps the first customer, frees the next three positions, and fixes every later customer and the depot return. It uses unchanged [TSPTW::Transition](../../../examples/tsptw-transition.hh) with OFF residual inference. Native repair roots use EAGER positions. `SpacePortfolioAdapter::neighbourhood_root` creates a fresh root, posts strict improvement against the checked incumbent, and fixes the complement groups. BAB and CABS then search that root; CABS uses PREFIX guidance throughout. + +Explicit replay calls `status`, `prepare`, `status`, obtains current actions and a fresh choice, commits the requested action, then checks status. It replays the prefix and segment, clones the stable boundary, and applies the complete suffix including return. It never retains a choice across a changed Space. EAGER and LAZY replay agree with independent exact checking. + +The integer fixture has incumbent `1 2 3 4 5 6`, prefix customer `1`, and suffix `5 6 0`. Prefix travel costs 2; arrival at customer 1 is 2, followed by waiting until time 10. All six segment permutations were independently enumerated with `misc/tsptw-check.py` and exact `Fraction` arithmetic. The suffix cost includes its changed connecting arc and depot return. Infeasible rows show arithmetic totals, not feasible objectives. + +| Segment | Boundary (location, time, prefix cost) | Segment cost | Suffix cost | Total | Suffix result | +|---|---|---:|---:|---:|---| +| `2 3 4` | `(4, 34, 26)` | 24 | 10 | 36 | feasible | +| `2 4 3` | `(3, 20, 12)` | 10 | 5 | 17 | feasible | +| `3 2 4` | `(4, 13, 5)` | 3 | 10 | 15 | feasible | +| `3 4 2` | `(2, 21, 13)` | 11 | 54 | 67 | late customer 5: 71 > 45 | +| `4 2 3` | `(3, 22, 14)` | 12 | 5 | 19 | feasible | +| `4 3 2` | `(2, 14, 6)` | 4 | 54 | 60 | late customer 5: 64 > 45 | + +Cost 15 is the neighborhood optimum; its return time is 23, versus 44 for the incumbent. Costs 17 and 19 also demonstrate a valid changed boundary location. The two cheaper segments ending at customer 2 leave customer 6 executable, so replay reaches a stable boundary before rejecting retained customer 5. A decimal fixture divides every cost and window by ten; it reproduces the same decisions with exact objectives 3.6 → 1.5. Every emitted full tour passed the independent checker. Drained BAB publishes 17 then 15; drained CABS publishes 15 and continues to natural exhaustion. CABS with maximum width 1 also returns 15, but reports stopped and supplies no completion proof. Both engines resume after an ordinary node stop. Local exhaustion proves only the fixed neighborhood. + +The actual `SpacePortfolioLnsRecipe` uses one finite attempt, fixed complement groups, repeatable BAB/CABS factories, and the existing `RandomLnsThenComplete` worker policy with target 1. Its complete asset (identity 100) receives a fresh unrestricted original-model root, with no initial incumbent or neighborhood restrictions. Its LNS asset has identity 200. The [recipe implementation](../../../gecode/search/portfolio.cpp), lines 2543–2640, calls inner `next()` once and destroys the transient engine. The factory runs twice: validation and execution. + +| Repair engine/probe | Publications `(asset: cost)` | Outcome | +|---|---|---| +| BAB | `100:36`, `200:17`, `100:15` | unrestricted complete asset closes globally | +| CABS | `100:36`, `200:15` | unrestricted complete asset closes globally | +| BAB local failure limit 1 | `100:36`, `100:17`, `100:15` | local repair stops; complete asset still closes | +| Cancellation after first publication | `100:36` | incomplete; no exhaustion | + +Independent enumeration of all 720 full tours confirms global optimum 15 on this fixture. That separate check corroborates the complete asset's result; the LNS asset has no coverage authority. The facade requires a complete recipe. Clone-mode local roots, a transferred CABS root, and transferred recipe roots all finished with zero tracked Spaces alive. Each local engine received an external incumbent that was then destroyed. Each caller-owned portfolio publication was destroyed before checking that the portfolio still retained the same incumbent; later publications strictly improved it. + +The final capture records the following single-run development observations. Both seeds were independently checked. The closed comparison uses exactly the frozen `closed-size-bab` arguments from `examples/data/tsptw/cp-baseline-tuning.json`: `--formulation closed --engine bab --circuit-propagation dom --cost-branching size`, with the same seed. No LNS default was selected or retuned. + +| Input / engine | Best | Search ms | Nodes | Peak tracked Spaces | Process peak RSS bytes | +|---|---:|---:|---:|---:|---:| +| Dumas `n20w20.001`, repair BAB | 378 | 3.205 | 6147 | 18 | 2441216 | +| Dumas, repair CABS | 378 | 10.742 | 22244 | 902 | 5275648 | +| Dumas, closed BAB | 378 | 0.245 | 7 | unavailable | unavailable | +| AFG `rbg010a`, repair BAB | 671 | 0.433 | 986 | 9 | 2310144 | +| AFG, repair CABS | 671 | 1.491 | 3341 | 155 | 2850816 | +| AFG, closed BAB | 671 | 0.224 | 17 | unavailable | unavailable | + +Repair searches exhausted their restricted neighborhoods with no publication; closed BAB proved global optimality. Their search scopes and propagation differ. Dumas repair incumbent replay took 0.042–0.043 ms, root creation 0.0044–0.0045 ms, complement fixing 0.0026–0.0053 ms, and total neighborhood setup 0.025–0.028 ms. AFG took 0.029–0.030, 0.0027–0.0034, 0.0025–0.0026, and 0.017–0.019 ms respectively. Closed preprocessing took 3.306 ms and 0.387 ms. Tiny LAZY replay measured root creation 0.0007–0.014 ms, prefix replay 0.0008–0.0045 ms, segment replay 0.0015–0.0034 ms, and suffix clone plus rollout 0.0026–0.016 ms. Tiny local BAB's first setup was 0.587 ms; subsequent setup observations were about 0.013–0.015 ms. These timings include instrumentation and initialization and are not repeated performance estimates. + +Completed position paths retain `n` values including return (7, 21 and 11 here); the customer tour vector has `n-1` values. EAGER allocates all 7 positions at the tiny boundary; LAZY allocates 4 there and all 7 after successful completion. Space counts observe only the temporary `Tracked` subclass. macOS `getrusage` RSS includes process/runtime/library storage; it does not isolate paths or prove a general memory advantage. Launch-through-reap captures also include startup and waiting: final normal samples took 0.0066–0.312 s, distinct from native search timings. + +Concrete gaps remain. `Transition::actions()` enumerates applicable visits without consulting fixed EAGER position domains; `commit()` rejects conflicting fixed values before changing time/cost. Thus even this three-position neighborhood can enumerate many doomed alternatives. Fresh LAZY roots expose no public future-position materialization seam, so the fixed-group adapter cannot post the complement there. Separately, the recipe discards inner continuation after its first publication, so a BAB repair can publish 17 while its drained reference reaches 15. The checked development cases show no quality gain, so this study recommends no production follow-up. A future request for LAZY repair or retained inner continuation should receive its own bounded review. + +All prototype files and raw captures are in `/tmp/open-search-015-study`; authoritative final captures are `results/final`, with saved full tours under `results/final/tours`. `runner-contract-checks.json` confirms an unchanged-name resume performed no launches or capture changes, while changing the cap to 3 was rejected. Partial named runs were resumed during collection. Saved analysis rechecks every tour and all six neighborhood alternatives without launching a solver. + +`launch-ledger.jsonl` was created before the first launch and includes all 51 launches: 4 pilot, 23 study and 24 final, including both runs' deliberate timeout and invalid-mode failure. Implementation launch-through-reap totals **1.2248288770206273 s** (pilot 0.41784162499243394, study 0.24608321001869626, final 0.5609040420094971). The maximum launch took 0.37867308300337754 s; none remain pending. Normal caps are 4 s with 0.5 s reaping reserve; deliberate timeout caps are 0.001 s. The runner enforces implementation ≤300 s, verification ≤300 s and combined ≤600 s. At implementation handoff, verification usage was 0 s, leaving its full 300 s reserve. Compilation is separate: three attempts total **4.9647195829602425 s**. The first duplicate-header include failed; two subsequent builds succeeded. `compile-first-attempt.json` and `compile-history.jsonl` retain commands, diagnostics and timings. The existing libraries emitted macOS deployment-target warnings; the current machine ran the resulting binary successfully. + +For independent replay, use the existing fixture files and a separate verification capture name. Keep one runner writer. These commands neither edit the repository nor reset the ledger: + +```sh +cd /Users/zayenz/.codex/worktrees/2d06/gecode +export UV_CACHE_DIR=/tmp/open-search-015-study/uv-cache +uv run --script /tmp/open-search-015-study/run-study.py build +uv run --script /tmp/open-search-015-study/run-study.py run --name verification-015 --role verification --cap 4 --max-samples 8 +uv run --script /tmp/open-search-015-study/run-study.py run --name verification-015 --role verification --cap 4 +uv run --script /tmp/open-search-015-study/run-study.py analyze --name verification-015 +python3 misc/tsptw-check.py check /tmp/open-search-015-study/fixtures/integer.txt /tmp/open-search-015-study/results/final/tours/integer-replay-lazy-2.tour --cost 15 +``` + +The same `run` invocation resumes unfinished samples and skips terminal captures. Use a new name if commands, caps, inputs, tours or binary contents change. `results/*/config.json` records the exact commands and content digests. `results/final/analysis.json` reports 24 completed expected outcomes with no failures or unfinished samples. diff --git a/.zdev/open-search/brief.md b/.zdev/open-search/brief.md new file mode 100644 index 0000000000..24aa424b54 --- /dev/null +++ b/.zdev/open-search/brief.md @@ -0,0 +1,439 @@ +## Approved branch split (2026-10-13) + +This section supersedes the earlier combined-branch topology and validation scope. +The user approved reconstructing `feature/open-search` on `feature/open-cp`, +and rebuilding `feature/open-model` on `feature/portfolio` with the native +search changes integrated there. Preserve native heuristic behavior and the +existing Model/portfolio integration. Keep only generic native dependencies +needed by the search engines; no Model or portfolio implementation belongs in +this branch. Ranking, sound bounds and optional state pruning stay separate. + +Backups are `backup/open-search-before-split-20261013` at +`83f9574c3e4d3f097532c81040a09d76135b5510` and +`backup/open-model-before-split-20261013` at +`e105fa6efb690a8b04febdd12fa23400de0707fc`. The native base is +`55dc729c80ed5d60a742177612c870bdf366c532`; the integration base is +`198661dfa15af030936ea66ccedd95c88c82232b`. Parent branches stay untouched. +Retain historical tasks/research as completed evidence from the combined +branch; references to Model behavior describe the later integration branch. +The coordinator repaired this area's parent/base anchor after the explicitly +authorized reconstruction, since the old portfolio boundary is no longer an +ancestor. The backup retains the original metadata and history. + +For native extraction, run a fresh Release build and existing full check, +focused native search/open-list/regular checks, ordinary native examples and +tiny TSPTW correctness. Verify Model and portfolio source/dependencies are +absent. Port relevant semantic tests into the existing test system; do not +add a new harness or performance campaign. Generic prerequisite changes need +focused existing comparison/continuation/worker-control coverage. Any generic +hooks retained only for integration must have a concrete compatibility need. +The integration task has its own area and validates Model/portfolio behavior +and the four open Model examples. All newly completed work receives independent +verification before its final commit. Keep PR-only zdev records during work. + +# Heuristic search and open transition models + +## Objective + +Add heuristic search to Gecode, including A* and complete anytime beam search +(CABS), usable with ordinary `Space` models and the `Model` interface. Neither +the Model layer, the portfolio system, nor open variables/lists are required. Open constraint models are an important application. Native engines +are usable directly from ordinary handwritten `Space` subclasses. Demonstrate +an actual state-transition formulation of TSPTW using open lists, then measure +when heuristic search, dominance, and propagation repay their costs. + +Success means a user can configure heuristic search, obtain valid solutions and +improving incumbents, and distinguish optimality or infeasibility proofs from +resource termination. An ordinary bounded CP model and an open transition model +must both exercise the public journey. TSPTW solutions must pass an independent +tour/time-window checker. Measurements must report time and memory as well as +expansions, and separate model changes from engine changes. + +## Boundaries and settled decisions + +- Work lives on `feature/open-search`, combining `feature/portfolio` and + `feature/open-model`; see [integration notes](background/integration.md). + The zdev area is isolated, with `portfolio` as its parent. Open-model changes + are recorded in one linear integration commit because zdev task work rejects + merge history; the resulting tree matches the validated combination. +- Keep `.zdev` committed during development, then remove it before squash merge + using the existing pull-request record policy. +- Keep native engines and guidance in the search layer, independent of Model + declarations, compilation, analysis, and portfolio configuration. Ordinary + closed Spaces and Spaces using native open lists use the same engine API. +- Start with sequential engines and explicit expert-supplied guidance. Model + configuration and portfolio composition are optional conveniences around + those engines. Build working increments before tuning or generalizing APIs. +- Reuse `Space`, `Search` ownership/stops/statistics, compiled Model handles, + objective comparison, and existing portfolio asset boundaries where they fit. + Do not introduce a separate modeling language or rebuild the portfolio. +- Keep ranking separate from a bound allowed to prune. For a CP node, an + objective bound may already be a bound on total cost; adding a prefix cost to + it again is incorrect. Additive transition models can supply `g + h`. +- State merging is optional and model-justified. Generic CP spaces cannot be + merged by an arbitrary key without proving equivalent residual problems. + Tree search remains available when no such declaration exists. +- Beam truncation is heuristic loss of coverage. CABS recovers alternatives by + widening and restarting while retaining the incumbent. Width, time, or memory + limits do not constitute an infeasibility or optimality proof. +- General unbounded cyclic planning, parallel beams, learned guidance, LNBS, + GPU search, arbitrary cost algebras, and automatic heuristic discovery are + outside the initial delivery. Reconsider only after the core measurements. +- Do not import the older A* prototype or its Model layer wholesale. Consult it + for concrete expansion and planning examples, and port only what is useful. + +## Research and implementation sequence + +The first four tasks delivered native A*, native CABS, optional Model guidance, +and ordinary-Space portfolio integration. The next bundle adds a real TSPTW +workload, optional state pruning, and a column-search alternative, then measures +the choices before expanding the engine family further. + +| Increment | Outcome | Evidence before moving on | +| --- | --- | --- | +| 1. Settle the search contract | Define ranking, admissible total bounds, solution comparison, optional state equivalence/dominance, ownership, stopping, and proof status. Compare ordinary CP and transition-model needs with the existing APIs. | A short contract and worked calls for both model forms; check finite search, inconsistent admissible estimates, overflow, and solution-return semantics. | +| 2. Sequential A* vertical slice | A usable native `Space` engine with queued stable states, n-ary choice expansion, deterministic ties, and correct stop/cleanup behavior. Initially use cloned frontier states. | Small independently checkable optima, a misleading first goal, no-solution exhaustion, and interruption. Measure frontier memory; defer recomputation machinery until needed. | +| 3. Sequential CABS | Layer beam search and widening restarts on the native Space journey, carrying incumbents and recording truncation independently of valid pruning. | A standalone width-1 run that misses the optimum or every solution, recovery at larger width, variable-depth goals, and resource/width termination without false proof. | +| 4. Optional integration | Model supplies retained guidance, scalar objective-bound defaults, roots and rendering for both engines. Ordinary-Space portfolio recipes compose their native builders. | The same native engines work standalone and in the portfolio. Model analysis offers supported defaults/diagnostics; resource-limited CABS never supplies a coverage proof. | +| 5. Open TSPTW transition model | Model visits, residual state, travel cost, time windows, waiting, and return-to-depot semantics using current open-list/Model features. Provide A*/CABS and DFS/BAB comparisons on the same formulation. | Tiny exhaustive tours and independent path checking; demonstrate lazy growth and clone independence. Confirm one search layer means what the beam algorithm expects. | +| 6. TSPTW state merging and bounds | Add sufficient residual keys, cost-aware resource dominance, and inexpensive admissible bounds where measured useful. Integrate with engines through the settled optional contract. | Cross-check dominance on/off on tiny instances; include earlier arrival with worse prefix cost and incomparable resource/cost labels. Measure retained-state memory. | +| 7. Evaluation, tuning, and integration | Compare engines and formulations on loose, tight, and infeasible TSPTW plus one bounded CP case and one existing planning example. Expose eligible engines through existing portfolio/driver seams when lifecycle and proof contracts fit. | Held-out results at equal resources, ablations, useful documentation, and regression checks for inherited model/open/portfolio behavior. | + +An increment may change after an investigation. In particular, do not force a +single generic callback framework before the native and Model calls establish +what is shared. CABS requires no dominance registry for correctness on a finite +search tree; optional state merging should be driven by the TSPTW workload. + +## Initial search contract + +The first approved bundle settled and implemented the decisions below. Later +numerical/objective families remain area work only when separately planned. + +- Deliver scalar integral minimization and maximization. Guidance values use + signed 64-bit integers; compare directions directly instead of negating values. + Model float, lexicographic, and custom objectives receive an explicit unsupported + diagnostic in this slice. Arithmetic in user-supplied estimates must stay in + range; Gecode helpers must check their own additions. +- Use a read-only evaluation of a propagated stable Space to provide an optimistic + bound on total objective value. It is a lower bound for minimization and an upper + bound for maximization, and equals the scalar objective at a solved node. The + caller must justify the bound. A weaker valid bound is sufficient. The engine + never adds an implicit prefix cost; transition models compute their own `g+h`. +- A* expands the best total bound first, with an optional separate ranking score + to break equal-bound ties and a deterministic insertion-order final tie. CABS + may use an arbitrary ranking score to select the beam, with bound order as its + default. Ranking is never a reason for sound pruning. +- Both engines use `next()` to return owning, strictly improving solutions as + they are discovered. A* can publish a generated goal before the remaining + frontier proves it best. A first returned solution therefore does not imply + optimality. Continue until natural exhaustion with `stopped()==false` to prove + optimality of the last incumbent, or infeasibility if none was found. This is + an incumbent-returning extension of A* expansion order, not a claim of classical + first-goal return semantics. Global dual-bound output can wait until required. +- Reuse `Space::compare` for incumbent acceptance and `Space::constrain` for + strict-improvement restrictions. The scalar guidance, comparison, and pruning + contracts must agree. Adopt an external incumbent only when it strictly + improves the retained one; reject incomparable incumbents consistently with + existing exact engines. Keep an owned incumbent independently of caller-owned + returned solutions. +- Start with tree search and cloned frontier spaces. No state key, dominance + registry, replay/recomputation scheme, or new Space virtual method is required. + Statistics depth means committed search edges, never objective cost. +- Follow the existing `Space::prepare` / `Worker::prepare` protocol before + generating a choice or changing the search path. Interrupted brancher + preparation remains resumable and its work is counted in existing statistics. + Reuse the completion fixture when checking this behavior in both engines. +- Queued nodes are stable. Save choices only while they remain compatible with + their Space. An incumbent cut can invalidate a choice: propagate and obtain a + fresh choice before expanding the changed Space. For a partially expanded + parent, retain its original choice/alternative cursor and apply new cuts to + child clones, or use another demonstrated coverage-preserving approach; a new + choice may enumerate different alternatives. Preserve unvisited n-ary + alternatives when `next()` returns midway through an expansion or a stop fires. +- The initial engines are sequential. Support ordinary stop objects, root-clone + ownership options, engine builders, and external incumbent adoption. Reuse + the sequential worker-control adapter for capacity-one pause/resume; a paused + asset is not exhausted. Diagnose unsupported parallel/tracer settings explicitly. + Do not imply replay behavior from `c_d`/`a_d`; document cloned-frontier storage. No-goods use + the existing empty-result convention rather than inventing extraction. +- CABS layers count one native `Choice` commit. Roots/terminal nodes may have + different depths. Every layer's terminal states are considered without dropping + deeper improving alternatives. A pass that returns early after an improvement + is incomplete unless the remaining alternatives were preserved for continuation. +- CABS retains an unconstrained stable root for widening passes and its incumbent + across passes. Begin at width one and double after an incomplete pass, with + checked growth. A configured maximum width or live-frontier node limit is a + resource stop, reported through `stopped()`, not natural exhaustion. The node + limit is a stored-node quota, not a byte or process-memory guarantee. A complete + finite pass that exhausts improving alternatives can close the search. +- Model engine selection can use the propagated scalar objective lower/upper + bound as its safe total-bound default. Expert-supplied bounds and optional + ranking have explicit declarations with dependencies retained through + compilation, not a side effect of printing or dummy posting. Reuse existing + authored-handle resolution and retention patterns. No ordinary model pays for + guidance unless it enables this search route or declares custom guidance. Evaluation exposes declared variables/open + lists where needed through a read context and must not post constraints. The + Model route must not require a Space subclass or retaining every lowered variable. + +Read `gecode/search.hh`, `gecode/search/seq/bab.hpp`, and the Model runtime, +compile, optimize, and custom-objective declaration seams before implementing +these contracts. The source-backed DIDP context remains in the background index. +Exact public names and placement are implementation choices to document alongside +working calls, subject to these semantics and the existing Gecode conventions. + +## Native, Model, and portfolio integration + +The native search API takes a `Space`, its normal branching/comparison contract, +and read-only guidance. It owns the frontier and search lifecycle. It must build +and run without linking the Model library or constructing a portfolio. No open +list or special transition representation is needed. A native user can provide +bounds and ranking directly from variables or state in their Space subclass. + +Model adds convenience around the same native engines: objective sense and +family validation, a propagated scalar objective-bound default, declared custom +bound/ranking dependencies, handle remapping, root creation, output, and driver +selection. Reuse `CompiledModel::has_integer_objective`, `objective_sense`, and +the objective materialized in `model/runtime.cpp`. Enabling heuristic search +need not require a custom guidance callback for a supported scalar objective. +This safe default is useful even when it is weak; users can replace it with a +problem-specific valid total bound and select a separate beam ranking. + +Existing objective-connected groups, functional-dependency generators, and +scheduling metadata may later help suggest branching or ranking configurations. +Investigate these suggestions against worked models before adding an analyzer. +A structural group or guessed rank alone does not justify an admissible bound, +a sufficient transition state, or a dominance rule. The initial Model task uses +objective/dependency information already available; it does not discover domain +heuristics or add a general analysis framework. Every Model-assisted setting +must translate to ordinary native engine/guidance calls. + +Portfolio integration uses `Search::Builder` and +`SpacePortfolioSearchFactory`, with owned solutions, external incumbents, stops, +and honest exhaustion. Reuse `WorkerToEngine` in `search/support.hh` for the +existing capacity-one control protocol instead of adding a scheduler. First +exercise ordinary-Space recipes with A*/CABS and BAB, including parked assets and +resource-stopped CABS. Forward external incumbents at a safe engine boundary; +the current complete-recipe adapter inherits a no-op adoption method. Treat +CABS-local width/frontier limits as coverage-free asset termination so another +complete asset can finish; preserve shared/global stop semantics. +The Model adapter can supply the same builders/guidance; +future Model portfolio presets can select them without changing native engines. +Portfolio scheduling and policy remain separate from search implementation. + +## Staged work and remaining investigations + +The initial bundle is complete, including its bounded closed CP example. The +remaining TSPTW, state-pruning, and tuning work is described below. Completing +either bundle does not silently close the area. + +For TSPTW, investigate how existing open-list factories and custom brancher posting +can represent clone-local `(U,i,t,g)` state and assigned visit effects. A list +position is materialized before its visit decision is committed. Prefer a +problem-specific n-ary transition brancher, so a commit means one visit, over +changing the engine to understand list horizons or adding a generic transition +language. Determine return-to-depot, service, and window conventions from the +chosen data and the independent checker before implementing the example. + +Optional state equality/resource dominance must include every fact affecting +future feasibility and must compare prefix cost. Design it against the actual +TSPTW state, without changing the tree-search contract. Measure bounds and residual +propagation on loose/tight windows before strengthening them. The Gecode fixpoint +policy and the paper's one-pass Pumpkin policy remain distinct experimental setups. +Parallel search, float/custom/lexicographic support, and cyclic planning remain +outside this initial bundle. The area's later integration work can revisit them +with concrete demand and lifecycle evidence. + +## Next bundle: state pruning, column search, and TSPTW + +Read [search extensions and benchmark decisions](background/next-search-and-benchmarks.md) +before implementing this bundle. It records the literature comparison and the +reasons for the proposed scope. Approval of the next bundle settles these +choices; it does not authorize every algorithm in the survey. + +Add one native progressive column engine with per-depth queues. Default quota +is one and grows by one after each complete depth cycle; fixed quota one is +CBFS. Pending alternatives remain stored, so quota controls expansion effort +and does not justify deletion. Use existing total bounds for pruning and an +optional separate lower-first rank for ordering within each queue. Preserve +partially expanded n-ary choices and the cycle cursor across publications, +ordinary stops, and worker parking. Checked quota saturation preserves a +positive finite quota; a retained-frontier cap is an incomplete resource stop. +Keep sequential finite-tree semantics, ordinary builders, and scalar min/max. +No generic engine-policy framework, Space virtual method, or Model prerequisite +is required. BAB with deliberate branching is the depth-first baseline. + +Add a separate optional native state-pruning declaration, driven by the worked +transition model. It extracts an owned descriptor with an exact grouping key +and cost/resource labels, then compares only matching keys. Hash collisions +must not imply equality. Distinguish equivalence, dominance, and incomparable +labels, preserving at least one equivalent representative. The author must +justify every omitted historical fact and include prefix cost in dominance. +Tree search remains the default. Registry descriptors must not retain native +handles or extra Space clones. Registry forgetting can only reduce pruning; +it cannot delete pending search work. Document descriptor/storage costs. + +For A* and column search, a better label admits fresh work even after the old +representative was expanded; do not rely on heuristic consistency. For CABS, +initial pruning is among live next-layer candidates only. Remove registry +entries when their candidate is removed and clear between layers and passes. +Never use a beam-discarded representative to suppress a surviving candidate. +Width loss, sound dominance pruning, and resource termination stay distinct. +Reusing an exact representative is the merging supported here; relaxed store +merging and generic transition-dominance rules remain separate future work. + +Model supplies optional descriptor dependencies and callbacks through its +existing retention/remapping/read-context conventions and the same native +declaration. Explicit declarations alone retain auxiliaries. Extend its engine +selection and ordinary portfolio builders for column search, preserving the +safe asset-thread adoption already implemented. Structural metadata can inform +explicit branching suggestions; this bundle adds no automatic key, dominance, +or admissibility inference and no general analysis framework. + +Use exact scaled canonical matrix costs for TSPTW, minimizing the sum of arcs +including return and excluding waiting. Service already folded into arcs is +not added again. Start at time zero; wait to customer ready times, enforce +customer due times and the depot window on return. Reject nonzero start-depot +ready times in the initial reader. Check scaling, dimensions, nonnegativity, +and Gecode variable/arithmetic limits. Support asymmetric and nonmetric data; +do not impose tour-reversal symmetry or assume direct-distance reachability +is a sound bound on a path through other customers. + +Build two ordinary native Space formulations using identical inputs/output and +an independent checker: a costed-circuit closed CP model with time propagation, +and a finite transition model with clone-local (U,i,t,g) and an open visit list. +A problem-specific n-ary brancher commits one assigned visit, with an explicit +final return; list materialization does not update the residual state. Start +with g as the total lower bound except that solved bounds equal full tour cost. +Compare a fixed path-variable control with the same transition brancher to +isolate lazy list allocation. Keep Model optional throughout. + +Use cheap minimum incoming/outgoing remaining-cost bounds, checked g+h, and +shortest-path lower bounds for nonmetric reachability. Compare a residual +scheduling relaxation separately, with ordinary Gecode fixpoint propagation. +At equal (U,i), (t1,g1) dominates (t2,g2) only if both components are no greater +and the residual formulation preserves the earlier-arrival preference. Test +incomparable labels and propagation/history that invalidates a proposed key. +Do not add a 1-tree, generic transition DSL, or new propagator before measurements. + +Treat BSS regeneration and LNBS suffix-compatible repair as separate bounded +investigations. Each produces a concrete feasibility conclusion and, if useful, +a proposed follow-up task; neither silently adds a production engine. Existing +BAB/LDS/LNS capabilities remain serious comparison points. APPS, a separate +DFBnB/DBDFS port, weighted A*, BULB/BUDDS, learned ranking, relaxed DDs, parallel +and GPU engines are deferred until the retained results justify them. + +For the first measurements, separate a tiny correctness set from 12 development +and 24 held-out cases selected before tuning, across canonical families and +separately marked deterministic window variants. Record the selection and +input content hashes in a small data manifest, not a new data-management system. +The named runner executes one process at a time, records all incumbents for +checking, and compares equal thread/time/RSS-watchdog limits. A node quota is +not a byte limit; a sampled RSS watchdog has documented granularity. + +Development tuning is bounded to six candidate configurations, two repetitions, +15 seconds per case, with at most 45 minutes of solver wall budget. Held-out +evaluation uses six frozen native configurations (closed BAB/CABS, transition +BAB/A*/CABS/column) and pinned DIDPPy A*/CABS, 30 seconds per case, at most 120 +minutes total solver wall budget including bounded development ablations. +Use one thread and the same 1 GiB RSS-watchdog policy; include startup, +preprocessing, and compilation where applicable. Record incomplete runs and +unsupported resource enforcement honestly. Do not launch a larger campaign +without a separately agreed budget or tune on held-out outcomes. + +## Additional engine work + +The approved extension adds configurable native best-first ordering and a +bounded sequential CADDS investigation. Best-first ordering reuses the native +frontier, descriptor registry and Choice continuation, with sound bounds kept +independent of arbitrary ranking. Preserve AStar's default ordering. Greedy and +fixed weighted ranking are examples for explicit additive costs; dynamic +weighted-search repair and approximation guarantees remain deferred. + +The CADDS study freezes a fragment at the deepest nonempty layer and expands +that whole fragment before descending, retaining unselected work. Compare it +with CABS and the documented fixed-width source/alternative cursor using equal +model and heuristic settings. This is a CPU study, with no production-engine +commitment or GPU implementation. It has a separate 300-second cumulative +solver allowance, shared with verification, and uses development cases only. + +Primary CPKB reading: Hansen and Zhou, *Anytime Heuristic Search* +(`2007-hansen-anytime-heuristic-search`, fulltext lines 140–176), and Tardivo, +Michel and van Hoeve, *Complete Anytime Decision Diagram Search with +GPU-Accelerated State Expansion* (`2026-tardivo-gpu-accelerated-state-expansion-for-anytime-exac`, +fulltext lines 279–403; DOI `10.1007/978-3-032-27242-3_34`). Read these passages +when implementing ordering or investigating the retained-fragment schedule. +The earlier algorithm deferrals still apply outside these two additions. + +## Testing + +Use existing checks for the combined base; add no new tests for planning. +For engine and state-pruning implementations, add focused semantic coverage in +existing search/Model test suites. Test plausible correctness failures rather +than queue layout, callback wiring, or every option combination. Tiny exhaustive +TSPTW enumeration and an independent solution checker are valuable because +incorrect bounds, state keys, or dominance can silently remove the optimum. + +For the next bundle, test hash collisions, equivalent and incomparable labels, +better-label reopening, beam-discarded representatives, registry forgetting, +and disabled-policy behavior in existing suites. Reuse native lifecycle tests +for column search and Model retention tests for declared state descriptors. +Use focused tiny TSPTW checks rather than a combinatorial option matrix. No +new test harness or tests of documentation-only planning are expected. The +existing ASan search selection can use the main-thread test runner (`-threads 1`): +the automatic parallel harness overflowed its stack during root propagation +in the portfolio fixture, while concurrent portfolio asset tests passed on the +main harness thread. Preserve that validation limitation. + +Cover ownership/stops, all Space status cases, goal acceptance, n-ary choices, +strict improvement, beam-loss/proof distinction, and compilation retention where +the implementation actually touches them. Reuse existing lifecycle and +portfolio tests. Use sanitizers for new frontier ownership when supported; +choose additional configurations from touched feature boundaries, not a full +matrix by default. + +## Validation + +Build with CMake/Ninja and run the existing `check` target, open-list lifecycle, +regular/NFA tests touched by the merge, and targeted portfolio/search tests. +Build and run the four inherited open examples. Record exact successful +commands and limits in [integration notes](background/integration.md). + +For performance, first use fixed tiny/development cases to detect algorithmic +mistakes. Then select public TSPTW families and separate training from held-out +instances. Record data semantics, solver versions, seed where relevant, thread +count, elapsed-time and memory limits, and preprocessing/compilation costs. +Report feasible quality over time, time to first solution, final objective, +proof/infeasibility count, stop reason, expansions, propagation time, clone +cost, and peak frontier/registry memory. Add counters only when needed to +answer these comparisons. Do not treat fewer expansions as a runtime gain. + +Compare engine changes on the same model, then compare closed versus open +formulations separately. Use ordinary Gecode DFS/BAB and suitable DIDPPy/RPID +references, with separately checked formulations and equal resource limits. +Ablate dominance, bound strength, propagation, beam growth, and representation +one at a time where feasible. Select recommendations among the frozen +configurations from held-out runtime, solution quality, proof, and memory +evidence, without further tuning on those outcomes; there is no universal speedup +requirement across every model. + +## Background index + +- [Sequential CADDS retained fragments](background/cadds-retained-fragments.md): + read before proposing a production retained-fragment engine. Includes checked + coverage, reproducible development comparisons and the decision to defer adoption. + +- [DIDP, CABS, and TSPTW](background/didp-and-tsptw.md): read before choosing + search semantics, state dominance, bounds, or benchmark families. Includes + CPKB reading paths, primary sources, and inspected implementation links. +- [Combined base and integration](background/integration.md): read before + implementation or a parent refresh. Identifies the current interfaces, older + prototype, merge resolutions, and validation commands. +- [Search extensions and benchmark decisions](background/next-search-and-benchmarks.md): + read before the next bundle's column engine, state pruning, BSS/LNBS studies, + or TSPTW models and evaluation. Separates selected work from deferred algorithms. +- [Fixed-width beam-stack regeneration study](background/beam-stack-regeneration.md): + read before proposing deterministic-transition continuation or evaluating BSS + memory claims. Includes checked coverage, cursor comparison, and replay commands. +- [Suffix-compatible TSPTW repair](background/suffix-compatible-repair.md): read + before proposing LNBS integration. Records checked boundary changes, suffix + rollout, existing recipe limitations, and bounded reproduction commands. diff --git a/.zdev/open-search/tasks/001-deliver-sequential-a-with-incumbent-publication.md b/.zdev/open-search/tasks/001-deliver-sequential-a-with-incumbent-publication.md new file mode 100644 index 0000000000..8d258cd17c --- /dev/null +++ b/.zdev/open-search/tasks/001-deliver-sequential-a-with-incumbent-publication.md @@ -0,0 +1,50 @@ ++++ +schema_version = 1 +id = "open-search-001" +key = "native-astar" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "high" +blocked_by = [] ++++ +# Deliver sequential A* with incumbent publication + +## Outcome + +Ordinary Space users can run bound-ordered A*, obtain improving integer solutions, and distinguish interruption from complete exhaustion. + +## Context + +Follow the Initial search contract in ../brief.md. Read ../background/integration.md for the combined base and ../background/didp-and-tsptw.md for bound/proof semantics. Start with gecode/search.hh, search/seq/bab.hpp, search/build.hpp, and test/search.cpp. The current best-solution engine accepts external incumbents through compare/constrain; cuts can invalidate choices. There is no reusable native A* engine in this base. + +## Boundaries + +- Use sequential cloned-frontier tree search for scalar integer minimization/maximization; add no Space virtual methods, graph registry, recomputation scheme, or parallel implementation. +- Keep bound-first A* expansion and incumbent publication semantics from the brief; do not prune by an arbitrary ranking score. +- Native engine/guidance APIs build and run without the Model library, portfolio configuration, or open variables/lists. + +## Done when + +- [x] A native engine and builder accept explicit read-only total-bound guidance, optional tie ranking, and ordinary Search::Options; a standalone closed Space example returns caller-owned improving solutions. +- [x] Minimization/maximization use direct signed-64-bit comparisons, and existing compare/constrain accept only strictly improving compatible incumbents. +- [x] Root cloning/transfer, failed/solved roots, n-ary expansion, destruction, stop/resume, and external incumbent adoption preserve ownership and search coverage. The existing Space::prepare/Worker::prepare protocol preserves interrupted brancher preparation and its statistics. New cuts cannot reuse an incompatible choice or skip pending alternatives. +- [x] Natural exhaustion after continuing next() certifies the final optimum or infeasibility. Resource interruption reports stopped; statistics count search depth independently of objective cost. +- [x] Reuse the existing sequential control adapter for capacity-one pause/resume; unsupported parallel and tracer settings have explicit diagnostics. Document cloned-frontier storage and the absence of a hard memory guarantee. +- [x] Focused existing-suite tests exercise a misleading first generated goal, signed min/max objectives, n-ary continuation after publication/stop, and external incumbent adoption while work remains. Reuse CompletionNative for interrupted preparation/resumption; tiny optima are independently checkable. + +## Validation + +- Build the search module and gecode-test in build/open-search; run the new focused Search selection and existing best-solution comparison/stop tests relevant to the touched code. Verify a native-only consumer links search without the Model library or portfolio setup through the established consumer pattern. +- Run the existing check target. Run a focused sanitizer ownership check if supported; record any unavailable sanitizer evidence without adding a harness. + +## Result + +Implemented native sequential A* with owned frontier clones, total-bound guidance, optional tie ranking, strictly improving publication, resumable preparation/n-ary expansion, external incumbents, capacity-one control, standalone example and documentation. Independently verified against unchanged snapshot W6319d6b22f9c2448. + +Validation: + +- Release search/test/example/FlatZinc build, five focused A*/completion/comparison/incumbent/pause tests, relevant existing BAB/RBS/control tests, and the check target passed. +- Standalone example and installed find_package(search int) consumer published 4, -2, -7 and proved -7; consumer linked no Model library. +- Native-only AddressSanitizer build and five focused tests passed without reports; inherited open examples and portfolio public contract passed. diff --git a/.zdev/open-search/tasks/002-deliver-standalone-widening-cabs.md b/.zdev/open-search/tasks/002-deliver-standalone-widening-cabs.md new file mode 100644 index 0000000000..b265f88295 --- /dev/null +++ b/.zdev/open-search/tasks/002-deliver-standalone-widening-cabs.md @@ -0,0 +1,50 @@ ++++ +schema_version = 1 +id = "open-search-002" +key = "native-cabs" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-001"] ++++ +# Deliver standalone widening CABS + +## Outcome + +Ordinary Space users obtain improving integer solutions through sequential layer beams that widen until complete, independently of Model and portfolio, with honest width/resource termination. + +## Context + +Read Initial search contract and Native, Model, and portfolio integration in ../brief.md, plus CABS proof/code references in ../background/didp-and-tsptw.md. Reuse native A* guidance, ownership, preparation, and builder conventions. Start with gecode/search.hh, the new A* implementation, search/support.hh, and test/search.cpp. CABS must recover width-discarded alternatives and deeper goals; a narrow empty beam cannot prove infeasibility. + +## Boundaries + +- Use native Choice commits as layers, width one followed by checked doubling, cloned states, and the same scalar objective scope. +- No graph dominance, parallel beam, learned ranking, global dual-bound API, or portfolio policy rewrite in this task. +- Use the native API without requiring Model compilation, portfolio configuration, or open lists. + +## Done when + +- [x] A native engine and builder run CABS with optional heuristic ranking and admissible bound pruning; equal priorities use deterministic ties. The same capacity-one pause/resume protocol works without losing beam/pass state. +- [x] An immutable root and owned incumbent survive widening passes. Increasing width regenerates omitted work; external incumbent adoption affects remaining work and subsequent passes without mutating the stored restart root. Reuse the native preparation/stop-resume protocol. +- [x] Terminal nodes at different depths and publication during a pass do not lose improving alternatives. A pass reports complete only when all improving alternatives were exhausted with sound pruning. +- [x] Maximum width, live-frontier node limit, ordinary stop objects, and width-growth overflow end incompletely with stopped reporting; node limits are documented as node quotas rather than hard process-memory limits. +- [x] Focused tests show width-one loss of an optimum and loss of all solutions, recovery on widening, variable-depth goals, min/max behavior, and width/resource termination without false proof. +- [x] A standalone bounded closed Space example compares A*, CABS, and BAB under the same formulation; documentation explains their result and resource semantics. + +## Validation + +- Build/run focused native CABS tests plus the standalone example, including brancher-preparation interruption/resumption using the existing completion fixture; compare tiny optimum/infeasibility results with exhaustive/BAB references. +- Run the existing check target and inherited open-list lifecycle/portfolio public-contract checks. Record a small reproducible width/frontier/time comparison; do not tune defaults or claim benchmark gains yet. + +## Result + +Delivered standalone sequential CABS with bounded beams, widening, incumbent sharing, resumable preparation, and honest resource stops. + +Validation: + +- Independent verifier passed snapshot W478094cb560bd1c9 with no findings and unchanged checkout. +- Release and AddressSanitizer focused CABS/AStar/completion/worker-control tests and native comparison passed. +- Repository check, inherited open-list/regular/portfolio checks, and four open examples passed; native target has no Model dependency. diff --git a/.zdev/open-search/tasks/003-configure-both-native-engines-through-optional-model-guidanc.md b/.zdev/open-search/tasks/003-configure-both-native-engines-through-optional-model-guidanc.md new file mode 100644 index 0000000000..d513415c94 --- /dev/null +++ b/.zdev/open-search/tasks/003-configure-both-native-engines-through-optional-model-guidanc.md @@ -0,0 +1,50 @@ ++++ +schema_version = 1 +id = "open-search-003" +key = "model-guidance" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-002"] ++++ +# Configure both native engines through optional Model guidance + +## Outcome + +A Model user selects A* or CABS with a safe scalar objective-bound default or explicit expert guidance, compiles a bounded integer optimization model, and obtains/renders solutions without a Space subclass. + +## Context + +Read Initial search contract and Native, Model, and portfolio integration in ../brief.md, plus Model seams in ../background/integration.md. Start with gecode/model/runtime.hh, model/runtime.cpp, model/model.cpp, model/compile.cpp, model/optimize.cpp, model/driver.hh, and test/model/compile.cpp. CompiledModel already exposes objective family/sense and materializes a scalar objective variable. Custom-objective dependencies demonstrate validation, retention, alias barriers, and context resolution. Explicit guidance must retain its own dependencies even when they are not output or branching variables. + +## Boundaries + +- Support scalar integer minimization/maximization and diagnose float, lexicographic, and custom objective families for this initial route. +- Keep guidance lazy and explicit; do not retain every variable, use dummy output/posting to keep handles alive, or build a generic callback framework. +- Keep all engine/frontier algorithms in the native search layer. Reuse existing objective/dependency information; do not add automatic domain-heuristic discovery or a general model analyzer. + +## Done when + +- [x] The owning Model search journey configures the same native A*/CABS engines, preserves artifact/root lifetimes, and renders returned solutions through existing output APIs. No Model helper becomes a prerequisite for native callers. +- [x] Declared guidance dependencies are validated and survive applicable compilation levels, handle remapping, repeated root creation, and independent clones without adding guidance cost to undeclared models. +- [x] A small closed CP example demonstrates minimization/maximization and compares A*, CABS, and BAB. Document scalar objective-bound defaults, explicit valid bounds/ranking, incumbent publication, and stop versus proof. +- [x] Explicit driver selection of either engine derives scalar sense and uses the propagated objective lower/upper bound when no expert bound is declared. Unsupported objective families and invalid dependencies produce clear diagnostics; explicit expert guidance can replace the default. +- [x] Focused Model tests use dependencies absent from output/ordinary branching, compare optima with BAB or tiny exhaustive reference results, and exercise compile/root lifetime and unsupported-family diagnostics. +- [x] Documentation explains what existing Model objective/dependency information configures today and what structural branching/ranking suggestions remain investigations; all settings translate to native calls. + +## Validation + +- Build gecode-test and the changed Model example(s); run the focused guidance/compilation tests and existing Model lifecycle, objective, and open-list checks relevant to the declaration changes. +- Run the existing check target and a focused installed-header consumer through the established repository pattern; no new installation harness. + +## Result + +Added optional retained Model guidance, an owning native A*/CABS facade, explicit driver selection, and a min/max quickstart. + +Validation: + +- Independent verifier passed snapshot Wd89080cd583215fc with no findings and unchanged checkout. +- Release build, 18 focused Model/native tests, complete check target, quickstart and both driver routes passed. +- Installed exported-target consumer and both installed driver routes, inherited checks, and four open examples passed. diff --git a/.zdev/open-search/tasks/004-compose-native-heuristic-engines-in-ordinary-space-portfolio.md b/.zdev/open-search/tasks/004-compose-native-heuristic-engines-in-ordinary-space-portfolio.md new file mode 100644 index 0000000000..4f3f370822 --- /dev/null +++ b/.zdev/open-search/tasks/004-compose-native-heuristic-engines-in-ordinary-space-portfolio.md @@ -0,0 +1,47 @@ ++++ +schema_version = 1 +id = "open-search-004" +key = "portfolio-integration" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-002"] ++++ +# Compose native heuristic engines in ordinary-Space portfolios + +## Outcome + +An ordinary Space portfolio combines A* or CABS with an existing exact engine, shares improving incumbents, and reports proof or interruption correctly without requiring Model. + +## Context + +Read Native, Model, and portfolio integration in ../brief.md and ../background/integration.md. Start with gecode/search/portfolio.hpp SpacePortfolioSearchFactory and complete recipes, search/portfolio.cpp SpaceRecipeCompleteAsset, search/support.hh WorkerToEngine, and test/search.cpp (ordinary-Space portfolio and worker-control fixtures). Reuse native builders and the existing adapter lifecycle. Natural recipe exhaustion is a coverage claim; a width-limited beam cannot certify closure. Model conveniences can produce the same builders later. SpaceRecipeCompleteAsset currently inherits a no-op adopt(), so forward accepted external incumbents to best-engine constrain() at a safe engine boundary. Its stopped() currently promotes intrinsic engine stops to a whole-portfolio stop; use the existing coverage-free asset End handling for CABS-local width/frontier limits while preserving shared stop semantics. + +## Boundaries + +- Use existing ordinary-Space portfolio recipes/adapters and capacity-one native engines; add no scheduler, portfolio policy, Model prerequisite, or parallel heuristic engine. +- Retain direct standalone construction and the same native guidance/comparison contract. + +## Done when + +- [x] A documented ordinary closed Space example configures A* and CABS builders through SpacePortfolioSearchFactory alongside BAB, without Model declarations or compilation. +- [x] At a safe engine boundary, accepted external incumbents reach best-engine constrain(). Owning publications, shared stop propagation, and capacity-one parking/resumption work for both heuristic assets without losing unvisited alternatives. +- [x] Only genuine complete exhaustion contributes a coverage proof. CABS-local width/frontier stops end that asset coverage-free and let other eligible complete assets continue; a shared ordinary/global stop still stops the portfolio. If no asset proves closure the result remains incomplete. +- [x] Focused existing-suite checks compare standalone and portfolio optima and exercise external incumbent sharing and parked/resumed work. Force CABS-local resource termination before BAB closure and verify BAB can still certify optimum/infeasibility; separately verify shared stop terminates incompletely. + +## Validation + +- Build/run the focused ordinary-Space portfolio tests and example with independently checkable tiny optima; reuse existing worker-control/lifecycle fixtures. +- Run the existing check target and relevant inherited portfolio public-contract tests. Verify the native example and its portfolio variant use the same Space and guidance. + +## Result + +Ordinary-Space A*/CABS recipes queue owned accepted incumbents at safe engine boundaries. Local CABS limits release the asset without proof; BAB can complete optimality or infeasibility. The native example and docs show the same standalone Space and guidance in portfolio recipes. + +Validation: + +- Independent verifier passed all task conditions at unchanged snapshot W6e53afa60e6e1aa6. +- Release focused and inherited selections, full check, native portfolio example/linkage and four inherited open examples passed. +- ASan build, full focused selection with -threads 1 and native example passed. Automatic parallel test harness -threads 0 overflowed its stack during root propagation before asset construction; log preserved. diff --git a/.zdev/open-search/tasks/005-establish-exact-tsptw-data-and-independent-checking.md b/.zdev/open-search/tasks/005-establish-exact-tsptw-data-and-independent-checking.md new file mode 100644 index 0000000000..7faf2e2002 --- /dev/null +++ b/.zdev/open-search/tasks/005-establish-exact-tsptw-data-and-independent-checking.md @@ -0,0 +1,48 @@ ++++ +schema_version = 1 +id = "open-search-005" +key = "tsptw-data-checker" +area = "open-search" +status = "done" +complexity = "standard" +afk = true +priority = "normal" +blocked_by = [] ++++ +# Establish exact TSPTW data and independent checking + +## Outcome + +A small canonical TSPTW corpus, reusable native input reader, and independent checker establish identical semantics for every formulation and reference solver. + +## Context + +Read the brief's Next bundle data/measurement rules and ../background/next-search-and-benchmarks.md TSPTW data and comparison. Start from examples/tsp.cpp, misc/ benchmark conventions, the publisher's canonical matrices and check_solution.cpp. Use a small examples/data/tsptw correctness set and a simple acquisition/selection manifest for larger cases; exact paths are implementation choices. + +## Boundaries + +- Preserve canonical matrix values by exact scaling; no coordinate reconstruction, hidden rounding, double service, makespan objective, or new data-management framework. +- Use fixed departure zero and return-depot window; reject unsupported start-ready or integer-limit inputs explicitly. Tiny generated cases are separate from canonical families. + +## Done when + +- [x] The reader validates dimensions, nonnegative arcs, windows, exact decimal scaling and Gecode arithmetic ranges; canonical travel-cost and service-folded conventions are documented. +- [x] A standalone checker recomputes every visit, waiting, return, coverage and objective independently of model/search internals, accepts a valid tour and rejects a duplicate, omission, late customer, late return and incorrect cost. +- [x] Tiny enumerated optima/infeasibility cover waiting, asymmetric/nonmetric travel, decimal input and folded service. Published tours are checked under the same convention; best-known values are distinguished from proof. +- [x] A reproducible manifest selects a tiny correctness set, 12 development and 24 disjoint held-out cases across Dumas, GendreauDumasExtended, SolomonPotvinBengio, AFG and marked window variants before any tuning, with source URLs and content hashes. Larger acquisition is explicit; routine tests run offline. + +## Validation + +- Run the reader/checker on tiny positive and negative cases and independently enumerate tiny tours. +- Acquire the selected public data and verify at least one published tour per selected canonical family against the publisher's conventions. Check the manifest's disjoint split and integer limits. +- No broad new test harness; focused parser/checker behavior and CLI smoke checks are sufficient. + +## Result + +Established exact native TSPTW reading, independent checking, offline tiny cases and frozen development/held-out data. + +Validation: + +- C++ reader build; offline 9-case and full 45-case validation passed with exact parser agreement and input/tour rejection checks. +- Acquisition/hash validation passed for 36 selected cases; independent enumeration confirmed five tiny results and publisher checker accepted four canonical-family tours. +- Fresh independent whole-task verifier passed against unchanged W598a61b451c35a74, including the leading-dot zero regression. diff --git a/.zdev/open-search/tasks/006-deliver-a-strong-conventional-cp-tsptw-model.md b/.zdev/open-search/tasks/006-deliver-a-strong-conventional-cp-tsptw-model.md new file mode 100644 index 0000000000..69f32942ad --- /dev/null +++ b/.zdev/open-search/tasks/006-deliver-a-strong-conventional-cp-tsptw-model.md @@ -0,0 +1,48 @@ ++++ +schema_version = 1 +id = "open-search-006" +key = "tsptw-closed-cp" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-005"] ++++ +# Deliver a strong conventional CP TSPTW model + +## Outcome + +An ordinary native Space solves canonical TSPTW with costed-circuit and time propagation, provides valid improving solutions, and serves as a serious CP baseline. + +## Context + +Read the brief's TSPTW conventions and ../background/next-search-and-benchmarks.md closed model discussion. examples/tsp.cpp already posts a costed circuit and regret-based branching; its reversal symmetry break cannot be copied for asymmetric/windowed tours. Reuse the input/checker and existing native example/build patterns. + +## Boundaries + +- Use existing Gecode globals, propagation and BAB; keep Model optional and add no new routing global. +- Do not post time precedence around a cyclic single depot time: separate start and return treatment. Avoid redundant branching over arbitrary waiting schedules. + +## Done when + +- [x] The native executable reads the shared data, enforces one complete tour and all windows/return, minimizes the supplied arc sum, and emits checker-compatible tours and honest proof/stop status. +- [x] Costed-circuit propagation, sound unavailable-arc filtering and customer timing are active; branching is explicit and a feasible seed is independently checked before use. +- [x] BAB and existing native A*/CABS run on the same Space with propagated total-objective guidance; engine comparisons preserve formulation and branching. +- [x] Tiny exhaustive cases and checked canonical cases agree on optimum/infeasibility, including asymmetric tours that the inherited reversal restriction would remove. + +## Validation + +- Build/run the new native target with BAB/A*/CABS on the correctness set and independently check every returned tour. +- Run existing native search/portfolio lifecycle selections and the repository check target for touched interfaces; inspect linkage to confirm no Model requirement. +- Use existing semantic test seams; do not add an options matrix. + +## Result + +Added a native costed-circuit TSPTW baseline with timing propagation, checked seeds, and BAB/A*/CABS comparisons. + +Validation: + +- Independent whole-task review passed on unchanged snapshot W96474f0503f57f45. +- Release build, reader/checker validator, five native lifecycle/search selections, and repository check target passed. +- All 12 canonical engine runs proved matching objectives; Model-disabled ASan tiny, seed, and stop journeys passed; linkage has no Model dependency. diff --git a/.zdev/open-search/tasks/007-deliver-open-list-tsptw-transitions-and-an-eager-control.md b/.zdev/open-search/tasks/007-deliver-open-list-tsptw-transitions-and-an-eager-control.md new file mode 100644 index 0000000000..595f80d424 --- /dev/null +++ b/.zdev/open-search/tasks/007-deliver-open-list-tsptw-transitions-and-an-eager-control.md @@ -0,0 +1,48 @@ ++++ +schema_version = 1 +id = "open-search-007" +key = "tsptw-open-transitions" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-005"] ++++ +# Deliver open-list TSPTW transitions and an eager control + +## Outcome + +A finite ordinary Space expresses DIDP-style TSPTW transitions with a native open visit list and clone-local residual state, plus an eager path-variable control with identical decisions. + +## Context + +Read the brief's transition rules, ../background/didp-and-tsptw.md and ../background/next-search-and-benchmarks.md. Begin at gecode/int/open-var-list.hpp, native brancher prepare/choice/commit contracts, test/int/open-var-list.cpp and test/search.cpp completion fixtures. Consult the older feature/open-cp-model:models/open-puzzle.cpp only for the concrete move-brancher pattern. + +## Boundaries + +- Use existing native open variables and a problem-specific brancher, with no Model prerequisite, generic transition language, subset automaton or engine understanding of list horizons. +- The eager control changes only path-variable allocation, not transition applicability, ordering, propagation policy or search engine. + +## Done when + +- [x] Stable nodes represent concrete clone-local (U,i,t,g); a n-ary Choice commit assigns exactly one applicable visit and updates state once. Materialization uses prepare where required and is not a committed transition. +- [x] A final return action enforces the depot window and contributes cost exactly once; terminal output and bound equal full tour cost. The initial valid total bound is prefix cost at nonsolutions. +- [x] BAB/A*/CABS find the same tiny optima/infeasibility as enumeration and checker. Partial publication, clone independence, inherited cuts and parked/resumed original alternatives preserve unvisited work. +- [x] Open-list allocation grows with the committed prefix. The eager control reserves path variables upfront and emits the same tours; report basic Space/allocation sizes without claiming compact DP representation. + +## Validation + +- Build/run both representations on the offline correctness set, check every tour, and compare against tiny exhaustive enumeration. +- Exercise materialized-but-unassigned positions and cloned prefixes in existing open-list/completion tests. Run focused native search, worker-control and open-list selections plus check. +- Use the existing native ASan build with -threads 1 for new ownership paths. + +## Result + +Added clone-local native TSPTW transitions with lazy open-list and eager path allocation, archived replay, checked seeds, and explicit depot return. + +Validation: + +- Independent whole-task review passed on unchanged snapshot Wf2f02ad91f4651bd. +- Release and Model-disabled ASan builds, focused transition/search/completion/pause/open-list selections, reader/checker validation, linkage inspection, and repository check passed. +- Tiny enumeration and lazy/eager tour sequences agree; canonical runs produced 22 proofs and 14 honest stops under five-second caps; all 45 retained/acquired inputs passed reader checking. diff --git a/.zdev/open-search/tasks/008-add-optional-exact-state-and-pareto-pruning-to-a-and-cabs.md b/.zdev/open-search/tasks/008-add-optional-exact-state-and-pareto-pruning-to-a-and-cabs.md new file mode 100644 index 0000000000..a7e6305aeb --- /dev/null +++ b/.zdev/open-search/tasks/008-add-optional-exact-state-and-pareto-pruning-to-a-and-cabs.md @@ -0,0 +1,49 @@ ++++ +schema_version = 1 +id = "open-search-008" +key = "native-state-pruning" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-007"] ++++ +# Add optional exact-state and Pareto pruning to A* and CABS + +## Outcome + +Native A*/CABS accept an optional model-justified descriptor/relation and prune equivalent or dominated work safely, demonstrated on TSPTW Pareto labels. + +## Context + +Read the brief's complete state-pruning rules and ../background/next-search-and-benchmarks.md Guidance, equality, dominance, and merging. Start from gecode/search.hh, seq/astar.hh, seq/cabs.hh and their pending-node/Choice ownership. The worked transition Space now supplies sufficient U/i and t/g; cost cannot be inferred from a total bound. + +## Boundaries + +- Add no Space virtual method, mandatory registry, Model/portfolio prerequisite, relaxed CP-store merge or generic transition-dominance framework. +- Descriptors own values and may decline pruning at a node where sufficient state is unavailable. Hashes are buckets; exact equality is required. Disabled policy keeps ordinary tree behavior. + +## Done when + +- [x] The native declaration extracts owned exact keys/labels and distinguishes equivalence, dominance and incomparability. A documented TSPTW declaration groups (U,i) and compares both t and g; ordinary partial CP nodes can remain unregistered. +- [x] A* admits fresh dominating work and reopens better labels after earlier expansion, with incompatible/obsolete pending entries handled safely. Equal representatives retain one route. +- [x] CABS compares only live next-layer candidates, removes registry entries when candidates leave the beam, and clears between layers/passes. A beam-discarded dominator cannot suppress surviving work or erase truncation. +- [x] Collision, incomparable cost/time, better-label reopening with inconsistent admissible bounds, equivalent representatives, beam removal and registry forgetting preserve tiny optima. Returned tours remain independently owned and valid. +- [x] Document retention/forgetting and resource behavior; only forgetting registry information is allowed to lose pruning. Expose the minimal counts/storage evidence needed for the benchmark, without retaining duplicate Space clones. + +## Validation + +- Add focused semantic cases in existing native search fixtures that would lose an optimum under an incorrect key/registry policy, including forced hash collisions. +- Run TSPTW pruning off/on against enumeration; validate all publications with the checker. +- Run focused search/completion/worker-control/portfolio checks, full check and native ASan -threads 1; show no descriptor/registry work when disabled. + +## Result + +Added optional native exact-state and Pareto pruning with A* reopening, CABS live-candidate descriptors, metadata-only forgetting, and default-off TSPTW guidance. + +Validation: + +- Independent whole-task review passed on unchanged snapshot Wa0bd57435da19d5f. +- Release and Model-disabled ASan builds, native semantic/lifecycle/portfolio selections, full repository check, native examples, and no-Model linkage inspection passed. +- Pruning off/on matched tiny enumeration and every returned tour passed independent checking; bounded canonical validation produced 22 proofs and 14 honest stops under five-second caps. diff --git a/.zdev/open-search/tasks/009-add-cheap-tsptw-bounds-and-compare-residual-propagation.md b/.zdev/open-search/tasks/009-add-cheap-tsptw-bounds-and-compare-residual-propagation.md new file mode 100644 index 0000000000..d1fb4cd6a6 --- /dev/null +++ b/.zdev/open-search/tasks/009-add-cheap-tsptw-bounds-and-compare-residual-propagation.md @@ -0,0 +1,48 @@ ++++ +schema_version = 1 +id = "open-search-009" +key = "tsptw-bounds-propagation" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-006", "open-search-007"] ++++ +# Add cheap TSPTW bounds and compare residual propagation + +## Outcome + +Checked inexpensive bounds and optional residual CP inference strengthen TSPTW without changing feasibility, objective or transition-layer meaning. + +## Context + +Read ../background/didp-and-tsptw.md bound and propagation discussions and the brief's next-bundle rules. Start with the two worked models and current guidance callbacks. Direct travel is not a path lower bound on nonmetric input; terminal cases and return must be handled explicitly. Existing unary/scheduling and costed-circuit facilities are candidates for a residual relaxation. + +## Boundaries + +- Keep bound evaluation separate from beam ranking. Add no 1-tree or new propagator before measurements. +- Residual inference is an optional independently switchable relaxation run through normal Gecode status() fixpoints; do not copy Pumpkin's one-pass policy or silently change the sufficient state contract. + +## Done when + +- [x] Transition guidance supplies checked g plus the maximum of valid minimum incoming/outgoing residual bounds, including root/terminal return conventions; closed CP guidance uses its propagated total objective without double-counting g. +- [x] Nonmetric reachability uses sound shortest-path lower bounds. Optional residual scheduling/CP inference can reject dead residual problems or filter actions only through justified relaxation consequences. +- [x] Every retained stronger bound and filter is cross-checked on enumerated residual completions, including waiting, nonmetric travel and unequal time/cost labels. +- [x] Bound strength and residual propagation have independent switches. A bounded development comparison records evaluation/propagation time, expansions, elapsed time and storage; a slower option may remain explicit rather than become a default. + +## Validation + +- Compare baseline, cheap bounds, and residual propagation on tiny exhaustive cases; checker validates all returned tours. +- Use at most 15 minutes of solver wall budget for the initial development ablation, record exact settings and inputs, and make no held-out tuning decision. +- Run existing touched propagation/search/open-list selections and check; no broad bound-option matrix. + +## Result + +Added checked minimum-arc total bounds and independent residual unary inference, preserving native transition semantics and defaults; retained a bounded development comparison. + +Validation: + +- Independent PASS at We368f449ded56b48; exact snapshot comparison unchanged. +- Release and Model-disabled ASan builds; exhaustive residual suffix and offline tour checks; search, unary, open-list, check, Model/portfolio and inherited open-example regressions passed. +- Verified all 18 development comparison settings, input/binary hashes, checked incumbents and termination/timing/storage caveats; 33.855 seconds external capture, no held-out outcomes. diff --git a/.zdev/open-search/tasks/010-deliver-native-progressive-column-search-with-cbfs-mode.md b/.zdev/open-search/tasks/010-deliver-native-progressive-column-search-with-cbfs-mode.md new file mode 100644 index 0000000000..de8a105e18 --- /dev/null +++ b/.zdev/open-search/tasks/010-deliver-native-progressive-column-search-with-cbfs-mode.md @@ -0,0 +1,49 @@ ++++ +schema_version = 1 +id = "open-search-010" +key = "native-column-search" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-006", "open-search-008"] ++++ +# Deliver native progressive column search with CBFS mode + +## Outcome + +An ordinary-Space sequential column-search engine offers progressive ACPS and fixed-quota CBFS behavior with retained alternatives, optional native state pruning and honest optimization proofs. + +## Context + +Read the brief's column-search contract and ../background/next-search-and-benchmarks.md algorithm comparison and inspected ACPS reference. Start with native A*/CABS expansion/prepare/ownership and WorkerToEngine adapters, now using the optional descriptor declaration. Reuse actual mechanisms only; depth queues/cycle state belong to this engine, not a universal policy framework. + +## Boundaries + +- Keep normal Space, scalar min/max, owning improving next(), native builders and capacity-one worker control. Model and portfolio remain optional. +- Default initial quota 1 grows by 1 each full cycle; growth 0 is fixed-quota CBFS. Quotas schedule expansion rather than truncate queues. A retained-frontier resource cap must stop incompletely. + +## Done when + +- [x] A public native engine/builder expands up to the positive quota from successive depth queues, admits deeper successors during the cycle, and retains all deferred work. Queue ordering uses lower-first rank when supplied, otherwise optimistic bound; only bound/dominance prune. +- [x] Cycle/depth/quota and original n-ary Choice cursor survive solution publication, ordinary stops and parking. Checked growth saturates safely; resource caps latch incomplete status. +- [x] Optional equality/dominance works with fresh-label reopening; policy omission performs tree search. Ownership and external incumbents agree with existing heuristic engine contracts. +- [x] Closed CP and transition TSPTW executables expose this engine and fixed-quota mode using the same formulations/guidance. A misleading early goal still yields the optimum on natural exhaustion. +- [x] Focused semantic fixtures cover min/max, finite no-solution exhaustion, mixed goal depth, quota cycling, interrupted n-ary expansion and frontier-cap termination; unsupported parallel/tracer settings receive explicit diagnostics. + +## Validation + +- Build/run native examples and TSPTW correctness cases, compare fixed and progressive quotas with BAB/A*/CABS, and independently check all tours. +- Run native search/completion/worker-control/portfolio selections, check and native ASan -threads 1. +- Verify direct native linkage and public installed builder use without Model. + +## Result + +Added native progressive Column search and fixed-quota CBFS, preserving full cycles, choices, pruning and incomplete cap status; exposed ordinary builders and TSPTW controls. + +Validation: + +- Independent PASS at W79937a33b1892d77 with exact unchanged snapshot. +- Release and Model-disabled ASan builds; native quota/min-max/ownership/reopening/completion/parking/portfolio checks; exhaustive checked TSPTW fixed/progressive cases and bounded canonical validation passed. +- Repository check, inherited Model/open-list/regular selections and four open examples passed. Both installed ordinary-Space consumers run Column and column builders with search/int/kernel/support linkage and no Model/driver dependency. diff --git a/.zdev/open-search/tasks/011-expose-state-pruning-and-column-search-through-optional-inte.md b/.zdev/open-search/tasks/011-expose-state-pruning-and-column-search-through-optional-inte.md new file mode 100644 index 0000000000..039f66d2e7 --- /dev/null +++ b/.zdev/open-search/tasks/011-expose-state-pruning-and-column-search-through-optional-inte.md @@ -0,0 +1,47 @@ ++++ +schema_version = 1 +id = "open-search-011" +key = "optional-state-and-column-integration" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-008", "open-search-010"] ++++ +# Expose state pruning and column search through optional integrations + +## Outcome + +Model can retain explicit descriptor dependencies and configure the same native state pruning/column engine; ordinary-Space portfolio recipes safely compose column search with BAB. + +## Context + +Read the brief's Model/portfolio integration rules and native state-pruning declaration. Start at ModelHeuristicDependencies/ReadContext, CompiledModel::heuristic_guidance(), compile liveness/remapping, ModelHeuristicSearch and driver seams. SpaceRecipeCompleteAsset already queues owned incumbents and classifies local engine stops. Reuse these contracts. + +## Boundaries + +- No automatic residual-key/dominance/admissibility inference or new general analyzer; no Model/portfolio changes are required for native use. +- Only explicit dependencies retain auxiliaries. Keep existing A*/CABS calls compatible and avoid keep-all retention or output/dummy-constraint workarounds. + +## Done when + +- [x] A Model declaration produces owned native descriptors/relations from declared clone-local integer/Boolean/open-list reads, with absence/unsupported dependence handled explicitly. Artifacts and callbacks remain valid across cloning, compilation levels and source Model destruction. +- [x] Model facade/driver can select column search and configure its quota/native resource limits through the supported public path; default bound remains propagated scalar objective. +- [x] The ordinary-Space column builder composes with BAB, receives external incumbents safely while parked, and ends locally on its resource cap without falsely stopping peers or certifying coverage. +- [x] Document worked native, Model and portfolio calls, and what current objective/dependency information supplies. Structural branching suggestions are explicit; no guessed key or estimate is presented as sound. +- [x] Focused retention/remapping and ordinary-Space portfolio checks verify declared versus unrelated auxiliary survival, descriptor ownership, local-cap recovery, and native use without Model linkage. + +## Validation + +- Run existing Model heuristic/compile retention/driver and native portfolio lifecycle selections, check and relevant examples. +- Build and run an installed Model consumer using the existing find_package/exported-target pattern, and a native consumer linking only search/int. +- Use tiny semantic cases rather than a new integration harness. + +## Result + +Added explicit retained Model state-pruning dependencies and native callback conversion; exposed Column facade and driver quota/cap controls with worked native, Model, and portfolio examples. + +Validation: + +- Independent verifier PASS at Wa12df7a5d16a6b12; unchanged candidate. Release build/check, focused Model/native/inherited suites, examples, driver proof/cap/rejection journeys, native ASan and installed Model/native consumers passed. Native search/int linkage excludes Model/driver; Model ASan remains unavailable and known parallel harness limitation retained. diff --git a/.zdev/open-search/tasks/012-build-a-small-resumable-tsptw-comparison-runner.md b/.zdev/open-search/tasks/012-build-a-small-resumable-tsptw-comparison-runner.md new file mode 100644 index 0000000000..5434fac624 --- /dev/null +++ b/.zdev/open-search/tasks/012-build-a-small-resumable-tsptw-comparison-runner.md @@ -0,0 +1,47 @@ ++++ +schema_version = 1 +id = "open-search-012" +key = "tsptw-benchmark-runner" +area = "open-search" +status = "done" +complexity = "standard" +afk = true +priority = "normal" +blocked_by = ["open-search-006", "open-search-007"] ++++ +# Build a small resumable TSPTW comparison runner + +## Outcome + +A named run captures correct comparable TSPTW results, resumes missing work, and regenerates analysis without rerunning solvers. + +## Context + +Read the brief's budget/timing rules and ../background/next-search-and-benchmarks.md comparison. Start with misc/ existing uv benchmark CLIs and their subprocess measurement helpers; reuse only suitable low-level pieces. Apply the zayenz-benchmark-script skill when implementing. Do not import the unrelated FlatZinc pipeline wholesale. + +## Boundaries + +- One measured subprocess at a time; no new benchmarking framework or option cross-product. +- Keep collection and analysis separate, use exact data/solver/config identities and simple records. Large campaigns remain bounded by the brief. + +## Done when + +- [x] An executable uv run --script CLI provides preview, named run/resume and analyze. Configuration changes invalidate reuse; finished raw records remain unchanged and interrupted rows alone resume. +- [x] The runner captures checked incumbent traces, final objective, proof/incomplete/stop reason, startup/preprocessing/search wall time, expansions, peak RSS and supported frontier/descriptor/clone measurements. Missing measurements are explicit. +- [x] Every returned incumbent is independently checked. Invalid results, crashes, watchdog timeout and RSS termination are retained as failures, never converted into proof. +- [x] A pinned DIDPPy adapter runs travel-cost TSPTW with identical scaling, depot return rules, valid bounds and nonmetric reachability; its tours use the same checker. Native and DIDPPy processes share thread/time/RSS-watchdog settings. +- [x] Tiny smoke collection, resume, one intentionally missing row, configuration mismatch, timeout/failure, and offline reanalysis are demonstrated. Matrix preview calculates the full budget before execution. + +## Validation + +- Exercise the actual CLI with disposable named correctness runs and inspect records, then resume and analyze without target invocation. +- Compare tiny DIDPPy A*/CABS optima/infeasibility with enumeration and both native formulations. +- Keep new tests only for nontrivial record matching/parser/measurement correctness; use direct smoke checks for CLI/help. + +## Result + +Implemented a serial resumable TSPTW comparison runner with eight native and DIDPPy configurations, independent incumbent validation, pinned DIDPPy adapter, phase timing, and offline analysis. + +Validation: + +- Independent whole-task PASS against Wad702d9acbcd63ae; build, five focused tests, executable help, diff check, and budget preview passed. Forty fresh tiny-instance rows across eight configurations matched exhaustive enumeration. Resume, selective recovery, mismatch rejection, guarded offline analysis, six failure modes, RSS cleanup, and actual solver-constructor timing passed. No development or held-out campaign was launched; RSS sampling and unavailable frontier/clone measurements are documented. diff --git a/.zdev/open-search/tasks/013-tune-and-freeze-the-conventional-gecode-baseline.md b/.zdev/open-search/tasks/013-tune-and-freeze-the-conventional-gecode-baseline.md new file mode 100644 index 0000000000..86971c0114 --- /dev/null +++ b/.zdev/open-search/tasks/013-tune-and-freeze-the-conventional-gecode-baseline.md @@ -0,0 +1,45 @@ ++++ +schema_version = 1 +id = "open-search-013" +key = "tsptw-cp-baseline-tuning" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-006", "open-search-012"] ++++ +# Tune and freeze the conventional Gecode baseline + +## Outcome + +Development measurements select a strong conventional CP baseline using existing propagation, branching and search facilities, then freeze it before held-out evaluation. + +## Context + +Read the brief's development budget and conventional-versus-transition distinction. Start with the closed model's costed circuit/time constraints, examples/tsp.cpp regret branching, native BAB/restart options and existing ordinary-Space LNS recipe seams. This task makes best Gecode can offer a measured comparison rather than a deliberately weak straw baseline. + +## Boundaries + +- At most six named candidate configurations, including the initial BAB baseline; no new routing global or engine. +- Use development cases only and at most 45 minutes solver wall budget, two repetitions at 15 seconds/case. No tuning on held-out outcomes. + +## Done when + +- [x] The candidates justify propagation level and at most a small set of branching choices; include an existing restart or LNS recipe when it can be configured soundly and adds a meaningful alternative within the six-candidate cap. +- [x] Each candidate uses the same objective/data/checker and reports its resources. Local LNS completion never becomes a global proof; exact peers retain proof responsibility. +- [x] Paired development results include validity, time/quality/proof and RSS. Freeze the selected closed CP formulation/branching/BAB configuration and, if useful, an explicit anytime recipe; retaining the initial baseline is a valid measured outcome. +- [x] Document the actual comparison, counterproductive variants and the frozen baseline command so later engine/formulation results can be interpreted. + +## Validation + +- Run the bounded named development matrix, check all incumbents and replay analysis from saved records. +- Run tiny correctness and existing native portfolio/restart checks for touched configuration changes; no new tests are expected for tuning-only settings. + +## Result + +Measured five conventional CP candidates and froze DOM/size BAB. Across 24 paired development samples it produced 20 feasible runs and 18 proofs versus initial regret BAB 16/12; the AFG regression is documented. Restart did not improve coverage, so no anytime recipe is recommended. + +Validation: + +- Independent whole-task PASS against W3b9ba286a0e3b921. Release build, existing runner/TSPTW/RBS/portfolio checks and bounded five-candidate tiny, stop, resume, mismatch, interruption and offline-analysis checks passed. All 120 campaign records and 806 incumbents were independently rechecked; completed resume preserved all 360 raw files and launched no samples. Campaign wall was 792.229 seconds; tuning and compilation before review used 896.421 seconds. Charging the entire verifier interval a conservative 600 seconds gives 1496.421 seconds within the 2700-second tuning ceiling; exact duration of its unwrapped correctness command is unavailable. No held-out collection or second tuning matrix. Sampled RSS and unavailable frontier/clone/isolated phase metrics remain explicit. diff --git a/.zdev/open-search/tasks/014-investigate-fixed-width-beam-stack-regeneration-in-gecode.md b/.zdev/open-search/tasks/014-investigate-fixed-width-beam-stack-regeneration-in-gecode.md new file mode 100644 index 0000000000..3f2f2f4594 --- /dev/null +++ b/.zdev/open-search/tasks/014-investigate-fixed-width-beam-stack-regeneration-in-gecode.md @@ -0,0 +1,45 @@ ++++ +schema_version = 1 +id = "open-search-014" +key = "beam-stack-feasibility" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "low" +blocked_by = ["open-search-007", "open-search-012"] ++++ +# Investigate fixed-width beam-stack regeneration in Gecode + +## Outcome + +A bounded study establishes whether BSS-style regeneration can preserve coverage and reduce retained Space memory, and gives a concrete go/no-go follow-up recommendation. + +## Context + +Read ../background/next-search-and-benchmarks.md BSS recommendation, the CP 2026 original border/total-order passages, and native n-ary/prepare/incumbent invariants. Start with the worked transition model and eager control; compare a concrete-state border with a retained source clone/original Choice cursor. Gecode CP domains and weakly monotonic propagation are not the paper's concrete DP states. + +## Boundaries + +- Investigation only: keep prototypes outside tracked source and retain a concise reproducible conclusion, not a production engine or generic replay framework. +- At most ten minutes solver wall budget on tiny/development cases; no claim of O(Bd) bytes or universal Space ordering. + +## Done when + +- [x] Work out a coverage-preserving continuation contract for arbitrary native Spaces or identify the exact model restriction needed. Include equal ranking/bounds, n-ary alternatives, changing incumbents, variable-depth goals and interrupted expansion. +- [x] A tiny prototype or established counterexample checks no lost alternative against enumeration; report regenerations, peak retained Spaces/Choice/border data and measured process/Space cost separately. +- [x] The conclusion compares BSS with current CABS storage/repetition on the same transition model and explains whether total ordering, descriptors or saved choices suffice. +- [x] Retain source-backed findings and reproduction commands in an indexed area background document. Recommend a bounded implementation task only if supported, otherwise specify why to defer; production adoption requires a separately reviewed follow-up. + +## Validation + +- Read original definitions/proofs and inspect the prototype's exact tiny coverage cases independently. +- Replay the recorded bounded commands and compare checked tours and memory counts; no new production tests are expected. + +## Result + +Retained an indexed BSS continuation study: immutable border pagination and original-Choice cursors preserve checked coverage on restricted deterministic transitions. Cursor used fewer commitments with different ordering; generic Space heuristic border replay is deferred. No production engine or follow-up task added. + +Validation: + +- Independent verifier PASS at Wf4c8eae006f1eef7; original sources and native contracts inspected; fresh compilation plus all 73 documented replay launches/checks passed. Tiny tour identities and 720 failed paths match enumeration, optimum 9/infeasibility and external adoption checked, all logical counts reproduced. Combined 221 solver launches took 4.292976246913895 s under 600 s; compilation 11.489148541998584 s separate. RSS varies and does not establish exact Space bytes or universal gains; deployment-target warnings recorded. Exact snapshot comparisons stayed equal. diff --git a/.zdev/open-search/tasks/015-investigate-suffix-compatible-beam-repair-with-existing-lns.md b/.zdev/open-search/tasks/015-investigate-suffix-compatible-beam-repair-with-existing-lns.md new file mode 100644 index 0000000000..7df8dd5b48 --- /dev/null +++ b/.zdev/open-search/tasks/015-investigate-suffix-compatible-beam-repair-with-existing-lns.md @@ -0,0 +1,45 @@ ++++ +schema_version = 1 +id = "open-search-015" +key = "lnbs-repair-feasibility" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "low" +blocked_by = ["open-search-007", "open-search-012", "open-search-013"] ++++ +# Investigate suffix-compatible beam repair with existing LNS + +## Outcome + +A bounded TSPTW repair study determines how LNBS-style path neighborhoods can reuse Gecode search and portfolio recipes without requiring identical boundary resource states. + +## Context + +Read the LNBS repair study in ../background/next-search-and-benchmarks.md. Under /Users/zayenz/lab/knowledge/cpkb, read wiki/sources/2023-kuroiwa-large-neighborhood-beam-search-for-domain-indepe.md and sources/derived/papers/2023-kuroiwa-large-neighborhood-beam-search-for-domain-indepe/fulltext.md, especially suffix rollout. Start with the open transition state, its checker, and SpacePortfolioLnsRecipe in gecode/search/portfolio.hpp for neighborhood/build/search lifecycle; compare with the frozen existing CP LNS/BAB configuration. + +## Boundaries + +- Investigation only, with one fixed segment-neighborhood rule; no new universal LNBS engine, bandit selector or scheduler. +- At most ten minutes solver wall budget on tiny/development cases. Repair quality and global proof are separate. + +## Done when + +- [x] Demonstrate prefix preservation and independent suffix executability after repairing a segment. A changed boundary time/cost is allowed when the complete suffix stays feasible; exact old boundary equality is not imposed accidentally. +- [x] Tiny cases include a repair with changed resources, an infeasible suffix and objective accounting across prefix/repair/suffix. Existing inner BAB/CABS can be compared without promoting a local optimum to global proof. +- [x] Report checked improvements, repair overhead/storage and the concrete existing recipe/brancher gaps. +- [x] Retain an indexed source-backed conclusion and exact reproduction commands, recommending one bounded follow-up only if useful. No production LNBS integration is silently included. + +## Validation + +- Check every repaired full tour independently and compare tiny segment optima with enumeration. +- Replay the bounded study and assess ownership/incumbent/stop behavior in the existing recipe seams; no production test harness is added. + +## Result + +Retained indexed LNBS-style fixed-segment repair findings: complete suffix rollout permits changed boundary resources, native BAB/CABS reach checked local optima, and actual one-attempt recipes publish the first inner candidate. Local proof and unrestricted global coverage remain separate. Development neighborhoods found no improvement; no production follow-up recommended. + +Validation: + +- Independent whole-task PASS at W5ec7ddc9c0849cfd with equal snapshot comparisons. Original LNBS passage and native recipe/Transition contracts checked; fresh compile and all 24 replay outcomes passed, partial resume and material-change rejection checked. Exact six-permutation neighborhoods, all full tours and 720 full permutations independently checked; local/global proof, interruption, ownership and incumbent probes passed. Combined 75 solver attempts took 1.8115819989761803 s under 600 s; compilation 7.448068998957751 s separate. Two development comparisons use frozen DOM/size BAB. RSS/timing are limited single-run process observations; deployment-target warnings retained. diff --git a/.zdev/open-search/tasks/016-evaluate-frozen-formulations-and-engines-on-held-out-tsptw.md b/.zdev/open-search/tasks/016-evaluate-frozen-formulations-and-engines-on-held-out-tsptw.md new file mode 100644 index 0000000000..0c350164a3 --- /dev/null +++ b/.zdev/open-search/tasks/016-evaluate-frozen-formulations-and-engines-on-held-out-tsptw.md @@ -0,0 +1,47 @@ ++++ +schema_version = 1 +id = "open-search-016" +key = "tsptw-heldout-evaluation" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-009", "open-search-010", "open-search-012", "open-search-013", "open-search-014", "open-search-015"] ++++ +# Evaluate frozen formulations and engines on held-out TSPTW + +## Outcome + +A bounded held-out study compares strong closed CP, open transitions and DIDPPy, separates engine/representation/pruning effects, and recommends defaults and any further search work from measured evidence. + +## Context + +Read the brief's exact evaluation matrix/budget and the retained runner, frozen development choices and BSS/LNBS conclusions. The six native configurations are closed BAB/CABS and transition BAB/A*/CABS/column; DIDPPy A*/CABS adds two references. The eager transition control and pruning/bound/propagation ablations use a small development subset inside the total budget. + +## Boundaries + +- Freeze configurations and held-out selection before measurement; do not retune on held-out results or claim an overall Gecode/DIDP ranking. +- At most 120 minutes total solver wall budget, 24 held-out cases, 30 seconds each, one thread and the same documented 1 GiB RSS-watchdog policy. A larger campaign needs a separate budget. + +## Done when + +- [x] The runner records the complete frozen matrix, checker results and honest failures/time/resource stops. Missing references or unsupported measurements remain visible rather than being estimated. +- [x] Report first solution and quality over time, final cost/gap where a valid reference exists, proof/infeasibility coverage, elapsed time, expansions, RSS and retained frontier/descriptor/Space costs, including setup and inference overhead. +- [x] Compare engines on identical models first; compare closed/open formulations separately. Bounded development ablations isolate eager/open path allocation, dominance, cheap bounds and residual propagation one change at a time. +- [x] Reproduce summaries from retained raw records and distinguish canonical families from generated variants. A slow or unsuccessful option is an acceptable finding. +- [x] Publish useful public documentation and benchmark commands, chosen explicit defaults and a concrete next-work recommendation grounded in the BSS/LNBS studies and measured bottlenecks. Do not silently implement deferred algorithms or close the area. + +## Validation + +- Preview budget and frozen identities, execute the bounded named campaign, independently check all publications and replay analysis without solver invocation. +- Run the applicable correctness/search/open-list/Model/portfolio checks and repository check target for any final default/config changes. +- No tests for report formatting or broad configuration matrix; retain exact commands, counts, limitations and results. + +## Result + +Implemented the frozen benchmark presets, development selector, exact argument validation, cumulative budget accounting and offline comparison support. Completed all 192 held-out observations and published the reproducible summary and README results. + +Validation: + +- Independent whole-task PASS against W8867a7f4bdb6afaa; final comparison equal. Verified all 192 frozen rows, 101 identities, 569 terminal and three archived publications, 81 summaries, 34 optimal and ten infeasible proofs. Public summary reproduces byte-for-byte; completed resume and changed-cap rejection launch no solvers or alter captures. Native search/Model/open-list/portfolio selections, seven benchmark tests, repository check and full TSPTW validator passed; historical tuning remains analyzable. Required correctness replay passed in its original no-RSS wrapper after the extra 1 GiB wrapper stopped an initial attempt; that failure remains charged and benchmark limits are unchanged. Verification used 141.712165 seconds of 300; total charge 4031.446727 of 7200 comprises 3845.615011 measured seconds and 185.831716 conservative cleanup seconds, no pending process. Two interrupted termination attempts are preserved and disclosed. Raw captures and exact commands remain under build/tsptw-results/heldout-comparison-016 and /tmp/open-search-016-study; findings empty. diff --git a/.zdev/open-search/tasks/017-keep-column-frontier-selection-interruptible.md b/.zdev/open-search/tasks/017-keep-column-frontier-selection-interruptible.md new file mode 100644 index 0000000000..1840d44dc7 --- /dev/null +++ b/.zdev/open-search/tasks/017-keep-column-frontier-selection-interruptible.md @@ -0,0 +1,41 @@ ++++ +schema_version = 1 +id = "open-search-017" +key = "interrupt-column-selection" +area = "open-search" +status = "done" +complexity = "standard" +afk = true +priority = "high" +blocked_by = [] ++++ +# Keep column frontier selection interruptible + +## Outcome + +Column honors ordinary stops and worker parking while discarding obsolete frontier entries, and resumes with its search position intact. + +## Context + +Review at f4523c04be found that gecode/search/column.cpp:187–218 drains bound-pruned entries inside select() without consulting Worker::stop; next() checks only before entering it. A newly adopted incumbent can invalidate a large frontier. A*/CABS already return through a stop check between discarded entries. Read the existing Column lifecycle fixtures in test/search.cpp and the worker-control adapter before changing selection. + +## Boundaries + +- Preserve cycle depth, quota accounting, original Choice coverage and natural-exhaustion semantics; this is an interruption fix, not a different column algorithm. + +## Done when + +- [x] Selection yields to an ordinary failure/time stop or worker-control request during a stale-frontier drain. +- [x] Resuming preserves the remaining queue and reaches the same optimum or exhaustion as uninterrupted search. + +## Validation + +- Add one deterministic existing-fixture regression that queues several nodes, makes them obsolete with an incumbent, stops after a small number of discarded nodes and resumes. Run the focused Column and existing parked-portfolio checks; no new harness. + +## Result + +Column polls the existing stop hook during frontier selection, preserving queued work and search position across interruption. + +Validation: + +- Build and focused Search::Column and Search::Portfolio::PublicContract checks passed; the new stale-frontier regression failed on the original code and passes with the fix. Independent verification passed and the reviewed context remained unchanged. diff --git a/.zdev/open-search/tasks/018-keep-benchmark-cleanup-and-proof-outcomes-honest.md b/.zdev/open-search/tasks/018-keep-benchmark-cleanup-and-proof-outcomes-honest.md new file mode 100644 index 0000000000..c49c2c77e2 --- /dev/null +++ b/.zdev/open-search/tasks/018-keep-benchmark-cleanup-and-proof-outcomes-honest.md @@ -0,0 +1,43 @@ ++++ +schema_version = 1 +id = "open-search-018" +key = "repair-benchmark-outcomes" +area = "open-search" +status = "done" +complexity = "standard" +afk = true +priority = "high" +blocked_by = [] ++++ +# Keep benchmark cleanup and proof outcomes honest + +## Outcome + +The TSPTW runner cannot resume past an unconfirmed child exit or publish contradictory feasible and infeasible outcomes for one case. + +## Context + +misc/tsptw-benchmark.py:455–461 writes a reap event even if kill_group() raises; budget_state():74–85 then removes that pending launch. README.md:646 records observed termination-permission interruptions that required manual recovery. comparison_report():631–656 rejects inconsistent optimality claims but omits the symmetric check for an infeasibility claim alongside another row's independently checked tour. + +## Boundaries + +- Keep recovery local to the existing runner and single-writer workflow. Preserve archived runs and their historical interpretation; do not rewrite frozen results or rerun a campaign. +- A failed spawn that created no child may close its attempt; an unconfirmed termination must remain unresolved. Do not signal a recovered PID without confirming it still belongs to the recorded launch. + +## Done when + +- [x] Only confirmed child exit, confirmed absence during explicit recovery, or a failed spawn closes the launch; unresolved termination blocks new solver launches and reports useful owned-process identity. +- [x] Cleanup failures preserve the original error and partial captures, and elapsed cleanup work is accounted for without claiming an unobserved reap time. +- [x] An infeasibility proof combined with any checked tour for the same case is rejected, including tours from timeout/RSS rows. + +## Validation + +- Add focused fixtures to misc/test-tsptw-benchmark.py for kill/wait failure and blocked resume, successful exit/recovery, and cross-row infeasibility contradictions. Run the existing offline runner tests. No solver campaign is needed. + +## Result + +Runner preserves unresolved launch identity and partial captures, blocks unsafe resume, supports absence-only recovery with conservative timing, and rejects infeasibility claims alongside checked tours. + +Validation: + +- All 11 offline runner tests, recovery help, syntax compilation and diff checks passed. Independent verification passed with unchanged context; no solver campaign or archived-result changes. diff --git a/.zdev/open-search/tasks/019-state-the-progress-and-incumbent-obligations-of-heuristic-se.md b/.zdev/open-search/tasks/019-state-the-progress-and-incumbent-obligations-of-heuristic-se.md new file mode 100644 index 0000000000..740cb622aa --- /dev/null +++ b/.zdev/open-search/tasks/019-state-the-progress-and-incumbent-obligations-of-heuristic-se.md @@ -0,0 +1,42 @@ ++++ +schema_version = 1 +id = "open-search-019" +key = "clarify-pruning-and-proof-contracts" +area = "open-search" +status = "done" +complexity = "standard" +afk = true +priority = "high" +blocked_by = [] ++++ +# State the progress and incumbent obligations of heuristic search + +## Outcome + +Native and Model users can supply sound state pruning and interpret exhaustion correctly after external incumbent adoption. + +## Context + +gecode/search.hh:1199–1203 and docs/astar.md require preservation of residual feasibility/cost but omit progress toward a solution. In a finite root→A→B→goal chain, A/B can have identical domains/costs but different brancher stages; a persistent ancestor descriptor can suppress B. Kuroiwa and Beck, Domain-Independent Dynamic Programming (doi:10.1016/j.artint.2026.104506), Definition 14 explicitly requires an at-least-as-good completion with no more transitions (CPKB paper 2026-kuroiwa-domain-independent-dynamic-programming, fulltext lines 574–606). TSPTW's remaining-visit key already preserves progress. Separately docs/model/README.md:2501–2503 overlooks an accepted external incumbent, while test/model/runtime.cpp:17683–17692 already covers exhaustion without a new publication. + +## Boundaries + +- Treat pruning as an expert-supplied soundness obligation; do not infer stage, dominance or admissibility automatically or add mandatory descriptor fields. +- Preserve the existing native engine behavior and scalar finite-tree scope. + +## Done when + +- [x] The native and Model pruning contracts explicitly cover residual brancher/search progress, with a sufficient-stage-key or declined-extraction example and the unsafe domain-only counterexample. +- [x] All affected proof instructions refer to the best accepted incumbent, including external adoption; no newly returned solution does not imply infeasibility when an incumbent was adopted. + +## Validation + +- Independently check the analytical finite-stage counterexample and sufficient-key explanation against the persistent registry code and paper Definition 14. Reuse the existing graph and external-incumbent fixtures as evidence; no new automated tests are expected for this documentation/contract clarification. + +## Result + +Native and Model contracts preserve residual brancher progress, show the unsafe domain-only chain and stage-key or declined-extraction remedy, and interpret proof using the best accepted incumbent including external adoption. + +Validation: + +- Independent analytical/source verification checked persistent registries, DIDP Definition 14, graph/history/reopening fixtures and all three Model incumbent-adoption cases. Diff check passed; documentation-only change required no new tests. diff --git a/.zdev/open-search/tasks/020-compose-model-bounds-and-ranking-with-independent-dependenci.md b/.zdev/open-search/tasks/020-compose-model-bounds-and-ranking-with-independent-dependenci.md new file mode 100644 index 0000000000..04c2fd9d03 --- /dev/null +++ b/.zdev/open-search/tasks/020-compose-model-bounds-and-ranking-with-independent-dependenci.md @@ -0,0 +1,43 @@ ++++ +schema_version = 1 +id = "open-search-020" +key = "separate-model-bound-rank" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = [] ++++ +# Compose Model bounds and ranking with independent dependencies + +## Outcome + +Separately authored Model bound and ranking helpers compose without sharing or rewriting their dependency indexes, and export the existing native guidance values. + +## Context + +gecode/model/runtime.hh:705–707 takes one dependency list for both callbacks, model.cpp:2591–2593 rejects a second declaration, and runtime.cpp:1522–1526 gives both callbacks the same index context. A bound helper using index zero for X and an independent rank helper using index zero for Y cannot be directly composed. Native HeuristicGuidance already has independently replaceable callbacks. Read Model declaration, retention/remapping and compile/runtime tests before choosing the smallest separate declaration API. + +## Boundaries + +- Keep the combined heuristic_guidance call as a compatible convenience; define conflicts between combined and separate declarations clearly. +- Retain only the union of explicitly declared dependencies while preserving each callback's own indexing. Export the unchanged native HeuristicGuidance; no new generic policy framework or automatic heuristic inference. + +## Done when + +- [x] A bound and a rank with different index-zero dependencies can be authored independently, compiled together and evaluated correctly on clones. +- [x] Bound-only, rank-only with the safe objective default, and the existing combined call retain their documented behavior and lifetime guarantees. +- [x] Conflicting declarations fail clearly and leave the Model declaration state consistent. + +## Validation + +- Extend the existing Model guidance fixtures with one independent-dependency composition witness and conflicting declaration coverage; reuse O0–O3 retention, remapping and artifact-lifetime checks. Demonstrate reuse with native A*/CABS/Column without duplicating their ordering tests. + +## Result + +Added independent heuristic_bound and heuristic_rank declarations, callback-specific dependency contexts and union retention while preserving atomic combined convenience and unchanged native guidance. + +Validation: + +- Baseline and final build and all four Model heuristic fixtures passed. O0-O3 remapping, independent indexes, clone and artifact lifetime, open-list permissions, transactional conflicts and native AStar/CABS/Column reuse were independently verified. Diff check passed and review context remained unchanged. diff --git a/.zdev/open-search/tasks/021-let-the-owning-model-search-facade-accept-a-native-builder.md b/.zdev/open-search/tasks/021-let-the-owning-model-search-facade-accept-a-native-builder.md new file mode 100644 index 0000000000..81342f9f30 --- /dev/null +++ b/.zdev/open-search/tasks/021-let-the-owning-model-search-facade-accept-a-native-builder.md @@ -0,0 +1,45 @@ ++++ +schema_version = 1 +id = "open-search-021" +key = "compose-model-native-builders" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = [] ++++ +# Let the owning Model search facade accept a native builder + +## Outcome + +Model root/output ownership composes with an already configured native Search::Builder, while the existing engine-selection constructor remains convenient. + +## Context + +ModelHeuristicSearch in gecode/model/runtime.hh:1292–1307 and runtime.cpp:1554–1578 currently selects through an enum, reconstructs guidance/pruning and carries options for unrelated engines. CompiledModel already exports native guidance and pruning, and Search::Builder exposes options() and best(). A builder-taking path lets one compiled artifact vary ranking, pruning and engine configuration without adding another Model switch. + +## Boundaries + +- Reuse Search::Builder and the existing ownership conventions; preserve the convenience constructor by delegating through the same construction path. +- The new path consumes the builder, sets its clone option to true for the facade's temporary owned root, and uses its stop option for root construction. Stop objects remain borrowed for the search lifetime. Accept compatible best-search builders, including BAB, within the facade's scalar-integer objective scope; reject null or non-best builders before creating the root. ModelSpace compatibility remains the caller's obligation. +- Do not make native engines depend on Model or introduce a second Model search-policy hierarchy. + +## Done when + +- [x] A caller can pass a configured native builder and reuse one compiled Model with changed/disabled ranking or pruning. +- [x] The old convenience call uses the same ownership-safe path, and metadata/rendering remain valid for the documented lifetime. +- [x] Construction failure, null/unsuitable builder and capped incomplete termination have clear behavior without leaking or double-owning roots. + +## Validation + +- Extend existing Model facade tests with configured native heuristic builders and BAB, one lifetime/construction-failure case and a capped run. Check the consumed builder/root ownership and borrowed stop behavior. Reuse existing native semantic checks; no duplicate engine matrix. + +## Result + +Added a consuming native-builder Model facade; preserved caller configuration and delegated enum convenience through the same ownership-safe path. + +Validation: + +- Independent review PASS at Wcd81d673bb35ab96; both completion comparisons equal. +- Release builds, focused Model/AStar/CABS/Column tests, check target, heuristic quickstart and four inherited open examples passed; git diff --check clean. diff --git a/.zdev/open-search/tasks/022-compose-explicit-model-portfolio-recipes-with-native-heurist.md b/.zdev/open-search/tasks/022-compose-explicit-model-portfolio-recipes-with-native-heurist.md new file mode 100644 index 0000000000..edcf9c9ff6 --- /dev/null +++ b/.zdev/open-search/tasks/022-compose-explicit-model-portfolio-recipes-with-native-heurist.md @@ -0,0 +1,46 @@ ++++ +schema_version = 1 +id = "open-search-022" +key = "compose-model-portfolio-heuristics" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-021"] ++++ +# Compose explicit Model portfolio recipes with native heuristic builders + +## Outcome + +An owning Model portfolio can run explicitly configured native heuristic recipes alongside BAB through the same builder composition boundary. + +## Context + +gecode/model/portfolio.cpp:2153 limits CompleteEngine to DFS/BAB and make_engine():2444–2463 constructs that closed set. Ordinary-Space portfolios already compose A*/CABS/Column builders with safe incumbent adoption and local resource-stop handling. Use the builder/Model ownership boundary introduced by compose-model-native-builders and existing authored-root recipes; read ModelEngineAsset restriction/rebuild and capacity protocols before integration. + +## Boundaries + +- Start with explicit scalar-integer authored-root recipes. Keep existing portfolio defaults and scheduling policies; no automatic heuristic portfolio selection. +- Preserve restriction and incumbent delivery at safe asset-thread boundaries and authored branchers. Reject unsupported restart/parallel combinations rather than silently altering the engine. +- Do not add a second scheduler or Model-specific bound/rank/pruning implementation. + +## Done when + +- [x] Explicit A*, CABS and Column recipes reuse native builders, guidance and pruning through the Model portfolio. +- [x] A width/frontier-stopped heuristic asset contributes no coverage proof, while a BAB peer can finish and establish the result. +- [x] Incumbents, supported restrictions/rebuilds, worker parking and recipe lifetimes preserve the existing Model portfolio contracts. + +## Validation + +- Extend the existing bounded Model portfolio journey with a configured heuristic recipe and BAB, an external improvement, and capped incomplete termination followed by BAB completion. Reuse focused native parking and Model restriction/rebuild checks; add only regressions needed at the new adapter boundary. + +## Result + +Added explicit owning Model portfolio recipes using native builders, authored roots and existing asset protocols; local caps permit BAB peer completion and ordinary incumbent delivery preserves native frontiers. + +Validation: + +- Independent whole-task PASS at W28f384ad4dbf4430; both snapshot comparisons equal. +- Release builds, twelve focused Model/native/worker selections, inherited open-list/extensional checks, check target, quickstart and four open examples passed; git diff --check clean. +- Worked CABS width-one example returned -2 Incomplete alone and -7 Optimal with BAB; capped Column also remained incomplete alone and allowed BAB completion. diff --git a/.zdev/open-search/tasks/023-expose-cabs-limits-in-the-model-driver.md b/.zdev/open-search/tasks/023-expose-cabs-limits-in-the-model-driver.md new file mode 100644 index 0000000000..80c6c3dda6 --- /dev/null +++ b/.zdev/open-search/tasks/023-expose-cabs-limits-in-the-model-driver.md @@ -0,0 +1,42 @@ ++++ +schema_version = 1 +id = "open-search-023" +key = "model-driver-cabs-limits" +area = "open-search" +status = "done" +complexity = "standard" +afk = true +priority = "normal" +blocked_by = [] ++++ +# Expose CABS limits in the Model driver + +## Outcome + +Model CLI users can set native CABS width and retained-frontier limits without writing a custom search wrapper. + +## Context + +gecode/model/driver.hh:829–834 always constructs Search::CABSOptions() while the adjacent Column wrapper receives configured limits. docs/model/README.md documents this restriction. Native CABS and ModelHeuristicSearch already support both limits. + +## Boundaries + +- Keep unlimited defaults and pass options directly to native CABSOptions. Preserve the distinction between local resource termination and proof. + +## Done when + +- [x] Two documented driver controls reach CABSOptions and appear in help. +- [x] Width- and frontier-limited runs report stopped/incomplete rather than infeasible or optimal solely because the configured limit was reached. + +## Validation + +- Extend the existing Model driver fixture with one width-limited and one frontier-limited run; reuse native CABS proof/stop tests. Build the worked Model example and inspect help output. + +## Result + +Added unlimited-default CABS width/frontier controls to the Model driver, preserving native stopped versus proof semantics and documenting both controls in help. + +Validation: + +- Independent whole-task PASS at Wde5d6ab343ac2e2b; both exact snapshot comparisons equal. +- Required builds, focused driver/native/inherited tests, check target, worked-example help/default/limited runs and four open examples passed; git diff --check clean. diff --git a/.zdev/open-search/tasks/024-separate-persistent-descriptor-retention-from-search-schedul.md b/.zdev/open-search/tasks/024-separate-persistent-descriptor-retention-from-search-schedul.md new file mode 100644 index 0000000000..594d088981 --- /dev/null +++ b/.zdev/open-search/tasks/024-separate-persistent-descriptor-retention-from-search-schedul.md @@ -0,0 +1,45 @@ ++++ +schema_version = 1 +id = "open-search-024" +key = "share-persistent-state-registry" +area = "open-search" +status = "done" +complexity = "standard" +afk = true +priority = "normal" +blocked_by = [] ++++ +# Separate persistent descriptor retention from search scheduling + +## Outcome + +A* and Column share one small internal value-only registry, while each engine continues to own its search frontier and scheduling. + +## Context + +gecode/search/astar.cpp:110–152 and column.cpp:117–168 duplicate exact-key lookup, label comparison, dominated-label removal, forgetting and descriptor accounting; their seq headers duplicate storage. These are the same persistent-descriptor semantics with two real callers. CABS instead ties descriptors to live candidates and must keep that lifetime rule. + +## Boundaries + +- The registry owns descriptors and comparison/forgetting behavior, never Spaces, Choices, queue order or depth. +- Keep the engine sequence explicit: extract, check subsumption, admit frontier work, then retain its descriptor. Replacing metadata must not remove old pending work. +- Keep CABS's live-candidate storage separate; do not create a registry with algorithm modes or a universal search base class. + +## Done when + +- [x] Both A*/Column use one implementation for persistent registry semantics, limits and current storage accounting. +- [x] Engines retain responsibility for rejection counts and peak statistics; pruning disabled incurs no registry/extraction work. +- [x] Reset, better-label reopening, exact-key collision handling and metadata forgetting preserve current results. + +## Validation + +- Run the existing state-pruning, A*, Column and lifecycle cases before and after the behavior-preserving extraction. No new tests are expected unless a previously untested changed contract is identified. + +## Result + +Extracted a shared value-only StateRegistry for A*/Column; frontier admission, scheduling, rejection and peak statistics stay with each engine, and CABS storage remains separate. + +Validation: + +- Independent whole-task PASS at Wb77139e403543c13; both exact comparisons equal. +- Existing six native/state-pruning/lifecycle cases passed before and after extraction. Independent final Release and ASAN builds/cases/examples, repository check, inherited tests and four open examples passed; packaging includes new header; git diff --check clean. diff --git a/.zdev/open-search/tasks/025-share-resumable-choice-expansion-and-avoid-the-final-clone.md b/.zdev/open-search/tasks/025-share-resumable-choice-expansion-and-avoid-the-final-clone.md new file mode 100644 index 0000000000..37432297e5 --- /dev/null +++ b/.zdev/open-search/tasks/025-share-resumable-choice-expansion-and-avoid-the-final-clone.md @@ -0,0 +1,44 @@ ++++ +schema_version = 1 +id = "open-search-025" +key = "share-choice-expansion" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "low" +blocked_by = [] ++++ +# Share resumable Choice expansion and avoid the final clone + +## Outcome + +The three native engines reuse a small internal owner of original-Choice continuation and can transfer the final parent into its child instead of cloning it. + +## Context + +The prepare/choice/commit/cut/status/feedback sequence repeats in gecode/search/astar.cpp:203–236, cabs.cpp:269–305 and column.cpp:246–286. Every alternative currently clones the parent; CABS/Column immediately retire it after the final commit. gecode/search/seq/path.hpp:235–245 provides the established last-alternative reuse precedent. Gecode's separation of state restoration, tree construction and exploration is also discussed by Tack and Le Botlan, Compositional Abstractions for Search Factories (CPKB paper 2004-tack-compositional-abstractions-for-search-factories, fulltext lines 104–148). + +## Boundaries + +- Design the small call sequence first: an internal component owns the selected parent, original Choice and cursor; engines still own frontier selection, quota/layer advancement, incumbent acceptance and proof status. +- Keep incumbent revision/depth explicit and preserve branch feedback and preparation suspension. No recomputation, new public Space virtuals, common engine inheritance or policy-mode framework. + +## Done when + +- [x] A*/CABS/Column reuse the same original-choice continuation mechanism without changing their documented search order or coverage. +- [x] Final-alternative transfer avoids one expansion clone per fully expanded parent, with no use of a Choice after its lifetime ends. +- [x] Publication, exceptions, external cuts, ordinary stop/resume and frontier-quota accounting retain correct ownership and continuation. + +## Validation + +- Run existing native lifecycle/preparation, n-ary continuation, incumbent, cap/reset and focused portfolio tests before and after the change. Use one bounded clone-count comparison on unary and n-ary fixtures to establish eliminated clones; use the existing sanitizer selection for changed ownership. Do not infer a runtime speedup from clone counts. + +## Result + +AStar, CABS and Column share original-Choice continuation and reuse the selected parent for its final alternative. Scheduling, incumbent cuts, stops, cap accounting and proof responsibilities remain separate. Contract-valid bounded evidence confirms one avoided expansion copy per fully expanded parent. + +Validation: + +- Independently verified the whole open-search-025 task against admitted snapshot W39fd00b0331993bd at HEAD 14d6ad440b852ff4380e03e63fca6f62f011e22e. Reviewed all 12 changed paths, including the new internal header, callers, ownership, tests, documentation and installation rules. ChoiceExpansion preserves original choices and preparation across interruptions/publications, transfers the final parent with its Choice alive through commit/cut/status/feedback, destroys the Choice before its source on exception/reset/destruction, and retires the parent before cap admission. Engine scheduling, explicit revisions/depth, incumbent ownership, registries, descriptor accounting and proof decisions remain intact; no public framework or Model dependency was added. The focused feedback tests meaningfully cover final-alternative feedback and final-commit exception cleanup. Corrected temporary clone evidence satisfies scalar objective, comparison, strict-cut and admissible-bound contracts. Confirmed the six baseline engine sources/headers byte-exact against HEAD, baseline factory/next definitions through nm/link map, and runtime factory images through dladdr. Ran each bounded clone executable once; outputs exactly match stored corrected evidence. Unary expansion clones decrease 5→0 for all engines, with five fully expanded parents. Ternary AStar/Column decrease 39→26 with 13 fully expanded parents; CABS decreases 57→38 with 19. Every other field matches, including the strict sequence 0 through -26, 27 solutions, node/failure/restart/depth counts, construction/restart/publication/adoption copies and stop/proof outcomes. These corrected counts supersede invalid earlier evidence and imply no runtime claim. Required commands passed: cmake --build build/open-search --target gecode-test astar model-open-maintenance model-open-miconic model-open-valves model-open-puzzle -j 8; build/open-search/bin/gecode-test -iter 1 -threads 1 -test Search::AStar -test Search::CABS -test Search::Column -test Search::Completion::Native -test Search::TSPTW::Transition -test Search::BranchFeedback -test Search::WorkerControl::Pause -test Search::Portfolio::PublicContract; cmake --build build/open-search --target check -j 8; build/open-search/bin/gecode-test -iter 1 -threads 1 -test Int::OpenList -test Model::OpenLists -test Int::Extensional::Reg -test Model::Portfolio::Restrictions -test Model::Portfolio::Sequential -test Model::Portfolio::ActivationRoots -test Search::Portfolio::PublicContract; build/open-search/bin/astar; and all four default model-open examples. Also passed cmake --build build/open-search-asan --target gecode-test astar -j 8, the identical focused selection using build/open-search-asan/bin/gecode-test, and build/open-search-asan/bin/astar without sanitizer diagnostics. ASan remains Debug with Model/set/float/FlatZinc disabled; main-thread harness preserves the known parallel-harness stack limitation while concurrent asset cases still run. git diff --check passed and snapshot comparison returned equal:true. Validation wrote only temporary clone-output files and ignored build/check artifacts; no source, task, index or completed task028 ledger changes. +- Verifier evidence: work_context_snapshot: W39fd00b0331993bd. Exact baseline14d6ad440b852ff4380e03e63fca6f62f011e22e source/next linkage confirmed. Corrected unary counts5->0/full5 perengine; ternary AStar/Column39->26/full13, CABS57->38/full19. All strict improvement sequences, node/failure/restart/depth counts and stop/proof outcomes matched. Counts establish copies avoided, not runtime gain. diff --git a/.zdev/open-search/tasks/026-remove-cabs-full-beam-descriptor-recounts.md b/.zdev/open-search/tasks/026-remove-cabs-full-beam-descriptor-recounts.md new file mode 100644 index 0000000000..f8701602f0 --- /dev/null +++ b/.zdev/open-search/tasks/026-remove-cabs-full-beam-descriptor-recounts.md @@ -0,0 +1,45 @@ ++++ +schema_version = 1 +id = "open-search-026" +key = "incremental-cabs-descriptor-accounting" +area = "open-search" +status = "done" +complexity = "standard" +afk = true +priority = "normal" +blocked_by = [] ++++ +# Remove CABS full-beam descriptor recounts + +## Outcome + +CABS maintains descriptor counts and storage incrementally instead of scanning every candidate after each admission. + +## Context + +gecode/search/cabs.cpp:145,155,165 call record_descriptors():170–185, which scans the entire beam. Even an extractor that always declines with nullopt causes quadratic descriptor inspections while filling width W. Frozen Pareto-OFF results do not measure this cost. Matching candidates also uses a linear scan, but changing that index is a separate question. + +## Boundaries + +- Preserve live-next-layer-only pruning, multiple-victim replacement, beam truncation and metadata forgetting. +- Keep counters local to candidate ownership. Do not introduce a hash index, persistent CABS registry or generalized beam container as part of this task. + +## Done when + +- [x] Admission/replacement/removal updates current descriptor count and bytes without a full-beam statistics scan. +- [x] Layer/pass/reset and descriptor-limit forgetting update accounting correctly, and public peak statistics keep their documented meaning. +- [x] Declining extraction does not add work proportional to retained beam size solely for statistics. + +## Validation + +- Run existing beam-discarded representative, incomparable label, replacement and forgetting fixtures. Add only a focused accounting regression if current assertions cannot check the new totals; use a bounded declining-extractor work comparison, not a benchmark campaign. + +## Result + +CABS maintains live candidate descriptor count/bytes incrementally, preserving forgetting, replacement, layer/reset and public peak semantics. + +Validation: + +- Independent whole-task PASS at W99f8fb3bca8bf9ff; both exact comparisons equal. +- Before/after native tests and tightened baseline-library fixture, Release/ASAN builds and main-thread tests, check target, inherited tests and examples passed; git diff --check clean. +- Bounded declining-extractor comparison preserved extracts/nodes/restarts/zero peaks and removed source-derived 43946/700074/11188906 full-beam statistics inspections; timings illustrative only. diff --git a/.zdev/open-search/tasks/027-close-cabs-passes-when-discarded-work-is-bounded-out.md b/.zdev/open-search/tasks/027-close-cabs-passes-when-discarded-work-is-bounded-out.md new file mode 100644 index 0000000000..c0ed50fa09 --- /dev/null +++ b/.zdev/open-search/tasks/027-close-cabs-passes-when-discarded-work-is-bounded-out.md @@ -0,0 +1,46 @@ ++++ +schema_version = 1 +id = "open-search-027" +key = "cabs-discarded-bound-completion" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = [] ++++ +# Close CABS passes when discarded work is bounded out + +## Outcome + +CABS can prove completion after a truncated pass when every width-discarded alternative is unable to improve the accepted incumbent. + +## Context + +gecode/search/cabs.cpp:148–155 records only truncated, and advance():213–219 always widens or stops afterward. At width one, keeping A(bound 0), dropping B(bound 20), then finding cost 10 under A permits a proof once retained work finishes. Kuroiwa and Beck's DIDP Theorem 20 accounts for discarded-state bounds (doi:10.1016/j.artint.2026.104506; CPKB paper 2026-kuroiwa-domain-independent-dynamic-programming, fulltext 1871–1903). This is a new capability, not a defect in the existing conservative contract. + +## Boundaries + +- Keep proof accounting independent of beam ranking and retain only an optimistic aggregate for width-discarded work. +- Check the shortcut only after all retained work and original alternatives in the pass are processed. Frontier-cap loss and ordinary interruption remain incomplete unless separately proven under an explicit contract. +- No global dual-bound reporting API, weighted A*, new engine family or changed defaults. + +## Done when + +- [x] Rejected and evicted width candidates contribute sound directional bounds, including arbitrary ranking and inconsistent admissible estimates. +- [x] A completed pass closes before widening/width-limit termination when its discarded work cannot improve the incumbent. +- [x] If any dropped bound still permits improvement, widening and capped-stop behavior remain conservative; reset/pass accounting and external incumbents are correct. + +## Validation + +- Extend existing CABS fixtures with minimization/maximization proof shortcuts, an opposite-ranking case and a discarded improving branch that must prevent proof. Reuse interrupted-pass and dominance tests to check that pending work is never omitted. + +## Result + +CABS proves completed truncated passes when the directional aggregate of actual width-discarded bounds cannot improve the accepted incumbent; unresolved loss remains conservative. + +Validation: + +- Independent whole-task PASS at W184b370fc65c9fc8; both exact comparisons equal. +- Baseline/final Release tests and new finite-graph proofs, Release/ASAN builds/tests/examples, repository check and inherited open integration passed; git diff --check clean. +- Tests cover min/max reject/evict equality, opposite ranking/inconsistent admissible bounds/multiple layers, original alternatives, interruption/resume, external optimum, and reset after cap/widening. diff --git a/.zdev/open-search/tasks/028-evaluate-a-cheap-tsptw-residual-deadline-filter.md b/.zdev/open-search/tasks/028-evaluate-a-cheap-tsptw-residual-deadline-filter.md new file mode 100644 index 0000000000..cc3f4c81c3 --- /dev/null +++ b/.zdev/open-search/tasks/028-evaluate-a-cheap-tsptw-residual-deadline-filter.md @@ -0,0 +1,46 @@ ++++ +schema_version = 1 +id = "open-search-028" +key = "cheap-tsptw-deadline-filter" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-018"] ++++ +# Evaluate a cheap TSPTW residual deadline filter + +## Outcome + +TSPTW has a sound cheap inference option and a bounded development comparison showing when it is useful between OFF and full unary inference. + +## Context + +Transition::refresh_inference in examples/tsptw-transition.hh:291–319 offers OFF or a fresh ResidualSchedule and full fixpoint on each committed successor. Individual shortest-path deadline bounds already appear in ResidualSchedule:275–280. Normal held-out summaries report roughly 80% of BAB/CABS search time in inference, with killed runs unmeasured. Marijnissen et al., Domain-Independent Dynamic Programming with Constraint Propagation (doi:10.1609/icaps.v36i1.42826), equations 28–30, use a shortest-path residual dead-end test (CPKB paper 2026-marijnissen-domain-independent-dynamic-programming-with-cons, fulltext 475–504). + +## Boundaries + +- Add an explicit necessary customer-deadline/depot-return check using shortest paths; keep nonmetric/asymmetric data sound. Do not change Gecode's fixpoint policy. +- Preserve frozen configurations, held-out observations and defaults. Keep bound, ranking, dominance and inference choices independent; compare native engines on the same formulation. +- Before running, select six existing tiny/development cases covering loose, tight and infeasible behavior; use the existing tiny infeasible fixtures as controls. Compare OFF/cheap/UNARY with BAB/CABS, two repetitions and a two-second cap: 144 nominal solver seconds. Cap total launch-through-exit solver work including validation at 300 seconds; report builds separately. No held-out or larger campaign. +- Use fresh task-local admission/accounting for one shared 300-second allowance across implementation and independent verification, recording solver validation, failed/interrupted launches and cleanup. The historical 7200-second ledger and phase reserves do not apply. Reuse the existing runner/capture and saved-record format with the smallest necessary budget extension; preserve historical interpretation. + +## Done when + +- [x] The cheap mode preserves all feasible completions and uses no residual unary Space. +- [x] The small fixed-case comparison reports checked solutions/proofs, inference time, node counts, stops and missing measurements without claiming a universal speedup. +- [x] Results recommend whether to keep the option and whether a separately scoped incremental unary or fixed-width continuation study is justified; defaults remain unchanged. + +## Validation + +- Extend the existing residual-completion enumeration with the cheap filter and the nonmetric counterexample, plus waiting/return boundaries already represented in fixtures. Run only the declared bounded comparison using the repaired runner; preserve partial failures and stop when the budget is spent. + +## Result + +Added sound CHEAP residual deadline inference without a unary Space and completed the fixed 72-row OFF/CHEAP/UNARY BAB/CABS comparison. Retain the explicit option; defaults remain unchanged. + +Validation: + +- Reviewed all eight source/report paths and the attributed task clarification against the original clean baseline and admitted frozen snapshot. CHEAP uses sound shortest-path customer/return bounds for asymmetric, nonmetric, folded-service and waiting semantics, constructs no residual unary Space, and preserves independent bound/ranking/pruning choices, defaults, frozen configuration arguments, historical ledger interpretation, manifest and held-out reports. Required verification passed: prebuild; cmake check target with 25-second cap; Search::TSPTW::Transition with 3-second cap; inherited open-list, regular/NFA, portfolio and search selection with 15-second cap; four inherited default examples with 1-second caps; six independently checked CHEAP BAB/CABS waiting/late-customer/late-return checks with 2-second caps; 12 Python benchmark tests; git diff --check. Rechecked all 72 saved terminal rows, 476 terminal publications and 3 partial publications; confirmed 49 summaries and 23 timeouts, proof consistency, input hashes, resume/rejection evidence and exact offline regenerated core-report equality. Recommendations and missing-measurement limitations match the evidence. Verification charged 26.873740248091053 seconds, including the reaped sandbox-interrupted attempt; final shared usage is 197.83128824434243 of 300 seconds, with no pending launches. Verification build took 0.11097958299797028 seconds separately. Validation wrote only ignored build evidence and the authorized external ledger; no source, report or .zdev validation writes occurred. +- Verifier evidence: work_context_snapshot: Wcafb228c60862ab9. Shared task budget final charge 197.83128824434243/300 seconds: implementation170.95754799625138, verification26.873740248091053; no pending launches. Builds implementation12.273696000047494 and verification0.11097958299797028 seconds separately. diff --git a/.zdev/open-search/tasks/029-add-configurable-native-best-first-ordering.md b/.zdev/open-search/tasks/029-add-configurable-native-best-first-ordering.md new file mode 100644 index 0000000000..38aab327da --- /dev/null +++ b/.zdev/open-search/tasks/029-add-configurable-native-best-first-ordering.md @@ -0,0 +1,47 @@ ++++ +schema_version = 1 +id = "open-search-029" +key = "rank-first-best-first" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-024", "open-search-025"] ++++ +# Add configurable native best-first ordering + +## Outcome + +Ordinary Spaces can use bound-first or rank-first best-first exploration through shared native search machinery, with ranking independent of sound pruning and completion. + +## Context + +AStar currently compares bound, then rank, then insertion order in gecode/search/astar.cpp. HeuristicGuidance already separates bound and rank. Read the brief's Additional engine work section, search.hh, astar.cpp, its seq header and native search tests. Tasks 024 and 025 provide the shared descriptor registry and original-Choice continuation. Hansen and Zhou's Anytime Heuristic Search (CPKB 2007-hansen-anytime-heuristic-search, fulltext lines 140–176) motivates separating inadmissible exploration from admissible pruning. + +## Boundaries + +- Keep the existing AStar API and its default ordering compatible. Choose the smallest native API that exposes ordering through a normal Search::Builder; do not introduce a universal policy framework or require Model, portfolio, open lists, or new Space virtual methods. +- Reuse the existing frontier implementation, shared descriptor registry and Choice continuation. Preserve ownership, incumbent updates, stops/resume, reset and optional StatePruning contracts. +- Rank is lower-first and may be inadmissible. Bound retains its sense-aware pruning/proof contract. Define deterministic ties and missing-rank behavior; use bound-first fallback when no rank is supplied. +- Provide greedy h and weighted g+w*h examples only for additive minimization with explicit g and h and checked arithmetic. Do not reinterpret a CP total bound as remaining h. Dynamic weights, ARA* repair/rekeying, first-goal optimality and approximation-ratio guarantees are outside this task. + +## Done when + +- [x] A documented native builder exposes bound-first and rank-first exploration without duplicating the engine, and existing AStar callers retain their behavior. +- [x] Ordering, sound incumbent pruning and completion remain separate: misleading rank may change the first incumbent but cannot cause an incorrect optimum or completion claim for either objective sense. +- [x] A small ordinary-Space example demonstrates greedy and fixed weighted ranking; arbitrary ranking and optional descriptor pruning remain composable. +- [x] Documentation explains callback lifetime, ties, fallback, numeric requirements and when completion proves optimality, using existing native contracts where they already suffice. + +## Validation + +- Build the affected search library and example. Add focused semantic coverage showing an actual exploration-order difference and a misleading first goal followed by the optimum; cover both objective senses without a full option matrix. +- Run existing AStar/state-pruning, lifecycle, stop/resume and relevant native builder/portfolio tests. Reuse these tests rather than adding callback-wiring tests or a performance campaign. + +## Result + +Added native configurable best-first ordering through the existing AStar worker, preserving legacy defaults and independent admissible-bound pruning; demonstrated greedy and fixed weighted ranking on an ordinary Space. + +Validation: + +- Verified open-search-029 against snapshot Wa9e80630fff49a1b at HEAD 01928ba0870add826081661d50e7828c64cefa67. Reviewed the whole attributed delta, public APIs and callers, shared frontier comparator, bound-only pruning, continuation/reset ownership, optional descriptor pruning, documentation and example arithmetic. Existing AStar signatures/default ordering remain compatible; rank-first changes exploration without changing sound completion. Independently passed Release configure/build, all six focused native selections, full check, inherited open-list/regular/Model portfolio selection, both native examples and all four inherited examples, native linkage inspection and git diff --check. Greedy example returned 10 then 5; fixed weighted ranking returned 5; both proved completion. Reviewed tracked contents, index, HEAD and untracked example blob still match the frozen snapshot. Validation intentionally updated ignored build artifacts and /private/tmp verification logs; no task-owned source or record writes occurred. Baseline validation was historical evidence rather than independently recreated. Autotools, Windows and sanitizer configurations were not exercised; no new ownership boundary required conditional ASan, and no benchmark campaign ran. diff --git a/.zdev/open-search/tasks/030-evaluate-sequential-cadds-retained-fragment-search.md b/.zdev/open-search/tasks/030-evaluate-sequential-cadds-retained-fragment-search.md new file mode 100644 index 0000000000..64a0eeedfa --- /dev/null +++ b/.zdev/open-search/tasks/030-evaluate-sequential-cadds-retained-fragment-search.md @@ -0,0 +1,49 @@ ++++ +schema_version = 1 +id = "open-search-030" +key = "retained-fragment-study" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = ["open-search-018", "open-search-025"] ++++ +# Evaluate sequential CADDS retained-fragment search + +## Outcome + +A bounded CPU prototype study determines whether deepest-layer retained-fragment search merits a production Gecode engine, based on checked coverage, anytime behavior and memory cost. + +## Context + +Read the brief's Additional engine work section, background/beam-stack-regeneration.md and the completed TSPTW measurements before prototyping. The existing Column engine retains depth queues but uses a different progressive schedule. The primary CADDS reference is Tardivo, Michel and van Hoeve, Complete Anytime Decision Diagram Search with GPU-Accelerated State Expansion, CPAIOR 2026, DOI 10.1007/978-3-032-27242-3_34 (CPKB 2026-tardivo-gpu-accelerated-state-expansion-for-anytime-exac, fulltext lines 279–403). Study its sequential retained-fragment schedule, not its GPU speedup claims. Task 025 provides Choice continuation; task 018 repairs run outcome and process cleanup evidence. + +## Boundaries + +- Use a temporary sequential prototype with ordinary Space/Choice semantics and existing guidance. Freeze the best min(B, layer size) nodes at the deepest nonempty layer, expand the entire fragment before descending, retain unselected nodes and surviving successors, and check goals at every committed depth. Preserve fragment and Choice cursors across incumbent publication or suspension. +- Do not truncate pending work. Keep admissible pruning separate from ranking. Disable optional state pruning for this initial comparison so an unbounded registry cannot silently invalidate the memory analysis. +- No GPU, relaxed decision-diagram merging, generic transition DSL, deterministic replay requirement, public production engine, or broad benchmark campaign. A documented negative adoption result completes this task. +- Use a fixed small development selection and a few fragment widths selected before measuring; keep formulation, bound, rank, propagation and branching equal across comparisons. Do not tune on or rerun held-out cases. +- Allow at most 300 seconds cumulative solver wall time, measured from launch through confirmed exit, shared by implementation and independent verification including correctness runs; build time is separate. Use sequential execution and the existing 1 GiB RSS watchdog policy. Keep a task-local budget record and do not reset or consume the historical 7200-second campaign ledger. + +## Done when + +- [x] Tiny ordinary-Space cases establish n-ary coverage, goals at varying depths, strict incumbent improvement, stop/resume and exact finite-tree exhaustion; the complete-fragment schedule is distinguished from descending after each parent. +- [x] The prototype is compared with CABS and the existing fixed-width source/alternative cursor on the same small development selection. If temporary cursor code is unavailable, reconstruct the minimal documented control; do not relabel it as beam-stack search. +- [x] Results report checked solutions, proof/stop outcomes, time to first and best solution, runtime and peak retained state/memory where measurable. Explain the O(d*B*delta) retained-node argument and its assumptions; do not equate it with byte usage or the BSS O(B*d) claim. +- [x] A concise durable background report, indexed from the brief, records replay commands, source references, the bounded results and an adopt/defer/reject conclusion. Any production follow-up is proposed separately; no production engine is added by this study. + +## Validation + +- Read and validate the sequential schedule against the primary CADDS source. Use tiny exhaustive or independently checked cases for correctness and the existing TSPTW checker for benchmark incumbents. +- Retain enough prototype source and invocation detail with the report to reproduce the comparisons without relying on /tmp survival. Reserve part of the shared 300-second allowance for independent verification and report incomplete or unsupported measurements honestly. + +## Result + +Retained a replayable standalone sequential CADDS/cursor/CABS study with checked coverage and bounded development comparisons. Results support deferring a production engine; no production search API or library was changed. + +Validation: + +- Independently reviewed the complete task-030 candidate at Wf5b649718be1301c and expected HEAD. CADDS follows the primary source's frozen whole-fragment schedule; original Choice continuation, sound pruning, stop/resume, ownership, finite coverage and memory qualifications are supported. Independent compilation succeeded in 2.459331292 seconds, with deployment-target warnings only. The semantic witness, eight selected tiny rows and seven frozen development controls passed. Rechecked all 25 original tiny rows and 30 development rows offline, accepting 19 and 48 publications; regenerated compact artifact matches exactly, and report numerical claims match records. Final shared ledger usage is 61.300001249008346 seconds, including 11.879608832939994 verification seconds: 76 launches, 76 reaps, no pending processes or launch records. Historical 016/028 ledger hashes remain unchanged. Validation wrote only temporary binaries, captures and analyses; candidate Git status and diffs still match the immutable snapshot. Production checks appropriately reuse the unchanged task-029 baseline. Evidence remains limited to the frozen development selection, sampled RSS and standalone ASan executable linked with uninstrumented Release libraries; the report states these limits and supports defer. +- Implementation compilation 12.985523124982137 seconds plus independent compilation 2.459331292 seconds, counted separately from the final shared 61.300001249008346-second solver ledger. From 9ef6d841b44b3638892219e936e7c6ef25c9dd4b Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 13 Oct 2026 06:49:54 +0200 Subject: [PATCH 02/24] Add tasks to open-search Zdev-Change-Id: Zf7164128cb1c60f3502e6dd904bfae78a1c067fc6b1e32fac4d8d76ab5c76bec --- .zdev/open-search/TASKS.md | 5 ++- ...uct-standalone-native-search-on-open-cp.md | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 .zdev/open-search/tasks/031-reconstruct-standalone-native-search-on-open-cp.md diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index dc4ece8b33..64f0f4b4e7 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -2,8 +2,8 @@ # Tasks: open-search -- Total: 30 -- Ready: 0 +- Total: 31 +- Ready: 1 - Blocked: 0 - Done: 30 @@ -39,3 +39,4 @@ | [open-search-028](tasks/028-evaluate-a-cheap-tsptw-residual-deadline-filter.md) | Evaluate a cheap TSPTW residual deadline filter | done | open-search-018 | | [open-search-029](tasks/029-add-configurable-native-best-first-ordering.md) | Add configurable native best-first ordering | done | open-search-024, open-search-025 | | [open-search-030](tasks/030-evaluate-sequential-cadds-retained-fragment-search.md) | Evaluate sequential CADDS retained-fragment search | done | open-search-018, open-search-025 | +| [open-search-031](tasks/031-reconstruct-standalone-native-search-on-open-cp.md) | Reconstruct standalone native search on open-cp | ready | — | diff --git a/.zdev/open-search/tasks/031-reconstruct-standalone-native-search-on-open-cp.md b/.zdev/open-search/tasks/031-reconstruct-standalone-native-search-on-open-cp.md new file mode 100644 index 0000000000..453e9b65ec --- /dev/null +++ b/.zdev/open-search/tasks/031-reconstruct-standalone-native-search-on-open-cp.md @@ -0,0 +1,38 @@ ++++ +schema_version = 1 +id = "open-search-031" +key = "split-native-branch" +area = "open-search" +status = "open" +complexity = "advanced" +afk = true +priority = "normal" +blocked_by = [] ++++ +# Reconstruct standalone native search on open-cp + +## Outcome + +feature/open-search contains the implemented native heuristic search and benchmarks on feature/open-cp without Model or portfolio dependencies. + +## Context + +Read the approved branch split section of brief.md. The backup branch preserves the complete implementation. Extract native AStar, configurable best-first ordering, CABS and Column engines, guidance, optional state pruning, native examples and TSPTW tooling, and only necessary generic kernel/search contracts. Preserve newer open-cp worker-control and open-list fixes. Existing task records describe completed combined-branch work. + +## Boundaries + +- Do not modify backup or parent branches, Model or portfolio implementations, or zdev records as a source worker. +- Preserve implemented native behavior and orthogonal ranking, bounds and state pruning; no new engine or performance campaign. +- Use existing tests and build systems; adapt focused semantic coverage only where extraction changes contracts. + +## Done when + +- [ ] Native AStar, configurable best-first ordering, CABS and Column engines, plus native examples/benchmarks, build and run on the open-cp base. +- [ ] Model and portfolio source, build targets and dependencies are absent from native search additions. +- [ ] Minimal generic prerequisite contracts preserve ordinary Space use and newer base behavior. + +## Validation + +- Fresh Release build and full existing check suite. +- Focused search, open-list, regular, comparison/continuation/worker-control checks as applicable; native examples and tiny TSPTW correctness. +- Inspect extracted diff against the backup and open-cp base for omitted behavior or unrelated dependencies. From be91368abf580fcd9e1e7aae1c33d2e56917336f Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 13 Oct 2026 07:20:26 +0200 Subject: [PATCH 03/24] Extract standalone heuristic search onto open-cp Zdev-Change-Id: Zc598681fcab190d75394270ba287657f523f67370c84776cda96117c3db3269a --- .zdev/open-search/TASKS.md | 6 +- ...uct-standalone-native-search-on-open-cp.md | 17 +- Makefile.in | 15 +- cmake/GecodeSources.cmake | 3 + docs/astar.md | 206 + docs/cabs.md | 152 + docs/column.md | 84 + docs/tsptw-cheap-deadline-filter.md | 187 + examples/CMakeLists.txt | 13 + examples/astar.cpp | 130 + examples/best-first.cpp | 147 + examples/data/tsptw/README.md | 778 ++ examples/data/tsptw/bounds-comparison.json | 1457 +++ examples/data/tsptw/cadds-comparison.json | 3344 +++++ .../data/tsptw/cheap-deadline-comparison.json | 4662 +++++++ examples/data/tsptw/cp-baseline-tuning.json | 1335 ++ examples/data/tsptw/heldout-comparison.json | 10068 ++++++++++++++++ examples/data/tsptw/manifest.json | 443 + examples/data/tsptw/published/AFG/rbg010a.tw | 24 + .../data/tsptw/published/AFG/rbg010a.tw.tour | 1 + .../data/tsptw/published/Dumas/n20w20.001.txt | 43 + .../tsptw/published/Dumas/n20w20.001.txt.tour | 1 + .../GendreauDumasExtended/n20w120.001.txt | 43 + .../n20w120.001.txt.tour | 1 + .../SolomonPotvinBengio/rc_201.1.txt | 41 + .../SolomonPotvinBengio/rc_201.1.txt.tour | 1 + examples/data/tsptw/tiny/decimal.txt | 7 + examples/data/tsptw/tiny/folded-service.txt | 8 + examples/data/tsptw/tiny/late-customer.txt | 7 + examples/data/tsptw/tiny/late-return.txt | 7 + examples/data/tsptw/tiny/waiting.txt | 8 + examples/tsptw-data.hh | 143 + examples/tsptw-transition.hh | 355 + examples/tsptw.cpp | 516 + gecode/kernel/core.cpp | 49 +- gecode/kernel/core.hpp | 56 +- gecode/kernel/exception.cpp | 3 + gecode/kernel/exception.hpp | 7 + gecode/minimodel.hh | 6 + gecode/minimodel/optimize.cpp | 26 + gecode/search.hh | 298 + gecode/search/astar.cpp | 244 + gecode/search/astar.hpp | 103 + gecode/search/branch-feedback.hh | 176 + gecode/search/cabs.cpp | 357 + gecode/search/cabs.hpp | 88 + gecode/search/column.cpp | 284 + gecode/search/column.hpp | 90 + gecode/search/completion.hh | 142 + gecode/search/exception.cpp | 37 + gecode/search/exception.hpp | 60 + gecode/search/seq/astar.hh | 104 + gecode/search/seq/cabs.hh | 114 + gecode/search/seq/choice-expansion.hh | 133 + gecode/search/seq/column.hh | 110 + gecode/search/seq/state-registry.hh | 105 + gecode/search/statistics.hpp | 7 +- gecode/search/support.hh | 25 + gecode/search/worker.hh | 9 + misc/test-tsptw-benchmark.py | 284 + misc/tsptw-acquire.py | 80 + misc/tsptw-benchmark.py | 902 ++ misc/tsptw-cadds-study.cpp | 441 + misc/tsptw-cadds-study.py | 284 + misc/tsptw-check.py | 127 + misc/tsptw-didppy.py | 86 + misc/tsptw-read.cpp | 14 + misc/tsptw-validate.py | 395 + test/search.cpp | 1864 ++- 69 files changed, 31341 insertions(+), 22 deletions(-) create mode 100644 docs/astar.md create mode 100644 docs/cabs.md create mode 100644 docs/column.md create mode 100644 docs/tsptw-cheap-deadline-filter.md create mode 100644 examples/astar.cpp create mode 100644 examples/best-first.cpp create mode 100644 examples/data/tsptw/README.md create mode 100644 examples/data/tsptw/bounds-comparison.json create mode 100644 examples/data/tsptw/cadds-comparison.json create mode 100644 examples/data/tsptw/cheap-deadline-comparison.json create mode 100644 examples/data/tsptw/cp-baseline-tuning.json create mode 100644 examples/data/tsptw/heldout-comparison.json create mode 100644 examples/data/tsptw/manifest.json create mode 100644 examples/data/tsptw/published/AFG/rbg010a.tw create mode 100644 examples/data/tsptw/published/AFG/rbg010a.tw.tour create mode 100644 examples/data/tsptw/published/Dumas/n20w20.001.txt create mode 100644 examples/data/tsptw/published/Dumas/n20w20.001.txt.tour create mode 100644 examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt create mode 100644 examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt.tour create mode 100644 examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt create mode 100644 examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt.tour create mode 100644 examples/data/tsptw/tiny/decimal.txt create mode 100644 examples/data/tsptw/tiny/folded-service.txt create mode 100644 examples/data/tsptw/tiny/late-customer.txt create mode 100644 examples/data/tsptw/tiny/late-return.txt create mode 100644 examples/data/tsptw/tiny/waiting.txt create mode 100644 examples/tsptw-data.hh create mode 100644 examples/tsptw-transition.hh create mode 100644 examples/tsptw.cpp create mode 100644 gecode/search/astar.cpp create mode 100644 gecode/search/astar.hpp create mode 100644 gecode/search/branch-feedback.hh create mode 100644 gecode/search/cabs.cpp create mode 100644 gecode/search/cabs.hpp create mode 100644 gecode/search/column.cpp create mode 100644 gecode/search/column.hpp create mode 100644 gecode/search/completion.hh create mode 100644 gecode/search/seq/astar.hh create mode 100644 gecode/search/seq/cabs.hh create mode 100644 gecode/search/seq/choice-expansion.hh create mode 100644 gecode/search/seq/column.hh create mode 100644 gecode/search/seq/state-registry.hh create mode 100644 misc/test-tsptw-benchmark.py create mode 100644 misc/tsptw-acquire.py create mode 100755 misc/tsptw-benchmark.py create mode 100644 misc/tsptw-cadds-study.cpp create mode 100755 misc/tsptw-cadds-study.py create mode 100644 misc/tsptw-check.py create mode 100755 misc/tsptw-didppy.py create mode 100644 misc/tsptw-read.cpp create mode 100644 misc/tsptw-validate.py diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index 64f0f4b4e7..4892a70b69 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -3,9 +3,9 @@ # Tasks: open-search - Total: 31 -- Ready: 1 +- Ready: 0 - Blocked: 0 -- Done: 30 +- Done: 31 | ID | Task | State | Blocked by | | --- | --- | --- | --- | @@ -39,4 +39,4 @@ | [open-search-028](tasks/028-evaluate-a-cheap-tsptw-residual-deadline-filter.md) | Evaluate a cheap TSPTW residual deadline filter | done | open-search-018 | | [open-search-029](tasks/029-add-configurable-native-best-first-ordering.md) | Add configurable native best-first ordering | done | open-search-024, open-search-025 | | [open-search-030](tasks/030-evaluate-sequential-cadds-retained-fragment-search.md) | Evaluate sequential CADDS retained-fragment search | done | open-search-018, open-search-025 | -| [open-search-031](tasks/031-reconstruct-standalone-native-search-on-open-cp.md) | Reconstruct standalone native search on open-cp | ready | — | +| [open-search-031](tasks/031-reconstruct-standalone-native-search-on-open-cp.md) | Reconstruct standalone native search on open-cp | done | — | diff --git a/.zdev/open-search/tasks/031-reconstruct-standalone-native-search-on-open-cp.md b/.zdev/open-search/tasks/031-reconstruct-standalone-native-search-on-open-cp.md index 453e9b65ec..b3462b5244 100644 --- a/.zdev/open-search/tasks/031-reconstruct-standalone-native-search-on-open-cp.md +++ b/.zdev/open-search/tasks/031-reconstruct-standalone-native-search-on-open-cp.md @@ -3,7 +3,7 @@ schema_version = 1 id = "open-search-031" key = "split-native-branch" area = "open-search" -status = "open" +status = "done" complexity = "advanced" afk = true priority = "normal" @@ -27,12 +27,21 @@ Read the approved branch split section of brief.md. The backup branch preserves ## Done when -- [ ] Native AStar, configurable best-first ordering, CABS and Column engines, plus native examples/benchmarks, build and run on the open-cp base. -- [ ] Model and portfolio source, build targets and dependencies are absent from native search additions. -- [ ] Minimal generic prerequisite contracts preserve ordinary Space use and newer base behavior. +- [x] Native AStar, configurable best-first ordering, CABS and Column engines, plus native examples/benchmarks, build and run on the open-cp base. +- [x] Model and portfolio source, build targets and dependencies are absent from native search additions. +- [x] Minimal generic prerequisite contracts preserve ordinary Space use and newer base behavior. ## Validation - Fresh Release build and full existing check suite. - Focused search, open-list, regular, comparison/continuation/worker-control checks as applicable; native examples and tiny TSPTW correctness. - Inspect extracted diff against the backup and open-cp base for omitted behavior or unrelated dependencies. + +## Result + +Reconstructed standalone AStar/best-first, CABS and Column on open-cp with native guidance, pruning, examples and TSPTW tooling; extracted only required generic contracts and preserved sequential worker control. + +Validation: + +- Independent Release build, full check 474/474 and focused search/open-list/regular/comparison/completion/feedback/worker-control checks82/82 passed. Native examples, tiny TSPTW exhaustive/tour/stop checks,12 benchmark tests and CADDS witness passed; implementation additionally validated36 bundled published smokes (22 proven,14 stopped). Canonical data/frozen reports remain byte-identical. No Model or newer portfolio dependency remains. +- Independent review found and verified correction of the Make .hh header installation list. Actual Make expansion/prerequisite dry-run passes. Final snapshot Wc076ed98042cd853 compared equal before completion. Native base remains the approved55dc729c; parent later advanced independently. Canonical input whitespace intentionally preserved; source formatting passes. diff --git a/Makefile.in b/Makefile.in index f45570dc2b..51eeefb971 100755 --- a/Makefile.in +++ b/Makefile.in @@ -263,22 +263,22 @@ endif SEARCHSRC0 = \ stop options cutoff engine \ - dfs bab lds \ + dfs bab astar cabs column lds \ seq/rbs seq/dead seq/pbs par/pbs \ rbs pbs nogoods exception tracer \ cpprofiler/tracer worker-control SEARCHHDR0 = \ statistics.hpp stop.hpp options.hpp cutoff.hpp \ - support.hh worker.hh worker-control.hh exception.hpp engine.hpp base.hpp \ + support.hh branch-feedback.hh completion.hh worker.hh worker-control.hh exception.hpp engine.hpp base.hpp \ nogoods.hh nogoods.hpp build.hpp traits.hpp sebs.hpp \ seq/path.hh seq/path.hpp seq/dfs.hh seq/dfs.hpp \ - seq/bab.hh seq/bab.hpp seq/lds.hh seq/lds.hpp \ - seq/rbs.hh seq/rbs.hpp seq/dead.hh \ + seq/bab.hh seq/bab.hpp seq/astar.hh seq/cabs.hh seq/column.hh seq/lds.hh seq/lds.hpp \ + seq/rbs.hh seq/rbs.hpp seq/dead.hh seq/state-registry.hh seq/choice-expansion.hh \ seq/pbs.hh seq/pbs.hpp \ par/path.hh par/path.hpp par/engine.hh par/engine.hpp \ par/dfs.hh par/dfs.hpp par/bab.hh par/bab.hpp \ par/pbs.hh par/pbs.hpp \ - dfs.hpp bab.hpp lds.hpp rbs.hpp pbs.hpp \ + dfs.hpp bab.hpp astar.hpp cabs.hpp column.hpp lds.hpp rbs.hpp pbs.hpp \ relax.hh tracer.hpp trace-recorder.hpp \ cpprofiler/message.hpp cpprofiler/connector.hpp @@ -900,7 +900,7 @@ endif INTEXAMPLEHDR0 = \ scowl job-shop-instances INTEXAMPLESRC0 = \ - alpha bacp bibd donald efpa eq20 golomb-ruler \ + alpha astar best-first tsptw bacp bibd donald efpa eq20 golomb-ruler \ graph-color grocery ind-set magic-sequence magic-square \ money ortho-latin partition photo queens sudoku sudoku-advanced kakuro \ nonogram pentominoes crowded-chess black-hole \ @@ -912,7 +912,8 @@ INTEXAMPLESRC0 = \ qcp job-shop open-route common-pattern fifteen-puzzle -INTEXAMPLEHDR = $(INTEXAMPLEHDR0:%=examples/%.hpp) +INTEXAMPLEHDR = $(INTEXAMPLEHDR0:%=examples/%.hpp) \ + examples/tsptw-data.hh examples/tsptw-transition.hh INTEXAMPLESRC = $(INTEXAMPLESRC0:%=examples/%.cpp) ifeq "@enable_examples@" "yes" INSTALLEXAMPLEHDR = $(INTEXAMPLEHDR) diff --git a/cmake/GecodeSources.cmake b/cmake/GecodeSources.cmake index ad39499d45..cd319fe88e 100644 --- a/cmake/GecodeSources.cmake +++ b/cmake/GecodeSources.cmake @@ -31,7 +31,10 @@ set(GECODE_KERNEL_SOURCES ) set(GECODE_SEARCH_SOURCES + gecode/search/astar.cpp gecode/search/bab.cpp + gecode/search/cabs.cpp + gecode/search/column.cpp gecode/search/cpprofiler/tracer.cpp gecode/search/cutoff.cpp gecode/search/dfs.cpp diff --git a/docs/astar.md b/docs/astar.md new file mode 100644 index 0000000000..18e23e6108 --- /dev/null +++ b/docs/astar.md @@ -0,0 +1,206 @@ +# Native A* and best-first search + +`AStar` searches an ordinary `Space` subclass with its existing branching, +`compare`, and `constrain` methods. It returns caller-owned, strictly improving +solutions. The native API needs only the search and integer libraries. +[The closed example](../examples/astar.cpp) compares A*, CABS, and BAB on the same bounded model with objectives +4, -2, -7, and 1. Continuing A* proves -7 optimal. +[CABS guidance](cabs.md) explains the widening and resource-limited runs. + +```cpp +Search::HeuristicGuidance guidance(Search::HeuristicGuidance::MINIMIZE, + [](const Space& s) -> std::int64_t { + return static_cast(s).objective.min(); + }); +AStar search(root, guidance); +while (std::unique_ptr solution{search.next()}) { + // Read or retain the improvement; the caller owns it. +} +if (search.stopped()) { + // Raise or reset the stop limit and call next() again to resume. +} else { + // The best accepted incumbent is optimal; without one, the problem is infeasible. +} +``` + +The bound must be optimistic **total** objective value on a propagated stable +space: a lower bound for `MINIMIZE`, an upper bound for `MAXIMIZE`, and the exact +objective on every solution. A transition model supplies its own prefix plus +remaining-cost bound. The engine adds no cost and compares signed 64-bit values +directly. The bound, `compare`, and strict-improvement `constrain` must describe +the same compatible scalar integer objective. The caller must justify the bound +and keep callback arithmetic in range. + +A* selects the best total bound first. An optional third guidance argument +supplies a signed 64-bit rank; lower ranks break equal-bound ties, followed by +insertion order. Rank never prunes. A generated goal can be returned before a +better queued node is expanded, so the first result need not be optimal. +Only natural exhaustion after repeated `next()` calls proves the best accepted +incumbent optimal, including an adopted external incumbent. It proves +infeasibility only if no incumbent was accepted. Returning no new solution after +external adoption therefore does not imply infeasibility. + +`bestfirst` exposes a fixed exploration order through the same native +engine and ordinary `Search::Builder`: + +```cpp +std::unique_ptr builder( + bestfirst(guidance, Search::RANK_FIRST, options, pruning)); +std::unique_ptr engine((*builder)(root)); +while (std::unique_ptr solution{ + static_cast(engine->next())}) { + // Keep searching after the first incumbent. +} +``` + +`Search::BOUND_FIRST` compares optimistic total bound, lower rank, then insertion +order, preserving `AStar` and `astar` behavior. `Search::RANK_FIRST` +compares lower rank, optimistic total bound, then insertion order. Bounds follow +the objective sense; ranks are lower-first for either sense. A missing rank +callback gives every node rank zero, so rank-first falls back to bound-first. +The engine caches both values when admitting a stable branching node. Ordering +is fixed for the engine, including `reset`; there is no rekeying. + +Rank can be arbitrary or inadmissible. It selects the next pending branching +Space, while sound total bounds and the compatible incumbent restriction prune. +Generated solutions are still published immediately. Neither ordering guarantees +that the first goal is optimal or has the best rank. Natural finite-tree +exhaustion with `stopped()==false` proves the best accepted incumbent optimal, +or infeasibility when none exists, provided the bound, comparison and optional +state-pruning contracts preserve coverage. Best-first uses the callback lifetime, +numeric, ownership, stop, exception and descriptor contracts below. The native +factory is `Search::bestfirstengine(root, guidance, ordering, options, pruning)`; +`Search::astarengine` retains its signature and selects bound-first. + +The [additive example](../examples/best-first.cpp) minimizes two explicit stages. +The first-stage costs are 0 and 9, with final-arc costs `[5,6]` and `[1,2]`. +After a route decision, `g` is its prefix cost and `h` is its cheapest remaining +arc. At the root `g=0` and `h=5`; at a solution `g` is the full cost and `h=0`. +The admissible total bound is `g+h` throughout. + +Greedy rank `h` prefers the second route and publishes 10, then 5. Fixed weight +2 with rank `g+2*h` prefers the first route and publishes 5. Both runs continue +to prove the independently enumerated optimum 5. The example checks addition +and multiplication before evaluating them and rejects signed 64-bit overflow. +These ranks use actual remaining costs; a CP total objective bound is not a +remaining-cost estimate. The fixed weight supplies exploration order only, +with no first-goal optimality or approximation-ratio guarantee. Dynamic weights +and weighted-search repair are outside this API. + +Guidance is copied into engines and builders. Callbacks read the space passed to +them without posting constraints. Captured dependencies must outlive every +engine and builder using them; do not capture another space's local variables. +If guidance, comparison, or a model operation throws during search, destroy the +engine before using it again. Constructor failure leaves the supplied root with +the caller. After successful construction, `Options::clone=false` transfers the +root to the engine; the default clones it and leaves the supplied root with the +caller. + +Builders and external incumbents use the native engine interface: + +```cpp +std::unique_ptr builder(astar(guidance, options)); +std::unique_ptr engine((*builder)(root)); +engine->constrain(external_solution); +while (std::unique_ptr solution{engine->next()}) { + // Each returned solution strictly improves the accepted incumbent. +} +``` + +Equivalent or worse external solutions do not replace the incumbent; +incomparable ones raise `Search::Incomparable`. Each accepted incumbent has an +independent owned copy. Queued parents +and saved choices stay unchanged while alternatives remain. The engine commits +each original alternative on an owned child, cloning the selected parent for +earlier alternatives and transferring it for the final one. It then applies a +newer improvement restriction if the child has not inherited it. +Publication and interruption preserve all remaining n-ary alternatives and +interrupted brancher preparation. + +The engine stores a cloned frontier and a selected parent, with no hard memory +guarantee. `c_d` and `a_d` do not enable replay or recomputation. No-good extraction +returns the existing empty result. Ordinary stop objects preserve continuation; +capacity-one `WorkerControl` supports pause/resume. A* rejects expanded thread +counts other than one and rejects tracers explicitly. Statistics depth counts +committed search edges, independently of objective value. + +Optional state pruning is a separate, copied `Search::StatePruning` value. +Tree search is the default. An extractor returns `std::optional`: +`std::nullopt` leaves a node unregistered, while a descriptor owns a cached +`std::size_t hash`, a `std::vector key`, and a vector `label`. +Hashes select buckets; exact key equality is required before comparing labels. +The relation reports `EQUIVALENT`, `DOMINATES`, `DOMINATED`, or `INCOMPARABLE` +relative to its first argument. Equal labels keep one representative. + +The model author must justify every omitted historical fact and guarantee a +consistent, transitive relation preserving at-least-as-good feasible completions. +It must also preserve progress toward a solution under repeated pruning, +including residual brancher/search stages. A sufficient argument shows that +every completion of a suppressed node has an at-least-as-good completion from +its representative with no more remaining search transitions. This follows the +progress condition in Kuroiwa and Beck's +[Definition 14](https://doi.org/10.1016/j.artint.2026.104506). +Prefix objective cost belongs in this comparison; it cannot be inferred from +the optimistic total bound. Earlier time at greater cost may be incomparable. +Partially assigned CP stores need a sufficient key or declined extraction. +Callbacks read only the supplied stable Space, and captured dependencies follow +the guidance lifetime rules. Providing just one callback raises +`Search::UninitializedStatePruning` before root ownership transfers. + +For example, consider the finite chain `root -> A -> B -> goal`, where `A` and +`B` have identical variable domains and prefix costs but different brancher +stages. A domain-only key with equal labels is unsafe: A* and column search +retain `A`'s descriptor after expanding it, reject `B` as equivalent, and lose +the only route to the goal. `A` needs two remaining commits; `B` needs one. +For this chain, adding the exact brancher stage to the key distinguishes the +residual problems and preserves progress. More generally, include enough stage +and residual state to justify the relation, or return `std::nullopt` wherever +that information is unavailable. Search depth alone need not be sufficient. +The TSPTW example's remaining-visit set already distinguishes successive visit +stages. + +The [native TSPTW transition model](../examples/tsptw-transition.hh) supplies +a worked declaration for its own residual rules: + +```cpp +Search::StatePruning pruning = TSPTW::Transition::pareto_pruning(); +AStar search(root, TSPTW::Transition::guidance(), + options, pruning); +std::unique_ptr builder( + astar(TSPTW::Transition::guidance(), options, pruning)); +// The native factory also accepts pruning as its final argument. +``` + +A* retains nondominated descriptor values after expansion, without extra Space +clones. A better label enters the frontier before replacing dominated metadata, +even when an older representative already expanded. Admissible inconsistent +bounds therefore remain supported. Older pending Spaces and saved Choice +cursors remain intact and can do redundant work. Reset clears the metadata. +An optional `descriptor_limit` (the declaration's final argument, zero for +unlimited) clears registry metadata when admitting another label would exceed +it. Forgetting never deletes pending work or reports a stop; it only reduces +pruning. An empty declaration performs no extraction, descriptor allocation, +or registry construction. + +`Search::Statistics::state_pruned` counts rejected equivalent/dominated arrivals. +`state_descriptors` and `state_bytes` are peaks, aggregated by maximum when +statistics are combined. Bytes estimate descriptor objects and their owned +key/label vector capacities; bucket/container capacity, optional wrappers and +allocator overhead are excluded. They measure neither frontier Spaces nor +process memory, and a descriptor-count ceiling is not a byte limit. Disabled +pruning leaves all three fields zero. Pruning and forgetting preserve the +existing solution ownership, stop and proof rules. + +With the repository CMake build, run +`cmake --build build/open-search --target astar` and +`build/open-search/bin/astar`. Installed consumers can use +`find_package(Gecode CONFIG REQUIRED COMPONENTS search int)` and link +`Gecode::gecodesearch` plus `Gecode::gecodeint`. +Build and run the additive example with +`cmake --build build/open-search --target best-first` and +`build/open-search/bin/best-first`; it links the same native libraries. + +[Native column search](column.md) uses the same ownership, guidance and state +pruning declarations with retained depth queues. Its optional rank orders each +queue independently of bound, and full cycles increase the expansion quota. +Quota growth zero selects fixed-quota CBFS. diff --git a/docs/cabs.md b/docs/cabs.md new file mode 100644 index 0000000000..9d467cbc55 --- /dev/null +++ b/docs/cabs.md @@ -0,0 +1,152 @@ +# Native complete anytime beam search + +`CABS` searches an ordinary `Space` subclass with its existing branching, +`compare`, and strict-improvement `constrain` methods. It needs no Model, +portfolio, or open list. Like [A*](astar.md), it returns caller-owned, strictly +improving scalar integer solutions through `next()`. + +```cpp +Search::HeuristicGuidance guidance(Search::HeuristicGuidance::MINIMIZE, + [](const Space& s) -> std::int64_t { + return static_cast(s).objective.min(); + }); +CABS search(root, guidance); +while (std::unique_ptr solution{search.next()}) { + // Read or retain this improvement; the caller owns it. +} +if (search.stopped()) { + // Search has no proof. Ordinary stop limits can be raised to resume. +} else { + // The best accepted incumbent is optimal; without one, the model is infeasible. +} +``` + +The bound is an optimistic **total** objective value on a stable propagated +space: lower for minimization, upper for maximization, and exact on solutions. +The engine adds no prefix cost. Bounds, comparison, and constraints must agree +on the same scalar integer objective. Values are signed 64-bit integers and +compared directly; callback arithmetic must stay in range. Callbacks read only +the supplied space, must not post constraints, and must not capture another +space's local variables. Captured dependencies must outlive engines and builders. + +An optional third guidance argument supplies a rank. Lower rank selects the +beam, independently of the bound. Without rank, the beam prefers the optimistic +bound in the objective direction. Equal priorities use generation order. Rank +never justifies sound pruning. + +Each native `Choice` commit advances one layer. The engine starts at width one, +considers every generated terminal node, and retains at most that many unresolved +next-layer candidates. A shallow goal can be returned before deeper improvements; +publication preserves the remaining parents, alternatives, and preparation. +After all retained nodes and original alternatives in a pass finish, CABS can +prove optimality when every width-discarded bound excludes strict improvement +of the accepted incumbent. It keeps the minimum discarded bound for minimization +or maximum for maximization across every layer, independently of rank. This +also applies when a discarded bound equals the incumbent objective. If a bound +still permits improvement, or no incumbent exists after width loss, CABS doubles +the width and restarts from an unchanged stable root, retaining the incumbent. +A pass with no width loss can prove optimality or infeasibility. Finite search +trees are required for the widening completeness claim. +Natural exhaustion proves optimality of the best accepted incumbent, including +external adoption, or infeasibility if no incumbent was accepted. No newly +returned solution after adoption does not imply infeasibility. + +Beam-specific limits are separate from ordinary search options: + +```cpp +Search::Options options; +options.clone = false; +Search::CABSOptions limits(/*maximum width=*/16, /*frontier nodes=*/1000); +CABS search(new MySpace, guidance, options, limits); + +std::unique_ptr builder(cabs(guidance, options, limits)); +std::unique_ptr engine((*builder)(new MySpace)); +engine->constrain(external_solution); +while (std::unique_ptr solution{engine->next()}) { + // Each solution improves the accepted incumbent, including external ones. +} +``` + +Zero means unlimited for either limit. Pass widths are 1, 2, 4, and so on; a +maximum of 3 permits widths 1 and 2. A complete pass may prove its result at the +maximum width. When an incomplete pass needs a wider restart, exceeding the +maximum or overflowing width growth reports `stopped()`. Frontier exhaustion +also reports a stop. These intrinsic stops remain stopped on repeated `next()` +calls. `Search::Engine::reset(new_root)` starts fresh, clearing the stop, +incumbent, pending work, and statistics; it takes ownership of the new root. + +The frontier quota counts retained pending search spaces: unexpanded entries +in the current layer, next-layer candidates, and a selected parent with remaining +alternatives. Moving a parent between these owners does not consume an extra +slot, and replacing a candidate uses its existing slot. The quota excludes the +immutable restart root, owned incumbent, caller-owned returned solutions, a +temporary successor, and hidden brancher trial spaces. It is a **node quota**, +without a byte or process-memory guarantee. + +Ordinary stop objects retain continuation. Raise or reset their limits and call +`next()` again to continue the same pass. Capacity-one `WorkerControl` supports +parking and resuming without reporting exhaustion. CABS rejects expanded thread +counts other than one and rejects tracers before taking root ownership. + +The default `Options::clone=true` keeps the supplied root with the caller; +`clone=false` transfers it after successful construction. Returned solutions and +the retained incumbent have independent ownership. Equivalent or worse external +incumbents are ignored, and incomparable ones raise `Search::Incomparable`. +Cuts affect committed children, leaving saved source choices and the restart root +unchanged. Already inherited cuts are not repeated, so retained completion +branchers can finish. If a callback, comparison, or model operation throws, +destroy the engine before using it again. + +The engine stores cloned states and reuses each selected parent for its final +alternative. `c_d` and `a_d` do not enable replay or recomputation. No-good +extraction returns the existing empty result. Statistics +count depth as committed search edges and restarts as widening passes. + +CABS accepts the same optional [state-pruning declaration](astar.md) as A*, +with the same expert obligation to preserve feasibility, cost and residual +brancher/search progress, owned keys/labels and exact collision checks: + +```cpp +auto pruning = TSPTW::Transition::pareto_pruning(); +CABS search(root, TSPTW::Transition::guidance(), + options, limits, pruning); +std::unique_ptr builder( + cabs(TSPTW::Transition::guidance(), options, limits, pruning)); +``` + +Descriptors belong only to retained **next-layer candidates**. CABS compares +neither selected/current-layer nodes nor previously expanded nodes. A live +equivalent or dominating candidate can reject an arrival. A new dominator takes +one victim's retained slot before removing any other victims, even if its beam +rank is worse. Sound replacement does not lose coverage. An arrival discarded +by ranking registers nothing; an evicted candidate loses its descriptor. +Descriptors are cleared as candidates become the current layer, between passes +and on reset. A beam-discarded representative therefore cannot suppress a later +surviving route. Earlier width-discarded bounds remain in the pass aggregate; +sound label pruning does not erase them. Completion still requires processing +all retained work and bounding out every width-discarded alternative. + +`descriptor_limit` forgets all candidate descriptors if their count exceeds the +ceiling, preserving the candidates themselves. It is independent of width and +frontier limits and never reports a stop. Empty policy performs no extraction, +descriptor allocation or registry work. `state_pruned` counts rejected arrivals +and removed dominated live candidates, separately from failures and beam loss. +`state_descriptors`/`state_bytes` report retained descriptor peaks using the +[A* storage estimate](astar.md); all three are zero when pruning is disabled. +The existing stop, proof and independently owned solution contracts still apply. + +Build and run the [closed example](../examples/astar.cpp): + +```sh +cmake --build build/open-search --target astar +build/open-search/bin/astar +``` + +The example runs A*, BAB, and CABS on one bounded formulation. A misleading rank +makes capped width one return 4 and -2, then stop without a proof. Width two, width four, and +unrestricted widening recover -7 and prove it optimal. A frontier quota of one +stops after 4. The example prints elapsed milliseconds, nodes, and restarts for +fixed width/frontier settings. Its tiny timings illustrate the reporting and do +not establish performance gains or justify default tuning. It links only the +search and integer libraries; installed consumers can link `Gecode::gecodesearch` +and `Gecode::gecodeint`. diff --git a/docs/column.md b/docs/column.md new file mode 100644 index 0000000000..2faa86d998 --- /dev/null +++ b/docs/column.md @@ -0,0 +1,84 @@ +# Native progressive column search + +`Column` searches ordinary Spaces with sequential depth queues. It requires +no Model declaration or compilation. The Space supplies `compare` and strict +`constrain`, and `Search::HeuristicGuidance` supplies an optimistic total bound: +a lower bound for minimization or upper bound for maximization, exact on a +solution. The engine adds no prefix cost. Guidance follows the [A* callback +contract](astar.md). + +```cpp +Search::Options options; +options.threads = 1; +Search::ColumnOptions quotas(1, 1); // initial quota, growth per full cycle +Column search(root, guidance, options, quotas, pruning); +while (MySpace* solution = search.next()) { + // Each returned solution is owned by the caller and strictly improves. + delete solution; +} +// !search.stopped() proves the best accepted incumbent optimal, +// including external adoption, or infeasibility if none was accepted. +``` + +The optional `pruning` argument defaults to `Search::StatePruning()`. The native +`Search::columnengine(root, guidance, options, quotas, pruning)` and ordinary +`column(guidance, options, quotas, pruning)` builder use the same +configuration. Builders copy callbacks and quotas. Root ownership follows +`options.clone`: the default preserves the caller's root; false transfers it +after configuration validation. Returned solutions and external incumbents +are independently owned clones. `constrain(incumbent)` adopts a compatible, +strictly improving incumbent and ignores equivalent or worse values. + +A cycle visits successive committed depths from zero. At each depth it selects +up to the positive quota of parents, finishes every original n-ary Choice, and +then advances. Newly generated deeper queues participate in that same cycle. +Queued alternatives remain available for later cycles. Publication preserves +cycle depth, quota usage, selected parent and alternative cursor. Ordinary +stops and capacity-one worker parking preserve the same continuation, including +interrupted preparation. Skipping bound-pruned queued entries consumes no +expansion quota. A new cycle starts only after all depths have been visited; +it adds `quota_growth` with saturation at the largest unsigned-long value. + +Defaults are `initial_quota=1`, `quota_growth=1`, and `frontier_limit=0`. +`Search::ColumnOptions(1, 0)` selects fixed-quota CBFS. The quota schedules +expansion; it never truncates a queue. Within each depth, supplied rank orders +lower first independently of objective bound. Without rank, the most optimistic +bound comes first in the objective direction. Ties use insertion order. Rank +never prunes. A misleading early goal can precede a better one, so continue +search to natural exhaustion for a proof. + +An optional state registry uses owned descriptors, exact keys after hash +bucket selection, and sound equivalence/dominance relations. It admits fresh +improving labels even after an earlier representative expanded, including with +inconsistent admissible bounds. Replaced metadata does not delete old pending +Spaces or their Choice cursors. A descriptor ceiling forgets metadata alone; +it may increase repeated work but preserves coverage. Omitted pruning creates +no registry or descriptors. `state_pruned`, `state_descriptors` and `state_bytes` +follow [A*'s accounting](astar.md); registry labels must include prefix objective +cost and every residual fact relevant to feasibility and brancher/search progress. +The [A* pruning contract](astar.md) explains why identical domains and costs at +different stages are insufficient, and when to include stage state or decline +extraction. + +The frontier cap counts queued Spaces plus the selected unfinished parent. +A fully expanded parent is retired before its last child is admitted, so a +unary chain fits cap one. Incumbents, caller-owned solutions, temporary children +and brancher trials are excluded. If an otherwise admissible child cannot fit, +search stops incompletely and stays stopped until `Search::Engine::reset`. +An ordinary stop can resume. + +This is a Space count, without a byte or process-RSS guarantee. Each retained +node owns a stable Space; the engine reuses each selected parent for its final +alternative. Descriptor vectors and queue/container capacity add storage. +`c_d` and `a_d` do not enable recomputation. No-good extraction is empty. +Only one expanded thread and no tracer are supported, with explicit exceptions +for unsupported settings and zero initial quota. If a callback, comparison or +model operation throws, destroy the engine before using it again. + +Build `astar` to run progressive and fixed quotas against the same misleading +rank model used by A*/BAB/CABS. The [TSPTW example](../examples/data/tsptw/README.md) +accepts `--engine column`, `--initial-quota`, `--quota-growth`, and +`--frontier-limit` with closed, transition and eager formulations. Its defaults +remain prefix bounds and residual propagation off. Installed consumers use +`find_package(Gecode CONFIG REQUIRED COMPONENTS search int)` and link +`Gecode::gecodesearch` and `Gecode::gecodeint`. diff --git a/docs/tsptw-cheap-deadline-filter.md b/docs/tsptw-cheap-deadline-filter.md new file mode 100644 index 0000000000..fb14b9bc19 --- /dev/null +++ b/docs/tsptw-cheap-deadline-filter.md @@ -0,0 +1,187 @@ +# Cheap residual deadlines for TSPTW + +The native transition example accepts `--residual-propagation off|cheap|unary`. +`off` remains the default. `cheap` checks necessary customer and depot deadlines +after each committed visit, without constructing a residual unary Space. +`unary` retains its existing scheduling relaxation and Gecode fixpoint policy. + +For current location `i`, time `t`, and each unvisited customer `u`, let +`d[i,u]` be the shortest path length in the supplied matrix. The cheap check uses + +```text +L = max(ready[u], t + d[i,u]) +reject if L > due[u] or L + d[u,0] > due[0] +``` + +Any completion must reach `u` no earlier than `L` and return to the depot no +earlier than `L + d[u,0]`. The matrix contains nonnegative elapsed times, including +any folded service. Shortest paths may traverse already visited or unavailable +vertices; allowing those paths weakens the test and preserves its necessity. +This reasoning does not require symmetry or the triangle inequality. With no +customers left, the check uses the actual direct return arc. All additions in +the customer check use the existing checked cost helper. + +The customer test follows equation 28 of [Marijnissen et al., *Domain-Independent +Dynamic Programming with Constraint Propagation*](https://doi.org/10.1609/icaps.v36i1.42826). +The depot test adds the corresponding necessary condition for this example's +explicit return window. Waiting affects arrival time and contributes no cost. +Bounds, ranking, Pareto pruning and representation remain separate choices. + +The shortest-path matrix is computed once per root and shared by clones. +Each cheap refresh scans the remaining customers. Its work contributes to +`inference_calls` and `inference_ms`; `propagation_ms` remains zero. These +counters include repeated work during BAB replay. Native BAB does not consume +the separate heuristic bound callback. + +## Fixed comparison + +The [saved comparison](../examples/data/tsptw/cheap-deadline-comparison.json) +records four development cases and two tiny infeasible controls selected before +any solver launch. It compares OFF, CHEAP and UNARY with native BAB and CABS: + +| Case | Role | +| --- | --- | +| `Dumas/n20w20.001.txt` | canonical narrow windows | +| `variants/Dumas/customer-due-plus-100/n20w100.003.txt` | separately marked looser customer windows | +| `GendreauDumasExtended/n20w120.001.txt` | canonical wider windows | +| `SolomonPotvinBengio/rc_201.1.txt` | canonical Solomon family | +| `tiny/late-customer.txt` | infeasible customer deadline | +| `tiny/late-return.txt` | infeasible depot return | + +Each configuration uses the same lazy transition formulation, prefix bound, +default ranking, Pareto OFF, no seed, one thread and a two-second outer cap. +Two repetitions reverse configuration order on the second pass: 72 rows and +144 nominal solver seconds. Every published incumbent passes the independent +Fraction checker. Natural exhaustion alone supplies a proof. The runner checks +conflicting optimality and infeasibility claims across each case. + +The sampled RSS watchdog sums the process group, limits it to 1 GiB and requests +samples every 50 ms. Probe and cleanup time belongs to launch-through-reap wall +time. Sampling can miss peaks and overshoot limits. Reported publication clocks +begin at native main entry; launch-inclusive publication times are unavailable. +Missing post-kill summaries leave inference time, nodes and other phase metrics +unavailable. Propagation time is part of inference time, not an additive phase. +The runner does not measure frontier or clone buffer bytes. + +## Observed results and decision + +All 72 rows completed. The checker accepted 476 terminal-row publications and +three more publications in retained interrupted streams. There were 23 outer +timeouts, no RSS stops and no terminal execution, measurement or invalid-output +failures. Forty-nine rows have summaries; the other 23 have unavailable phase +times and node counts. Each configuration has 12 observations, including four +tiny infeasibility proofs: + +| Configuration | Checked tour | Optimal | Infeasible | Ordinary stop | Outer timeout | +| --- | ---: | ---: | ---: | ---: | ---: | +| BAB OFF | 4 | 2 | 4 | 3 | 3 | +| BAB CHEAP | 8 | 4 | 4 | 1 | 3 | +| BAB UNARY | 8 | 2 | 4 | 4 | 2 | +| CABS OFF | 8 | 0 | 4 | 1 | 7 | +| CABS CHEAP | 8 | 4 | 4 | 0 | 4 | +| CABS UNARY | 8 | 2 | 4 | 2 | 4 | + +Both repetitions agree on these useful differences: + +- On narrow-window Dumas, CHEAP and UNARY prove cost 378 with either engine. + BAB visits 1,079 nodes with CHEAP, 1,014 with UNARY and 2,785,283 with OFF. + CHEAP BAB search takes 0.882–0.886 ms, including 0.055 ms of inference; + UNARY takes 7.626–7.797 ms, including 6.873–6.924 ms of inference. + Launch wall times are 93–100 ms for both inference modes, where process startup + and observation dominate the small native search. OFF CABS finds 378 but + supplies no proof within the cap. +- On the looser Dumas variant, BAB CHEAP ends at 310 and BAB UNARY at 337; + OFF finds no tour. Every CABS mode ends at 249. No mode proves this case. +- On Gendreau, BAB CHEAP ends at 302 and BAB OFF/UNARY at 305. Every CABS mode + ends at 267, with no proof. The available second-repetition BAB summary records + 3,268,871 CHEAP nodes and 125.113 ms of inference, compared with 627,236 UNARY + nodes and 1,576.976 ms of inference. Fewer nodes do not establish a runtime gain. +- On Solomon, CHEAP proves exact original cost 444.5425 with both engines. + BAB proof wall times are 0.562–0.580 s and CABS proof wall times 1.121–1.123 s. + BAB UNARY ends at 447.6347 then 444.5425; CABS OFF and UNARY reach 444.5425 + without proof. BAB OFF finds no tour. CHEAP BAB inference costs 34.910–35.054 ms; + UNARY's stopped summaries record 1,545.946–1,548.630 ms. + +Retain CHEAP as an explicit option. It removes enough deadline dead ends to +improve this small comparison while avoiding full unary inference. These four +development cases and two easy controls do not establish a universal speedup +or settle defaults. The variant and Gendreau cases remain unproved. + +The available two-second UNARY summaries spend roughly 77–79% of search time +in inference. This supports a separately scoped incremental-unary study to ask +whether reusable residual state can preserve its stronger pruning at lower +cost. This comparison adds no RSS-stop evidence for a fixed-width continuation +study: its largest sampled CABS peaks are about 283 MiB OFF, 97 MiB CHEAP and +77 MiB UNARY, and frontier bytes are unmeasured. Any continuation study should +retain its separate justification and budget. No such study is implemented here. + +The 72 terminal rows use 77.537 s of launch-through-reap wall time. Two capture +cleanup calls failed with a permission error; inspection found both children +absent. The ledger charges 39.635 s and 20.234 s conservatively through absence +confirmation, including their observed 2.008 s and 2.006 s attempts. Their partial +streams remain available. Validation includes two retained short-cap timeouts +before successful retries of the required integration checks. + +At implementation handoff, shared usage is **170.957548 s**: correctness +33.551175 s and development 137.406373 s, with no pending launches. Of the +300-second allowance, 129.042452 s remains, including the unused 60-second +verification reserve and 20-second cleanup reserve. Observed capture periods +total 115.103497 s; the remaining 55.854051 s is conservative absence-confirmation +accounting. Builds took 2.111298 s and +10.162398 s separately, totalling 12.273696 s. Independent verification will +append to this ledger without changing this implementation snapshot. + +## Reproduction + +Use one writer for the named run. The task ledger begins with this distinct +header, created only when the file does not already exist: + +```json +{"event":"budget","scope":"task","task_id":"open-search-028","total_seconds":300,"verification_reserve_seconds":60,"cleanup_reserve_seconds":20} +``` + +The shared allowance counts solver validation, comparison attempts, interrupted +launches and cleanup. Builds are measured separately. Admission preserves +unused verification and cleanup reserves and rejects pending child cleanup. +Task ledgers exclude compilation and held-out collection. Historical +7,200-second evaluation ledgers keep their original accounting and reserves. + +The exact preview, collection and analysis commands are: + +```sh +python3 misc/tsptw-benchmark.py preview --split development --selected-root /private/tmp/open-search-tsptw-selected-network --case-ids Dumas/n20w20.001.txt,variants/Dumas/customer-due-plus-100/n20w100.003.txt,GendreauDumasExtended/n20w120.001.txt,SolomonPotvinBengio/rc_201.1.txt,tiny/late-customer.txt,tiny/late-return.txt --configs transition-bab,transition-bab-lazy-cheap,transition-bab-lazy-unary,transition-cabs,transition-cabs-lazy-cheap,transition-cabs-lazy-unary --repetitions 2 --seconds 2 --budget-ledger /private/tmp/open-search-028-budget.jsonl --budget-phase development --compilation-seconds 12.273696000047494 +python3 misc/tsptw-benchmark.py run --name cheap-deadline-028 --split development --selected-root /private/tmp/open-search-tsptw-selected-network --case-ids Dumas/n20w20.001.txt,variants/Dumas/customer-due-plus-100/n20w100.003.txt,GendreauDumasExtended/n20w120.001.txt,SolomonPotvinBengio/rc_201.1.txt,tiny/late-customer.txt,tiny/late-return.txt --configs transition-bab,transition-bab-lazy-cheap,transition-bab-lazy-unary,transition-cabs,transition-cabs-lazy-cheap,transition-cabs-lazy-unary --repetitions 2 --seconds 2 --budget-ledger /private/tmp/open-search-028-budget.jsonl --budget-phase development --compilation-seconds 12.273696000047494 +python3 misc/tsptw-benchmark.py analyze --name cheap-deadline-028 --output examples/data/tsptw/cheap-deadline-comparison.json +``` + +Repeating collection skips validated terminal rows, including failures. Changing +the cap rejects reuse before launching. Interrupted streams remain beside a +replacement sample. After inspecting a pending child, the existing `recover` +command confirms absence and charges conservatively through that confirmation: + +```sh +python3 misc/tsptw-benchmark.py recover --name cheap-deadline-028 +``` + +Raw configuration, terminal records and streams remain in +`build/tsptw-results/cheap-deadline-028`. Input identities and hashes are frozen +there and in the compact report. Analysis reads saved records and launches no +solver. The report's budget snapshot covers implementation; independent +verification appends to the same external ledger, and the task result records +the final shared total. + +## Historical correctness checks + +The existing `Search::TSPTW::Transition` test independently enumerates numeric +suffixes and checks CHEAP and UNARY against every feasible completion, including +failed committed residuals. It covers waiting, zero durations, pending and +completed return, and replay for lazy and eager paths. A nonmetric fixture has +customer 2 due at 6 and the depot due at 8: direct `0->2` and the return `1->0` +after waiting miss deadlines, but route `1,2` returns at 8 with cost 5. Both +inference modes preserve it. CHEAP also rejects infeasible residuals. + +The frozen report records checks from the earlier combined branch. Its tiny +CLI checks with BAB and CABS proved waiting cost 5 and both late fixtures +infeasible with `residual_propagation=cheap` and `propagation_ms=0`. The report's +build paths and timings describe that run, rather than current native branch +validation. The standalone branch retains the same suffix tests and CLI checker. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 6282beaeb8..55f5038180 100755 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -115,3 +115,16 @@ endfunction() foreach(example_source IN LISTS GECODE_EXAMPLE_SOURCES) gecode_add_example("${example_source}") endforeach() + +# Standalone native search: no Model, driver, or portfolio setup. +add_executable(astar astar.cpp) +target_link_libraries(astar PRIVATE gecodesearch gecodeint) +set_property(TARGET astar PROPERTY FOLDER "Examples") + +add_executable(best-first best-first.cpp) +target_link_libraries(best-first PRIVATE gecodesearch gecodeint) +set_property(TARGET best-first PROPERTY FOLDER "Examples") + +add_executable(tsptw tsptw.cpp) +target_link_libraries(tsptw PRIVATE gecodesearch gecodeint) +set_property(TARGET tsptw PROPERTY FOLDER "Examples") diff --git a/examples/astar.cpp b/examples/astar.cpp new file mode 100644 index 0000000000..455db17d14 --- /dev/null +++ b/examples/astar.cpp @@ -0,0 +1,130 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include + +using namespace Gecode; + +// A bounded closed model: option zero is a shallow goal, while the other +// options retain a second decision. Rank deliberately disfavors the optimum. +class Alternative : public Space { +public: + IntVar option, detail, objective; + Alternative(void) + : option(*this,0,3), detail(*this,0,1), objective(*this,-7,4) { + element(*this,IntArgs({4,-2,-7,1}),option,objective); + TupleSet tuples(2); + for (int i=0;i<4;i++) { + tuples.add(IntArgs({i,0})); + if (i) tuples.add(IntArgs({i,1})); + } + tuples.finalize(); + extensional(*this,IntVarArgs({option,detail}),tuples); + branch(*this,option,INT_VALUES_MIN()); + branch(*this,detail,INT_VALUES_MIN()); + } + Alternative(Alternative& s) : Space(s) { + option.update(*this,s.option); + detail.update(*this,s.detail); + objective.update(*this,s.objective); + } + Space* copy(void) override { return new Alternative(*this); } + SpaceComparison compare(const Space& s0) const override { + const auto& s = dynamic_cast(s0); + if (objective.val() == s.objective.val()) return SC_EQUIVALENT; + return objective.val() < s.objective.val() ? SC_BETTER : SC_WORSE; + } + void constrain(const Space& s0) override { + const auto& s = static_cast(s0); + rel(*this,objective,IRT_LE,s.objective.val()); + } +}; + +// Use one model and one ordinary options value for all engines. The tiny +// elapsed times illustrate measurement output; they do not establish speedups. +static bool run(const char* name, Search::Engine* raw, + unsigned long int width=0, unsigned long int frontier=0) { + std::unique_ptr engine(raw); + const auto start=std::chrono::steady_clock::now(); + int last=0; + bool found=false; + std::cout << name << " improvements:"; + while (std::unique_ptr s{ + static_cast(engine->next())}) { + last=s->objective.val(); + found=true; + std::cout << ' ' << last; + } + const double milliseconds=std::chrono::duration( + std::chrono::steady_clock::now()-start).count(); + const auto statistics=engine->statistics(); + std::cout << "\n" << name << " width=" << width << " frontier=" << frontier + << " objective="; + if (found) std::cout << last; else std::cout << "none"; + std::cout << " status=" << (engine->stopped() ? "stopped" : "proven") + << " ms=" << std::fixed << std::setprecision(3) << milliseconds + << " nodes=" << statistics.node << " restarts=" << statistics.restart + << '\n'; + if (width==1) return found && last==-2 && engine->stopped(); + if (frontier==1) return found && last==4 && engine->stopped(); + return found && last==-7 && !engine->stopped(); +} + +int main(void) { + Search::HeuristicGuidance guidance(Search::HeuristicGuidance::MINIMIZE, + [](const Space& s) -> std::int64_t { + return static_cast(s).objective.min(); + }, + [](const Space& s) -> std::int64_t { + const auto& a=static_cast(s); + return a.option.assigned() && a.option.val()==2 ? 1000 : 0; + }); + Search::Options options; + options.clone=false; + bool valid=run("A*",Search::astarengine(new Alternative,guidance,options)); + for (const auto limits : {Search::ColumnOptions(), Search::ColumnOptions(1,0)}) + valid=run(limits.quota_growth ? "Column" : "CBFS", + Search::columnengine(new Alternative,guidance,options,limits)) && valid; + valid=run("BAB",Search::babengine(new Alternative,options)) && valid; + for (const auto limits : {Search::CABSOptions(1,0), Search::CABSOptions(2,0), + Search::CABSOptions(4,0), Search::CABSOptions(), + Search::CABSOptions(0,1), Search::CABSOptions(0,4)}) + valid=run("CABS",Search::cabsengine(new Alternative,guidance,options,limits), + limits.max_width,limits.frontier_limit) && valid; + return valid ? 0 : 1; +} diff --git a/examples/best-first.cpp b/examples/best-first.cpp new file mode 100644 index 0000000000..d8af29d163 --- /dev/null +++ b/examples/best-first.cpp @@ -0,0 +1,147 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Gecode; + +// All costs and weights in this additive minimization example are nonnegative. +static std::int64_t add_cost(std::int64_t a, std::int64_t b) { + if (a > std::numeric_limits::max()-b) + throw std::overflow_error("additive cost overflow"); + return a+b; +} + +static std::int64_t weighted_cost(std::int64_t g, std::int64_t h, + std::int64_t weight) { + if (h && weight > std::numeric_limits::max()/h) + throw std::overflow_error("weighted remaining cost overflow"); + return add_cost(g,weight*h); +} + +// Choose a route, then its final arc. Prefix costs are 0 and 9; remaining +// arc costs are [5,6] and [1,2]. All root alternatives remain nonterminal. +class Additive : public Space { + static constexpr int prefix[2]={0,9}; + static constexpr int tail[2][2]={{5,6},{1,2}}; +public: + IntVar route, detail, objective; + Additive(void) + : route(*this,0,1), detail(*this,0,1), objective(*this,0,11) { + TupleSet tuples(3); + for (int r=0; r<2; r++) + for (int d=0; d<2; d++) + tuples.add(IntArgs({r,d,static_cast(add_cost(prefix[r],tail[r][d]))})); + tuples.finalize(); + extensional(*this,IntVarArgs({route,detail,objective}),tuples); + branch(*this,route,INT_VALUES_MIN()); + branch(*this,detail,INT_VALUES_MIN()); + } + Additive(Additive& s) : Space(s) { + route.update(*this,s.route); + detail.update(*this,s.detail); + objective.update(*this,s.objective); + } + Space* copy(void) override { return new Additive(*this); } + SpaceComparison compare(const Space& s0) const override { + const auto& s=static_cast(s0); + if (objective.val()==s.objective.val()) return SC_EQUIVALENT; + return objective.val()(s0).objective.val()); + } + // g counts determined arcs; after both decisions it is the full cost. + std::int64_t g(void) const { + if (!route.assigned()) return 0; + return detail.assigned() ? objective.val() : prefix[route.val()]; + } + // h bounds remaining arcs only. Restrictions may strengthen these bounds, + // but the original cheapest arc remains a valid lower bound. + std::int64_t h(void) const { + if (!route.assigned()) + return std::min(add_cost(prefix[0],std::min(tail[0][0],tail[0][1])), + add_cost(prefix[1],std::min(tail[1][0],tail[1][1]))); + if (detail.assigned()) return 0; + return std::min(tail[route.val()][0],tail[route.val()][1]); + } + static std::int64_t optimum(void) { + std::int64_t best=std::numeric_limits::max(); + for (int r=0; r<2; r++) + for (int d=0; d<2; d++) + best=std::min(best,add_cost(prefix[r],tail[r][d])); + return best; + } +}; + +static bool run(const char* name, Search::HeuristicGuidance::Evaluate rank, + const std::vector& expected) { + Search::HeuristicGuidance guidance(Search::HeuristicGuidance::MINIMIZE, + [](const Space& s0)->std::int64_t { + const auto& s=static_cast(s0); + return add_cost(s.g(),s.h()); + },std::move(rank)); + Search::Options options; options.clone=false; + std::unique_ptr builder( + bestfirst(guidance,Search::RANK_FIRST,options)); + std::unique_ptr engine((*builder)(new Additive)); + std::vector costs; + std::cout << name << " improvements:"; + while (std::unique_ptr solution{ + static_cast(engine->next())}) { + costs.push_back(solution->objective.val()); + std::cout << ' ' << costs.back(); + } + std::cout << " status=" << (engine->stopped() ? "stopped" : "proven") << '\n'; + return costs==expected && costs.back()==Additive::optimum() && !engine->stopped(); +} + +int main(void) { + const bool greedy=run("Greedy h",[](const Space& s)->std::int64_t { + return static_cast(s).h(); + },{10,5}); + const bool weighted=run("Fixed weight 2: g+2*h",[](const Space& s0)->std::int64_t { + const auto& s=static_cast(s0); + return weighted_cost(s.g(),s.h(),2); + },{5}); + return greedy && weighted ? 0 : 1; +} + +// STATISTICS: example-any diff --git a/examples/data/tsptw/README.md b/examples/data/tsptw/README.md new file mode 100644 index 0000000000..a04c36cae2 --- /dev/null +++ b/examples/data/tsptw/README.md @@ -0,0 +1,778 @@ +# TSPTW inputs and checking + +The frozen comparison reports below describe runs from the earlier combined +branch. Their validation results are historical; native branch validation is +reported separately. + +The native reader in `examples/tsptw-data.hh` and the independent checker in +`misc/tsptw-check.py` use the canonical matrix format from the +[López-Ibáñez/Blum collection](https://lopez-ibanez.eu/tsptw-instances): a node +count including depot 0, an n-by-n matrix, then n ready/due pairs. Comments +begin with `#`. Rows are read as whitespace tokens; the reader requires exactly +n*n + 2*n numeric values after the node count. + +Every supplied arc is both elapsed time and objective cost. The objective sums +all tour arcs, including the return arc. Waiting affects feasibility and never +adds objective cost. The canonical conversion includes service at the origin +in outgoing arcs where the original data supplies service. Do not add service +again or subtract it when comparing these inputs. Keep the supplied asymmetric +and nonmetric matrix; coordinates and reversed tours are not substitutes. + +Departure is fixed at zero. Each customer visit waits until its ready time and +must begin no later than its due time. Return to depot 0 also observes its time +window. The reader rejects a nonzero depot ready time. Tours contain each +customer ID 1 through n-1 once, without either depot visit. The checker adds +the start and return itself, reports each arrival, wait and visit time, and +compares an optional claimed cost exactly in original units. + +The native reader scales all matrix and window decimals by their common power +of ten, ignoring trailing zeroes. It accepts ordinary nonnegative decimal +notation, without exponents, signs or rounding. `Instance::scale` converts +native integer results back to original units. Dimensions, scaled values and +scale must fit `Gecode::Int::Limits::max`. The reader also requires the +conservative n*maximum-arc cost bound and maximum-due + n*maximum-arc time bound +to fit that limit. `cost_limit` and `time_limit` expose those checked bounds for +native variable domains and prefix arithmetic. The independent Python reader +uses rational numbers and checks the same 32-bit Gecode limits separately. + +The five generated `tiny/` instances are separate from canonical benchmarks: + +| Case | Semantic check | Exhaustive optimum | +| --- | --- | --- | +| waiting | waiting, asymmetric and nonmetric arcs | 5 | +| decimal | exact decimal arcs and waiting | 1.5 | +| folded-service | service already included in outgoing arcs | 9 | +| late-customer | every tour misses a customer due time | infeasible | +| late-return | customer visits can succeed but every return is late | infeasible | + +The transition example also offers `--residual-propagation cheap` between OFF +and the existing unary relaxation. [The deadline-filter note](../../../docs/tsptw-cheap-deadline-filter.md) +explains its shortest-path checks and bounded development comparison; +[the saved report](cheap-deadline-comparison.json) retains each case and repetition. +The default remains OFF. + +`published/` contains one unchanged canonical input and one published +travel-cost tour per selected family. The manifest records their exact costs, +source URLs and content hashes. These are feasible best-known tours; checking a +tour does not prove optimality. The Solomon sample has exact cost 444.5425; +the publisher's table displays 444.54. The validation compares display precision +separately from exact cost. Makespan tables are not travel-cost references. + +`manifest.json` freezes 12 development and 24 held-out cases before tuning. +Each family contributes two development and five held-out canonical cases, plus +one window variant in each split. Selection varies sizes and named window +classes without using solver results. Source members and final content hashes +are disjoint across splits, including variant bases. The two deterministic +variants preserve every arc and the depot window: `customer-due-plus-100` adds +100 original time units to each customer due time; `customer-width-half` +replaces each customer due time with ready + (due-ready)/2. These variants are +marked separately and are not published canonical instances or published +best-known references. Tightening can make a variant infeasible. + +Acquire the larger selection explicitly; it is not part of routine tests: + +```sh +python3 misc/tsptw-acquire.py /tmp/tsptw-selected +``` + +The acquisition downloads the four pinned family archives, verifies archive +and member SHA-256 hashes, generates the marked variants, and checks their +final hashes and integer limits. It reads named members without extracting an +archive tree. To reuse downloaded archives offline, add `--archives DIRECTORY`. +The publisher may update an archive; a changed hash requires an explicit +selection update rather than silently changing the evaluation inputs. + +Use an existing configured CMake build for the generated Gecode headers +(replace `build/open-search` below with that build directory): + +```sh +c++ -std=c++17 -Ibuild/open-search -I. misc/tsptw-read.cpp -o /tmp/tsptw-read +python3 misc/tsptw-validate.py --reader /tmp/tsptw-read +python3 misc/tsptw-check.py check examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt.tour --cost 444.5425 +python3 misc/tsptw-check.py enumerate examples/data/tsptw/tiny/decimal.txt +``` + +The focused validation runs offline. It compares every native scaled value +with the independent reader, exhaustively enumerates tiny tours, checks all +four published tours, and rejects malformed data, integer overflow, duplicates, +omissions, late customer visits, late returns and incorrect objective costs. +To validate every acquired case as well, add `--selected /tmp/tsptw-selected`. +The checker exits with status 1 for invalid input or tours, and status 0 for +valid tours or completed enumeration. Enumeration is limited to nine customers. + +## Native closed CP solver + +Build `tsptw` in the same CMake tree. It links the integer and search libraries +and uses an ordinary `Space` with a domain-propagated costed circuit. Reified +successor constraints propagate customer times from a fixed departure at zero +to a separate depot return time. Shortest-path elapsed bounds preserve +asymmetric and nonmetric inputs. Cost-regret branching followed by successor +branching is identical for BAB, A* and CABS. Timing variables are not branched; +the solver reconstructs the earliest schedule for each complete route. + +```sh +cmake --build build/open-search --target tsptw -j 8 +build/open-search/bin/tsptw examples/data/tsptw/tiny/waiting.txt --engine bab +build/open-search/bin/tsptw examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt --engine cabs --seed examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt.tour --time-ms 5000 +python3 misc/tsptw-validate.py --reader /tmp/tsptw-read --solver build/open-search/bin/tsptw +``` + +`--engine` accepts `bab` (the default), `astar` or `cabs`. A* and CABS use the +propagated total-cost minimum as their optimistic total bound. `--seed TOUR` +accepts the checker's whitespace customer IDs. The solver checks the seed's +earliest schedule and exact arc cost before fixing a cloned model to that +route and adopting it through the native engine's incumbent interface. It +publishes the seed before any improvements. The offline validator also checks +each seed with the independent Python checker before invoking the solver. + +`--time-ms N` and `--node-limit N` use ordinary search stops. All limits are +nonnegative integers; omitting an ordinary limit disables it. Time is measured +from the start of instance reading and preprocessing. Stops are checked at +engine boundaries and do not interrupt propagation. CABS also accepts +`--max-width N` and `--frontier-limit N`; zero disables either cap. The frontier +cap counts retained nodes and does not limit process memory. Runs use one +search thread. + +The executable emits JSON lines. Each `solution` record contains a customer +`tour`, `scaled_cost`, `scale`, exact original-unit `cost` and `return_time` +strings, and `elapsed_ms`. Costs strictly improve after the initial seed or +solution. The final `summary` record contains `engine`, `termination`, the last +`formulation`, `incumbent` (the same tour/cost/time fields, or `null`), `elapsed_ms`, and native +`statistics` (`node`, `fail`, `depth`, `propagate`, `restart`, `nogood`, `state_pruned`, +`state_descriptors`, `state_bytes`). Every +published route is checked without using the model's timing variables. + +Natural exhaustion reports `optimal` when an incumbent exists, including an +unchanged seed, and `infeasible` when none exists. Any ordinary stop or CABS +resource stop reports `stopped` and supplies no proof. A first solution or +published best-known cost alone does not prove optimality. Invalid inputs or +seeds exit with status 1 and an error on stderr; successful complete and +stopped runs exit with status 0. + +With `--solver`, the offline validator checks every native solution, proves the +tiny cases with all three engines and formulations, and exercises checked suboptimal/optimal +seeds, invalid seeds, nonmetric reachability and resource stops. It runs the +four canonical samples with independently checked published seeds and a +five-second limit per engine. It compares objectives when runs exhaust and +reports stopped canonical runs as incomplete. These are correctness checks; +they do not tune the frozen development or held-out selection. + +## Native transition solver and eager control + +`--formulation closed` is the default. `--formulation transition` uses the +ordinary native `Space` in `examples/tsptw-transition.hh`. Each stable node +holds its own unvisited customers, current location, time after waiting, prefix +travel cost, committed depth and incumbent ceiling. A shared immutable instance +supplies the arcs and windows. Applicable visits are ordered by ascending +customer ID and checked against their actual incoming arc and due time. +There is no direct-distance test for other residual customers, which would be +unsound for nonmetric matrices. + +The visit list has at most n positions: n-1 customer visits and one explicit +depot return. Brancher preparation materializes the next position without +changing the concrete state. One n-ary choice commit assigns one action and +updates that state once. Archived choices retain both position and customer +IDs; historical BAB replay can materialize an absent position before assigning +it. An unfinished state with no applicable action fails. The final return +checks the depot due time, adds the return arc exactly once and closes the list. +Output omits the return marker, so all formulations use the same tour format. + +The default total bound is prefix travel cost g at unfinished nodes and full +tour cost after return. Nonnegative arcs make this an optimistic bound without +additional distance assumptions. Strict incumbent cuts are copied with each residual +state. A seed is checked independently, then constructed through the same +prepare/choice/commit journey before native incumbent adoption. + +`--formulation eager` reserves all n path variables upfront. It uses the same +Space, brancher, applicability, ordering, incumbent cuts and search engines. +Only path allocation changes. The two transition representations emit identical +improving tours under the same engine; the closed model retains its own +cost-regret and successor branching. + +```sh +build/open-search/bin/tsptw examples/data/tsptw/tiny/waiting.txt --formulation transition --engine astar +build/open-search/bin/tsptw examples/data/tsptw/tiny/waiting.txt --formulation eager --engine cabs +``` + +Transition summaries also contain `allocation`: `space_bytes` is the C++ +`sizeof` the Space subclass, `intvar_handle_bytes` is `sizeof(IntVar)`, and +`root_positions`, `prepared_root_positions` and `solution_positions` count +materialized path variables before preparation, after preparing the root and +in the last incumbent. A failed root may have no prepared position; a run +without an incumbent reports zero solution positions. Lazy roots start at zero, +prepare one position, and grow with the committed prefix plus its next pending +decision. Eager roots start with n positions; both complete tours have n. +These are basic sizes and counts, excluding variable implementations, allocator +storage, list capacity, residual buffers and search frontier storage. They do +not measure process memory or imply a compact dynamic-programming state. + + +## Optional transition Pareto pruning + +`--state-pruning off|pareto` defaults to `off`. Pareto pruning is explicitly +supported for native A*/CABS/column with `--formulation transition` or `eager`; closed +CP and BAB runs reject it. Both transition representations use the same +`Transition::pareto_pruning()` declaration: + +```sh +build/open-search/bin/tsptw examples/data/tsptw/tiny/waiting.txt --formulation transition --engine astar --state-pruning pareto +build/open-search/bin/tsptw examples/data/tsptw/tiny/waiting.txt --formulation eager --engine cabs --state-pruning pareto +``` + +The exact key is current vertex i followed by the sorted unvisited customers U. +The label is `(t,g)`: time after waiting and prefix travel cost. Equal labels +are equivalent. At an equal key, one label dominates another only if both its +time and cost are no greater; earlier arrival at greater cost is incomparable. +Hashes select comparisons and never substitute for exact key equality. + +This key is sufficient for the example's authored transition rules. The same +remaining arcs and customer/depot windows apply after equal `(U,i)`. Waiting +lets an earlier arrival follow every suffix feasible for a later arrival, and +a no-greater prefix cost preserves a no-greater final objective. Nonnegative +arcs preserve strict incumbent cuts. The ceiling is a monotone global cut, so +only globally improving completions matter. Committed depth follows from U; +the assigned path prefix serves output and adds no future restrictions. Lazy +and eager path variables use the same unrestricted position domains. + +Optional residual inference rebuilds its schedule from immutable input and +`(U,i,t)` after each committed visit, including BAB replay. Clones copy only +the resulting allowed first actions. It carries no propagated domains from a +previous path. Every feasible suffix induces a feasible schedule, including +its earliest first visit, so the relaxation cannot remove that suffix. Earlier +arrival still preserves every feasible later suffix under the same windows. +The `(U,i)` key and `(t,g)` relation therefore remain sufficient with inference +enabled. + +Arbitrary extra constraints on path history or prefix variables invalidate +that argument. Include their residual effects in a stronger exact key, or +return `std::nullopt` from extraction where sufficient state is unavailable. +The declaration does not justify pruning ordinary partial successor CP stores. +Returned tours remain independently owned and use the same checker. + +A* and column retain descriptor metadata after expansion and admit improved labels +again, including with inconsistent admissible bounds. CABS compares only live +next-layer candidates; beam-discarded states never become representatives. +See [A*](../../../docs/astar.md) and [CABS](../../../docs/cabs.md) for callback +contracts, optional metadata forgetting and candidate lifetimes. Forgetting +removes metadata alone and never removes search work or causes a stop. + +Summary records identify `state_pruning`. `state_pruned` counts sound state +removals; `state_descriptors` and `state_bytes` record retained peaks. The byte +estimate includes descriptor objects and owned key/label vector capacities, +excluding container capacity and allocator overhead. It does not include +Space/frontier storage or measure process RSS. These counters stay zero with +pruning off. Sound pruning is independent of beam loss and resource termination; +only natural complete exhaustion reports a proof. + +The existing offline validator compares pruning off/on with exhaustive tiny +enumeration, includes duplicate and cost/time Pareto cases, and independently +checks every published solution. Its canonical runs remain capped at five +seconds each and do not form a development or held-out tuning campaign. + +## Minimum-arc bounds and residual scheduling + +`--bound prefix|min-arcs` and `--residual-propagation off|unary` are independent +switches for transition and eager formulations. Their defaults remain `prefix` +and `off`. Closed CP uses its propagated total objective minimum (`cp-total` +in summaries) and rejects stronger transition options. Bound evaluation adds +no separate beam ranking. + +For nonempty U, `min-arcs` sums minimum outgoing arcs from i and every remaining +customer, and minimum incoming arcs to every remaining customer and the depot. +The current location can enter only U; a remaining customer can enter another +remaining customer or the depot. Incoming customer arcs can originate at i or +another remaining customer, and the depot receives an arc from U. Self arcs +and premature return are excluded. Every completion uses one arc per source +and target, so `g + max(incoming,outgoing)` is a valid total lower bound. The +root follows the same convention. With no customer left, the remaining bound +is the actual return arc; after return it is zero. All sums use checked signed +64-bit arithmetic. + +`unary` creates a temporary native scheduling Space without branching. Customer +u has a duration equal to its minimum outgoing arc to U minus u or the depot. +Its start lies between `max(ready[u],t+dist[i,u])` and +`min(due[u],due[0]-dist[u,0],due[0]-duration[u])`. Shortest paths may use any +vertex, including unavailable vertices; this relaxes reachability and is sound +for asymmetric, nonmetric input. Preprocessing happens once per root and the +immutable matrix is shared by clones. Prefix/off roots skip this preprocessing. + +Ordinary `status()` runs unary propagation to a fixpoint. Failure rejects the +residual state. A first action survives only if its actual earliest visit time +belongs to its propagated start domain. Each real residual tour yields +nonoverlapping task intervals: its outgoing arc is at least the chosen duration, +waiting can only separate intervals, and the last interval ends by depot due +time. This also covers zero arcs and service already folded into the matrix. +Empty residuals use the explicit return check; completed tours need no schedule. +Materializing a visit-list position still has no residual-state effects. + +Transition `measurements` report `bound_calls`, `bound_ms`, `inference_calls`, +`inference_ms` and `propagation_ms`. One run-owned aggregate is shared by clones +and guidance callbacks, counting replay and repeated evaluations. Bound timing +covers each guidance bound calculation. Inference timing covers temporary +Space construction, posting, `status()` and start-domain inspection; propagation +timing covers `status()` alone. Shortest-path preprocessing is outside inference +timing and inside elapsed time. These example counters are separate from native +search statistics. Times are wall milliseconds, rounded to three decimals. + +The existing `Search::TSPTW` test independently enumerates residual suffixes +and checks each bound, failure and removed first action. It includes waiting, +nonmetric arcs, unequal time/cost labels, tight scheduling failure/filtering, +zero durations, pending return and completed tours. The offline validator checks +all tiny optima with minimum-arc guidance with/without inference, one +prefix/unary independence case, and strengthened A*/CABS Pareto off/on cases. + +The initial [development comparison](bounds-comparison.json) ran three +configurations on the frozen development cases `Dumas/n20w20.001.txt`, +`GendreauDumasExtended/n20w120.001.txt` and +`variants/Dumas/customer-due-plus-100/n20w100.003.txt`. Each ran twice under +transition A*, Pareto off, one thread, no seed, a five-second native limit and +a seven-second external guard. Runs were sequential, interleaved by case, with +configuration order reversed for repetition two. The record retains exact +commands, input and binary hashes, every independently checked incumbent, +individual timings, node counts, stops and sampled storage. Compilation was +completed before capture and was not timed. No held-out outcomes were used. + +All six tight-Dumas runs proved cost 378 and emitted one checked incumbent each: + +| Configuration | Native elapsed ms (two runs) | Nodes per run | Bound ms (two runs) | Inference / propagation ms (two runs) | +| --- | --- | ---: | --- | --- | +| prefix/off | 2819.553 / 2685.122 | 2785287 | 34.640 / 34.164 | 0 / 0 | +| min-arcs/off | 3055.557 / 3434.020 | 2782969 | 169.566 / 169.037 | 0 / 0 | +| min-arcs/unary | 2.005 / 2.171 | 1006 | 0.019 / 0.019 | 1.215 / 0.791; 1.232 / 0.793 | + +The minimum-arc bound alone removed few nodes on this case and added evaluation +time. Unary inference sharply reduced the explored tree. These two observations +describe this input and build; they do not select a new default. + +On the extended-Gendreau case, both prefix/off and min-arcs/off runs hit the +RSS watchdog without an incumbent. Unary runs reached the native five-second +limit without an incumbent, visiting 2516046 and 2461061 nodes and spending +3346.832 and 3287.880 ms in inference, including 2194.261 and 2083.187 ms in +propagation. Their sampled RSS peaks were 663.1 and 655.5 MiB. All six runs on +the relaxed Dumas variant hit the RSS watchdog without an incumbent. A killed +process has no final native summary, so its node/evaluation/propagation metrics +remain unavailable rather than zero. Resource termination supplies no proof. + +The watchdog sampled `ps` RSS every nominal 50 ms plus probe overhead; observed +maximum gaps reached 123.563 ms. It killed the process group above 1 GiB, +with overshoot between samples. Tight-Dumas prefix/off and min-arcs/off sampled +peaks were about 951–975 MiB. Each fast unary run had only one early sample, +which cannot establish its true peak. The record uses sampled process RSS, +not a hard memory limit or native frontier-byte measurement. Descriptor peaks +were zero because Pareto pruning was off. The 18 runs consumed 33855.089 ms +of external capture wall time, below both the 90-second nominal solver budget +and the 15-minute ceiling. Six runs proved an optimum, two stopped at the +native time limit and ten were killed by the RSS watchdog. The evidence leaves +the stronger options explicit and the prefix/off defaults unchanged. + +## Progressive and fixed-quota column search + +Both ordinary Space formulations support `--engine column`. Its default quota +starts at one and grows by one after every complete depth cycle. Set +`--quota-growth 0` for fixed-quota CBFS, or `--initial-quota N` for another +positive initial quota. Quotas schedule expansion and retain deferred work. +`--frontier-limit N` caps pending Space count, including an unfinished parent; +zero is unlimited. Cap termination reports `stopped` and supplies no proof. +`--max-width` remains specific to CABS. Column summaries record `initial_quota`, +`quota_growth`, and `frontier_limit`. + +```sh +build/open-search/bin/tsptw examples/data/tsptw/tiny/waiting.txt --engine column +build/open-search/bin/tsptw examples/data/tsptw/tiny/waiting.txt --formulation transition --engine column --quota-growth 0 --state-pruning pareto +``` + +The formulations, exact tour checker, seed adoption and prefix/off defaults +remain the same. Transition/eager Pareto pruning supports column's fresh-label +registry; closed CP continues without that declaration. The offline validator +checks both quota modes against exhaustive tiny optima, independently checks +each published tour, and covers selected seed/Pareto/stop cases. Its canonical +loops and five-second limits remain unchanged. See [the native engine +contract](../../../docs/column.md) for full-cycle continuation, ownership, +frontier accounting and storage costs. + +## Named comparison runs + +`misc/tsptw-benchmark.py` collects one solver process at a time. Its eight default +configurations are closed BAB/CABS, transition BAB/A*/CABS/column and DIDPPy +A*/CABS. Development defaults to the six native configurations; `--configs` +selects a comma-separated subset, including reference solvers when needed. +The transition configurations use the existing prefix/off defaults. Four extra +closed CP tuning names require an explicit `--configs` selection and do not +enter development or held-out defaults. The runner adds no option cross-product +or tuning policy. + +```sh +uv run --script misc/tsptw-benchmark.py preview --split held-out +uv run --script misc/tsptw-benchmark.py run --name correctness --split tiny +uv run --script misc/tsptw-benchmark.py run --name correctness --split tiny +uv run --script misc/tsptw-benchmark.py analyze --name correctness +``` + +Results go to `build/tsptw-results/NAME` unless `--results-root` selects another +location. Relative paths resolve from the command's working directory. Run only +one writer for a name. `config.json` records the full schedule, exact input +hashes, native binary and sibling macOS library hashes, script/checker hashes, +DIDPPy pin, machine and limits. A changed identity requires a new name. Keep the +Python/uv environment and any externally installed native libraries fixed; +the runner hashes sibling build libraries on macOS, not every transitive system +dependency. Completed JSON rows and their stdout/stderr remain unchanged on +resume. Missing, malformed and interrupted rows execute again; partial streams +are renamed before retry. Crashes, timeout, RSS termination and invalid output +are completed failures and remain in the run. Use a new name for deliberate +retries. `--max-samples N` bounds a smoke invocation without changing its saved +experiment. Each row becomes complete by atomic JSON replacement after checking. + +For acquired development or held-out inputs, pass the directory produced by +`misc/tsptw-acquire.py` with `--selected-root`. Preview reads the frozen +manifest without inspecting held-out outcomes or requiring acquired files. +The development ceiling is six configurations, 12 cases, two repeats and +15 seconds: 2,160 seconds (36 minutes). The full held-out comparison is eight +configurations, 24 cases, one repeat and 30 seconds: 5,760 seconds (96 minutes). +Development tuning has its own 45-minute allowance. The separate 120-minute +evaluation allowance includes its bounded development ablations and compilation, +so the held-out matrix leaves at most 24 minutes for that work. Full tuning plus +held-out takes 132 minutes before compilation across the two separate budgets; +that total is allowed. Preview prints both budgets and their scopes; it does not +launch a campaign or enforce a budget shared across named runs. + +Both solver routes receive one thread, the same launch-to-reap wall deadline +and a 1 GiB sampled RSS watchdog. `ps` reports the summed RSS of the owned process +group, including uv and its Python child. Sampling happens every nominal 50 ms +plus probe overhead; records retain sample count, maximum gap and total probe +time. Termination between samples can miss the peak; no observed live RSS is +`null`. The watchdog can overshoot and is not a hard memory limit. Probe failure +terminates the sample as a measurement failure. Stops kill and reap the process +group and cannot establish a proof. + +End-to-end wall time includes launch, imports, preprocessing, search, target +incumbent checks, output, watchdog overhead and teardown. The outer independent +Fraction checks run after that measurement and check every emitted incumbent. +Native `preprocessing_ms` covers instance reading, root posting/propagation, +preparation and guidance setup. Native `search_ms` starts at engine construction +and ends at exhaustion/stop, including the target's own checks and output. +DIDPPy's preprocessing includes interpreter-side imports, instance reading and +model construction; its search starts before solver construction and includes +that construction, its own incumbent checks and output. +`startup_and_teardown_ms` is the residual of outer wall time minus target +elapsed time, including sampling/detection delay; it is not an isolated startup +measurement. Missing final summaries leave phase and search metrics unavailable. +Build before capture. `--compilation-seconds S` records a separately measured +build duration; omission explicitly leaves it unavailable. Compilation is never +silently attributed to search. + +Each row retains checked incumbent times and costs, the summary, final objective, +proof or stop reason, expansions and sampled peak RSS. Native descriptor peaks, +bound/inference timings and basic allocation sizes remain available in the +summary. Frontier bytes and actual clone footprint are unavailable. The native +`sizeof`/position counters exclude buffers, allocator overhead and frontier +storage. DIDPPy does not expose equivalent descriptor/clone counters here. +`analyze` reads only saved config and rows, writes `analysis.json`, and reports +expected/completed/unfinished/failed counts and valid-run wall-time medians by +configuration. With `closed-bab` present, it also pairs every other configuration +with that baseline on the same case and repetition. Pairs retain status, checked +final objective, first-incumbent time, quality at 100/1,000/5,000/15,000 reported +milliseconds when within the allowance, proof/time, termination, outer wall time +and sampled peak RSS. Counts distinguish wins, ties, losses, missing/failed pairs +and unavailable metrics. Cases mark canonical data and deterministic window +variants separately. Checked incumbents from timeout or RSS termination count +as feasible quality; such termination never counts as proof. Native reported +clocks start at main entry and include preprocessing; outer wall clocks measure +launch through reap. A shorter wall time after a resource stop need not mean +faster successful solving. Analysis never launches solvers; detailed objectives, +proofs and traces remain in the raw rows. Timing medians across different cases +are descriptive and do not rank solvers or estimate variation. + +`misc/tsptw-didppy.py` pins DIDPPy 0.11.1 and reports its installed version. +A* uses its sequential CAASDy solver; CABS uses its sequential CABS solver. +The formulation follows the pinned +[official TSPTW example](https://github.com/domain-independent-dp/didp-rs/blob/59a5330537962bcc2f93c3914cf5fd9ee361b23d/didppy/examples/tsptw.ipynb), +but checks each actual arrival and explicit depot return. It omits the example's +direct-arc residual deadline constraint, which is unsound for nonmetric input. +The incoming/outgoing bounds sum global minimum arcs for the remaining mandatory +sources/targets and take their maximum through DIDPPy's dual-bound interface. +They relax restrictions on the remaining vertices and remain lower bounds for asymmetric, +nonmetric matrices. Time is a less-is-better resource; DIDPPy's cost-aware +registry compares labels for the same unvisited set and location. The exact +scaled arcs, waiting, fixed start zero, folded service, travel-cost objective +and return window match the native model and Fraction checker. + +## Conventional CP tuning + +The development campaign `cp-baseline-tuning` compares five fixed candidates. +The costed circuit, shortest-path timing bounds, reified arc timing, objective, +successor completion branch and independent checker are identical. Circuit +propagation compares `IPL_DOM` with `IPL_VAL`. Cost-variable branching compares +`INT_VAR_REGRET_MAX_MAX()` from the initial TSP recipe with `INT_VAR_SIZE_MIN()`; +both use `INT_VAL_MIN()`. The successor branch remains size/min. + +| Runner name | Circuit propagation | Cost branching | Search | +| --- | --- | --- | --- | +| `closed-bab` | DOM | regret | BAB | +| `closed-val-bab` | VAL | regret | BAB | +| `closed-size-bab` | DOM | size | BAB | +| `closed-rbs-regret` | DOM | regret | RBS of BAB | +| `closed-rbs-size` | DOM | size | RBS of BAB | + +Native `--circuit-propagation dom|val` and `--cost-branching regret|size` apply +only to `--formulation closed`; other formulations reject explicit settings. +The defaults remain DOM/regret. `--engine rbs` is also closed-only and uses the +existing `RBS` builder, one thread, Luby cutoff scale 1,000 and +no-good depth 128. These restart parameters are fixed. Summary records report +the chosen model and restart settings. The default `Space::master` posts +incumbent cuts and no-goods; the default `Space::slave` marks each BAB search +complete. Only natural exhaustion reports optimality or infeasibility. A +resource stop supplies no proof. Existing LNS was considered, but restart search +provided this comparison without adding neighborhood wiring or an LNS framework. + +The frozen standalone baseline is **DOM/size BAB**, named `closed-size-bab`. +Across 12 development cases and two repetitions, it improves final checked +quality in eight pairs, ties in twelve, and loses in two against the initial +DOM/regret BAB. Neither candidate finds a tour in the other two pairs. Both +repetitions give the same final objectives and proof decisions. The 100 ms +quality checkpoint gives size BAB twelve wins and no losses; at 1/5/15 seconds, +its counts match final quality. These checkpoints use native reported time. + +| Candidate | Feasible samples / 24 | Proven samples / 24 | Final quality wins / ties / losses / neither vs initial | Largest sampled RSS (MiB) | +| --- | --- | --- | --- | --- | +| `closed-bab` | 16 | 12 | baseline | 10.06 | +| `closed-val-bab` | 14 | 12 | 0 / 14 / 2 / 8 | 10.34 | +| `closed-size-bab` | 20 | 18 | 8 / 12 / 2 / 2 | 11.14 | +| `closed-rbs-regret` | 16 | 12 | 0 / 16 / 0 / 8 | 14.36 | +| `closed-rbs-size` | 20 | 18 | 8 / 12 / 2 / 2 | 13.11 | + +Size branching proves cost 249 on the marked Dumas window variant and 267 on +canonical Gendreau n20, where regret branching finds no tour. On Gendreau n60 it +finds cost 527 without proof; regret branching finds no tour. On the marked +Gendreau window variant it proves 202, while regret candidates end at 208 without +proof. The counterexample is canonical `AFG/rbg038a.tw`: DOM/regret BAB and regret +RBS retain checked cost 2493 in both repetitions, while size candidates and +VAL/regret find no tour. No candidate finds a tour on `Dumas/n60w100.001.txt`. +Canonical cases and four deterministic window variants remain separately marked +in the report; scaled costs are compared only within each case. + +VAL/regret offers no quality or proof wins and loses the AFG tour. Size RBS +matches size BAB final quality and proof counts. Among their eighteen paired +proofs, RBS finishes earlier in six and later in twelve; it has higher sampled +RSS in eighteen comparable pairs. Its first-incumbent timing splits ten earlier +and ten later. Regret RBS adds no final-quality or proof improvement over regret +BAB. These results select size BAB and do not justify recommending an anytime +restart recipe. Both restart commands remain available as explicit alternatives. + +Use the frozen model settings explicitly in later engine/formulation comparisons: + +```sh +build/open-search/bin/tsptw INSTANCE --formulation closed --engine bab --circuit-propagation dom --cost-branching size --time-ms 15000 +# Evaluated restart recipes; fixed Luby 1000 and no-good depth 128: +build/open-search/bin/tsptw INSTANCE --formulation closed --engine rbs --circuit-propagation dom --cost-branching regret --time-ms 15000 +build/open-search/bin/tsptw INSTANCE --formulation closed --engine rbs --circuit-propagation dom --cost-branching size --time-ms 15000 +``` + +Those native commands use one thread and a native time stop. The campaign adds +the outer launch-to-reap deadline and sampled 1 GiB RSS watchdog through this +exact command; repeating it resumes the same collection: + +```sh +uv run --script misc/tsptw-benchmark.py preview --split development --configs closed-bab,closed-val-bab,closed-size-bab,closed-rbs-regret,closed-rbs-size --selected-root /tmp/open-search-tsptw-selected-network --solver build/open-search/bin/tsptw --repetitions 2 --seconds 15 --compilation-seconds 4.1385885410127235 +uv run --script misc/tsptw-benchmark.py run --name cp-baseline-tuning --split development --configs closed-bab,closed-val-bab,closed-size-bab,closed-rbs-regret,closed-rbs-size --selected-root /tmp/open-search-tsptw-selected-network --solver build/open-search/bin/tsptw --repetitions 2 --seconds 15 --compilation-seconds 4.1385885410127235 +uv run --script misc/tsptw-benchmark.py analyze --name cp-baseline-tuning +``` + +The 120-sample campaign had a nominal 1,800-second allowance and used 792.229 +seconds of actual process wall time. Native correctness checks, restart/portfolio +checks, tiny/resume smokes, one bounded capture interruption and option/resource +checks bring tuning process wall time to 892.282 seconds. The two Release +rebuilds used 4.139 seconds separately; including those builds gives a conservative +896.421-second total within the 2,700-second tuning ceiling, leaving 1,803.579 +seconds for bounded verification. No campaign attempts were interrupted. Two +cache-blocked uv launches started no native process. The later evaluation budget +remains separate; no held-out outcomes were inspected. + +All 806 emitted campaign incumbents passed the independent Fraction checker. +There were three outer timeouts, zero RSS terminations and no invalid output, +execution or measurement failures. The timeout rows retain checked feasible +quality where available and never proof. Eighteen short samples finished without +a live RSS observation. The largest observed sampling gap was 465 ms and the +longest sample took 15.101 wall seconds: the nominal 50 ms watchdog can miss +peaks and overshoot. Native reported timing and outer wall timing remain separate. +The runner does not measure frontier/clone footprint or isolate closed-model +propagation/clone time. + +[The compact report](cp-baseline-tuning.json) freezes exact candidate arguments, +input/build/runner hashes, paired counts, per-case scaled objectives/proofs, +resource limits, commands and budget use. Raw configuration, 120 sample records, +stdout/stderr and paired `analysis.json` remain under +`build/tsptw-results/cp-baseline-tuning`. The measured runner, native source/binary, +libraries, inputs and configuration stayed unchanged after collection began. +Final freeze edits affect only this README and the compact report. Offline +analysis rechecked every incumbent and reproduced the same report; completed +resume launched no samples and preserved all 360 sample files and their mtimes. +Verification should replay these records and the completed command instead of +launching another development matrix. The existing eight defaults still use +DOM/regret; later evaluation must apply the frozen settings explicitly. + +## Frozen held-out comparison + +Task 016 freezes eight configurations before measuring the 24 held-out cases: +20 canonical instances and four separately marked window variants, with five +canonical instances and one variant per family. Source instances remain disjoint +from development. Each case/configuration has one observation, a 30-second outer +cap, one search thread and the same sampled 1 GiB process-group RSS watchdog. +The watchdog requests samples every 50 ms; process-list probes and termination +can extend that interval and overshoot either limit. Captures retain the observed +gaps, sampled RSS overshoot and termination/reap time. Short processes can +finish during a probe, so a sampled peak can miss brief allocations and is not +a kernel peak-RSS measurement. + +`closed-dom-size-bab` and `closed-dom-size-cabs` explicitly select the frozen +DOM circuit propagation and size cost branching. The historical names and +arguments remain available. The four `transition-*-lazy-unary` configurations +use lazy paths, PREFIX guidance, unary residual propagation and Pareto OFF. +The development study fixed three case IDs before measurement, then compared +lazy PREFIX/OFF/Pareto-OFF A* with eager-only, Pareto-only, min-arcs-only and +unary-only changes, twice at two seconds each. Unary propagation solved the +tight Dumas case in both repetitions and avoided RSS termination on the other +two cases; min-arcs alone supplied no checked solution within those caps. These +development observations selected PREFIX/unary before held-out collection. + +Native BAB does not consume `HeuristicGuidance`: it reports zero bound calls, +and selecting min-arcs would not add a BAB prune. Unary propagation applies to +all four native transition engines. DIDPPy remains pinned to 0.11.1; its model +uses set/element/resource state, two global-minimum arc bounds and resource +dominance. It also imports the backend before reporting preprocessing. These +representation, inference and clock differences prevent an overall Gecode/DIDP +ranking. Compare native transition engines first, closed engines first, and +DIDPPy engines first; assess closed/transition formulation contrasts separately. + +The captured run is `build/tsptw-results/heldout-comparison-016`; its immutable +`config.json` records all 192 exact commands, input/source/binary/library hashes, +adapter/checker/runner identities, pin, host and limits before the first launch. +The actual shared ledger is `/tmp/open-search-016-budget.jsonl`. It includes +builds, validation, development, failed attempts and held-out launch-through-reap +time, enforces a 7,200-second total, and reserves 300 seconds for verification +and 600 seconds for termination overhead and contingency. Pre-freeze work stays +below 400 seconds. Resume retains this ledger and skips validated terminal rows, +including failures; partial attempts remain beside their replacements. The RSS +probe requires process-list and owned-process termination permission on sandboxed +hosts. Two held-out termination attempts failed after normal stopped summaries; +the runner recorded null return codes before confirming reap. Both outer +commands used escalation; it did not eliminate this seam. Escalated process +inspection found no owned child. The original interruptions and separate conservative +external cleanup intervals remain charged in the ledger, and the partial streams +remain archived beside the resumed capture. Those intervals are absence +confirmations, not measured runner reap times. + +Use the exact frozen command to resume; `--max-samples 0` saves and inspects the +configuration without launching a solver. A material change requires a new run +name. Keep one writer, and initialize a fresh shared budget ledger before any +measured build or validation when starting a separately budgeted campaign. + +```sh +uv run --script misc/tsptw-benchmark.py run --name heldout-comparison-016 \ + --split held-out --selected-root /tmp/open-search-tsptw-selected-network \ + --configs closed-dom-size-bab,closed-dom-size-cabs,transition-bab-lazy-unary,transition-astar-lazy-unary,transition-cabs-lazy-unary,transition-column-lazy-unary,didppy-astar,didppy-cabs \ + --repetitions 1 --seconds 30 --budget-ledger /tmp/open-search-016-budget.jsonl +uv run --script misc/tsptw-benchmark.py analyze --name heldout-comparison-016 \ + --output examples/data/tsptw/heldout-comparison.json +``` + +Analysis launches no solver. It reads the expected frozen rows, retains all +terminal outcomes, and groups families and canonical/variant cases. Publication +checkpoints at 100 ms, 1 s, 5 s, 15 s and 30 s use solver-reported clocks; outer +wall time includes launch and reap. Launch-inclusive publication timestamps are +unavailable. A final gap is reported only against a matching campaign optimality +proof; an observed best cost is not an established optimum. Killed processes +can retain checked incumbents but supply no proof or post-kill phase summaries. +Frontier/clone buffer bytes remain unavailable, and `sizeof` allocation fields +exclude dynamic storage. Supported counts, inference/setup times, descriptor +storage and sampled RSS remain in the individual records and compact comparison. +Runtime pairs retain terminal status and proof-reported time separately. A lower +launch-wall time from an RSS-stopped run records earlier termination; it does +not establish a speedup or completed search. Aggregate wall-time wins are not +algorithm runtime gains when observations have different completion and stop +conditions. + +The [saved comparison](heldout-comparison.json) summarizes all 192 terminal rows +and per-case costs/checkpoints from 569 independently checked publications. Raw +captures retain the full traces. Offline replay also checked three publications +in the two archived interrupted attempts; +those attempts do not contribute a better cost to a replacement row. The final +matrix has 81 normal exits (44 proofs and 37 ordinary stops), 61 outer time stops +and 50 RSS stops. It has no terminal execution or invalid-output error; the two +termination interruptions above remain separate failed attempts. + +| Frozen configuration | Tour by 1 s | Tour by 30 s | Optimal | Infeasible | Outer time | RSS | +|---|---:|---:|---:|---:|---:|---:| +| closed DOM/size BAB | 6 | 6 | 6 | 1 | 5 | 0 | +| closed DOM/size CABS | 6 | 7 | 6 | 1 | 10 | 3 | +| transition BAB | 13 | 16 | 4 | 2 | 4 | 0 | +| transition A* | 3 | 4 | 4 | 2 | 7 | 11 | +| transition CABS | 13 | 16 | 4 | 2 | 10 | 1 | +| transition column | 14 | 15 | 4 | 2 | 4 | 14 | +| DIDPPy A* | 2 | 3 | 3 | 0 | 0 | 21 | +| DIDPPy CABS | 5 | 7 | 3 | 0 | 21 | 0 | + +Tour columns use each solver's reported clock and include checked incumbents +from resource-stopped runs. A checked tour outranks no tour in paired +quality counts. Within the frozen transition model, CABS has better 30-second +quality than BAB on 11 cases, worse quality on two and equal cost on five; six +cases have no tour from either. Column has 10 better, three worse and five equal +outcomes. At one second, CABS has nine better and three worse outcomes than BAB, +and column has ten better and three worse. All four engines prove the same four +optimal and two infeasible cases. A* retains no additional tour over BAB in the +final observations. The closed engines each prove six optimal cases and one +infeasible case; CABS adds one feasible case. DIDPPy CABS adds four feasible cases +to A*'s three, with the same three optimality proofs. Runtime tallies remain +censored as described above; earlier RSS exits do not show faster search. + +Formulation contrasts change representation, branching and inference together. +Transition BAB has better final quality than closed BAB on ten cases and worse +quality on one; transition CABS has better quality than closed CABS on ten and +worse quality on none. Each closed engine proves five canonical optima, distributed Dumas/Gendreau– +Dumas/Solomon/AFG as 1/1/2/1; each transition engine proves three, distributed +1/0/2/0. All native engines prove the Dumas variant infeasible and the Solomon +variant optimal; transition engines also prove the Gendreau–Dumas variant +infeasible. The AFG variant remains unresolved. Variants use different source +cases from the canonical selection, so these groups do not measure a matched +window change. Only six cases have a matching proved optimum for final gaps. + +Available native setup medians range from 0.288 to 0.864 ms; DIDPPy medians are +9.327–9.604 ms after backend import. Native node counters range from 0 to +13,369,316, and DIDPPy counters from 10 to 4,711,127; they describe work in their +own models. Normal transition summaries report median unary-inference/search +ratios of 81.1% for BAB and 80.0% for CABS. Propagation timing lies inside inference +timing and must not be added to it. These subsets omit killed runs. BAB reports +zero bound calls. Available lazy allocation records have zero root positions, +at most one prepared root position and n positions for a completed tour, +including return. Space/IntVar handle sizes are 488/8 bytes; available Pareto +storage counts are zero with Pareto OFF. Dynamic buffers and frontier/clone +storage remain unmeasured. + +The terminal captures contain 33,321 RSS samples. Their largest observed sample +gap is 0.409194 s, time-cap overshoot 0.074148 s, RSS overshoot 70,402,048 bytes, +and termination/reap duration 0.042376 s. Process-list probes occupy 1,534.892 s +inside the captures while targets continue concurrently; this is not additive +solver CPU time or a correction to subtract. Terminal-row launch wall totals +3,488.308 s. The watchdog's cost and sampling gaps limit finer timing and peak +memory interpretations. + +At implementation handoff, the shared ledger charges **3,889.734562 s** of its +7,200 s allowance: prebuilt-target checking 0.207014 s, correctness groups +120.118458 s, development 35.248518 s and held-out work 3,734.160572 s. Held-out +work comprises 3,548.328856 measured group seconds and separate conservative +cleanup allowances of 128.747037 and 57.084679 s. The measured total across phases +is 3,703.902846 s. Development includes its first process-list failure +(0.005793 s); held-out includes both interrupted attempts (30.007945 and +30.008852 s). No process remains pending. Verification has used 0 s and retains +its 300 s reserve; 3,310.265438 s of the total remains. The ledger and raw captures +are external artifacts, and these figures describe this handoff. + +The observed 11 A* and 14 column RSS stops support one separately reviewed, +bounded fixed-width continuation experiment for deterministic TSPTW transitions. +The fixed-width study found that an original-Choice cursor matched border +regeneration's Space reduction with fewer keys and commitments on its worked +trees. Compare that restricted cursor +with CABS, recording inference work as well as retained Spaces: current normal +summaries spend much of search inside unary inference. Generic Space border +replay still lacks its required contract. The suffix-repair study found no +improvement on its two development neighborhoods and supports no LNBS +adoption. The held-out outcomes do not change frozen settings or solver defaults. diff --git a/examples/data/tsptw/bounds-comparison.json b/examples/data/tsptw/bounds-comparison.json new file mode 100644 index 0000000000..5c389284b3 --- /dev/null +++ b/examples/data/tsptw/bounds-comparison.json @@ -0,0 +1,1457 @@ +{ + "experiment": "open-search-009-bounds", + "date": "2026-10-07", + "question": "How do cheap residual cost bounds and unary inference change transition A* work on three development inputs?", + "inputs": [ + { + "path": "Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748", + "split": "development" + }, + { + "path": "GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba", + "split": "development" + }, + { + "path": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "sha256": "2aed1af8ef2756d0ecf902a6c0a9f9916893bb2a65ef9a284e3ff85848c1962d", + "split": "development" + } + ], + "configurations": [ + { + "name": "prefix/off", + "bound": "prefix", + "residual_propagation": "off" + }, + { + "name": "min-arcs/off", + "bound": "min-arcs", + "residual_propagation": "off" + }, + { + "name": "min-arcs/unary", + "bound": "min-arcs", + "residual_propagation": "unary" + } + ], + "repetitions": 2, + "build": { + "type": "Release", + "compiler": "AppleClang 21", + "cmake": "3.27.6", + "ninja": "1.11.1", + "command": "cmake --build build/open-search --target tsptw gecode-test -j 8", + "binary_sha256": "dfc5cc589a3b27d431b36f236367e44f5623a09228a24349814be1b7cbee6b51", + "source_head": "10b2c8adc9225de2b5f29db6712e51cc6d976165", + "compilation_timed": false + }, + "host": { + "platform": "macOS-26.6.2-arm64-arm-64bit-Mach-O", + "machine": "arm64", + "cpu": "Apple M1 Max" + }, + "settings": { + "engine": "astar", + "formulation": "transition", + "state_pruning": "off", + "threads": 1, + "seed": null, + "native_time_ms": 5000, + "external_wall_guard_ms": 7000, + "rss_watchdog_bytes": 1073741824, + "rss_nominal_sample_ms": 50, + "processes_at_once": 1, + "warmups": 0, + "nominal_solver_budget_ms": 90000 + }, + "boundaries": { + "end_to_end_ms": "Before launch through observing termination and reaping; includes startup, input reading, preprocessing, search, output and common ps/poll/sleep overhead. It can exceed actual process duration by the observation delay; excludes independent checking and compilation.", + "native_elapsed_ms": "Example elapsed from before input reading/preprocessing through summary emission, excluding process startup and compilation.", + "bound_ms": "All actual guidance bound calculations, including repeated work; one shared run aggregate.", + "inference_ms": "All residual inference calls: construction, posting, status and domain inspection, including replay and repeated work; excludes shortest-path preprocessing.", + "propagation_ms": "Residual scheduling status() calls only; part of inference_ms.", + "node": "Native search statistics.node records expanded/visited search nodes, not arc evaluations.", + "rss": "ps -o rss= -p PID reports process RSS in KiB, sampled serially at nominal 50ms plus ps/loop overhead; peaks between samples can be missed. Includes native process only. Kill above 1GiB is a sampled watchdog, not a hard allocation limit.", + "storage": "Sampled process RSS includes frontier and model storage; state_bytes reports descriptor objects/key/label capacities only, excluding containers/allocator overhead. No native frontier-byte counter exists; field is null.", + "guard": "7 seconds from launch, polled with the RSS loop. On guard/watchdog, kill the owned process group, reap it and retain partial output; no proof is assigned without a native complete summary.", + "timing_precision": "Example times have 0.001ms precision; sampled RSS has 1024-byte units. Probe overhead is common across configurations.", + "reported_native_elapsed_sum_ms": "Sum of eight available native summary times; excludes ten RSS-killed processes whose elapsed/counter summaries are unavailable." + }, + "schedule": [ + { + "input": "Dumas/n20w20.001.txt", + "configuration": "prefix/off", + "repetition": 1 + }, + { + "input": "Dumas/n20w20.001.txt", + "configuration": "min-arcs/off", + "repetition": 1 + }, + { + "input": "Dumas/n20w20.001.txt", + "configuration": "min-arcs/unary", + "repetition": 1 + }, + { + "input": "GendreauDumasExtended/n20w120.001.txt", + "configuration": "prefix/off", + "repetition": 1 + }, + { + "input": "GendreauDumasExtended/n20w120.001.txt", + "configuration": "min-arcs/off", + "repetition": 1 + }, + { + "input": "GendreauDumasExtended/n20w120.001.txt", + "configuration": "min-arcs/unary", + "repetition": 1 + }, + { + "input": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "configuration": "prefix/off", + "repetition": 1 + }, + { + "input": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "configuration": "min-arcs/off", + "repetition": 1 + }, + { + "input": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "configuration": "min-arcs/unary", + "repetition": 1 + }, + { + "input": "Dumas/n20w20.001.txt", + "configuration": "min-arcs/unary", + "repetition": 2 + }, + { + "input": "Dumas/n20w20.001.txt", + "configuration": "min-arcs/off", + "repetition": 2 + }, + { + "input": "Dumas/n20w20.001.txt", + "configuration": "prefix/off", + "repetition": 2 + }, + { + "input": "GendreauDumasExtended/n20w120.001.txt", + "configuration": "min-arcs/unary", + "repetition": 2 + }, + { + "input": "GendreauDumasExtended/n20w120.001.txt", + "configuration": "min-arcs/off", + "repetition": 2 + }, + { + "input": "GendreauDumasExtended/n20w120.001.txt", + "configuration": "prefix/off", + "repetition": 2 + }, + { + "input": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "configuration": "min-arcs/unary", + "repetition": 2 + }, + { + "input": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "configuration": "min-arcs/off", + "repetition": 2 + }, + { + "input": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "configuration": "prefix/off", + "repetition": 2 + } + ], + "results": [ + { + "input": "Dumas/n20w20.001.txt", + "configuration": "prefix/off", + "repetition": 1, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/Dumas/n20w20.001.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "prefix", + "--residual-propagation", + "off", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": 0, + "status": "complete-output", + "end_to_end_ms": 2899.537, + "rss_peak_sampled_bytes": 1022754816, + "rss_samples": 46, + "rss_max_observed_sample_gap_ms": 67.774, + "native_frontier_peak_bytes": null, + "checked_incumbents": [ + { + "record": { + "type": "solution", + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ], + "scaled_cost": 378, + "scale": 1, + "cost": "378", + "return_time": "387", + "elapsed_ms": 2819.345 + }, + "checked": true + } + ], + "first_solution_ms": 2819.345, + "native_summary": { + "type": "summary", + "engine": "astar", + "formulation": "transition", + "state_pruning": "off", + "bound": "prefix", + "residual_propagation": "off", + "termination": "optimal", + "incumbent": { + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ], + "scaled_cost": 378, + "scale": 1, + "cost": "378", + "return_time": "387" + }, + "allocation": { + "space_bytes": 488, + "intvar_handle_bytes": 8, + "root_positions": 0, + "prepared_root_positions": 1, + "solution_positions": 21 + }, + "measurements": { + "bound_calls": 1392648, + "bound_ms": 34.64, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "elapsed_ms": 2819.553, + "statistics": { + "node": 2785287, + "fail": 1392639, + "depth": 21, + "propagate": 1392646, + "restart": 0, + "nogood": 0, + "state_pruned": 0, + "state_descriptors": 0, + "state_bytes": 0 + } + }, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": null + }, + { + "input": "Dumas/n20w20.001.txt", + "configuration": "min-arcs/off", + "repetition": 1, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/Dumas/n20w20.001.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "min-arcs", + "--residual-propagation", + "off", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": 0, + "status": "complete-output", + "end_to_end_ms": 3101.83, + "rss_peak_sampled_bytes": 997769216, + "rss_samples": 47, + "rss_max_observed_sample_gap_ms": 122.693, + "native_frontier_peak_bytes": null, + "checked_incumbents": [ + { + "record": { + "type": "solution", + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ], + "scaled_cost": 378, + "scale": 1, + "cost": "378", + "return_time": "387", + "elapsed_ms": 3054.261 + }, + "checked": true + } + ], + "first_solution_ms": 3054.261, + "native_summary": { + "type": "summary", + "engine": "astar", + "formulation": "transition", + "state_pruning": "off", + "bound": "min-arcs", + "residual_propagation": "off", + "termination": "optimal", + "incumbent": { + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ], + "scaled_cost": 378, + "scale": 1, + "cost": "378", + "return_time": "387" + }, + "allocation": { + "space_bytes": 488, + "intvar_handle_bytes": 8, + "root_positions": 0, + "prepared_root_positions": 1, + "solution_positions": 21 + }, + "measurements": { + "bound_calls": 1392551, + "bound_ms": 169.566, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "elapsed_ms": 3055.557, + "statistics": { + "node": 2782969, + "fail": 1392548, + "depth": 21, + "propagate": 1390419, + "restart": 0, + "nogood": 0, + "state_pruned": 0, + "state_descriptors": 0, + "state_bytes": 0 + } + }, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": null + }, + { + "input": "Dumas/n20w20.001.txt", + "configuration": "min-arcs/unary", + "repetition": 1, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/Dumas/n20w20.001.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "min-arcs", + "--residual-propagation", + "unary", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": 0, + "status": "complete-output", + "end_to_end_ms": 66.532, + "rss_peak_sampled_bytes": 2473984, + "rss_samples": 1, + "rss_max_observed_sample_gap_ms": null, + "native_frontier_peak_bytes": null, + "checked_incumbents": [ + { + "record": { + "type": "solution", + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ], + "scaled_cost": 378, + "scale": 1, + "cost": "378", + "return_time": "387", + "elapsed_ms": 1.927 + }, + "checked": true + } + ], + "first_solution_ms": 1.927, + "native_summary": { + "type": "summary", + "engine": "astar", + "formulation": "transition", + "state_pruning": "off", + "bound": "min-arcs", + "residual_propagation": "unary", + "termination": "optimal", + "incumbent": { + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ], + "scaled_cost": 378, + "scale": 1, + "cost": "378", + "return_time": "387" + }, + "allocation": { + "space_bytes": 488, + "intvar_handle_bytes": 8, + "root_positions": 0, + "prepared_root_positions": 1, + "solution_positions": 21 + }, + "measurements": { + "bound_calls": 121, + "bound_ms": 0.019, + "inference_calls": 1006, + "inference_ms": 1.215, + "propagation_ms": 0.791 + }, + "elapsed_ms": 2.005, + "statistics": { + "node": 1006, + "fail": 891, + "depth": 21, + "propagate": 113, + "restart": 0, + "nogood": 0, + "state_pruned": 0, + "state_descriptors": 0, + "state_bytes": 0 + } + }, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": null + }, + { + "input": "GendreauDumasExtended/n20w120.001.txt", + "configuration": "prefix/off", + "repetition": 1, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/GendreauDumasExtended/n20w120.001.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "prefix", + "--residual-propagation", + "off", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": -9, + "status": "rss-watchdog", + "end_to_end_ms": 394.628, + "rss_peak_sampled_bytes": 1146388480, + "rss_samples": 7, + "rss_max_observed_sample_gap_ms": 65.769, + "native_frontier_peak_bytes": null, + "checked_incumbents": [], + "first_solution_ms": null, + "native_summary": null, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": "RSS watchdog killed the process before its final summary; expansions, bound/inference timings and native storage counters are unavailable." + }, + { + "input": "GendreauDumasExtended/n20w120.001.txt", + "configuration": "min-arcs/off", + "repetition": 1, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/GendreauDumasExtended/n20w120.001.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "min-arcs", + "--residual-propagation", + "off", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": -9, + "status": "rss-watchdog", + "end_to_end_ms": 462.089, + "rss_peak_sampled_bytes": 1102479360, + "rss_samples": 8, + "rss_max_observed_sample_gap_ms": 66.062, + "native_frontier_peak_bytes": null, + "checked_incumbents": [], + "first_solution_ms": null, + "native_summary": null, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": "RSS watchdog killed the process before its final summary; expansions, bound/inference timings and native storage counters are unavailable." + }, + { + "input": "GendreauDumasExtended/n20w120.001.txt", + "configuration": "min-arcs/unary", + "repetition": 1, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/GendreauDumasExtended/n20w120.001.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "min-arcs", + "--residual-propagation", + "unary", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": 0, + "status": "complete-output", + "end_to_end_ms": 5235.776, + "rss_peak_sampled_bytes": 695304192, + "rss_samples": 80, + "rss_max_observed_sample_gap_ms": 88.36, + "native_frontier_peak_bytes": null, + "checked_incumbents": [], + "first_solution_ms": null, + "native_summary": { + "type": "summary", + "engine": "astar", + "formulation": "transition", + "state_pruning": "off", + "bound": "min-arcs", + "residual_propagation": "unary", + "termination": "stopped", + "incumbent": null, + "allocation": { + "space_bytes": 488, + "intvar_handle_bytes": 8, + "root_positions": 0, + "prepared_root_positions": 1, + "solution_positions": 0 + }, + "measurements": { + "bound_calls": 455230, + "bound_ms": 69.648, + "inference_calls": 2516046, + "inference_ms": 3346.832, + "propagation_ms": 2194.261 + }, + "elapsed_ms": 5000.037, + "statistics": { + "node": 2516046, + "fail": 2060816, + "depth": 13, + "propagate": 213102, + "restart": 0, + "nogood": 0, + "state_pruned": 0, + "state_descriptors": 0, + "state_bytes": 0 + } + }, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": null + }, + { + "input": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "configuration": "prefix/off", + "repetition": 1, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "prefix", + "--residual-propagation", + "off", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": -9, + "status": "rss-watchdog", + "end_to_end_ms": 409.875, + "rss_peak_sampled_bytes": 1170522112, + "rss_samples": 7, + "rss_max_observed_sample_gap_ms": 65.887, + "native_frontier_peak_bytes": null, + "checked_incumbents": [], + "first_solution_ms": null, + "native_summary": null, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": "RSS watchdog killed the process before its final summary; expansions, bound/inference timings and native storage counters are unavailable." + }, + { + "input": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "configuration": "min-arcs/off", + "repetition": 1, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "min-arcs", + "--residual-propagation", + "off", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": -9, + "status": "rss-watchdog", + "end_to_end_ms": 466.028, + "rss_peak_sampled_bytes": 1088241664, + "rss_samples": 8, + "rss_max_observed_sample_gap_ms": 65.499, + "native_frontier_peak_bytes": null, + "checked_incumbents": [], + "first_solution_ms": null, + "native_summary": null, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": "RSS watchdog killed the process before its final summary; expansions, bound/inference timings and native storage counters are unavailable." + }, + { + "input": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "configuration": "min-arcs/unary", + "repetition": 1, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "min-arcs", + "--residual-propagation", + "unary", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": -9, + "status": "rss-watchdog", + "end_to_end_ms": 3850.778, + "rss_peak_sampled_bytes": 1076232192, + "rss_samples": 58, + "rss_max_observed_sample_gap_ms": 123.563, + "native_frontier_peak_bytes": null, + "checked_incumbents": [], + "first_solution_ms": null, + "native_summary": null, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": "RSS watchdog killed the process before its final summary; expansions, bound/inference timings and native storage counters are unavailable." + }, + { + "input": "Dumas/n20w20.001.txt", + "configuration": "min-arcs/unary", + "repetition": 2, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/Dumas/n20w20.001.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "min-arcs", + "--residual-propagation", + "unary", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": 0, + "status": "complete-output", + "end_to_end_ms": 67.496, + "rss_peak_sampled_bytes": 1556480, + "rss_samples": 1, + "rss_max_observed_sample_gap_ms": null, + "native_frontier_peak_bytes": null, + "checked_incumbents": [ + { + "record": { + "type": "solution", + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ], + "scaled_cost": 378, + "scale": 1, + "cost": "378", + "return_time": "387", + "elapsed_ms": 2.046 + }, + "checked": true + } + ], + "first_solution_ms": 2.046, + "native_summary": { + "type": "summary", + "engine": "astar", + "formulation": "transition", + "state_pruning": "off", + "bound": "min-arcs", + "residual_propagation": "unary", + "termination": "optimal", + "incumbent": { + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ], + "scaled_cost": 378, + "scale": 1, + "cost": "378", + "return_time": "387" + }, + "allocation": { + "space_bytes": 488, + "intvar_handle_bytes": 8, + "root_positions": 0, + "prepared_root_positions": 1, + "solution_positions": 21 + }, + "measurements": { + "bound_calls": 121, + "bound_ms": 0.019, + "inference_calls": 1006, + "inference_ms": 1.232, + "propagation_ms": 0.793 + }, + "elapsed_ms": 2.171, + "statistics": { + "node": 1006, + "fail": 891, + "depth": 21, + "propagate": 113, + "restart": 0, + "nogood": 0, + "state_pruned": 0, + "state_descriptors": 0, + "state_bytes": 0 + } + }, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": null + }, + { + "input": "Dumas/n20w20.001.txt", + "configuration": "min-arcs/off", + "repetition": 2, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/Dumas/n20w20.001.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "min-arcs", + "--residual-propagation", + "off", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": 0, + "status": "complete-output", + "end_to_end_ms": 3456.482, + "rss_peak_sampled_bytes": 999833600, + "rss_samples": 52, + "rss_max_observed_sample_gap_ms": 90.294, + "native_frontier_peak_bytes": null, + "checked_incumbents": [ + { + "record": { + "type": "solution", + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ], + "scaled_cost": 378, + "scale": 1, + "cost": "378", + "return_time": "387", + "elapsed_ms": 3432.149 + }, + "checked": true + } + ], + "first_solution_ms": 3432.149, + "native_summary": { + "type": "summary", + "engine": "astar", + "formulation": "transition", + "state_pruning": "off", + "bound": "min-arcs", + "residual_propagation": "off", + "termination": "optimal", + "incumbent": { + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ], + "scaled_cost": 378, + "scale": 1, + "cost": "378", + "return_time": "387" + }, + "allocation": { + "space_bytes": 488, + "intvar_handle_bytes": 8, + "root_positions": 0, + "prepared_root_positions": 1, + "solution_positions": 21 + }, + "measurements": { + "bound_calls": 1392551, + "bound_ms": 169.037, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "elapsed_ms": 3434.02, + "statistics": { + "node": 2782969, + "fail": 1392548, + "depth": 21, + "propagate": 1390419, + "restart": 0, + "nogood": 0, + "state_pruned": 0, + "state_descriptors": 0, + "state_bytes": 0 + } + }, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": null + }, + { + "input": "Dumas/n20w20.001.txt", + "configuration": "prefix/off", + "repetition": 2, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/Dumas/n20w20.001.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "prefix", + "--residual-propagation", + "off", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": 0, + "status": "complete-output", + "end_to_end_ms": 2730.928, + "rss_peak_sampled_bytes": 1022377984, + "rss_samples": 42, + "rss_max_observed_sample_gap_ms": 93.771, + "native_frontier_peak_bytes": null, + "checked_incumbents": [ + { + "record": { + "type": "solution", + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ], + "scaled_cost": 378, + "scale": 1, + "cost": "378", + "return_time": "387", + "elapsed_ms": 2685.032 + }, + "checked": true + } + ], + "first_solution_ms": 2685.032, + "native_summary": { + "type": "summary", + "engine": "astar", + "formulation": "transition", + "state_pruning": "off", + "bound": "prefix", + "residual_propagation": "off", + "termination": "optimal", + "incumbent": { + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ], + "scaled_cost": 378, + "scale": 1, + "cost": "378", + "return_time": "387" + }, + "allocation": { + "space_bytes": 488, + "intvar_handle_bytes": 8, + "root_positions": 0, + "prepared_root_positions": 1, + "solution_positions": 21 + }, + "measurements": { + "bound_calls": 1392648, + "bound_ms": 34.164, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "elapsed_ms": 2685.122, + "statistics": { + "node": 2785287, + "fail": 1392639, + "depth": 21, + "propagate": 1392646, + "restart": 0, + "nogood": 0, + "state_pruned": 0, + "state_descriptors": 0, + "state_bytes": 0 + } + }, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": null + }, + { + "input": "GendreauDumasExtended/n20w120.001.txt", + "configuration": "min-arcs/unary", + "repetition": 2, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/GendreauDumasExtended/n20w120.001.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "min-arcs", + "--residual-propagation", + "unary", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": 0, + "status": "complete-output", + "end_to_end_ms": 5273.597, + "rss_peak_sampled_bytes": 687292416, + "rss_samples": 80, + "rss_max_observed_sample_gap_ms": 100.22, + "native_frontier_peak_bytes": null, + "checked_incumbents": [], + "first_solution_ms": null, + "native_summary": { + "type": "summary", + "engine": "astar", + "formulation": "transition", + "state_pruning": "off", + "bound": "min-arcs", + "residual_propagation": "unary", + "termination": "stopped", + "incumbent": null, + "allocation": { + "space_bytes": 488, + "intvar_handle_bytes": 8, + "root_positions": 0, + "prepared_root_positions": 1, + "solution_positions": 0 + }, + "measurements": { + "bound_calls": 447565, + "bound_ms": 68.252, + "inference_calls": 2461061, + "inference_ms": 3287.88, + "propagation_ms": 2083.187 + }, + "elapsed_ms": 5000.022, + "statistics": { + "node": 2461061, + "fail": 2013496, + "depth": 13, + "propagate": 208251, + "restart": 0, + "nogood": 0, + "state_pruned": 0, + "state_descriptors": 0, + "state_bytes": 0 + } + }, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": null + }, + { + "input": "GendreauDumasExtended/n20w120.001.txt", + "configuration": "min-arcs/off", + "repetition": 2, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/GendreauDumasExtended/n20w120.001.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "min-arcs", + "--residual-propagation", + "off", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": -9, + "status": "rss-watchdog", + "end_to_end_ms": 461.656, + "rss_peak_sampled_bytes": 1081114624, + "rss_samples": 8, + "rss_max_observed_sample_gap_ms": 67.04, + "native_frontier_peak_bytes": null, + "checked_incumbents": [], + "first_solution_ms": null, + "native_summary": null, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": "RSS watchdog killed the process before its final summary; expansions, bound/inference timings and native storage counters are unavailable." + }, + { + "input": "GendreauDumasExtended/n20w120.001.txt", + "configuration": "prefix/off", + "repetition": 2, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/GendreauDumasExtended/n20w120.001.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "prefix", + "--residual-propagation", + "off", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": -9, + "status": "rss-watchdog", + "end_to_end_ms": 405.172, + "rss_peak_sampled_bytes": 1156661248, + "rss_samples": 7, + "rss_max_observed_sample_gap_ms": 65.902, + "native_frontier_peak_bytes": null, + "checked_incumbents": [], + "first_solution_ms": null, + "native_summary": null, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": "RSS watchdog killed the process before its final summary; expansions, bound/inference timings and native storage counters are unavailable." + }, + { + "input": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "configuration": "min-arcs/unary", + "repetition": 2, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "min-arcs", + "--residual-propagation", + "unary", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": -9, + "status": "rss-watchdog", + "end_to_end_ms": 3705.632, + "rss_peak_sampled_bytes": 1084768256, + "rss_samples": 58, + "rss_max_observed_sample_gap_ms": 104.467, + "native_frontier_peak_bytes": null, + "checked_incumbents": [], + "first_solution_ms": null, + "native_summary": null, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": "RSS watchdog killed the process before its final summary; expansions, bound/inference timings and native storage counters are unavailable." + }, + { + "input": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "configuration": "min-arcs/off", + "repetition": 2, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "min-arcs", + "--residual-propagation", + "off", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": -9, + "status": "rss-watchdog", + "end_to_end_ms": 473.888, + "rss_peak_sampled_bytes": 1118502912, + "rss_samples": 8, + "rss_max_observed_sample_gap_ms": 65.28, + "native_frontier_peak_bytes": null, + "checked_incumbents": [], + "first_solution_ms": null, + "native_summary": null, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": "RSS watchdog killed the process before its final summary; expansions, bound/inference timings and native storage counters are unavailable." + }, + { + "input": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "configuration": "prefix/off", + "repetition": 2, + "command": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/tmp/open-search-tsptw-selected-network/variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "--engine", + "astar", + "--formulation", + "transition", + "--state-pruning", + "off", + "--bound", + "prefix", + "--residual-propagation", + "off", + "--time-ms", + "5000" + ], + "cwd": "/Users/zayenz/.codex/worktrees/2d06/gecode", + "exit_code": -9, + "status": "rss-watchdog", + "end_to_end_ms": 393.165, + "rss_peak_sampled_bytes": 1099808768, + "rss_samples": 7, + "rss_max_observed_sample_gap_ms": 66.106, + "native_frontier_peak_bytes": null, + "checked_incumbents": [], + "first_solution_ms": null, + "native_summary": null, + "stderr": "", + "validation_error": null, + "native_metrics_unavailable_reason": "RSS watchdog killed the process before its final summary; expansions, bound/inference timings and native storage counters are unavailable." + } + ], + "total_end_to_end_ms": 33855.089, + "checked_incumbent_count": 6, + "incomplete_count": 12, + "reported_native_elapsed_sum_ms": 21998.487, + "native_summary_count": 8 +} diff --git a/examples/data/tsptw/cadds-comparison.json b/examples/data/tsptw/cadds-comparison.json new file mode 100644 index 0000000000..b63b59e9bd --- /dev/null +++ b/examples/data/tsptw/cadds-comparison.json @@ -0,0 +1,3344 @@ +{ + "completed_rows": 30, + "configuration": { + "binary": "/private/tmp/open-search-030-study/study", + "binary_sha256": "a4f906f3fdfd3e9373b0e2f1eb736b17f68c142e7ba7e99f301feb9a813b1c5c", + "configurations": [ + { + "engine": "cadds", + "name": "cadds-b1", + "width": 1 + }, + { + "engine": "cadds", + "name": "cadds-b4", + "width": 4 + }, + { + "engine": "cursor", + "name": "cursor-b1", + "width": 1 + }, + { + "engine": "cursor", + "name": "cursor-b4", + "width": 4 + }, + { + "engine": "cabs", + "name": "cabs", + "width": 1 + } + ], + "development_cases": [ + { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + } + ], + "development_nominal_seconds": 60, + "development_outer_cap_seconds": 2, + "development_rows": 30, + "model": { + "bound": "MIN_ARCS", + "branching": "ascending native visits", + "rank": "bound", + "representation": "lazy", + "residual_propagation": "OFF", + "seed": null, + "state_pruning": "OFF", + "threads": 1 + }, + "ordinary_stop_ms": 1500, + "repetitions": 2, + "rows": [ + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "1", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "config": "cadds-b1", + "id": "row000", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "4", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "config": "cadds-b4", + "id": "row001", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "1", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "config": "cursor-b1", + "id": "row002", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "4", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "config": "cursor-b4", + "id": "row003", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cabs", + "opt", + "1", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "config": "cabs", + "id": "row004", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "1", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "config": "cadds-b1", + "id": "row005", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "4", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "config": "cadds-b4", + "id": "row006", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "1", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "config": "cursor-b1", + "id": "row007", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "4", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "config": "cursor-b4", + "id": "row008", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cabs", + "opt", + "1", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "config": "cabs", + "id": "row009", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "1", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "cap_seconds": 2, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "config": "cadds-b1", + "id": "row010", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "4", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "cap_seconds": 2, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "config": "cadds-b4", + "id": "row011", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "1", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "cap_seconds": 2, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "config": "cursor-b1", + "id": "row012", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "4", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "cap_seconds": 2, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "config": "cursor-b4", + "id": "row013", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cabs", + "opt", + "1", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "cap_seconds": 2, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "config": "cabs", + "id": "row014", + "mode": "opt", + "repetition": 0 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cabs", + "opt", + "1", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "config": "cabs", + "id": "row015", + "mode": "opt", + "repetition": 1 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "4", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "config": "cursor-b4", + "id": "row016", + "mode": "opt", + "repetition": 1 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "1", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "config": "cursor-b1", + "id": "row017", + "mode": "opt", + "repetition": 1 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "4", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "config": "cadds-b4", + "id": "row018", + "mode": "opt", + "repetition": 1 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "1", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "config": "cadds-b1", + "id": "row019", + "mode": "opt", + "repetition": 1 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cabs", + "opt", + "1", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "config": "cabs", + "id": "row020", + "mode": "opt", + "repetition": 1 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "4", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "config": "cursor-b4", + "id": "row021", + "mode": "opt", + "repetition": 1 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "1", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "config": "cursor-b1", + "id": "row022", + "mode": "opt", + "repetition": 1 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "4", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "config": "cadds-b4", + "id": "row023", + "mode": "opt", + "repetition": 1 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "1", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "cap_seconds": 2, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "config": "cadds-b1", + "id": "row024", + "mode": "opt", + "repetition": 1 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cabs", + "opt", + "1", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "cap_seconds": 2, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "config": "cabs", + "id": "row025", + "mode": "opt", + "repetition": 1 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "4", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "cap_seconds": 2, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "config": "cursor-b4", + "id": "row026", + "mode": "opt", + "repetition": 1 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "1", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "cap_seconds": 2, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "config": "cursor-b1", + "id": "row027", + "mode": "opt", + "repetition": 1 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "4", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "cap_seconds": 2, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "config": "cadds-b4", + "id": "row028", + "mode": "opt", + "repetition": 1 + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "1", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "cap_seconds": 2, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "config": "cadds-b1", + "id": "row029", + "mode": "opt", + "repetition": 1 + } + ], + "rss_bytes": 1073741824, + "runner_sha256": "c366e66d369209aa83d43254a9e7418349f4b141634fb62a2b5c40861314eb3c", + "sample_interval_seconds": 0.05, + "schema_version": 1, + "source_sha256": "f3a998200a3ccda1aae337c181e346c80c8b8b114344f72a99fb829474e03aff", + "suite": "development" + }, + "expected_rows": 30, + "experiment": "development", + "missing_rows": [], + "rows": [ + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "1", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "best": { + "elapsed_ms": 55.2909, + "exact_cost": "378", + "scaled_cost": 378, + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ] + }, + "best_publication_ms": 55.2909, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "checked_publications": 1, + "config": "cadds-b1", + "end_to_end_wall_seconds": 1.5689090419909917, + "first_publication_ms": 55.2909, + "id": "row000", + "incumbent_trace": [ + { + "elapsed_ms": 55.2909, + "scaled_cost": 378 + } + ], + "maximum_sample_gap_seconds": 0.09563399996841326, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.588522418984212, + "rss_samples": 17, + "sampled_peak_rss_bytes": 2867200, + "status": "ok", + "summary": { + "best_scaled_cost": 378, + "bound_pruned": 2341, + "clones": 1305960, + "commits": 2609517, + "elapsed_ms": 1500.11, + "engine": "cadds", + "fragments_or_pages": 1303559, + "kind": "summary", + "measurements": { + "bound_calls": 1305962, + "bound_ms": 158.184, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 153, + "peak_pending_nodes": 151, + "peak_source_nodes": 1, + "peak_staged_nodes": 19, + "publications": 1, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 1303557, + "node": 2609518, + "propagate": 1303560, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 5.749985575675964e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "4", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "best": { + "elapsed_ms": 4.40117, + "exact_cost": "378", + "scaled_cost": 378, + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ] + }, + "best_publication_ms": 4.40117, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "checked_publications": 1, + "config": "cadds-b4", + "end_to_end_wall_seconds": 1.5452292499830946, + "first_publication_ms": 4.40117, + "id": "row001", + "incumbent_trace": [ + { + "elapsed_ms": 4.40117, + "scaled_cost": 378 + } + ], + "maximum_sample_gap_seconds": 0.09509870898909867, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5851339991204441, + "rss_samples": 17, + "sampled_peak_rss_bytes": 3653632, + "status": "ok", + "summary": { + "best_scaled_cost": 378, + "bound_pruned": 2465, + "clones": 1351305, + "commits": 2700034, + "elapsed_ms": 1500.13, + "engine": "cadds", + "fragments_or_pages": 509170, + "kind": "summary", + "measurements": { + "bound_calls": 1351308, + "bound_ms": 162.449, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 394, + "peak_pending_nodes": 390, + "peak_source_nodes": 4, + "peak_staged_nodes": 62, + "publications": 1, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 1348728, + "node": 2700035, + "propagate": 1348732, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 5.582987796515226e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "1", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "best": { + "elapsed_ms": 597.511, + "exact_cost": "378", + "scaled_cost": 378, + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ] + }, + "best_publication_ms": 597.511, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "checked_publications": 3, + "config": "cursor-b1", + "end_to_end_wall_seconds": 1.5764024590025656, + "first_publication_ms": 550.306, + "id": "row002", + "incumbent_trace": [ + { + "elapsed_ms": 550.306, + "scaled_cost": 382 + }, + { + "elapsed_ms": 578.211, + "scaled_cost": 380 + }, + { + "elapsed_ms": 597.511, + "scaled_cost": 378 + } + ], + "maximum_sample_gap_seconds": 0.09576870803721249, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5941572919255123, + "rss_samples": 17, + "sampled_peak_rss_bytes": 2457600, + "status": "ok", + "summary": { + "best_scaled_cost": 378, + "bound_pruned": 1474, + "clones": 1392555, + "commits": 2783634, + "elapsed_ms": 1476.15, + "engine": "cursor", + "fragments_or_pages": 1391081, + "kind": "summary", + "measurements": { + "bound_calls": 1392562, + "bound_ms": 172.088, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 21, + "peak_pending_nodes": 0, + "peak_source_nodes": 19, + "peak_staged_nodes": 1, + "publications": 3, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 1391076, + "node": 2783635, + "propagate": 1391083, + "restart": 0 + }, + "termination": "optimal" + }, + "termination_reap_seconds": 1.708976924419403e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "4", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "best": { + "elapsed_ms": 581.322, + "exact_cost": "378", + "scaled_cost": 378, + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ] + }, + "best_publication_ms": 581.322, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "checked_publications": 3, + "config": "cursor-b4", + "end_to_end_wall_seconds": 1.4619905420113355, + "first_publication_ms": 535.229, + "id": "row003", + "incumbent_trace": [ + { + "elapsed_ms": 535.229, + "scaled_cost": 382 + }, + { + "elapsed_ms": 562.489, + "scaled_cost": 380 + }, + { + "elapsed_ms": 581.322, + "scaled_cost": 378 + } + ], + "maximum_sample_gap_seconds": 0.09575400000903755, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5575372049352154, + "rss_samples": 16, + "sampled_peak_rss_bytes": 2555904, + "status": "ok", + "summary": { + "best_scaled_cost": 378, + "bound_pruned": 1474, + "clones": 1392555, + "commits": 2783634, + "elapsed_ms": 1433.04, + "engine": "cursor", + "fragments_or_pages": 550422, + "kind": "summary", + "measurements": { + "bound_calls": 1392562, + "bound_ms": 170.313, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 44, + "peak_pending_nodes": 0, + "peak_source_nodes": 42, + "peak_staged_nodes": 4, + "publications": 3, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 1391076, + "node": 2783635, + "propagate": 1391083, + "restart": 0 + }, + "termination": "optimal" + }, + "termination_reap_seconds": 1.625041477382183e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cabs", + "opt", + "1", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "best": { + "elapsed_ms": 15.6486, + "exact_cost": "378", + "scaled_cost": 378, + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ] + }, + "best_publication_ms": 15.6486, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "checked_publications": 1, + "config": "cabs", + "end_to_end_wall_seconds": 1.5802818750380538, + "first_publication_ms": 15.6486, + "id": "row004", + "incumbent_trace": [ + { + "elapsed_ms": 15.6486, + "scaled_cost": 378 + } + ], + "maximum_sample_gap_seconds": 0.09590904199285433, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5962389148189686, + "rss_samples": 17, + "sampled_peak_rss_bytes": 183025664, + "status": "ok", + "summary": { + "best_scaled_cost": 378, + "clones": 1212717, + "elapsed_ms": 1530.91, + "engine": "cabs", + "kind": "summary", + "measurements": { + "bound_calls": 1212717, + "bound_ms": 184.746, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 55660, + "publications": 1, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 565944, + "node": 1777891, + "propagate": 565177, + "restart": 15 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.8330174498260021e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "1", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "best": { + "elapsed_ms": 1424.95, + "exact_cost": "272", + "scaled_cost": 272, + "tour": [ + 16, + 6, + 9, + 19, + 17, + 18, + 12, + 10, + 11, + 5, + 1, + 15, + 2, + 7, + 4, + 13, + 20, + 3, + 8, + 14 + ] + }, + "best_publication_ms": 1424.95, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "checked_publications": 3, + "config": "cadds-b1", + "end_to_end_wall_seconds": 1.5671963339555077, + "first_publication_ms": 1344.09, + "id": "row005", + "incumbent_trace": [ + { + "elapsed_ms": 1344.09, + "scaled_cost": 304 + }, + { + "elapsed_ms": 1417.41, + "scaled_cost": 303 + }, + { + "elapsed_ms": 1424.95, + "scaled_cost": 272 + } + ], + "maximum_sample_gap_seconds": 0.09654795803362504, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.594099331763573, + "rss_samples": 17, + "sampled_peak_rss_bytes": 2932736, + "status": "ok", + "summary": { + "best_scaled_cost": 272, + "bound_pruned": 93141, + "clones": 1171160, + "commits": 2650837, + "elapsed_ms": 1500.13, + "engine": "cadds", + "fragments_or_pages": 1479691, + "kind": "summary", + "measurements": { + "bound_calls": 1572959, + "bound_ms": 107.121, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 180, + "peak_pending_nodes": 178, + "peak_source_nodes": 1, + "peak_staged_nodes": 20, + "publications": 3, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 1077882, + "node": 2650838, + "propagate": 1479682, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.5840050764381886e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "4", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "best": { + "elapsed_ms": 59.97, + "exact_cost": "274", + "scaled_cost": 274, + "tour": [ + 16, + 9, + 19, + 17, + 18, + 12, + 10, + 11, + 5, + 1, + 15, + 2, + 7, + 4, + 6, + 13, + 20, + 3, + 8, + 14 + ] + }, + "best_publication_ms": 59.97, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "checked_publications": 7, + "config": "cadds-b4", + "end_to_end_wall_seconds": 1.5705275829532184, + "first_publication_ms": 7.52292, + "id": "row006", + "incumbent_trace": [ + { + "elapsed_ms": 7.52292, + "scaled_cost": 379 + }, + { + "elapsed_ms": 7.64329, + "scaled_cost": 345 + }, + { + "elapsed_ms": 7.67783, + "scaled_cost": 331 + }, + { + "elapsed_ms": 9.06892, + "scaled_cost": 305 + }, + { + "elapsed_ms": 17.1653, + "scaled_cost": 294 + }, + { + "elapsed_ms": 29.2849, + "scaled_cost": 288 + }, + { + "elapsed_ms": 59.97, + "scaled_cost": 274 + } + ], + "maximum_sample_gap_seconds": 0.09625850000884384, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.589091916102916, + "rss_samples": 17, + "sampled_peak_rss_bytes": 3883008, + "status": "ok", + "summary": { + "best_scaled_cost": 274, + "bound_pruned": 1235882, + "clones": 1654204, + "commits": 2506441, + "elapsed_ms": 1500.21, + "engine": "cadds", + "fragments_or_pages": 280337, + "kind": "summary", + "measurements": { + "bound_calls": 2088598, + "bound_ms": 125.777, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 545, + "peak_pending_nodes": 543, + "peak_source_nodes": 4, + "peak_staged_nodes": 64, + "publications": 7, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 417851, + "node": 2506442, + "propagate": 852246, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.916952896863222e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "1", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "best": null, + "best_publication_ms": null, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "checked_publications": 0, + "config": "cursor-b1", + "end_to_end_wall_seconds": 1.5563660830375738, + "first_publication_ms": null, + "id": "row007", + "incumbent_trace": [], + "maximum_sample_gap_seconds": 0.09532087499974295, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5939206688781269, + "rss_samples": 17, + "sampled_peak_rss_bytes": 2359296, + "status": "ok", + "summary": { + "best_scaled_cost": null, + "bound_pruned": 0, + "clones": 1246877, + "commits": 2938533, + "elapsed_ms": 1500.13, + "engine": "cursor", + "fragments_or_pages": 1691669, + "kind": "summary", + "measurements": { + "bound_calls": 1691670, + "bound_ms": 120.983, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 19, + "peak_pending_nodes": 0, + "peak_source_nodes": 18, + "peak_staged_nodes": 1, + "publications": 0, + "scale": 1, + "statistics": { + "depth": 19, + "fail": 1246864, + "node": 2938534, + "propagate": 1691670, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 2.1669548004865646e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "4", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "best": null, + "best_publication_ms": null, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "checked_publications": 0, + "config": "cursor-b4", + "end_to_end_wall_seconds": 1.5693289580522105, + "first_publication_ms": null, + "id": "row008", + "incumbent_trace": [], + "maximum_sample_gap_seconds": 0.09604629199020565, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5921523370197974, + "rss_samples": 17, + "sampled_peak_rss_bytes": 2572288, + "status": "ok", + "summary": { + "best_scaled_cost": null, + "bound_pruned": 0, + "clones": 1270531, + "commits": 2994111, + "elapsed_ms": 1500.14, + "engine": "cursor", + "fragments_or_pages": 543784, + "kind": "summary", + "measurements": { + "bound_calls": 1723615, + "bound_ms": 124.223, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 50, + "peak_pending_nodes": 0, + "peak_source_nodes": 49, + "peak_staged_nodes": 4, + "publications": 0, + "scale": 1, + "statistics": { + "depth": 19, + "fail": 1270497, + "node": 2994112, + "propagate": 1723595, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.6249832697212696e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cabs", + "opt", + "1", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "best": { + "elapsed_ms": 1434.54, + "exact_cost": "267", + "scaled_cost": 267, + "tour": [ + 6, + 16, + 9, + 19, + 17, + 18, + 12, + 10, + 11, + 5, + 1, + 15, + 2, + 7, + 4, + 13, + 20, + 3, + 8, + 14 + ] + }, + "best_publication_ms": 1434.54, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "checked_publications": 1, + "config": "cabs", + "end_to_end_wall_seconds": 1.5544232499669306, + "first_publication_ms": 1434.54, + "id": "row009", + "incumbent_trace": [ + { + "elapsed_ms": 1434.54, + "scaled_cost": 267 + } + ], + "maximum_sample_gap_seconds": 0.09662445896537974, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.597637791885063, + "rss_samples": 17, + "sampled_peak_rss_bytes": 66584576, + "status": "ok", + "summary": { + "best_scaled_cost": 267, + "clones": 1477685, + "elapsed_ms": 1517.36, + "engine": "cabs", + "kind": "summary", + "measurements": { + "bound_calls": 1654723, + "bound_ms": 265.424, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 31537, + "publications": 1, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 69512, + "node": 1724187, + "propagate": 246505, + "restart": 14 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.2500095181167126e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "1", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "best": { + "elapsed_ms": 271.966, + "exact_cost": "444.5425", + "scaled_cost": 4445425, + "tour": [ + 14, + 18, + 13, + 9, + 5, + 4, + 6, + 8, + 7, + 16, + 19, + 11, + 17, + 1, + 10, + 3, + 12, + 2, + 15 + ] + }, + "best_publication_ms": 271.966, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "checked_publications": 2, + "config": "cadds-b1", + "end_to_end_wall_seconds": 1.5766163340304047, + "first_publication_ms": 266.113, + "id": "row010", + "incumbent_trace": [ + { + "elapsed_ms": 266.113, + "scaled_cost": 4524015 + }, + { + "elapsed_ms": 271.966, + "scaled_cost": 4445425 + } + ], + "maximum_sample_gap_seconds": 0.09632149996468797, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.6012247500475496, + "rss_samples": 17, + "sampled_peak_rss_bytes": 2932736, + "status": "ok", + "summary": { + "best_scaled_cost": 4445425, + "bound_pruned": 1100813, + "clones": 1536332, + "commits": 2398694, + "elapsed_ms": 1500.14, + "engine": "cadds", + "fragments_or_pages": 862378, + "kind": "summary", + "measurements": { + "bound_calls": 1963232, + "bound_ms": 178.65, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 149, + "peak_pending_nodes": 148, + "peak_source_nodes": 1, + "peak_staged_nodes": 19, + "publications": 2, + "scale": 10000, + "statistics": { + "depth": 20, + "fail": 435465, + "node": 2398695, + "propagate": 862366, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.8339487724006176e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "4", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "best": { + "elapsed_ms": 749.901, + "exact_cost": "444.5425", + "scaled_cost": 4445425, + "tour": [ + 14, + 18, + 13, + 9, + 5, + 4, + 6, + 8, + 7, + 16, + 19, + 11, + 17, + 1, + 10, + 3, + 12, + 2, + 15 + ] + }, + "best_publication_ms": 749.901, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "checked_publications": 1, + "config": "cadds-b4", + "end_to_end_wall_seconds": 1.5802404160494916, + "first_publication_ms": 749.901, + "id": "row011", + "incumbent_trace": [ + { + "elapsed_ms": 749.901, + "scaled_cost": 4445425 + } + ], + "maximum_sample_gap_seconds": 0.09632891602814198, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5992929548956454, + "rss_samples": 17, + "sampled_peak_rss_bytes": 3932160, + "status": "ok", + "summary": { + "best_scaled_cost": 4445425, + "bound_pruned": 691350, + "clones": 1417031, + "commits": 2597857, + "elapsed_ms": 1500.16, + "engine": "cadds", + "fragments_or_pages": 378499, + "kind": "summary", + "measurements": { + "bound_calls": 1872343, + "bound_ms": 153.25, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 492, + "peak_pending_nodes": 491, + "peak_source_nodes": 4, + "peak_staged_nodes": 60, + "publications": 1, + "scale": 10000, + "statistics": { + "depth": 20, + "fail": 725516, + "node": 2597858, + "propagate": 1180829, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.9579892978072166e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "1", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "best": null, + "best_publication_ms": null, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "checked_publications": 0, + "config": "cursor-b1", + "end_to_end_wall_seconds": 1.5878722909837961, + "first_publication_ms": null, + "id": "row012", + "incumbent_trace": [], + "maximum_sample_gap_seconds": 0.1006239999551326, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.6068056679796427, + "rss_samples": 17, + "sampled_peak_rss_bytes": 2424832, + "status": "ok", + "summary": { + "best_scaled_cost": null, + "bound_pruned": 0, + "clones": 1185472, + "commits": 2873198, + "elapsed_ms": 1500.16, + "engine": "cursor", + "fragments_or_pages": 1687738, + "kind": "summary", + "measurements": { + "bound_calls": 1687739, + "bound_ms": 154.366, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 15, + "peak_pending_nodes": 0, + "peak_source_nodes": 15, + "peak_staged_nodes": 1, + "publications": 0, + "scale": 10000, + "statistics": { + "depth": 16, + "fail": 1185460, + "node": 2873199, + "propagate": 1687739, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.4999532140791416e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "4", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "best": null, + "best_publication_ms": null, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "checked_publications": 0, + "config": "cursor-b4", + "end_to_end_wall_seconds": 1.5946133329998702, + "first_publication_ms": null, + "id": "row013", + "incumbent_trace": [], + "maximum_sample_gap_seconds": 0.09656141704181209, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5978845008648932, + "rss_samples": 17, + "sampled_peak_rss_bytes": 2555904, + "status": "ok", + "summary": { + "best_scaled_cost": null, + "bound_pruned": 0, + "clones": 1237114, + "commits": 2998342, + "elapsed_ms": 1500.19, + "engine": "cursor", + "fragments_or_pages": 572655, + "kind": "summary", + "measurements": { + "bound_calls": 1761256, + "bound_ms": 160.488, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 40, + "peak_pending_nodes": 0, + "peak_source_nodes": 40, + "peak_staged_nodes": 4, + "publications": 0, + "scale": 10000, + "statistics": { + "depth": 16, + "fail": 1237087, + "node": 2998343, + "propagate": 1761239, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.374981366097927e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cabs", + "opt", + "1", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "best": { + "elapsed_ms": 258.407, + "exact_cost": "444.5425", + "scaled_cost": 4445425, + "tour": [ + 14, + 18, + 13, + 9, + 5, + 4, + 6, + 8, + 7, + 16, + 19, + 11, + 17, + 1, + 10, + 3, + 12, + 2, + 15 + ] + }, + "best_publication_ms": 258.407, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "checked_publications": 1, + "config": "cabs", + "end_to_end_wall_seconds": 1.5799869999755174, + "first_publication_ms": 258.407, + "id": "row014", + "incumbent_trace": [ + { + "elapsed_ms": 258.407, + "scaled_cost": 4445425 + } + ], + "maximum_sample_gap_seconds": 0.09716866601957008, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.6042655849014409, + "rss_samples": 17, + "sampled_peak_rss_bytes": 185876480, + "status": "ok", + "summary": { + "best_scaled_cost": 4445425, + "clones": 1164489, + "elapsed_ms": 1528.12, + "engine": "cabs", + "kind": "summary", + "measurements": { + "bound_calls": 1425764, + "bound_ms": 192.215, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 58335, + "publications": 1, + "scale": 10000, + "statistics": { + "depth": 20, + "fail": 374695, + "node": 1612373, + "propagate": 447887, + "restart": 15 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.9589788280427456e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cabs", + "opt", + "1", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "best": { + "elapsed_ms": 15.7406, + "exact_cost": "378", + "scaled_cost": 378, + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ] + }, + "best_publication_ms": 15.7406, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "checked_publications": 1, + "config": "cabs", + "end_to_end_wall_seconds": 1.569264416990336, + "first_publication_ms": 15.7406, + "id": "row015", + "incumbent_trace": [ + { + "elapsed_ms": 15.7406, + "scaled_cost": 378 + } + ], + "maximum_sample_gap_seconds": 0.17749075003666803, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.6535789599874988, + "rss_samples": 16, + "sampled_peak_rss_bytes": 176128000, + "status": "ok", + "summary": { + "best_scaled_cost": 378, + "clones": 1148238, + "elapsed_ms": 1533.51, + "engine": "cabs", + "kind": "summary", + "measurements": { + "bound_calls": 1148239, + "bound_ms": 184.087, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 55660, + "publications": 1, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 527323, + "node": 1674792, + "propagate": 526557, + "restart": 15 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 2.1249870769679546e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "4", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "best": { + "elapsed_ms": 589.452, + "exact_cost": "378", + "scaled_cost": 378, + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ] + }, + "best_publication_ms": 589.452, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "checked_publications": 3, + "config": "cursor-b4", + "end_to_end_wall_seconds": 1.4811822090414353, + "first_publication_ms": 543.143, + "id": "row016", + "incumbent_trace": [ + { + "elapsed_ms": 543.143, + "scaled_cost": 382 + }, + { + "elapsed_ms": 570.563, + "scaled_cost": 380 + }, + { + "elapsed_ms": 589.452, + "scaled_cost": 378 + } + ], + "maximum_sample_gap_seconds": 0.09583250002469867, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5500446690712124, + "rss_samples": 16, + "sampled_peak_rss_bytes": 2555904, + "status": "ok", + "summary": { + "best_scaled_cost": 378, + "bound_pruned": 1474, + "clones": 1392555, + "commits": 2783634, + "elapsed_ms": 1452.39, + "engine": "cursor", + "fragments_or_pages": 550422, + "kind": "summary", + "measurements": { + "bound_calls": 1392562, + "bound_ms": 170.286, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 44, + "peak_pending_nodes": 0, + "peak_source_nodes": 42, + "peak_staged_nodes": 4, + "publications": 3, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 1391076, + "node": 2783635, + "propagate": 1391083, + "restart": 0 + }, + "termination": "optimal" + }, + "termination_reap_seconds": 1.9170111045241356e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "1", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "best": { + "elapsed_ms": 595.505, + "exact_cost": "378", + "scaled_cost": 378, + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ] + }, + "best_publication_ms": 595.505, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "checked_publications": 3, + "config": "cursor-b1", + "end_to_end_wall_seconds": 1.5479964170372114, + "first_publication_ms": 548.521, + "id": "row017", + "incumbent_trace": [ + { + "elapsed_ms": 548.521, + "scaled_cost": 382 + }, + { + "elapsed_ms": 576.327, + "scaled_cost": 380 + }, + { + "elapsed_ms": 595.505, + "scaled_cost": 378 + } + ], + "maximum_sample_gap_seconds": 0.09576870803721249, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5842495000106283, + "rss_samples": 17, + "sampled_peak_rss_bytes": 2490368, + "status": "ok", + "summary": { + "best_scaled_cost": 378, + "bound_pruned": 1474, + "clones": 1392555, + "commits": 2783634, + "elapsed_ms": 1471.31, + "engine": "cursor", + "fragments_or_pages": 1391081, + "kind": "summary", + "measurements": { + "bound_calls": 1392562, + "bound_ms": 170.088, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 21, + "peak_pending_nodes": 0, + "peak_source_nodes": 19, + "peak_staged_nodes": 1, + "publications": 3, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 1391076, + "node": 2783635, + "propagate": 1391083, + "restart": 0 + }, + "termination": "optimal" + }, + "termination_reap_seconds": 1.207983586937189e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "4", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "best": { + "elapsed_ms": 4.38367, + "exact_cost": "378", + "scaled_cost": 378, + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ] + }, + "best_publication_ms": 4.38367, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "checked_publications": 1, + "config": "cadds-b4", + "end_to_end_wall_seconds": 1.5782186249853112, + "first_publication_ms": 4.38367, + "id": "row018", + "incumbent_trace": [ + { + "elapsed_ms": 4.38367, + "scaled_cost": 378 + } + ], + "maximum_sample_gap_seconds": 0.09645129204727709, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5849762089783326, + "rss_samples": 17, + "sampled_peak_rss_bytes": 3538944, + "status": "ok", + "summary": { + "best_scaled_cost": 378, + "bound_pruned": 2480, + "clones": 1364624, + "commits": 2726636, + "elapsed_ms": 1500.12, + "engine": "cadds", + "fragments_or_pages": 514171, + "kind": "summary", + "measurements": { + "bound_calls": 1364627, + "bound_ms": 164.511, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 394, + "peak_pending_nodes": 390, + "peak_source_nodes": 4, + "peak_staged_nodes": 62, + "publications": 1, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 1362011, + "node": 2726637, + "propagate": 1362014, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.207983586937189e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "1", + "1500", + "examples/data/tsptw/published/Dumas/n20w20.001.txt" + ], + "best": { + "elapsed_ms": 55.2014, + "exact_cost": "378", + "scaled_cost": 378, + "tour": [ + 16, + 9, + 19, + 17, + 18, + 10, + 5, + 15, + 1, + 11, + 12, + 6, + 13, + 7, + 2, + 4, + 8, + 20, + 3, + 14 + ] + }, + "best_publication_ms": 55.2014, + "case": { + "id": "Dumas/n20w20.001.txt", + "path": "examples/data/tsptw/published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + "checked_publications": 1, + "config": "cadds-b1", + "end_to_end_wall_seconds": 1.5437604579492472, + "first_publication_ms": 55.2014, + "id": "row019", + "incumbent_trace": [ + { + "elapsed_ms": 55.2014, + "scaled_cost": 378 + } + ], + "maximum_sample_gap_seconds": 0.09660541597986594, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5823567501502112, + "rss_samples": 17, + "sampled_peak_rss_bytes": 2883584, + "status": "ok", + "summary": { + "best_scaled_cost": 378, + "bound_pruned": 2347, + "clones": 1318705, + "commits": 2635014, + "elapsed_ms": 1500.12, + "engine": "cadds", + "fragments_or_pages": 1316311, + "kind": "summary", + "measurements": { + "bound_calls": 1318708, + "bound_ms": 157.992, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 153, + "peak_pending_nodes": 151, + "peak_source_nodes": 1, + "peak_staged_nodes": 19, + "publications": 1, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 1316308, + "node": 2635015, + "propagate": 1316312, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.4170072972774506e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cabs", + "opt", + "1", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "best": { + "elapsed_ms": 1432.91, + "exact_cost": "267", + "scaled_cost": 267, + "tour": [ + 6, + 16, + 9, + 19, + 17, + 18, + 12, + 10, + 11, + 5, + 1, + 15, + 2, + 7, + 4, + 13, + 20, + 3, + 8, + 14 + ] + }, + "best_publication_ms": 1432.91, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "checked_publications": 1, + "config": "cabs", + "end_to_end_wall_seconds": 1.5355735420016572, + "first_publication_ms": 1432.91, + "id": "row020", + "incumbent_trace": [ + { + "elapsed_ms": 1432.91, + "scaled_cost": 267 + } + ], + "maximum_sample_gap_seconds": 0.0941542080254294, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5855820440337993, + "rss_samples": 17, + "sampled_peak_rss_bytes": 59310080, + "status": "ok", + "summary": { + "best_scaled_cost": 267, + "clones": 1479082, + "elapsed_ms": 1518.53, + "engine": "cabs", + "kind": "summary", + "measurements": { + "bound_calls": 1656252, + "bound_ms": 265.755, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 31537, + "publications": 1, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 69515, + "node": 1725719, + "propagate": 246640, + "restart": 14 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.5409896150231361e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "4", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "best": null, + "best_publication_ms": null, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "checked_publications": 0, + "config": "cursor-b4", + "end_to_end_wall_seconds": 1.5759980410221033, + "first_publication_ms": null, + "id": "row021", + "incumbent_trace": [], + "maximum_sample_gap_seconds": 0.14711079199332744, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.6601747510721907, + "rss_samples": 16, + "sampled_peak_rss_bytes": 2555904, + "status": "ok", + "summary": { + "best_scaled_cost": null, + "bound_pruned": 0, + "clones": 1137557, + "commits": 2680944, + "elapsed_ms": 1500.14, + "engine": "cursor", + "fragments_or_pages": 486856, + "kind": "summary", + "measurements": { + "bound_calls": 1543415, + "bound_ms": 131.638, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 50, + "peak_pending_nodes": 0, + "peak_source_nodes": 49, + "peak_staged_nodes": 4, + "publications": 0, + "scale": 1, + "statistics": { + "depth": 19, + "fail": 1137530, + "node": 2680945, + "propagate": 1543400, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.166015863418579e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "1", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "best": null, + "best_publication_ms": null, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "checked_publications": 0, + "config": "cursor-b1", + "end_to_end_wall_seconds": 1.5224550000275485, + "first_publication_ms": null, + "id": "row022", + "incumbent_trace": [], + "maximum_sample_gap_seconds": 0.13200949999736622, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5890202060691081, + "rss_samples": 16, + "sampled_peak_rss_bytes": 2424832, + "status": "ok", + "summary": { + "best_scaled_cost": null, + "bound_pruned": 0, + "clones": 1163778, + "commits": 2742715, + "elapsed_ms": 1500.14, + "engine": "cursor", + "fragments_or_pages": 1578950, + "kind": "summary", + "measurements": { + "bound_calls": 1578951, + "bound_ms": 141.147, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 19, + "peak_pending_nodes": 0, + "peak_source_nodes": 18, + "peak_staged_nodes": 1, + "publications": 0, + "scale": 1, + "statistics": { + "depth": 19, + "fail": 1163765, + "node": 2742716, + "propagate": 1578951, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.4579854905605316e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "4", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "best": { + "elapsed_ms": 60.3884, + "exact_cost": "274", + "scaled_cost": 274, + "tour": [ + 16, + 9, + 19, + 17, + 18, + 12, + 10, + 11, + 5, + 1, + 15, + 2, + 7, + 4, + 6, + 13, + 20, + 3, + 8, + 14 + ] + }, + "best_publication_ms": 60.3884, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "checked_publications": 7, + "config": "cadds-b4", + "end_to_end_wall_seconds": 1.5267765420139767, + "first_publication_ms": 7.54667, + "id": "row023", + "incumbent_trace": [ + { + "elapsed_ms": 7.54667, + "scaled_cost": 379 + }, + { + "elapsed_ms": 7.65958, + "scaled_cost": 345 + }, + { + "elapsed_ms": 7.69871, + "scaled_cost": 331 + }, + { + "elapsed_ms": 9.10637, + "scaled_cost": 305 + }, + { + "elapsed_ms": 17.1993, + "scaled_cost": 294 + }, + { + "elapsed_ms": 29.3709, + "scaled_cost": 288 + }, + { + "elapsed_ms": 60.3884, + "scaled_cost": 274 + } + ], + "maximum_sample_gap_seconds": 0.14308945799712092, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.602688500948716, + "rss_samples": 16, + "sampled_peak_rss_bytes": 3915776, + "status": "ok", + "summary": { + "best_scaled_cost": 274, + "bound_pruned": 1187136, + "clones": 1591496, + "commits": 2413557, + "elapsed_ms": 1500.22, + "engine": "cadds", + "fragments_or_pages": 270319, + "kind": "summary", + "measurements": { + "bound_calls": 2009622, + "bound_ms": 124.677, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 545, + "peak_pending_nodes": 543, + "peak_source_nodes": 4, + "peak_staged_nodes": 64, + "publications": 7, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 403943, + "node": 2413558, + "propagate": 822069, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.7500133253633976e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "1", + "1500", + "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt" + ], + "best": { + "elapsed_ms": 1436.18, + "exact_cost": "272", + "scaled_cost": 272, + "tour": [ + 16, + 6, + 9, + 19, + 17, + 18, + 12, + 10, + 11, + 5, + 1, + 15, + 2, + 7, + 4, + 13, + 20, + 3, + 8, + 14 + ] + }, + "best_publication_ms": 1436.18, + "case": { + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + "checked_publications": 3, + "config": "cadds-b1", + "end_to_end_wall_seconds": 1.5718783749616705, + "first_publication_ms": 1355.42, + "id": "row024", + "incumbent_trace": [ + { + "elapsed_ms": 1355.42, + "scaled_cost": 304 + }, + { + "elapsed_ms": 1428.73, + "scaled_cost": 303 + }, + { + "elapsed_ms": 1436.18, + "scaled_cost": 272 + } + ], + "maximum_sample_gap_seconds": 0.0968665000400506, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5900489600026049, + "rss_samples": 17, + "sampled_peak_rss_bytes": 3047424, + "status": "ok", + "summary": { + "best_scaled_cost": 272, + "bound_pruned": 84270, + "clones": 1158931, + "commits": 2631978, + "elapsed_ms": 1500.15, + "engine": "cadds", + "fragments_or_pages": 1473061, + "kind": "summary", + "measurements": { + "bound_calls": 1557460, + "bound_ms": 106.42, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 180, + "peak_pending_nodes": 178, + "peak_source_nodes": 1, + "peak_staged_nodes": 20, + "publications": 3, + "scale": 1, + "statistics": { + "depth": 21, + "fail": 1074522, + "node": 2631979, + "propagate": 1473052, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.4160177670419216e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cabs", + "opt", + "1", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "best": { + "elapsed_ms": 264.444, + "exact_cost": "444.5425", + "scaled_cost": 4445425, + "tour": [ + 14, + 18, + 13, + 9, + 5, + 4, + 6, + 8, + 7, + 16, + 19, + 11, + 17, + 1, + 10, + 3, + 12, + 2, + 15 + ] + }, + "best_publication_ms": 264.444, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "checked_publications": 1, + "config": "cabs", + "end_to_end_wall_seconds": 1.5741350839962251, + "first_publication_ms": 264.444, + "id": "row025", + "incumbent_trace": [ + { + "elapsed_ms": 264.444, + "scaled_cost": 4445425 + } + ], + "maximum_sample_gap_seconds": 0.09546158299781382, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5887993759824894, + "rss_samples": 17, + "sampled_peak_rss_bytes": 187072512, + "status": "ok", + "summary": { + "best_scaled_cost": 4445425, + "clones": 1163217, + "elapsed_ms": 1529.71, + "engine": "cabs", + "kind": "summary", + "measurements": { + "bound_calls": 1424076, + "bound_ms": 191.65, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 58335, + "publications": 1, + "scale": 10000, + "statistics": { + "depth": 20, + "fail": 374221, + "node": 1610211, + "propagate": 446996, + "restart": 15 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.4170072972774506e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "4", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "best": null, + "best_publication_ms": null, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "checked_publications": 0, + "config": "cursor-b4", + "end_to_end_wall_seconds": 1.548491458001081, + "first_publication_ms": null, + "id": "row026", + "incumbent_trace": [], + "maximum_sample_gap_seconds": 0.09527366701513529, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5865044990205206, + "rss_samples": 17, + "sampled_peak_rss_bytes": 2506752, + "status": "ok", + "summary": { + "best_scaled_cost": null, + "bound_pruned": 0, + "clones": 1238114, + "commits": 3000787, + "elapsed_ms": 1500.17, + "engine": "cursor", + "fragments_or_pages": 573121, + "kind": "summary", + "measurements": { + "bound_calls": 1762688, + "bound_ms": 161.024, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 40, + "peak_pending_nodes": 0, + "peak_source_nodes": 40, + "peak_staged_nodes": 4, + "publications": 0, + "scale": 10000, + "statistics": { + "depth": 16, + "fail": 1238100, + "node": 3000788, + "propagate": 1762681, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.667009200900793e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cursor", + "opt", + "1", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "best": null, + "best_publication_ms": null, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "checked_publications": 0, + "config": "cursor-b1", + "end_to_end_wall_seconds": 1.520594333007466, + "first_publication_ms": null, + "id": "row027", + "incumbent_trace": [], + "maximum_sample_gap_seconds": 0.12254587502684444, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5759455829393119, + "rss_samples": 16, + "sampled_peak_rss_bytes": 2490368, + "status": "ok", + "summary": { + "best_scaled_cost": null, + "bound_pruned": 0, + "clones": 1153319, + "commits": 2795264, + "elapsed_ms": 1500.16, + "engine": "cursor", + "fragments_or_pages": 1641956, + "kind": "summary", + "measurements": { + "bound_calls": 1641957, + "bound_ms": 160.738, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 15, + "peak_pending_nodes": 0, + "peak_source_nodes": 15, + "peak_staged_nodes": 1, + "publications": 0, + "scale": 10000, + "statistics": { + "depth": 16, + "fail": 1153308, + "node": 2795265, + "propagate": 1641957, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.416949089616537e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "4", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "best": { + "elapsed_ms": 746.492, + "exact_cost": "444.5425", + "scaled_cost": 4445425, + "tour": [ + 14, + 18, + 13, + 9, + 5, + 4, + 6, + 8, + 7, + 16, + 19, + 11, + 17, + 1, + 10, + 3, + 12, + 2, + 15 + ] + }, + "best_publication_ms": 746.492, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "checked_publications": 1, + "config": "cadds-b4", + "end_to_end_wall_seconds": 1.5609158339793794, + "first_publication_ms": 746.492, + "id": "row028", + "incumbent_trace": [ + { + "elapsed_ms": 746.492, + "scaled_cost": 4445425 + } + ], + "maximum_sample_gap_seconds": 0.09582145902095363, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5852417899295688, + "rss_samples": 17, + "sampled_peak_rss_bytes": 3866624, + "status": "ok", + "summary": { + "best_scaled_cost": 4445425, + "bound_pruned": 696444, + "clones": 1423547, + "commits": 2607635, + "elapsed_ms": 1500.16, + "engine": "cadds", + "fragments_or_pages": 379596, + "kind": "summary", + "measurements": { + "bound_calls": 1880676, + "bound_ms": 154.068, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 492, + "peak_pending_nodes": 491, + "peak_source_nodes": 4, + "peak_staged_nodes": 60, + "publications": 1, + "scale": 10000, + "statistics": { + "depth": 20, + "fail": 726961, + "node": 2607636, + "propagate": 1184090, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.6660196706652641e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + }, + { + "argv": [ + "/private/tmp/open-search-030-study/study", + "cadds", + "opt", + "1", + "1500", + "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt" + ], + "best": { + "elapsed_ms": 270.644, + "exact_cost": "444.5425", + "scaled_cost": 4445425, + "tour": [ + 14, + 18, + 13, + 9, + 5, + 4, + 6, + 8, + 7, + 16, + 19, + 11, + 17, + 1, + 10, + 3, + 12, + 2, + 15 + ] + }, + "best_publication_ms": 270.644, + "case": { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + "checked_publications": 2, + "config": "cadds-b1", + "end_to_end_wall_seconds": 1.5617384160286747, + "first_publication_ms": 264.775, + "id": "row029", + "incumbent_trace": [ + { + "elapsed_ms": 264.775, + "scaled_cost": 4524015 + }, + { + "elapsed_ms": 270.644, + "scaled_cost": 4445425 + } + ], + "maximum_sample_gap_seconds": 0.09538204199634492, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5807702100719325, + "rss_samples": 17, + "sampled_peak_rss_bytes": 2965504, + "status": "ok", + "summary": { + "best_scaled_cost": 4445425, + "bound_pruned": 1097720, + "clones": 1532616, + "commits": 2393553, + "elapsed_ms": 1500.14, + "engine": "cadds", + "fragments_or_pages": 860953, + "kind": "summary", + "measurements": { + "bound_calls": 1958709, + "bound_ms": 179.956, + "inference_calls": 0, + "inference_ms": 0, + "propagation_ms": 0 + }, + "peak_live_spaces": 149, + "peak_pending_nodes": 148, + "peak_source_nodes": 1, + "peak_staged_nodes": 19, + "publications": 2, + "scale": 10000, + "statistics": { + "depth": 20, + "fail": 434847, + "node": 2393554, + "propagate": 860940, + "restart": 0 + }, + "termination": "stopped" + }, + "termination_reap_seconds": 1.9579892978072166e-06, + "wall_cap_overshoot_seconds": 0, + "witness": null + } + ], + "schema_version": 1 +} diff --git a/examples/data/tsptw/cheap-deadline-comparison.json b/examples/data/tsptw/cheap-deadline-comparison.json new file mode 100644 index 0000000000..49a4598d56 --- /dev/null +++ b/examples/data/tsptw/cheap-deadline-comparison.json @@ -0,0 +1,4662 @@ +{ + "campaign": "cheap-deadline-028", + "comparisons": { + "cases": [ + { + "case": { + "family": "Dumas", + "id": "Dumas/n20w20.001.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748", + "variant": null + }, + "measurements": [ + { + "config": "transition-bab", + "end_to_end_wall_seconds": 1.4632397500099614, + "final_objective": 378, + "first_incumbent_reported_ms": 521.045, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0976108749746345, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 2785283, + "frontier_bytes": null, + "preprocessing_ms": 0.105, + "search_ms": 1394.738, + "startup_and_teardown_ms": 68.37875000996132 + }, + "proof": "optimal", + "proof_reported_ms": 1394.861, + "quality_at_reported_ms": { + "100": null, + "1000": 378 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5715047909179702, + "rss_samples": 16, + "sampled_peak_rss_bytes": 2555904, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 4.959001671522856e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-cheap", + "end_to_end_wall_seconds": 0.09271266602445394, + "final_objective": 378, + "first_incumbent_reported_ms": 0.28, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.003323499986436218, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1733, + "inference_ms": 0.055, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 1079, + "frontier_bytes": null, + "preprocessing_ms": 0.124, + "search_ms": 0.882, + "startup_and_teardown_ms": 91.69766602445394 + }, + "proof": "optimal", + "proof_reported_ms": 1.015, + "quality_at_reported_ms": { + "100": 378, + "1000": 378 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03331775002880022, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 5.749985575675964e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 0.09727745898999274, + "final_objective": 378, + "first_incumbent_reported_ms": 1.976, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0034472919651307166, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1626, + "inference_ms": 6.924, + "propagation_ms": 5.794 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 1014, + "frontier_bytes": null, + "preprocessing_ms": 0.381, + "search_ms": 7.626, + "startup_and_teardown_ms": 89.26045898999274 + }, + "proof": "optimal", + "proof_reported_ms": 8.017, + "quality_at_reported_ms": { + "100": 378, + "1000": 378 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.03639666602248326, + "rss_samples": 1, + "sampled_peak_rss_bytes": 2621440, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.5420373529195786e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs", + "end_to_end_wall_seconds": 2.005315500020515, + "final_objective": 378, + "first_incumbent_reported_ms": 13.981, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0980317079811357, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 378, + "1000": 378 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.797973666805774, + "rss_samples": 22, + "sampled_peak_rss_bytes": 297041920, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0043039589654654264, + "wall_cap_overshoot_seconds": 0.005315500020515174 + }, + { + "config": "transition-cabs-lazy-cheap", + "end_to_end_wall_seconds": 0.09337545797461644, + "final_objective": 378, + "first_incumbent_reported_ms": 0.269, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0031189160072244704, + "measurements": { + "bound_calls": 382, + "bound_ms": 0.01, + "inference_calls": 3381, + "inference_ms": 0.087, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 3385, + "frontier_bytes": null, + "preprocessing_ms": 0.121, + "search_ms": 1.595, + "startup_and_teardown_ms": 91.65045797461644 + }, + "proof": "optimal", + "proof_reported_ms": 1.725, + "quality_at_reported_ms": { + "100": 378, + "1000": 378 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.03411850001430139, + "rss_samples": 1, + "sampled_peak_rss_bytes": 2408448, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.625041477382183e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 0.09521762502845377, + "final_objective": 378, + "first_incumbent_reported_ms": 0.685, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.003049749997444451, + "measurements": { + "bound_calls": 382, + "bound_ms": 0.011, + "inference_calls": 3183, + "inference_ms": 3.687, + "propagation_ms": 2.487 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 3187, + "frontier_bytes": null, + "preprocessing_ms": 0.202, + "search_ms": 5.266, + "startup_and_teardown_ms": 89.73962502845377 + }, + "proof": "optimal", + "proof_reported_ms": 5.478, + "quality_at_reported_ms": { + "100": 378, + "1000": 378 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.03376966604264453, + "rss_samples": 1, + "sampled_peak_rss_bytes": 2736128, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.5830155462026596e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 0.09371170902159065, + "final_objective": 378, + "first_incumbent_reported_ms": 0.691, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.00316829199437052, + "measurements": { + "bound_calls": 382, + "bound_ms": 0.01, + "inference_calls": 3183, + "inference_ms": 3.738, + "propagation_ms": 2.549 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 3187, + "frontier_bytes": null, + "preprocessing_ms": 0.208, + "search_ms": 5.359, + "startup_and_teardown_ms": 88.13270902159066 + }, + "proof": "optimal", + "proof_reported_ms": 5.579, + "quality_at_reported_ms": { + "100": 378, + "1000": 378 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.03446637495653704, + "rss_samples": 1, + "sampled_peak_rss_bytes": 2818048, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.834006980061531e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-cheap", + "end_to_end_wall_seconds": 0.09993295796448365, + "final_objective": 378, + "first_incumbent_reported_ms": 0.289, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0033584159682504833, + "measurements": { + "bound_calls": 382, + "bound_ms": 0.009, + "inference_calls": 3381, + "inference_ms": 0.09, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 3385, + "frontier_bytes": null, + "preprocessing_ms": 0.138, + "search_ms": 1.658, + "startup_and_teardown_ms": 98.12395796448365 + }, + "proof": "optimal", + "proof_reported_ms": 1.809, + "quality_at_reported_ms": { + "100": 378, + "1000": 378 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.035995832993648946, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.6249832697212696e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs", + "end_to_end_wall_seconds": 2.006879208027385, + "final_objective": 378, + "first_incumbent_reported_ms": 13.997, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.09889633301645517, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 378, + "1000": 378 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7890545040136203, + "rss_samples": 22, + "sampled_peak_rss_bytes": 263913472, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.003542541991919279, + "wall_cap_overshoot_seconds": 0.006879208027385175 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 0.09961150004528463, + "final_objective": 378, + "first_incumbent_reported_ms": 1.83, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0032036250340752304, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1626, + "inference_ms": 6.873, + "propagation_ms": 5.802 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 1014, + "frontier_bytes": null, + "preprocessing_ms": 0.214, + "search_ms": 7.797, + "startup_and_teardown_ms": 91.58550004528463 + }, + "proof": "optimal", + "proof_reported_ms": 8.026, + "quality_at_reported_ms": { + "100": 378, + "1000": 378 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.03591304202564061, + "rss_samples": 1, + "sampled_peak_rss_bytes": 2719744, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.541979145258665e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-cheap", + "end_to_end_wall_seconds": 0.09679945901734754, + "final_objective": 378, + "first_incumbent_reported_ms": 0.293, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0034765839809551835, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1733, + "inference_ms": 0.055, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 1079, + "frontier_bytes": null, + "preprocessing_ms": 0.132, + "search_ms": 0.886, + "startup_and_teardown_ms": 95.77245901734754 + }, + "proof": "optimal", + "proof_reported_ms": 1.027, + "quality_at_reported_ms": { + "100": 378, + "1000": 378 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.035490790964104235, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 3.625056706368923e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab", + "end_to_end_wall_seconds": 1.4186669579939917, + "final_objective": 378, + "first_incumbent_reported_ms": 518.469, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.09896416601259261, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 2785283, + "frontier_bytes": null, + "preprocessing_ms": 0.117, + "search_ms": 1389.127, + "startup_and_teardown_ms": 29.406957993991682 + }, + "proof": "optimal", + "proof_reported_ms": 1389.26, + "quality_at_reported_ms": { + "100": null, + "1000": 378 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5514855431392789, + "rss_samples": 15, + "sampled_peak_rss_bytes": 2441216, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.6660196706652641e-06, + "wall_cap_overshoot_seconds": 0 + } + ], + "reference_kind": "matching campaign optimality proof", + "reference_scaled_cost": 378 + }, + { + "case": { + "family": "Dumas", + "id": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "sha256": "2aed1af8ef2756d0ecf902a6c0a9f9916893bb2a65ef9a284e3ff85848c1962d", + "variant": "customer-due-plus-100" + }, + "measurements": [ + { + "config": "transition-bab", + "end_to_end_wall_seconds": 2.0054478329839185, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0975102920201607, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7884615830262192, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2408448, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0005177080165594816, + "wall_cap_overshoot_seconds": 0.005447832983918488 + }, + { + "config": "transition-bab-lazy-cheap", + "end_to_end_wall_seconds": 2.002301874978002, + "final_objective": 310, + "first_incumbent_reported_ms": 0.25, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09736250003334135, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 347, + "1000": 310 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7948144970578142, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2523136, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0005540830316022038, + "wall_cap_overshoot_seconds": 0.002301874978002161 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 2.0038898329949006, + "final_objective": 337, + "first_incumbent_reported_ms": 1.093, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09701829199912027, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 354, + "1000": 347 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7872996618971229, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2883584, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0005757089820690453, + "wall_cap_overshoot_seconds": 0.003889832994900644 + }, + { + "config": "transition-cabs", + "end_to_end_wall_seconds": 2.0117814589757472, + "final_objective": 249, + "first_incumbent_reported_ms": 11.3, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11928904097294435, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 270, + "1000": 249 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.8075690021505579, + "rss_samples": 22, + "sampled_peak_rss_bytes": 103694336, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0018419999978505075, + "wall_cap_overshoot_seconds": 0.011781458975747228 + }, + { + "config": "transition-cabs-lazy-cheap", + "end_to_end_wall_seconds": 2.0024744169786572, + "final_objective": 249, + "first_incumbent_reported_ms": 0.297, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09684337495127693, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 249, + "1000": 249 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7901035439572297, + "rss_samples": 22, + "sampled_peak_rss_bytes": 99287040, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0017702499753795564, + "wall_cap_overshoot_seconds": 0.0024744169786572456 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 2.016916208958719, + "final_objective": 249, + "first_incumbent_reported_ms": 1.178, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09736304101534188, + "measurements": { + "bound_calls": 264383, + "bound_ms": 6.769, + "inference_calls": 624693, + "inference_ms": 1553.886, + "propagation_ms": 1194.938 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 624705, + "frontier_bytes": null, + "preprocessing_ms": 0.192, + "search_ms": 1999.814, + "startup_and_teardown_ms": 16.89420895871899 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 271, + "1000": 249 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7852172928978689, + "rss_samples": 22, + "sampled_peak_rss_bytes": 17088512, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 1.7919810488820076e-06, + "wall_cap_overshoot_seconds": 0.016916208958718926 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 2.0174620000179857, + "final_objective": 249, + "first_incumbent_reported_ms": 1.179, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10384566697757691, + "measurements": { + "bound_calls": 263435, + "bound_ms": 6.584, + "inference_calls": 623039, + "inference_ms": 1567.259, + "propagation_ms": 1200.572 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 623051, + "frontier_bytes": null, + "preprocessing_ms": 0.189, + "search_ms": 1999.811, + "startup_and_teardown_ms": 17.44200001798572 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 271, + "1000": 249 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7918920810334384, + "rss_samples": 22, + "sampled_peak_rss_bytes": 17235968, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 4.169996827840805e-07, + "wall_cap_overshoot_seconds": 0.0174620000179857 + }, + { + "config": "transition-cabs-lazy-cheap", + "end_to_end_wall_seconds": 2.0108514170278795, + "final_objective": 249, + "first_incumbent_reported_ms": 0.303, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09725966700352728, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 249, + "1000": 249 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7894864999689162, + "rss_samples": 22, + "sampled_peak_rss_bytes": 102187008, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.001792416034732014, + "wall_cap_overshoot_seconds": 0.010851417027879506 + }, + { + "config": "transition-cabs", + "end_to_end_wall_seconds": 2.002904250053689, + "final_objective": 249, + "first_incumbent_reported_ms": 11.274, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09698958398075774, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 270, + "1000": 249 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7863858331693336, + "rss_samples": 22, + "sampled_peak_rss_bytes": 104759296, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0018245000392198563, + "wall_cap_overshoot_seconds": 0.0029042500536888838 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 2.0277936249622144, + "final_objective": 337, + "first_incumbent_reported_ms": 0.964, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10939658398274332, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 984909, + "inference_ms": 1560.062, + "propagation_ms": 1126.777 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 670183, + "frontier_bytes": null, + "preprocessing_ms": 0.201, + "search_ms": 1999.803, + "startup_and_teardown_ms": 27.772624962214422 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 354, + "1000": 347 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.8213110409560613, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2916352, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 4.1601015254855156e-07, + "wall_cap_overshoot_seconds": 0.02779362496221438 + }, + { + "config": "transition-bab-lazy-cheap", + "end_to_end_wall_seconds": 2.003565915976651, + "final_objective": 310, + "first_incumbent_reported_ms": 0.25, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0985256660496816, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 347, + "1000": 310 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.79386220802553, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2490368, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.000703624973539263, + "wall_cap_overshoot_seconds": 0.003565915976651013 + }, + { + "config": "transition-bab", + "end_to_end_wall_seconds": 2.013342707999982, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11450308299390599, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 4078207, + "frontier_bytes": null, + "preprocessing_ms": 0.122, + "search_ms": 1999.879, + "startup_and_teardown_ms": 13.295707999981914 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.8094270828878507, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2457600, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 4.169996827840805e-07, + "wall_cap_overshoot_seconds": 0.01334270799998194 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "GendreauDumasExtended", + "id": "GendreauDumasExtended/n20w120.001.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba", + "variant": null + }, + "measurements": [ + { + "config": "transition-bab", + "end_to_end_wall_seconds": 2.002970458008349, + "final_objective": 305, + "first_incumbent_reported_ms": 1482.156, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0973331659915857, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7817426642868668, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2523136, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0005692500271834433, + "wall_cap_overshoot_seconds": 0.002970458008348942 + }, + { + "config": "transition-bab-lazy-cheap", + "end_to_end_wall_seconds": 2.0011469590244815, + "final_objective": 302, + "first_incumbent_reported_ms": 0.263, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0972864159848541, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 305, + "1000": 302 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7898561688489281, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2539520, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0005701250047422945, + "wall_cap_overshoot_seconds": 0.0011469590244814754 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 2.0061135409632698, + "final_objective": 305, + "first_incumbent_reported_ms": 1.417, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09730625001247972, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 305, + "1000": 305 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7954300410347059, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2916352, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0005694999708794057, + "wall_cap_overshoot_seconds": 0.00611354096326977 + }, + { + "config": "transition-cabs", + "end_to_end_wall_seconds": 2.0040974589646794, + "final_objective": 267, + "first_incumbent_reported_ms": 2.952, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09770833398215473, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 267, + "1000": 267 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7918685449985787, + "rss_samples": 22, + "sampled_peak_rss_bytes": 103120896, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.001826042018365115, + "wall_cap_overshoot_seconds": 0.00409745896467939 + }, + { + "config": "transition-cabs-lazy-cheap", + "end_to_end_wall_seconds": 2.0054432079778053, + "final_objective": 267, + "first_incumbent_reported_ms": 0.288, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09819170797709376, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 267, + "1000": 267 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7919986671186052, + "rss_samples": 22, + "sampled_peak_rss_bytes": 98566144, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0017455409979447722, + "wall_cap_overshoot_seconds": 0.005443207977805287 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 2.005281209014356, + "final_objective": 267, + "first_incumbent_reported_ms": 0.941, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0966270409990102, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 267, + "1000": 267 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7817516227951273, + "rss_samples": 22, + "sampled_peak_rss_bytes": 26935296, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0009819999686442316, + "wall_cap_overshoot_seconds": 0.005281209014356136 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 2.0062661669799127, + "final_objective": 267, + "first_incumbent_reported_ms": 0.947, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09827104100259021, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 267, + "1000": 267 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7882397499633953, + "rss_samples": 22, + "sampled_peak_rss_bytes": 27131904, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0008657500147819519, + "wall_cap_overshoot_seconds": 0.0062661669799126685 + }, + { + "config": "transition-cabs-lazy-cheap", + "end_to_end_wall_seconds": 2.0021997920121066, + "final_objective": 267, + "first_incumbent_reported_ms": 0.3, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09676095901522785, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 267, + "1000": 267 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7863480009837076, + "rss_samples": 22, + "sampled_peak_rss_bytes": 100335616, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0016844579949975014, + "wall_cap_overshoot_seconds": 0.0021997920121066272 + }, + { + "config": "transition-cabs", + "end_to_end_wall_seconds": 2.0269907920155674, + "final_objective": 267, + "first_incumbent_reported_ms": 2.887, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.17855420900741592, + "measurements": { + "bound_calls": 2426888, + "bound_ms": 65.371, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 2498875, + "frontier_bytes": null, + "preprocessing_ms": 0.126, + "search_ms": 1999.875, + "startup_and_teardown_ms": 26.974792015567346 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 267, + "1000": 267 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.8717493339790963, + "rss_samples": 21, + "sampled_peak_rss_bytes": 103825408, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 5.00003807246685e-07, + "wall_cap_overshoot_seconds": 0.026990792015567422 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 2.0076152499532327, + "final_objective": 305, + "first_incumbent_reported_ms": 1.395, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09733200003392994, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 926851, + "inference_ms": 1576.976, + "propagation_ms": 1179.711 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 627236, + "frontier_bytes": null, + "preprocessing_ms": 0.189, + "search_ms": 1999.813, + "startup_and_teardown_ms": 7.5962499532327 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 305, + "1000": 305 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7860742941265926, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2899968, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 9.170034900307655e-07, + "wall_cap_overshoot_seconds": 0.007615249953232706 + }, + { + "config": "transition-bab-lazy-cheap", + "end_to_end_wall_seconds": 2.0096187089802697, + "final_objective": 302, + "first_incumbent_reported_ms": 0.263, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09740033303387463, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 4874099, + "inference_ms": 125.113, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 3268871, + "frontier_bytes": null, + "preprocessing_ms": 0.123, + "search_ms": 1999.879, + "startup_and_teardown_ms": 9.598708980269748 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 305, + "1000": 302 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7799009179580025, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2490368, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 8.750357665121555e-07, + "wall_cap_overshoot_seconds": 0.00961870898026973 + }, + { + "config": "transition-bab", + "end_to_end_wall_seconds": 2.0109924590215087, + "final_objective": 305, + "first_incumbent_reported_ms": 1483.03, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09867791703436524, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 4020208, + "frontier_bytes": null, + "preprocessing_ms": 0.107, + "search_ms": 1999.894, + "startup_and_teardown_ms": 10.976459021508617 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7908349960343912, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2490368, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 4.5797787606716156e-07, + "wall_cap_overshoot_seconds": 0.010992459021508694 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "SolomonPotvinBengio", + "id": "SolomonPotvinBengio/rc_201.1.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f", + "variant": null + }, + "measurements": [ + { + "config": "transition-bab", + "end_to_end_wall_seconds": 2.004688457993325, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0973043330013752, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7957067069364712, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2473984, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0005507089663296938, + "wall_cap_overshoot_seconds": 0.004688457993324846 + }, + { + "config": "transition-bab-lazy-cheap", + "end_to_end_wall_seconds": 0.580493709014263, + "final_objective": 4445425, + "first_incumbent_reported_ms": 0.325, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.09730991598917171, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1386948, + "inference_ms": 35.054, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 20, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 923985, + "frontier_bytes": null, + "preprocessing_ms": 0.148, + "search_ms": 550.791, + "startup_and_teardown_ms": 29.539709014263053 + }, + "proof": "optimal", + "proof_reported_ms": 550.954, + "quality_at_reported_ms": { + "100": 5061437, + "1000": 4445425 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.2158567070728168, + "rss_samples": 6, + "sampled_peak_rss_bytes": 2506752, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 5.790963768959045e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 2.018044083029963, + "final_objective": 4476347, + "first_incumbent_reported_ms": 1.542, + "gap_to_proved_optimum": 0.006955915351175647, + "maximum_sample_gap_seconds": 0.09760733298026025, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1054588, + "inference_ms": 1548.63, + "propagation_ms": 1110.744 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 20, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 704400, + "frontier_bytes": null, + "preprocessing_ms": 0.212, + "search_ms": 1999.789, + "startup_and_teardown_ms": 18.027083029963023 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 5217715, + "1000": 5039994 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.795345499063842, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2981888, + "scale": 10000, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 5.830079317092896e-07, + "wall_cap_overshoot_seconds": 0.018044083029963076 + }, + { + "config": "transition-cabs", + "end_to_end_wall_seconds": 2.0090842499630526, + "final_objective": 4445425, + "first_incumbent_reported_ms": 64.087, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.09639766701729968, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 4445425, + "1000": 4445425 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7905464179348201, + "rss_samples": 22, + "sampled_peak_rss_bytes": 187039744, + "scale": 10000, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0026144160074181855, + "wall_cap_overshoot_seconds": 0.00908424996305257 + }, + { + "config": "transition-cabs-lazy-cheap", + "end_to_end_wall_seconds": 1.123474583029747, + "final_objective": 4445425, + "first_incumbent_reported_ms": 0.413, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.09643045801203698, + "measurements": { + "bound_calls": 450544, + "bound_ms": 11.157, + "inference_calls": 1708453, + "inference_ms": 44.767, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 20, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 1708468, + "frontier_bytes": null, + "preprocessing_ms": 0.142, + "search_ms": 1102.409, + "startup_and_teardown_ms": 20.90258302974712 + }, + "proof": "optimal", + "proof_reported_ms": 1102.572, + "quality_at_reported_ms": { + "100": 4445425, + "1000": 4445425 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.42876570898806676, + "rss_samples": 12, + "sampled_peak_rss_bytes": 98435072, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.8749851733446121e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 2.002119834010955, + "final_objective": 4445425, + "first_incumbent_reported_ms": 0.699, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.09751545795006678, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 4445425, + "1000": 4445425 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7903453342150897, + "rss_samples": 22, + "sampled_peak_rss_bytes": 80576512, + "scale": 10000, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0014950829790905118, + "wall_cap_overshoot_seconds": 0.0021198340109549463 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 2.001805958978366, + "final_objective": 4445425, + "first_incumbent_reported_ms": 0.706, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.09694908297387883, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 4445425, + "1000": 4445425 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.783147252863273, + "rss_samples": 22, + "sampled_peak_rss_bytes": 80936960, + "scale": 10000, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.00156879099085927, + "wall_cap_overshoot_seconds": 0.0018059589783661067 + }, + { + "config": "transition-cabs-lazy-cheap", + "end_to_end_wall_seconds": 1.1208939159987494, + "final_objective": 4445425, + "first_incumbent_reported_ms": 0.435, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0979892080067657, + "measurements": { + "bound_calls": 450544, + "bound_ms": 11.205, + "inference_calls": 1708453, + "inference_ms": 45.164, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 20, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 1708468, + "frontier_bytes": null, + "preprocessing_ms": 0.147, + "search_ms": 1104.706, + "startup_and_teardown_ms": 16.021915998749364 + }, + "proof": "optimal", + "proof_reported_ms": 1104.872, + "quality_at_reported_ms": { + "100": 4445425, + "1000": 4445425 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.43729366798652336, + "rss_samples": 12, + "sampled_peak_rss_bytes": 99811328, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.915963366627693e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs", + "end_to_end_wall_seconds": 2.0034055830328725, + "final_objective": 4445425, + "first_incumbent_reported_ms": 61.275, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.09736083401367068, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 4445425, + "1000": 4445425 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7956708751153201, + "rss_samples": 22, + "sampled_peak_rss_bytes": 188268544, + "scale": 10000, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.002590416988823563, + "wall_cap_overshoot_seconds": 0.003405583032872528 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 2.012138749996666, + "final_objective": 4445425, + "first_incumbent_reported_ms": 1.542, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.09774045797530562, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1066306, + "inference_ms": 1545.946, + "propagation_ms": 1116.778 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 20, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 712302, + "frontier_bytes": null, + "preprocessing_ms": 0.209, + "search_ms": 1999.796, + "startup_and_teardown_ms": 12.120749996665836 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 5217715, + "1000": 5039994 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7827272509457543, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2932736, + "scale": 10000, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 5.00003807246685e-07, + "wall_cap_overshoot_seconds": 0.012138749996665865 + }, + { + "config": "transition-bab-lazy-cheap", + "end_to_end_wall_seconds": 0.5618067919858731, + "final_objective": 4445425, + "first_incumbent_reported_ms": 0.325, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.09644045896129683, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1386948, + "inference_ms": 34.91, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 20, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 923985, + "frontier_bytes": null, + "preprocessing_ms": 0.15, + "search_ms": 551.898, + "startup_and_teardown_ms": 9.739791985873126 + }, + "proof": "optimal", + "proof_reported_ms": 552.067, + "quality_at_reported_ms": { + "100": 5061437, + "1000": 4445425 + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.2128655829001218, + "rss_samples": 6, + "sampled_peak_rss_bytes": 2490368, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.667009200900793e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab", + "end_to_end_wall_seconds": 2.0191828329698183, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.09724608296528459, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 3974257, + "frontier_bytes": null, + "preprocessing_ms": 0.131, + "search_ms": 1999.87, + "startup_and_teardown_ms": 19.134832969818262 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.7854762091301382, + "rss_samples": 22, + "sampled_peak_rss_bytes": 2473984, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 7.079797796905041e-07, + "wall_cap_overshoot_seconds": 0.019182832969818264 + } + ], + "reference_kind": "matching campaign optimality proof", + "reference_scaled_cost": 4445425 + }, + { + "case": { + "family": "tiny", + "id": "tiny/late-customer.txt", + "path": "/Users/zayenz/.codex/worktrees/2d06/gecode/examples/data/tsptw/tiny/late-customer.txt", + "sha256": "c1438cc1fa564c8b22011684aab37ae461e5f598d5d714a758c885ba14713ef6", + "variant": "tiny control" + }, + "measurements": [ + { + "config": "transition-bab", + "end_to_end_wall_seconds": 0.09465883299708366, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0032903330284170806, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 2, + "frontier_bytes": null, + "preprocessing_ms": 0.07, + "search_ms": 0.011, + "startup_and_teardown_ms": 94.53183299708367 + }, + "proof": "infeasible", + "proof_reported_ms": 0.127, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03625816700514406, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 2.1669548004865646e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-cheap", + "end_to_end_wall_seconds": 0.09331695799482986, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0032767080119811, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.061, + "search_ms": 0.005, + "startup_and_teardown_ms": 93.23395799482986 + }, + "proof": "infeasible", + "proof_reported_ms": 0.083, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03407254203921184, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.792039256542921e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 0.09690866700839251, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0030991670209914446, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.005, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.078, + "search_ms": 0.005, + "startup_and_teardown_ms": 96.80966700839251 + }, + "proof": "infeasible", + "proof_reported_ms": 0.099, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.034621624974533916, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.5409896150231361e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs", + "end_to_end_wall_seconds": 0.09471370803657919, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0034355000243522227, + "measurements": { + "bound_calls": 1, + "bound_ms": 0.0, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 2, + "frontier_bytes": null, + "preprocessing_ms": 0.074, + "search_ms": 0.013, + "startup_and_teardown_ms": 94.60970803657919 + }, + "proof": "infeasible", + "proof_reported_ms": 0.104, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03438887500669807, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.834006980061531e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-cheap", + "end_to_end_wall_seconds": 0.10157287499168888, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0032310420065186918, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.069, + "search_ms": 0.005, + "startup_and_teardown_ms": 101.48287499168887 + }, + "proof": "infeasible", + "proof_reported_ms": 0.09, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03779974998906255, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.667009200900793e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 0.0912854999769479, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0034790830104611814, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.005, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.067, + "search_ms": 0.005, + "startup_and_teardown_ms": 91.19749997694791 + }, + "proof": "infeasible", + "proof_reported_ms": 0.088, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03602200001478195, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.7909915186464787e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 0.09988795901881531, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.003738333994988352, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.005, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.072, + "search_ms": 0.005, + "startup_and_teardown_ms": 99.7949590188153 + }, + "proof": "infeasible", + "proof_reported_ms": 0.093, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03560508298687637, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.5409896150231361e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-cheap", + "end_to_end_wall_seconds": 0.10009908402571455, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.003074042033404112, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.066, + "search_ms": 0.005, + "startup_and_teardown_ms": 100.01208402571454 + }, + "proof": "infeasible", + "proof_reported_ms": 0.087, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.0364920410211198, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.7499551177024841e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs", + "end_to_end_wall_seconds": 0.09836495801573619, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0030049580382183194, + "measurements": { + "bound_calls": 1, + "bound_ms": 0.0, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 2, + "frontier_bytes": null, + "preprocessing_ms": 0.063, + "search_ms": 0.011, + "startup_and_teardown_ms": 98.2729580157362 + }, + "proof": "infeasible", + "proof_reported_ms": 0.092, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03485437500057742, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.3329554349184036e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 0.09304279199568555, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0030366249848157167, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.006, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.114, + "search_ms": 0.006, + "startup_and_teardown_ms": 92.90379199568555 + }, + "proof": "infeasible", + "proof_reported_ms": 0.139, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03415150003274903, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.9579892978072166e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-cheap", + "end_to_end_wall_seconds": 0.09579391602892429, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.003144791000522673, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.068, + "search_ms": 0.005, + "startup_and_teardown_ms": 95.70591602892429 + }, + "proof": "infeasible", + "proof_reported_ms": 0.088, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.034024499997030944, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.8749851733446121e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab", + "end_to_end_wall_seconds": 0.08848679199581966, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0030104999896138906, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 2, + "frontier_bytes": null, + "preprocessing_ms": 0.056, + "search_ms": 0.01, + "startup_and_teardown_ms": 88.40879199581966 + }, + "proof": "infeasible", + "proof_reported_ms": 0.078, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03392083296785131, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.500011421740055e-06, + "wall_cap_overshoot_seconds": 0 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "tiny", + "id": "tiny/late-return.txt", + "path": "/Users/zayenz/.codex/worktrees/2d06/gecode/examples/data/tsptw/tiny/late-return.txt", + "sha256": "f44a2168bc9638d0c657235b2eb9df1bc08fcc0dca06e6729782ed20235dae62", + "variant": "tiny control" + }, + "measurements": [ + { + "config": "transition-bab", + "end_to_end_wall_seconds": 0.09904079203261063, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.00330700003542006, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 4, + "frontier_bytes": null, + "preprocessing_ms": 0.072, + "search_ms": 0.012, + "startup_and_teardown_ms": 98.94079203261063 + }, + "proof": "infeasible", + "proof_reported_ms": 0.1, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03519562497967854, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.500011421740055e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-cheap", + "end_to_end_wall_seconds": 0.09717241604812443, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0036432500346563756, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.08, + "search_ms": 0.005, + "startup_and_teardown_ms": 97.06941604812444 + }, + "proof": "infeasible", + "proof_reported_ms": 0.103, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03411274997051805, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.5840050764381886e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 0.09899920894531533, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0036922089639119804, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.005, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.069, + "search_ms": 0.006, + "startup_and_teardown_ms": 98.90720894531533 + }, + "proof": "infeasible", + "proof_reported_ms": 0.092, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03480012499494478, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.6659614630043507e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs", + "end_to_end_wall_seconds": 0.09123620798345655, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0031092919525690377, + "measurements": { + "bound_calls": 2, + "bound_ms": 0.0, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 4, + "frontier_bytes": null, + "preprocessing_ms": 0.065, + "search_ms": 0.022, + "startup_and_teardown_ms": 91.13420798345655 + }, + "proof": "infeasible", + "proof_reported_ms": 0.102, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03374658396933228, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.5830155462026596e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-cheap", + "end_to_end_wall_seconds": 0.09599795896792784, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0030746249831281602, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.079, + "search_ms": 0.005, + "startup_and_teardown_ms": 95.89695896792784 + }, + "proof": "infeasible", + "proof_reported_ms": 0.101, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03517516702413559, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.7079873941838741e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 0.09762837499147281, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0032805409864522517, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.005, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.068, + "search_ms": 0.005, + "startup_and_teardown_ms": 97.53637499147281 + }, + "proof": "infeasible", + "proof_reported_ms": 0.092, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.0338662089779973, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.5420373529195786e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 0.09508512500906363, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.002998292038682848, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.005, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.113, + "search_ms": 0.005, + "startup_and_teardown_ms": 94.95112500906363 + }, + "proof": "infeasible", + "proof_reported_ms": 0.134, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03481949999695644, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.5409896150231361e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-cheap", + "end_to_end_wall_seconds": 0.09567183302715421, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0032833750010468066, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.073, + "search_ms": 0.005, + "startup_and_teardown_ms": 95.57483302715421 + }, + "proof": "infeasible", + "proof_reported_ms": 0.097, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.034789874975103885, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.8330174498260021e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs", + "end_to_end_wall_seconds": 0.09585812501609325, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0031281670089811087, + "measurements": { + "bound_calls": 2, + "bound_ms": 0.0, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 4, + "frontier_bytes": null, + "preprocessing_ms": 0.065, + "search_ms": 0.023, + "startup_and_teardown_ms": 95.74912501609326 + }, + "proof": "infeasible", + "proof_reported_ms": 0.109, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03392204100964591, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.374981366097927e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 0.009286458021961153, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": null, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.006, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.07, + "search_ms": 0.005, + "startup_and_teardown_ms": 9.194458021961152 + }, + "proof": "infeasible", + "proof_reported_ms": 0.092, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.0, + "rss_samples": 0, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 5.292007699608803e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-cheap", + "end_to_end_wall_seconds": 0.08831537503283471, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0027779160300269723, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.061, + "search_ms": 0.004, + "startup_and_teardown_ms": 88.23537503283471 + }, + "proof": "infeasible", + "proof_reported_ms": 0.08, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.033989917021244764, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.5830155462026596e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab", + "end_to_end_wall_seconds": 0.09689770801924169, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0037172919837757945, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 0, + "inference_ms": 0.0, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 4, + "frontier_bytes": null, + "preprocessing_ms": 0.072, + "search_ms": 0.013, + "startup_and_teardown_ms": 96.79570801924169 + }, + "proof": "infeasible", + "proof_reported_ms": 0.102, + "quality_at_reported_ms": { + "100": null, + "1000": null + }, + "quality_usable": true, + "repetition": 1, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.03588604199467227, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 2.040993422269821e-06, + "wall_cap_overshoot_seconds": 0 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + } + ], + "clocks": "publication checkpoints use solver-reported milliseconds; launch-inclusive publication timestamps are unavailable", + "family_groups": { + "Dumas/canonical": { + "transition-bab": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 2, + "resource_stops": 0, + "samples": 2 + }, + "transition-bab-lazy-cheap": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 2, + "resource_stops": 0, + "samples": 2 + }, + "transition-bab-lazy-unary": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 2, + "resource_stops": 0, + "samples": 2 + }, + "transition-cabs": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 2, + "samples": 2 + }, + "transition-cabs-lazy-cheap": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 2, + "resource_stops": 0, + "samples": 2 + }, + "transition-cabs-lazy-unary": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 2, + "resource_stops": 0, + "samples": 2 + } + }, + "Dumas/variant": { + "transition-bab": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 2 + }, + "transition-bab-lazy-cheap": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 2, + "samples": 2 + }, + "transition-bab-lazy-unary": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 2 + }, + "transition-cabs": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 2, + "samples": 2 + }, + "transition-cabs-lazy-cheap": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 2, + "samples": 2 + }, + "transition-cabs-lazy-unary": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 0, + "samples": 2 + } + }, + "GendreauDumasExtended/canonical": { + "transition-bab": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 2 + }, + "transition-bab-lazy-cheap": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 2 + }, + "transition-bab-lazy-unary": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 2 + }, + "transition-cabs": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 2 + }, + "transition-cabs-lazy-cheap": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 2, + "samples": 2 + }, + "transition-cabs-lazy-unary": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 2, + "samples": 2 + } + }, + "SolomonPotvinBengio/canonical": { + "transition-bab": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 2 + }, + "transition-bab-lazy-cheap": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 2, + "resource_stops": 0, + "samples": 2 + }, + "transition-bab-lazy-unary": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 0, + "samples": 2 + }, + "transition-cabs": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 2, + "samples": 2 + }, + "transition-cabs-lazy-cheap": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 2, + "resource_stops": 0, + "samples": 2 + }, + "transition-cabs-lazy-unary": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 2, + "samples": 2 + } + }, + "tiny/variant": { + "transition-bab": { + "failures": 0, + "feasible": 0, + "infeasible": 4, + "optimal": 0, + "resource_stops": 0, + "samples": 4 + }, + "transition-bab-lazy-cheap": { + "failures": 0, + "feasible": 0, + "infeasible": 4, + "optimal": 0, + "resource_stops": 0, + "samples": 4 + }, + "transition-bab-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 4, + "optimal": 0, + "resource_stops": 0, + "samples": 4 + }, + "transition-cabs": { + "failures": 0, + "feasible": 0, + "infeasible": 4, + "optimal": 0, + "resource_stops": 0, + "samples": 4 + }, + "transition-cabs-lazy-cheap": { + "failures": 0, + "feasible": 0, + "infeasible": 4, + "optimal": 0, + "resource_stops": 0, + "samples": 4 + }, + "transition-cabs-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 4, + "optimal": 0, + "resource_stops": 0, + "samples": 4 + } + } + }, + "formulation_contrasts": {}, + "missing_metrics": "frontier/clone buffers and phases after kill are unavailable; allocation sizeof excludes dynamic storage", + "same_model": { + "transition": { + "baseline": "transition-bab", + "counts": { + "transition-bab-lazy-cheap": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 1, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 11 + }, + "final_objective": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 2, + "unavailable": 4, + "win": 6 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 0, + "unavailable": 8, + "win": 4 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 0, + "unavailable": 6, + "win": 4 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 2, + "unavailable": 4, + "win": 6 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 0, + "unavailable": 4, + "win": 8 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 5, + "missing": 0, + "tie": 1, + "unavailable": 6, + "win": 0 + } + }, + "transition-bab-lazy-unary": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 5, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 7 + }, + "final_objective": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 4, + "unavailable": 4, + "win": 4 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 0, + "unavailable": 8, + "win": 4 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 1, + "missing": 0, + "tie": 0, + "unavailable": 6, + "win": 5 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 2, + "unavailable": 4, + "win": 6 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 0, + "unavailable": 4, + "win": 8 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 8, + "missing": 0, + "tie": 0, + "unavailable": 4, + "win": 0 + } + }, + "transition-cabs": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 8, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 4 + }, + "final_objective": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 2, + "unavailable": 4, + "win": 6 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 0, + "unavailable": 8, + "win": 4 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 3, + "missing": 0, + "tie": 0, + "unavailable": 8, + "win": 1 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 2, + "unavailable": 4, + "win": 6 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 0, + "unavailable": 4, + "win": 8 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 8, + "missing": 0, + "tie": 0, + "unavailable": 4, + "win": 0 + } + }, + "transition-cabs-lazy-cheap": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 3, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 9 + }, + "final_objective": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 2, + "unavailable": 4, + "win": 6 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 0, + "unavailable": 8, + "win": 4 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 0, + "unavailable": 6, + "win": 4 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 2, + "unavailable": 4, + "win": 6 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 0, + "unavailable": 4, + "win": 8 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 6, + "missing": 0, + "tie": 0, + "unavailable": 5, + "win": 1 + } + }, + "transition-cabs-lazy-unary": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 4, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 8 + }, + "final_objective": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 2, + "unavailable": 4, + "win": 6 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 0, + "unavailable": 8, + "win": 4 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 0, + "unavailable": 6, + "win": 4 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 2, + "unavailable": 4, + "win": 6 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 0, + "unavailable": 4, + "win": 8 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 8, + "missing": 0, + "tie": 0, + "unavailable": 4, + "win": 0 + } + } + }, + "note": "Costs are paired only within the same case and scale. Checked timeout/RSS incumbents count as feasible quality, never proof. Native reported clocks start at main entry and include preprocessing; outer wall clocks measure launch through reap. Missing metrics are unavailable; lower values win. Cases mark canonical data and deterministic window variants separately.", + "reported_time_checkpoints_ms": [ + 100, + 1000 + ] + } + } + }, + "completed": 72, + "configurations": { + "transition-bab": [ + "--formulation", + "transition", + "--engine", + "bab" + ], + "transition-bab-lazy-cheap": [ + "--formulation", + "transition", + "--engine", + "bab", + "--bound", + "prefix", + "--residual-propagation", + "cheap", + "--state-pruning", + "off" + ], + "transition-bab-lazy-unary": [ + "--formulation", + "transition", + "--engine", + "bab", + "--bound", + "prefix", + "--residual-propagation", + "unary", + "--state-pruning", + "off" + ], + "transition-cabs": [ + "--formulation", + "transition", + "--engine", + "cabs" + ], + "transition-cabs-lazy-cheap": [ + "--formulation", + "transition", + "--engine", + "cabs", + "--bound", + "prefix", + "--residual-propagation", + "cheap", + "--state-pruning", + "off" + ], + "transition-cabs-lazy-unary": [ + "--formulation", + "transition", + "--engine", + "cabs", + "--bound", + "prefix", + "--residual-propagation", + "unary", + "--state-pruning", + "off" + ] + }, + "expected": 72, + "freeze_sha256": "933f28186618bd1c5eecc3174d3c3b25f70b8aacc23b7927bb644a0ff921c6d7", + "groups": { + "transition-bab": { + "actual_launch_wall_seconds": 15.31761558202561, + "completed": 12, + "failed": 3, + "feasible": 4, + "infeasible": 4, + "median_wall_seconds": 1.4186669579939917, + "optimal": 2, + "ordinary_stopped": 3, + "proved": 6, + "resource_terminated": 3, + "sampled_peak_rss_bytes": 2555904, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 9, + "rss-limit": 0, + "timeout": 3 + } + }, + "transition-bab-lazy-cheap": { + "actual_launch_wall_seconds": 9.723044750106055, + "completed": 12, + "failed": 3, + "feasible": 8, + "infeasible": 4, + "median_wall_seconds": 0.09679945901734754, + "optimal": 4, + "ordinary_stopped": 1, + "proved": 8, + "resource_terminated": 3, + "sampled_peak_rss_bytes": 2539520, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 9, + "rss-limit": 0, + "timeout": 3 + } + }, + "transition-bab-lazy-unary": { + "actual_launch_wall_seconds": 12.570721166906878, + "completed": 12, + "failed": 2, + "feasible": 8, + "infeasible": 4, + "median_wall_seconds": 0.09930535449529998, + "optimal": 2, + "ordinary_stopped": 4, + "proved": 6, + "resource_terminated": 2, + "sampled_peak_rss_bytes": 2981888, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 10, + "rss-limit": 0, + "timeout": 2 + } + }, + "transition-cabs": { + "actual_launch_wall_seconds": 16.450631500105374, + "completed": 12, + "failed": 7, + "feasible": 8, + "infeasible": 4, + "median_wall_seconds": 0.09585812501609325, + "optimal": 0, + "ordinary_stopped": 1, + "proved": 4, + "resource_terminated": 7, + "sampled_peak_rss_bytes": 297041920, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 5, + "rss-limit": 0, + "timeout": 7 + } + }, + "transition-cabs-lazy-cheap": { + "actual_launch_wall_seconds": 10.85198749997653, + "completed": 12, + "failed": 4, + "feasible": 8, + "infeasible": 4, + "median_wall_seconds": 0.1000160209950991, + "optimal": 4, + "ordinary_stopped": 0, + "proved": 8, + "resource_terminated": 4, + "sampled_peak_rss_bytes": 102187008, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 8, + "rss-limit": 0, + "timeout": 4 + } + }, + "transition-cabs-lazy-unary": { + "actual_launch_wall_seconds": 12.622667671006639, + "completed": 12, + "failed": 4, + "feasible": 8, + "infeasible": 4, + "median_wall_seconds": 0.09642300000996329, + "optimal": 2, + "ordinary_stopped": 2, + "proved": 6, + "resource_terminated": 4, + "sampled_peak_rss_bytes": 80936960, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 8, + "rss-limit": 0, + "timeout": 4 + } + } + }, + "identities": { + "checker_sha256": "91bff4e5c1630f187eb816dbc2094507e362b6e349197e6680c2cbcee2338778", + "didppy": { + "adapter_sha256": "8361a24a494a698be1650f4d1560030abf707fea114da381f1e317deeb36c3d8", + "launcher": "uv", + "version": "0.11.1" + }, + "manifest_sha256": "946fb8cccbe4b580cdf1c4f29a0d85bd7524a84bd1431a6b3408a5736d8a7e87", + "native": { + "libraries": { + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodedriver.6.4.0.dylib": "d4332d9873d657cf1d58e825842dea0026a51a302894210745f5c5260077aaa4", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodeflatzinc.6.4.0.dylib": "bc878a20194cec96379975ea3d1fe3b88681d340bfba65bea3d7c2d14cc60110", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodefloat.6.4.0.dylib": "68b1aec0512dded3b80599cfbee34d8f301f50d60560ba5e8b2c06752efb20e7", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodeint.6.4.0.dylib": "635412b4c0cde700e0ec4dffa12b59b44e70b9f4252657ea42b4106ad078ccff", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodekernel.6.4.0.dylib": "2a7aced55c554a1449b007c127afb1e869ee7f58cdc22c634065b074f2597301", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodeminimodel.6.4.0.dylib": "48e26109dbbb45f8fef29262f3c3299d0486d2737de85f43fc65c66511c91f23", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodemodel.6.4.0.dylib": "75b6d62a8a227075998bc3341c7c0af7aab84f4eba1a746ea4e1cc317e1b996a", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodesearch.6.4.0.dylib": "62f26d809c80034ad3a4d54c2a7209ca195cbdca6c0a010e4db966123ceddc9c", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodeset.6.4.0.dylib": "f07b55be6931f62c1c9821f1578bec89bb08568e6a4bf03c1f6981f0c9b7ae8e", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodesupport.6.4.0.dylib": "6cfefbf1ceb061ee0eb42917e8e5eb3203019e133dbfb50a78ee2d4cdefcc2a9" + }, + "path": "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "sha256": "7e7e477d8ab0ac61fbde6db5b996d8262742ad043e8da48da57da7e1d87e5687" + }, + "runner_sha256": "6a71a42798901cfd120ef3813120864406f8046bcdb3cd47729546ff2ff591be", + "sources": { + "examples/tsptw-data.hh": "5737e87ec75569c333088b66e59d15c42b0b12c873a4f35d9ca39105093f8d66", + "examples/tsptw-transition.hh": "9247a4f1a0c322a052c93afd0d6d2c2268ce06750adddcf332b0dc7d3e1f7be2", + "examples/tsptw.cpp": "4a04b3634c9b2a6fabb83db32707a4adabae5df56ca43eafecc0f20332f48a91", + "gecode/search.hh": "8ef2a1bc865dcf34582bb3926b2ef09b2738a8ab6b28da55126d71992de04bf8", + "gecode/search/astar.cpp": "4680fe1e47ecea67d79ce093ab457be89e5e073dec0233d6bb7354c62904c69e", + "gecode/search/astar.hpp": "af350c1108b66712f67ad7bd19844c9362f4606e3623c36f4566ededbe522d15", + "gecode/search/bab.cpp": "00b4762f2401ea1f3c1e4ca761181f7771290252a94ec20c8cccc5bb39b348de", + "gecode/search/bab.hpp": "726f1e57ca1a18baf5c6d5645cb5dbffa0e6f76a03f26cdd797e5baf25abce6e", + "gecode/search/base.hpp": "fd1a98449e27876b48ee115185194416612e743e35a00a07a971450ff471daf7", + "gecode/search/build.hpp": "cfaf01a48a711de6a814a51cf4e514e44e4f98378a6dbca73a2ee172ad508bc6", + "gecode/search/cabs.cpp": "586a5105d40565a197f4a9a5672f578d97a36f9beb84d4f43f270c837d59c1a6", + "gecode/search/cabs.hpp": "6b6fcaa4e2d4388e3496629eb06b3e35532f93722a2a704d82fe17f40ec6c396", + "gecode/search/column.cpp": "a8511c038a7441eb672bb24fcb22ceb9160f02ef92c95ef23746ae6953681533", + "gecode/search/column.hpp": "4447bbf1a16b19d5cb32190d5c383019560fa4eb94c41ce74e48a6edeebc4954", + "gecode/search/cpprofiler/connector.hpp": "a2883f1711a6be340602019a62ce284dfa9b40dc6ba0bc0aec0c0d66f1effe69", + "gecode/search/cpprofiler/message.hpp": "91ef4240babfcc6a818d28257d0060863eeab0db4f18d8fa66458cab37c39210", + "gecode/search/cpprofiler/tracer.cpp": "eacb488b6ee27bcfd360ee411ec582c4321f4f014dfd3590df928c174f575875", + "gecode/search/cutoff.cpp": "c61236c2b09a4d7b4d628b08a7db62cb2552864437d8b7b178dde0f5a81a558f", + "gecode/search/cutoff.hpp": "726743ce80885e63892fd6b3814ca79f0fa73195a7e2770a2b97a4e4c74a64fc", + "gecode/search/dfs.cpp": "6fa3f7879f89e1650c2c144af8d4e5489a938efcbfa32532b8ab9bbfd88d185e", + "gecode/search/dfs.hpp": "63336435572ca66b23301003ada2a10917de200366e633bcbde9409a7c3bcd5a", + "gecode/search/engine.cpp": "ba44ee580ef5a7217e391a80c41e2e68ee8b9785de8fbb9694503b7dd79fccb5", + "gecode/search/engine.hpp": "46599fe8f8957080687aa18f938f71c97e587990202101aa6fdc4cb8cc240b79", + "gecode/search/exception.cpp": "b80d71aa47a856233e1de383fa84a14ec6eecf7738abdcf8e22384e49b89d612", + "gecode/search/exception.hpp": "35a76f421d21e2a8a5b319f08ec887972df68d686569fe6d30034e7673676cf0", + "gecode/search/lds.cpp": "90484573667912bd8e2eab75c483013447f9286773a2f5161aa65d260a970f06", + "gecode/search/lds.hpp": "b383af2b362e9c7a777e9ab9221835c2a5c11ad715be915c7d25095cdcb7f002", + "gecode/search/nogoods.cpp": "5ce5baedeb544c74b51f864f06669fa7dc723dc56ce2fda7a5a4973d7e27ec05", + "gecode/search/nogoods.hpp": "1cb7b897ab7cfc08bb4f033b59875cbe4a70f74a8998e398dc9148093b280393", + "gecode/search/options.cpp": "aa61ca08b1100b59737aed5492720c0d11be0a23b80d40f3f9efb75616d93921", + "gecode/search/options.hpp": "993a8154ba05bb2f47caa1e8145f5542703d762ffdfad52b79114a95393cb6db", + "gecode/search/par/bab.hpp": "6cafb3e779d117f59fba625829cf52f28e049e098f9b3649cd5491e6d8731ca7", + "gecode/search/par/dfs.hpp": "e742a3b4d83e843659f78fd76f8e4a02a68e62f5e9108890737699ee2dcc4774", + "gecode/search/par/engine.hpp": "48d4bf00fe4f417b48ece3aeffc680cc17950679a685b98b2e1c3383e351494a", + "gecode/search/par/path.hpp": "ff9908e5967b96ca4ce5b947b8c4f21e64a0251a02bb4a7045eb10820f936edb", + "gecode/search/par/pbs.cpp": "aaa024723d45065aaeb8fdbd3b87c0ae31ca32a1eecfca9ab21b77cd8fe754a2", + "gecode/search/par/pbs.hpp": "4bd4bae09a74aee478151e9fe63ac0b484470e28198020c5293a569a4dc4a985", + "gecode/search/pbs.cpp": "cf953d42395e1d096df0d991271dde4ae836dee35af60b87e297953272093206", + "gecode/search/pbs.hpp": "2fa9855e31bb690f6765f057544a0f0e451425df45125121f9bc625442cc748c", + "gecode/search/portfolio-cost.hpp": "cb63a32144a15433e5ef8da072914f8af8089cd6589663d40c692d3ac1334904", + "gecode/search/portfolio.cpp": "299993af771946f6d8ce4bd2561eabf100484169b56b465f68973845260d8516", + "gecode/search/portfolio.hpp": "75e25d112bd3419d8481ec3e503eb7ff03942180fbbdd347fee3048ac540b44c", + "gecode/search/rbs.cpp": "196abc8674d19fff5ea8211cd010391cf4f9662a2cff549cb7deeae5ed14a2ea", + "gecode/search/rbs.hpp": "3f5f38d243241eb395e0af0b77a3a8acae9aebd74d76f86be72a92ab9d1f5413", + "gecode/search/sebs.hpp": "893ba3bf4a1a9c8cf3d776582035bfb3ee3111b80fe5a9fa58ed653ca4870f08", + "gecode/search/seq/bab.hpp": "0854fd52139352d4b9a33ba9870ca8bd1d9bad9f1534ee140a491f412df783b7", + "gecode/search/seq/dead.cpp": "3baad801a8a24833905c45225cbfad74a8146cd16fe248763b28edff986d57cd", + "gecode/search/seq/dfs.hpp": "1c4d152fdb3b2adb0eddd6b1b7055e4e6c2ddf7190d415b4b8973343ce1169b5", + "gecode/search/seq/lds.hpp": "dde6c5560518de0833f92d60fea56c05adcdd9700fd9fb163c12607f8c87842e", + "gecode/search/seq/path.hpp": "ca92e28836620497a953117f1ce03ebb8e5482f03e303a14ed498c96fc9c693f", + "gecode/search/seq/pbs.cpp": "3f17ed9e52371ceec419e1fcfd9dc744ac1c5c8e177a2e95ee037a9b1376b6d3", + "gecode/search/seq/pbs.hpp": "b8438a8219aaf2173cc9e8c546b117bde893a241032b9ad5831ca46c807784e8", + "gecode/search/seq/rbs.cpp": "21f9b927a71bde6ea9c1dba6ac653c20f37bcf89214a588d2ba94167ae5bfb5d", + "gecode/search/seq/rbs.hpp": "04e9204bbd602f8fe49eb772d20c0676099e2bfb054db69683eec83b5f1ac2b0", + "gecode/search/statistics.hpp": "df01cbe88dd100946f00b5a747abf32550e2f8c3639b4274d5914168c91acea7", + "gecode/search/stop.cpp": "184572f90f6f9a3d754d3701d862bff607c0d09bf39d3353fd4321a0e12fd3ac", + "gecode/search/stop.hpp": "2a30137766f158679a7dde8ac45453000f44f29a3ec81cabfe5a34d04953eea5", + "gecode/search/trace-recorder.hpp": "ab495236abcf3819d2bfca47be90326e5fff9efdcb623de3f492675077ca00cd", + "gecode/search/tracer.cpp": "27266acb328d5f799a9639cefa8c2dd04333635847a7399e9ba1712115e07880", + "gecode/search/tracer.hpp": "af3481c6d85c7cc9b12b19a5fd1dd04d878a6c5a2bd6dc8f54af8120c1ebe09a", + "gecode/search/traits.hpp": "63d51e78557be9f5b241f8ea6db7afe73b554a9eac7e848f5b9ad00a64144c5f", + "gecode/search/worker-control.cpp": "57e9a4e6c497fbd67e030a65adca1d658f5458fd0984bafe1956f466b2cf09f2" + } + }, + "implementation": { + "analysis_argv": [ + "python3", + "misc/tsptw-benchmark.py", + "analyze", + "--name", + "cheap-deadline-028", + "--output", + "examples/data/tsptw/cheap-deadline-comparison.json" + ], + "budget": { + "charged_seconds_by_phase": { + "correctness": 33.5511747081182, + "development": 137.40637328813318 + }, + "charged_total_seconds": 170.95754799625138, + "cleanup_reserve_seconds": 20, + "conservative_absence_confirmation_extra_seconds": 55.85405086999526, + "header": { + "cleanup_reserve_seconds": 20, + "event": "budget", + "scope": "task", + "task_id": "open-search-028", + "total_seconds": 300, + "verification_reserve_seconds": 60 + }, + "label": "implementation snapshot; verification appends to the external ledger", + "launches": 90, + "ledger": "/private/tmp/open-search-028-budget.jsonl", + "ledger_sha256": "f904a0a328528d4dad902b5d76e09169c416802c3d71dd01e73fccec452d3032", + "measured_launch_through_reap_seconds": 115.10349712625612, + "pending_launches": 0, + "recoveries": [ + { + "event": "reap", + "id": "1791566025840977000", + "observed_wall_seconds": 2.008083750028163, + "returncode": null, + "status": "recovered-absent", + "timing": "launch-through-absence upper bound; exit time unobserved", + "wall_seconds": 39.63462686538696 + }, + { + "event": "reap", + "id": "1791566146160648000", + "observed_wall_seconds": 2.006190375017468, + "returncode": null, + "status": "recovered-absent", + "timing": "launch-through-absence upper bound; exit time unobserved", + "wall_seconds": 20.23369812965393 + } + ], + "total_remaining_seconds": 129.04245200374862, + "verification_remaining_seconds": 60, + "verification_used_seconds": 0 + }, + "build_total_seconds": 12.273696000047494, + "builds": [ + { + "argv": [ + "cmake", + "--build", + "build/open-search", + "--target", + "tsptw", + "gecode-test", + "-j", + "8" + ], + "returncode": 0, + "wall_seconds": 2.1112976250005886 + }, + { + "argv": [ + "cmake", + "--build", + "build/open-search", + "--target", + "tsptw", + "gecode-test", + "model-open-maintenance", + "model-open-miconic", + "model-open-valves", + "model-open-puzzle", + "gecode-test-blackbox-exec", + "gecode-test-blackbox-dll", + "-j", + "8" + ], + "returncode": 0, + "wall_seconds": 10.162398375046905 + } + ], + "collection_argv": [ + "python3", + "misc/tsptw-benchmark.py", + "run", + "--name", + "cheap-deadline-028", + "--split", + "development", + "--selected-root", + "/private/tmp/open-search-tsptw-selected-network", + "--case-ids", + "Dumas/n20w20.001.txt,variants/Dumas/customer-due-plus-100/n20w100.003.txt,GendreauDumasExtended/n20w120.001.txt,SolomonPotvinBengio/rc_201.1.txt,tiny/late-customer.txt,tiny/late-return.txt", + "--configs", + "transition-bab,transition-bab-lazy-cheap,transition-bab-lazy-unary,transition-cabs,transition-cabs-lazy-cheap,transition-cabs-lazy-unary", + "--repetitions", + "2", + "--seconds", + "2", + "--budget-ledger", + "/private/tmp/open-search-028-budget.jsonl", + "--budget-phase", + "development", + "--compilation-seconds", + "12.273696000047494" + ], + "limitations": "Four development cases, two tiny infeasible controls and two repetitions at two seconds do not establish a universal speedup. Missing summaries leave phase metrics and nodes unavailable. Conservative recovery charges are budget upper bounds, not measured search duration.", + "preview_argv": [ + "python3", + "misc/tsptw-benchmark.py", + "preview", + "--split", + "development", + "--selected-root", + "/private/tmp/open-search-tsptw-selected-network", + "--case-ids", + "Dumas/n20w20.001.txt,variants/Dumas/customer-due-plus-100/n20w100.003.txt,GendreauDumasExtended/n20w120.001.txt,SolomonPotvinBengio/rc_201.1.txt,tiny/late-customer.txt,tiny/late-return.txt", + "--configs", + "transition-bab,transition-bab-lazy-cheap,transition-bab-lazy-unary,transition-cabs,transition-cabs-lazy-cheap,transition-cabs-lazy-unary", + "--repetitions", + "2", + "--seconds", + "2", + "--budget-ledger", + "/private/tmp/open-search-028-budget.jsonl", + "--budget-phase", + "development", + "--compilation-seconds", + "12.273696000047494" + ], + "publication_checks": { + "checked_partial_publications": 3, + "checked_publications": 476, + "partial_attempts": [ + { + "checked_publications": 3, + "path": "build/tsptw-results/cheap-deadline-028/samples/00-001-transition-cabs-lazy-unary.stdout.partial-1791566085290259000", + "summary": "stopped" + }, + { + "checked_publications": 0, + "path": "build/tsptw-results/cheap-deadline-028/samples/01-003-transition-bab.stdout.partial-1791566179641238000", + "summary": "stopped" + } + ] + }, + "raw_results": "build/tsptw-results/cheap-deadline-028", + "recommendation": { + "change_defaults": false, + "fixed_width_continuation_study": "This comparison adds no memory-stop evidence. Retain separate justification and budget for any continuation study.", + "incremental_unary_study": "A separately scoped bounded study is justified by available unary inference time; distinguish rebuilding and propagation before choosing an implementation.", + "keep_cheap_option": true + }, + "resume": { + "changed_cap_rejected": true, + "ledger_unchanged": true, + "sample_files_unchanged": 220 + }, + "validation": [ + { + "argv": [ + "build/open-search/bin/gecode-test", + "-iter", + "1", + "-threads", + "1", + "-test", + "Search::TSPTW::Transition" + ], + "cap_seconds": 15, + "charged_seconds": 0.9274215000332333, + "phase": "correctness", + "status": "ok" + }, + { + "argv": [ + "build/open-search/bin/gecode-test", + "-iter", + "1", + "-threads", + "1", + "-test", + "Search::TSPTW::Transition" + ], + "cap_seconds": 15, + "charged_seconds": 0.9237225420074537, + "phase": "correctness", + "status": "ok" + }, + { + "argv": [ + "cmake", + "--build", + "build/open-search", + "--target", + "check", + "-j", + "8" + ], + "cap_seconds": 4, + "charged_seconds": 4.01117549999617, + "phase": "correctness", + "status": "timeout" + }, + { + "argv": [ + "cmake", + "--build", + "build/open-search", + "--target", + "check", + "-j", + "8" + ], + "cap_seconds": 40, + "charged_seconds": 16.89951016701525, + "phase": "correctness", + "status": "ok" + }, + { + "argv": [ + "build/open-search/bin/gecode-test", + "-iter", + "1", + "-threads", + "1", + "-test", + "Int::OpenList", + "-test", + "Model::OpenLists", + "-test", + "Int::Extensional::Reg", + "-test", + "Model::Portfolio::Restrictions", + "-test", + "Model::Portfolio::Sequential", + "-test", + "Model::Portfolio::ActivationRoots", + "-test", + "Search::Portfolio::PublicContract", + "-test", + "Search::CABS", + "-test", + "Search::AStar", + "-test", + "Search::Completion::Native", + "-test", + "Search::WorkerControl::Pause" + ], + "cap_seconds": 2, + "charged_seconds": 2.009822374966461, + "phase": "correctness", + "status": "timeout" + }, + { + "argv": [ + "build/open-search/bin/gecode-test", + "-iter", + "1", + "-threads", + "1", + "-test", + "Int::OpenList", + "-test", + "Model::OpenLists", + "-test", + "Int::Extensional::Reg", + "-test", + "Model::Portfolio::Restrictions", + "-test", + "Model::Portfolio::Sequential", + "-test", + "Model::Portfolio::ActivationRoots", + "-test", + "Search::Portfolio::PublicContract", + "-test", + "Search::CABS", + "-test", + "Search::AStar", + "-test", + "Search::Completion::Native", + "-test", + "Search::WorkerControl::Pause" + ], + "cap_seconds": 25, + "charged_seconds": 7.824548291042447, + "phase": "correctness", + "status": "ok" + }, + { + "argv": [ + "build/open-search/bin/model-open-maintenance" + ], + "cap_seconds": 1, + "charged_seconds": 0.0992180000175722, + "phase": "correctness", + "status": "ok" + }, + { + "argv": [ + "build/open-search/bin/model-open-miconic" + ], + "cap_seconds": 1, + "charged_seconds": 0.09944016602821648, + "phase": "correctness", + "status": "ok" + }, + { + "argv": [ + "build/open-search/bin/model-open-valves" + ], + "cap_seconds": 1, + "charged_seconds": 0.09520629199687392, + "phase": "correctness", + "status": "ok" + }, + { + "argv": [ + "build/open-search/bin/model-open-puzzle" + ], + "cap_seconds": 1, + "charged_seconds": 0.10322191601153463, + "phase": "correctness", + "status": "ok" + }, + { + "argv": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/Users/zayenz/.codex/worktrees/2d06/gecode/examples/data/tsptw/tiny/waiting.txt", + "--formulation", + "transition", + "--engine", + "bab", + "--bound", + "prefix", + "--residual-propagation", + "cheap", + "--state-pruning", + "off", + "--time-ms", + "2000" + ], + "cap_seconds": 2, + "charged_seconds": 0.08798262500204146, + "phase": "correctness", + "status": "ok" + }, + { + "argv": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/Users/zayenz/.codex/worktrees/2d06/gecode/examples/data/tsptw/tiny/waiting.txt", + "--formulation", + "transition", + "--engine", + "cabs", + "--bound", + "prefix", + "--residual-propagation", + "cheap", + "--state-pruning", + "off", + "--time-ms", + "2000" + ], + "cap_seconds": 2, + "charged_seconds": 0.08891379099804908, + "phase": "correctness", + "status": "ok" + }, + { + "argv": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/Users/zayenz/.codex/worktrees/2d06/gecode/examples/data/tsptw/tiny/late-customer.txt", + "--formulation", + "transition", + "--engine", + "bab", + "--bound", + "prefix", + "--residual-propagation", + "cheap", + "--state-pruning", + "off", + "--time-ms", + "2000" + ], + "cap_seconds": 2, + "charged_seconds": 0.09859704098198563, + "phase": "correctness", + "status": "ok" + }, + { + "argv": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/Users/zayenz/.codex/worktrees/2d06/gecode/examples/data/tsptw/tiny/late-customer.txt", + "--formulation", + "transition", + "--engine", + "cabs", + "--bound", + "prefix", + "--residual-propagation", + "cheap", + "--state-pruning", + "off", + "--time-ms", + "2000" + ], + "cap_seconds": 2, + "charged_seconds": 0.09600445901742205, + "phase": "correctness", + "status": "ok" + }, + { + "argv": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/Users/zayenz/.codex/worktrees/2d06/gecode/examples/data/tsptw/tiny/late-return.txt", + "--formulation", + "transition", + "--engine", + "bab", + "--bound", + "prefix", + "--residual-propagation", + "cheap", + "--state-pruning", + "off", + "--time-ms", + "2000" + ], + "cap_seconds": 2, + "charged_seconds": 0.09224170900415629, + "phase": "correctness", + "status": "ok" + }, + { + "argv": [ + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "/Users/zayenz/.codex/worktrees/2d06/gecode/examples/data/tsptw/tiny/late-return.txt", + "--formulation", + "transition", + "--engine", + "cabs", + "--bound", + "prefix", + "--residual-propagation", + "cheap", + "--state-pruning", + "off", + "--time-ms", + "2000" + ], + "cap_seconds": 2, + "charged_seconds": 0.09414833399932832, + "phase": "correctness", + "status": "ok" + } + ], + "validation_results": "build/cheap-deadline-028-validation" + }, + "limits": { + "rss_bytes": 1073741824, + "sample_interval_seconds": 0.05, + "seconds": 2.0, + "threads": 1 + }, + "machine": { + "machine": "arm64", + "processor": "arm", + "system": "macOS-26.6.2-arm64-arm-64bit-Mach-O" + }, + "schema_version": 1, + "unfinished": 0 +} diff --git a/examples/data/tsptw/cp-baseline-tuning.json b/examples/data/tsptw/cp-baseline-tuning.json new file mode 100644 index 0000000000..846e4cea90 --- /dev/null +++ b/examples/data/tsptw/cp-baseline-tuning.json @@ -0,0 +1,1335 @@ +{ + "budget": { + "actual_process_wall_seconds": { + "campaign": 792.2291470418568, + "capture_interruption": 0.1409827919851523, + "closed_only_rejections": 0.03478491507121362, + "existing_tsptw_correctness": 92.93035158302519, + "final_binary_tiny_smoke": 3.0591026659240015, + "native_restart_portfolio_checks": 1.300179832993308, + "rbs_resource_stop": 0.008123625011648983, + "tiny_resume_smokes": 2.5792432069720235 + }, + "cache_blocked_native_process_wall_seconds": 0, + "cache_blocked_uv_launches": 2, + "conservative_total_including_gecode_compilation_seconds": 896.420504203852, + "gecode_compilation_seconds": 4.138588541012723, + "interrupted_campaign_attempts": 0, + "nominal_campaign_solver_seconds": 1800, + "note": "All native validation, smoke, repeated tiny samples, one capture interruption and watchdog overshoot are included. The two Release rebuilds are measured separately and also included in the conservative total. Reader-helper compilation was not timed. Evaluation has a separate budget; no held-out outcomes were inspected.", + "remaining_tuning_seconds_including_gecode_compilation": 1803.579495796148, + "tuning_ceiling_seconds": 2700, + "tuning_process_wall_total_seconds": 892.2819156628393 + }, + "campaign": "cp-baseline-tuning", + "candidate_results": { + "closed-bab": { + "actual_process_wall_seconds": 182.8583972500346, + "feasible": 16, + "feasible_by_repetition": [ + 8, + 8 + ], + "missing_rss_samples": 3, + "paired_vs_initial": null, + "proofs": 12, + "proofs_by_repetition": [ + 6, + 6 + ], + "sampled_peak_rss_bytes": 10551296, + "samples": 24, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 24, + "rss-limit": 0, + "timeout": 0 + } + }, + "closed-rbs-regret": { + "actual_process_wall_seconds": 182.72863299792516, + "feasible": 16, + "feasible_by_repetition": [ + 8, + 8 + ], + "missing_rss_samples": 4, + "paired_vs_initial": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 13, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 11 + }, + "final_objective": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 16, + "unavailable": 8, + "win": 0 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 13, + "missing": 0, + "tie": 0, + "unavailable": 8, + "win": 3 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 10, + "missing": 0, + "tie": 0, + "unavailable": 12, + "win": 2 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 16, + "unavailable": 8, + "win": 0 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 14, + "unavailable": 10, + "win": 0 + }, + "quality_at_15000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 16, + "unavailable": 8, + "win": 0 + }, + "quality_at_5000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 16, + "unavailable": 8, + "win": 0 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 19, + "missing": 0, + "tie": 0, + "unavailable": 4, + "win": 1 + } + }, + "proofs": 12, + "proofs_by_repetition": [ + 6, + 6 + ], + "sampled_peak_rss_bytes": 15056896, + "samples": 24, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 22, + "rss-limit": 0, + "timeout": 2 + } + }, + "closed-rbs-size": { + "actual_process_wall_seconds": 121.69382937601767, + "feasible": 20, + "feasible_by_repetition": [ + 10, + 10 + ], + "missing_rss_samples": 4, + "paired_vs_initial": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 7, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 17 + }, + "final_objective": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 12, + "unavailable": 2, + "win": 8 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 7, + "missing": 0, + "tie": 0, + "unavailable": 10, + "win": 7 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 0, + "unavailable": 12, + "win": 10 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 12, + "unavailable": 2, + "win": 8 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 8, + "unavailable": 4, + "win": 10 + }, + "quality_at_15000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 12, + "unavailable": 2, + "win": 8 + }, + "quality_at_5000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 12, + "unavailable": 2, + "win": 8 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 14, + "missing": 0, + "tie": 0, + "unavailable": 4, + "win": 6 + } + }, + "proofs": 18, + "proofs_by_repetition": [ + 9, + 9 + ], + "sampled_peak_rss_bytes": 13746176, + "samples": 24, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 24, + "rss-limit": 0, + "timeout": 0 + } + }, + "closed-size-bab": { + "actual_process_wall_seconds": 121.4773782499542, + "feasible": 20, + "feasible_by_repetition": [ + 10, + 10 + ], + "missing_rss_samples": 4, + "paired_vs_initial": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 7, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 17 + }, + "final_objective": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 12, + "unavailable": 2, + "win": 8 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 8, + "missing": 0, + "tie": 0, + "unavailable": 10, + "win": 6 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 1, + "missing": 0, + "tie": 0, + "unavailable": 12, + "win": 11 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 12, + "unavailable": 2, + "win": 8 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 8, + "unavailable": 4, + "win": 12 + }, + "quality_at_15000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 12, + "unavailable": 2, + "win": 8 + }, + "quality_at_5000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 12, + "unavailable": 2, + "win": 8 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 8, + "missing": 0, + "tie": 0, + "unavailable": 4, + "win": 12 + } + }, + "proofs": 18, + "proofs_by_repetition": [ + 9, + 9 + ], + "sampled_peak_rss_bytes": 11681792, + "samples": 24, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 23, + "rss-limit": 0, + "timeout": 1 + } + }, + "closed-val-bab": { + "actual_process_wall_seconds": 183.47090916792513, + "feasible": 14, + "feasible_by_repetition": [ + 7, + 7 + ], + "missing_rss_samples": 3, + "paired_vs_initial": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 13, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 11 + }, + "final_objective": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 14, + "unavailable": 8, + "win": 0 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 7, + "missing": 0, + "tie": 0, + "unavailable": 10, + "win": 7 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 10, + "missing": 0, + "tie": 0, + "unavailable": 12, + "win": 2 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 14, + "unavailable": 8, + "win": 0 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 6, + "missing": 0, + "tie": 8, + "unavailable": 10, + "win": 0 + }, + "quality_at_15000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 14, + "unavailable": 8, + "win": 0 + }, + "quality_at_5000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 14, + "unavailable": 8, + "win": 0 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 9, + "missing": 0, + "tie": 0, + "unavailable": 4, + "win": 11 + } + }, + "proofs": 12, + "proofs_by_repetition": [ + 6, + 6 + ], + "sampled_peak_rss_bytes": 10846208, + "samples": 24, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 24, + "rss-limit": 0, + "timeout": 0 + } + } + }, + "cases": [ + { + "case": "Dumas/n20w20.001.txt", + "family": "Dumas", + "results": { + "closed-bab": { + "final_scaled_cost": [ + 378, + 378 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-rbs-regret": { + "final_scaled_cost": [ + 378, + 378 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-rbs-size": { + "final_scaled_cost": [ + 378, + 378 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-size-bab": { + "final_scaled_cost": [ + 378, + 378 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-val-bab": { + "final_scaled_cost": [ + 378, + 378 + ], + "proof": [ + "optimal", + "optimal" + ] + } + }, + "scale": 1, + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748", + "variant": null + }, + { + "case": "Dumas/n60w100.001.txt", + "family": "Dumas", + "results": { + "closed-bab": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + }, + "closed-rbs-regret": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + }, + "closed-rbs-size": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + }, + "closed-size-bab": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + }, + "closed-val-bab": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + } + }, + "scale": 1, + "sha256": "35d3690fbb7c4eb31775e6d8e32c5a36068498ca69c6097aed3d7eeb2976c6bb", + "variant": null + }, + { + "case": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "family": "Dumas", + "results": { + "closed-bab": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + }, + "closed-rbs-regret": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + }, + "closed-rbs-size": { + "final_scaled_cost": [ + 249, + 249 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-size-bab": { + "final_scaled_cost": [ + 249, + 249 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-val-bab": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + } + }, + "scale": 1, + "sha256": "2aed1af8ef2756d0ecf902a6c0a9f9916893bb2a65ef9a284e3ff85848c1962d", + "variant": "customer-due-plus-100" + }, + { + "case": "GendreauDumasExtended/n20w120.001.txt", + "family": "GendreauDumasExtended", + "results": { + "closed-bab": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + }, + "closed-rbs-regret": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + }, + "closed-rbs-size": { + "final_scaled_cost": [ + 267, + 267 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-size-bab": { + "final_scaled_cost": [ + 267, + 267 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-val-bab": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + } + }, + "scale": 1, + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba", + "variant": null + }, + { + "case": "GendreauDumasExtended/n60w200.001.txt", + "family": "GendreauDumasExtended", + "results": { + "closed-bab": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + }, + "closed-rbs-regret": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + }, + "closed-rbs-size": { + "final_scaled_cost": [ + 527, + 527 + ], + "proof": [ + null, + null + ] + }, + "closed-size-bab": { + "final_scaled_cost": [ + 527, + 527 + ], + "proof": [ + null, + null + ] + }, + "closed-val-bab": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + } + }, + "scale": 1, + "sha256": "d8dd1073870ba85b8bf82085df4056c97234889c57baf03dc459ff0aa61256c5", + "variant": null + }, + { + "case": "variants/GendreauDumasExtended/customer-due-plus-100/n20w200.003.txt", + "family": "GendreauDumasExtended", + "results": { + "closed-bab": { + "final_scaled_cost": [ + 208, + 208 + ], + "proof": [ + null, + null + ] + }, + "closed-rbs-regret": { + "final_scaled_cost": [ + 208, + 208 + ], + "proof": [ + null, + null + ] + }, + "closed-rbs-size": { + "final_scaled_cost": [ + 202, + 202 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-size-bab": { + "final_scaled_cost": [ + 202, + 202 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-val-bab": { + "final_scaled_cost": [ + 208, + 208 + ], + "proof": [ + null, + null + ] + } + }, + "scale": 1, + "sha256": "8c7655127d49bc58a9198198138894dd3fe6180d1036cc6f0adbe2a768b62824", + "variant": "customer-due-plus-100" + }, + { + "case": "SolomonPotvinBengio/rc_201.1.txt", + "family": "SolomonPotvinBengio", + "results": { + "closed-bab": { + "final_scaled_cost": [ + 4445425, + 4445425 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-rbs-regret": { + "final_scaled_cost": [ + 4445425, + 4445425 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-rbs-size": { + "final_scaled_cost": [ + 4445425, + 4445425 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-size-bab": { + "final_scaled_cost": [ + 4445425, + 4445425 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-val-bab": { + "final_scaled_cost": [ + 4445425, + 4445425 + ], + "proof": [ + "optimal", + "optimal" + ] + } + }, + "scale": 10000, + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f", + "variant": null + }, + { + "case": "SolomonPotvinBengio/rc_202.2.txt", + "family": "SolomonPotvinBengio", + "results": { + "closed-bab": { + "final_scaled_cost": [ + 3041418, + 3041418 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-rbs-regret": { + "final_scaled_cost": [ + 3041418, + 3041418 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-rbs-size": { + "final_scaled_cost": [ + 3041418, + 3041418 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-size-bab": { + "final_scaled_cost": [ + 3041418, + 3041418 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-val-bab": { + "final_scaled_cost": [ + 3041418, + 3041418 + ], + "proof": [ + "optimal", + "optimal" + ] + } + }, + "scale": 10000, + "sha256": "28f5bbe945a1d11efd7a0be418a6bc647a565a6a3189d702ed085a7e3309567d", + "variant": null + }, + { + "case": "variants/SolomonPotvinBengio/customer-due-plus-100/rc_205.1.txt", + "family": "SolomonPotvinBengio", + "results": { + "closed-bab": { + "final_scaled_cost": [ + 29109260, + 29109260 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-rbs-regret": { + "final_scaled_cost": [ + 29109260, + 29109260 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-rbs-size": { + "final_scaled_cost": [ + 29109260, + 29109260 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-size-bab": { + "final_scaled_cost": [ + 29109260, + 29109260 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-val-bab": { + "final_scaled_cost": [ + 29109260, + 29109260 + ], + "proof": [ + "optimal", + "optimal" + ] + } + }, + "scale": 100000, + "sha256": "a9d2e771ada12cce6d1b43ee722596479ba3fa0bbb7d173ff662145587b7641f", + "variant": "customer-due-plus-100" + }, + { + "case": "AFG/rbg010a.tw", + "family": "AFG", + "results": { + "closed-bab": { + "final_scaled_cost": [ + 671, + 671 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-rbs-regret": { + "final_scaled_cost": [ + 671, + 671 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-rbs-size": { + "final_scaled_cost": [ + 671, + 671 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-size-bab": { + "final_scaled_cost": [ + 671, + 671 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-val-bab": { + "final_scaled_cost": [ + 671, + 671 + ], + "proof": [ + "optimal", + "optimal" + ] + } + }, + "scale": 1, + "sha256": "4a8e0c37052047063b19d698487bae0ed5256bb22de4f675fca3868975f40ef3", + "variant": null + }, + { + "case": "AFG/rbg038a.tw", + "family": "AFG", + "results": { + "closed-bab": { + "final_scaled_cost": [ + 2493, + 2493 + ], + "proof": [ + null, + null + ] + }, + "closed-rbs-regret": { + "final_scaled_cost": [ + 2493, + 2493 + ], + "proof": [ + null, + null + ] + }, + "closed-rbs-size": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + }, + "closed-size-bab": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + }, + "closed-val-bab": { + "final_scaled_cost": [ + null, + null + ], + "proof": [ + null, + null + ] + } + }, + "scale": 1, + "sha256": "1279df2651b9937f944e25a6c33182dd93f18ac849ebca663b7e069abceb46af", + "variant": null + }, + { + "case": "variants/AFG/customer-due-plus-100/rbg016a.tw", + "family": "AFG", + "results": { + "closed-bab": { + "final_scaled_cost": [ + 926, + 926 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-rbs-regret": { + "final_scaled_cost": [ + 926, + 926 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-rbs-size": { + "final_scaled_cost": [ + 926, + 926 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-size-bab": { + "final_scaled_cost": [ + 926, + 926 + ], + "proof": [ + "optimal", + "optimal" + ] + }, + "closed-val-bab": { + "final_scaled_cost": [ + 926, + 926 + ], + "proof": [ + "optimal", + "optimal" + ] + } + }, + "scale": 1, + "sha256": "eace798b57d686dd5d0406178060daa9aeb1777b7eab0e87de33bec696cb5384", + "variant": "customer-due-plus-100" + } + ], + "collection": { + "analysis_sha256": "b93bcce6765d2daa0d6088f3cfe81399f0c8bdb371eac8dde20cf08570564a65", + "checker_sha256": "91bff4e5c1630f187eb816dbc2094507e362b6e349197e6680c2cbcee2338778", + "config_sha256": "b0a52c73c4dec73401e72ba26b00b7ccb08c428488f38d70ce59e96f9ad19887", + "directory": "build/tsptw-results/cp-baseline-tuning", + "freeze": "Runner, native source/binary, libraries, input files and collection configuration were unchanged after collection began. Final edits only affect README.md and this report.", + "machine": { + "machine": "arm64", + "processor": "arm", + "system": "macOS-26.6.2-arm64-arm-64bit-Mach-O" + }, + "native": { + "libraries": { + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodedriver.6.4.0.dylib": "d4332d9873d657cf1d58e825842dea0026a51a302894210745f5c5260077aaa4", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodeflatzinc.6.4.0.dylib": "dfbaa6f5c97951771ee2ea90f5d85dff9b6fb6ef4617404142894b2b8e45232f", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodefloat.6.4.0.dylib": "68b1aec0512dded3b80599cfbee34d8f301f50d60560ba5e8b2c06752efb20e7", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodeint.6.4.0.dylib": "635412b4c0cde700e0ec4dffa12b59b44e70b9f4252657ea42b4106ad078ccff", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodekernel.6.4.0.dylib": "2a7aced55c554a1449b007c127afb1e869ee7f58cdc22c634065b074f2597301", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodeminimodel.6.4.0.dylib": "48e26109dbbb45f8fef29262f3c3299d0486d2737de85f43fc65c66511c91f23", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodemodel.6.4.0.dylib": "b6be32f2f100c1b7fae00a361d48dfef82eb94eae5fd1eb643bc3d6ed057e0fa", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodesearch.6.4.0.dylib": "6c87b891455bad523659806c4920ff66f990fa960136c0fcb6e731d8c27bb01c", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodeset.6.4.0.dylib": "f07b55be6931f62c1c9821f1578bec89bb08568e6a4bf03c1f6981f0c9b7ae8e", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodesupport.6.4.0.dylib": "6cfefbf1ceb061ee0eb42917e8e5eb3203019e133dbfb50a78ee2d4cdefcc2a9" + }, + "path": "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "sha256": "7359159c0e6525dcc2e0865abc7aceecfee5059c797b7918cebcf6e137e23862" + }, + "runner_sha256": "c6faae014f2c1ef5145faa3b0157ca9993b905babd529bdcee11b03a004091c1" + }, + "commands": { + "analyze": "uv run --script misc/tsptw-benchmark.py analyze --name cp-baseline-tuning", + "baseline": "build/open-search/bin/tsptw INSTANCE --formulation closed --engine bab --circuit-propagation dom --cost-branching size --time-ms 15000", + "evaluated_restart_regret": "build/open-search/bin/tsptw INSTANCE --formulation closed --engine rbs --circuit-propagation dom --cost-branching regret --time-ms 15000", + "evaluated_restart_size": "build/open-search/bin/tsptw INSTANCE --formulation closed --engine rbs --circuit-propagation dom --cost-branching size --time-ms 15000", + "preview": "uv run --script misc/tsptw-benchmark.py preview --split development --configs closed-bab,closed-val-bab,closed-size-bab,closed-rbs-regret,closed-rbs-size --selected-root /tmp/open-search-tsptw-selected-network --solver build/open-search/bin/tsptw --repetitions 2 --seconds 15 --compilation-seconds 4.1385885410127235", + "run_and_resume": "uv run --script misc/tsptw-benchmark.py run --name cp-baseline-tuning --split development --configs closed-bab,closed-val-bab,closed-size-bab,closed-rbs-regret,closed-rbs-size --selected-root /tmp/open-search-tsptw-selected-network --solver build/open-search/bin/tsptw --repetitions 2 --seconds 15 --compilation-seconds 4.1385885410127235" + }, + "frozen_baseline": { + "candidate": "closed-size-bab", + "native_args": [ + "--formulation", + "closed", + "--engine", + "bab", + "--circuit-propagation", + "dom", + "--cost-branching", + "size" + ], + "reason": "DOM/size BAB improves final quality in eight pairs and loses in two; it proves eighteen samples instead of twelve. Both repetitions give the same final quality and proof decisions. AFG/rbg038a remains a regret-branching advantage." + }, + "measured_candidates": { + "closed-bab": [ + "--formulation", + "closed", + "--engine", + "bab" + ], + "closed-rbs-regret": [ + "--formulation", + "closed", + "--engine", + "rbs" + ], + "closed-rbs-size": [ + "--formulation", + "closed", + "--engine", + "rbs", + "--cost-branching", + "size" + ], + "closed-size-bab": [ + "--formulation", + "closed", + "--engine", + "bab", + "--cost-branching", + "size" + ], + "closed-val-bab": [ + "--formulation", + "closed", + "--engine", + "bab", + "--circuit-propagation", + "val" + ] + }, + "measurements": "Pair costs only within a case and its integer scale. Native reported milliseconds start at main entry and include preprocessing; end-to-end wall seconds cover launch through reap. Checked timeout/RSS incumbents count as feasible quality, never proof. Lower outer wall time can reflect earlier resource termination. Timing counts and medians across distinct cases do not establish a universal speedup.", + "observed_tradeoffs": [ + "VAL/regret BAB loses the AFG/rbg038a tour in both repetitions and adds no quality or proof wins against DOM/regret.", + "DOM/size BAB finds no tour on AFG/rbg038a; DOM/regret BAB and regret RBS retain checked cost 2493 without proof.", + "No candidate finds a tour on Dumas/n60w100 within the allowance.", + "Size BAB and size RBS prove 249 on the Dumas window variant and 267 on canonical Gendreau n20, find cost 527 on Gendreau n60 without proof, and prove 202 rather than the regret candidates final 208 on the Gendreau window variant." + ], + "question": "Select the conventional closed CP baseline on frozen development cases before held-out evaluation.", + "recommended_anytime_recipe": null, + "resources": { + "maximum_observed_sample_gap_seconds": 0.4652284579933621, + "maximum_sample_wall_seconds": 15.10077416599961, + "nominal_sample_interval_seconds": 0.05, + "rss_policy": "Sampled owned-process-group RSS, not a hard limit. Three outer timeouts; zero RSS terminations. Eighteen short samples have no live RSS observation.", + "rss_watchdog_bytes": 1073741824, + "seconds_per_sample": 15, + "threads": 1 + }, + "restart_decision": "Size RBS matches size BAB final quality and proof counts; proof time is faster in six and slower in twelve paired proofs, with higher sampled RSS in eighteen comparable pairs. Keep it available explicitly, but do not recommend a restart recipe from this campaign.", + "restart_size_vs_size_bab": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 11, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 13 + }, + "final_objective": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 20, + "unavailable": 4, + "win": 0 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 10, + "missing": 0, + "tie": 0, + "unavailable": 4, + "win": 10 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 12, + "missing": 0, + "tie": 0, + "unavailable": 6, + "win": 6 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 18, + "missing": 0, + "tie": 1, + "unavailable": 4, + "win": 1 + } + }, + "schema_version": 1, + "unavailable": [ + "Frontier bytes and clone footprint are not instrumented.", + "Closed-model phase timings do not isolate propagation/clone costs.", + "Short process completion can precede the first RSS observation.", + "RSS is sampled and can miss peaks; the watchdog can overshoot.", + "System/transitive-library and Python/uv environment contents are kept fixed by the caller, not exhaustively hashed." + ], + "validation": { + "all_saved_records_valid": true, + "checked_campaign_incumbents": 806, + "checks": [ + "cmake --build build/open-search --target tsptw gecode-test --parallel 4", + "python3 misc/test-tsptw-benchmark.py", + "build/open-search/bin/gecode-test -iter 1 -threads 1 -test Search::RBS -test RBS::BAB -test Search::Portfolio::PublicContract", + "python3 misc/tsptw-validate.py --reader /tmp/open-search-013-tsptw-read --solver build/open-search/bin/tsptw", + "All five candidates match exhaustive tiny optima/infeasibility; every tiny incumbent independently checked.", + "Partial/selective/no-op resume, unchanged records/streams, configuration mismatch, closed-only option rejection, fixed restart-summary settings, interrupted capture kill/reap and resource-stop proof rejection." + ], + "completed_resume_launched_samples": 0, + "offline_analysis_identical_on_replay": true, + "replay": "Read and independently check every saved stdout; compare checked trace/summary with each record; regenerate analysis twice with subprocess launch disabled; repeat the completed run command and compare sample-file hashes and mtimes.", + "unchanged_sample_files": 360 + }, + "workload": { + "canonical_cases": 8, + "repetitions": 2, + "samples": 120, + "split": "development", + "window_variants": 4 + } +} diff --git a/examples/data/tsptw/heldout-comparison.json b/examples/data/tsptw/heldout-comparison.json new file mode 100644 index 0000000000..7c9c970f92 --- /dev/null +++ b/examples/data/tsptw/heldout-comparison.json @@ -0,0 +1,10068 @@ +{ + "campaign": "heldout-comparison-016", + "comparisons": { + "cases": [ + { + "case": { + "family": "Dumas", + "id": "Dumas/n40w20.002.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/Dumas/n40w20.002.txt", + "sha256": "460887b23bae03441567010f3b1524b68121ca74a739d66ef705d0852879e8c5", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 0.21212754197767936, + "final_objective": 552, + "first_incumbent_reported_ms": 10.638, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.013277707999804989, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 1375, + "frontier_bytes": null, + "preprocessing_ms": 2.208, + "search_ms": 61.348, + "startup_and_teardown_ms": 148.55854197767934 + }, + "proof": "optimal", + "proof_reported_ms": 63.569, + "quality_at_reported_ms": { + "100": 552, + "1000": 552, + "15000": 552, + "30000": 552, + "5000": 552 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.13753879099385813, + "rss_samples": 1, + "sampled_peak_rss_bytes": 196608, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 3.249995643272996e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 0.1536432910070289, + "final_objective": 552, + "first_incumbent_reported_ms": 24.555, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.006651207979302853, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 3288, + "frontier_bytes": null, + "preprocessing_ms": 0.584, + "search_ms": 105.674, + "startup_and_teardown_ms": 47.36629100702889 + }, + "proof": "optimal", + "proof_reported_ms": 106.277, + "quality_at_reported_ms": { + "100": 552, + "1000": 552, + "15000": 552, + "30000": 552, + "5000": 552 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.0889737909892574, + "rss_samples": 1, + "sampled_peak_rss_bytes": 3899392, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.5420082490891218e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 17.08556816697819, + "final_objective": 552, + "first_incumbent_reported_ms": 10.15, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.4091940830112435, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 3746410, + "inference_ms": 13945.982, + "propagation_ms": 11135.48 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 41, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 2489158, + "frontier_bytes": null, + "preprocessing_ms": 0.505, + "search_ms": 17041.151, + "startup_and_teardown_ms": 43.870166978191264 + }, + "proof": "optimal", + "proof_reported_ms": 17041.698, + "quality_at_reported_ms": { + "100": 581, + "1000": 567, + "15000": 552, + "30000": 552, + "5000": 554 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 8.739446088846307, + "rss_samples": 146, + "sampled_peak_rss_bytes": 3588096, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.6249832697212696e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 4.909619666985236, + "final_objective": 552, + "first_incumbent_reported_ms": 4679.828, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.19664670899510384, + "measurements": { + "bound_calls": 308391, + "bound_ms": 7.981, + "inference_calls": 2505472, + "inference_ms": 2599.571, + "propagation_ms": 1480.942 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 41, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 2505472, + "frontier_bytes": null, + "preprocessing_ms": 0.434, + "search_ms": 4739.514, + "startup_and_teardown_ms": 169.64866698523656 + }, + "proof": "optimal", + "proof_reported_ms": 4739.971, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": 552, + "30000": 552, + "5000": 552 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 2.507646125071915, + "rss_samples": 42, + "sampled_peak_rss_bytes": 132513792, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 2.9999937396496534e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 13.991056625003694, + "final_objective": 552, + "first_incumbent_reported_ms": 2.76, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.2265343340113759, + "measurements": { + "bound_calls": 866609, + "bound_ms": 22.914, + "inference_calls": 7662964, + "inference_ms": 8548.001, + "propagation_ms": 5382.11 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 41, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 7662979, + "frontier_bytes": null, + "preprocessing_ms": 0.696, + "search_ms": 13970.923, + "startup_and_teardown_ms": 19.40962500369278 + }, + "proof": "optimal", + "proof_reported_ms": 13971.647, + "quality_at_reported_ms": { + "100": 552, + "1000": 552, + "15000": 552, + "30000": 552, + "5000": 552 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 6.524382784788031, + "rss_samples": 131, + "sampled_peak_rss_bytes": 145113088, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.374981366097927e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 4.132333167013712, + "final_objective": 552, + "first_incumbent_reported_ms": 2.454, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.16942129097878933, + "measurements": { + "bound_calls": 288146, + "bound_ms": 7.365, + "inference_calls": 2488469, + "inference_ms": 2401.263, + "propagation_ms": 1403.465 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 41, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 2488469, + "frontier_bytes": null, + "preprocessing_ms": 0.398, + "search_ms": 4100.857, + "startup_and_teardown_ms": 31.04216701371206 + }, + "proof": "optimal", + "proof_reported_ms": 4101.291, + "quality_at_reported_ms": { + "100": 552, + "1000": 552, + "15000": 552, + "30000": 552, + "5000": 552 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 1.870979835948674, + "rss_samples": 39, + "sampled_peak_rss_bytes": 46907392, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.207983586937189e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 4.58798970899079, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.2593214590160642, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 2752512, + "rss_probe_seconds": 2.628024874953553, + "rss_samples": 35, + "sampled_peak_rss_bytes": 1076494336, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0012370000185910612, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.016479417012306, + "final_objective": 552, + "first_incumbent_reported_ms": 127.71604198496789, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.2167507919948548, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": 552, + "15000": 552, + "30000": 552, + "5000": 552 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.428711208893219, + "rss_samples": 289, + "sampled_peak_rss_bytes": 212385792, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0011397070193197578, + "wall_cap_overshoot_seconds": 0.01647941701230593 + } + ], + "reference_kind": "matching campaign optimality proof", + "reference_scaled_cost": 552 + }, + { + "case": { + "family": "Dumas", + "id": "Dumas/n80w40.002.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/Dumas/n80w40.002.txt", + "sha256": "b5d6dc6c794fd2daa6e362604ac31f2562f4d829af3110226fbc4496ab8f471a", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.01079162498354, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.2512936250132043, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.328153033187846, + "rss_samples": 289, + "sampled_peak_rss_bytes": 14123008, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0007063750235829502, + "wall_cap_overshoot_seconds": 0.010791624983539805 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 30.012842416996136, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.26908075000392273, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.433092626044527, + "rss_samples": 290, + "sampled_peak_rss_bytes": 953696256, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.012256833986612037, + "wall_cap_overshoot_seconds": 0.012842416996136308 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.032128791994182, + "final_objective": 663, + "first_incumbent_reported_ms": 263.386, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.2358443750126753, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 12200675, + "inference_ms": 21303.877, + "propagation_ms": 15797.975 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 81, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 7565492, + "frontier_bytes": null, + "preprocessing_ms": 2.619, + "search_ms": 29997.386, + "startup_and_teardown_ms": 30.99779199418117 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": 667, + "15000": 663, + "30000": 663, + "5000": 664 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.417107246728847, + "rss_samples": 290, + "sampled_peak_rss_bytes": 4587520, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 7.500057108700275e-07, + "wall_cap_overshoot_seconds": 0.03212879199418239 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 30.012406541995006, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.31613454199396074, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 14.234879290830577, + "rss_samples": 276, + "sampled_peak_rss_bytes": 370458624, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.007681041985051706, + "wall_cap_overshoot_seconds": 0.012406541995005682 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.008092208008748, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.25218333399971016, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 15.386563501815544, + "rss_samples": 255, + "sampled_peak_rss_bytes": 29442048, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0015953749825712293, + "wall_cap_overshoot_seconds": 0.008092208008747548 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 22.331666249985574, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.21663783298572525, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 10649600, + "rss_probe_seconds": 10.339226085314294, + "rss_samples": 211, + "sampled_peak_rss_bytes": 1084391424, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.01996299999882467, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 2.876234333991306, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11004179102019407, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 6946816, + "rss_probe_seconds": 1.3312461668683682, + "rss_samples": 28, + "sampled_peak_rss_bytes": 1080688640, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0010559159854892641, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.01823395799147, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.24839579200488515, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 14.256623183580814, + "rss_samples": 275, + "sampled_peak_rss_bytes": 157433856, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0014699999883305281, + "wall_cap_overshoot_seconds": 0.01823395799146965 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "Dumas", + "id": "Dumas/n100w60.002.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/Dumas/n100w60.002.txt", + "sha256": "0b00a2eac6bf37c0bc94120e072cea9bf4a7891e4b2edf17a6497d0ea3c74b8b", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.026880167017225, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.19111662500654347, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 2859491, + "frontier_bytes": null, + "preprocessing_ms": 2.933, + "search_ms": 29997.075, + "startup_and_teardown_ms": 26.81616701722669 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.558627876220271, + "rss_samples": 280, + "sampled_peak_rss_bytes": 21921792, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 7.500057108700275e-07, + "wall_cap_overshoot_seconds": 0.026880167017225176 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 30.03152666700771, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1973022920137737, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 150832, + "frontier_bytes": null, + "preprocessing_ms": 3.183, + "search_ms": 29996.905, + "startup_and_teardown_ms": 31.406667007711803 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.804590715270024, + "rss_samples": 276, + "sampled_peak_rss_bytes": 403456000, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 5.830079317092896e-07, + "wall_cap_overshoot_seconds": 0.031526667007710785 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.045212541008368, + "final_objective": 755, + "first_incumbent_reported_ms": 16114.586, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.17859325002063997, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 5272369, + "inference_ms": 25648.9, + "propagation_ms": 21069.379 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 101, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 3219233, + "frontier_bytes": null, + "preprocessing_ms": 1.919, + "search_ms": 29998.088, + "startup_and_teardown_ms": 45.17454100836767 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": 755, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.6825085800956, + "rss_samples": 280, + "sampled_peak_rss_bytes": 5095424, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.33995558321476e-07, + "wall_cap_overshoot_seconds": 0.045212541008368134 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 30.01517170801526, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.2210825409856625, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 14.051686287100893, + "rss_samples": 271, + "sampled_peak_rss_bytes": 273121280, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.005009000014979392, + "wall_cap_overshoot_seconds": 0.015171708015259355 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.003858957992634, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.2082130830094684, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.335591177019523, + "rss_samples": 283, + "sampled_peak_rss_bytes": 27656192, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0013267919712234288, + "wall_cap_overshoot_seconds": 0.0038589579926338047 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 16.268165207991842, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.14573570899665356, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 13991936, + "rss_probe_seconds": 7.288460207812022, + "rss_samples": 153, + "sampled_peak_rss_bytes": 1087733760, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.016463084000861272, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 2.6952302919817157, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1201617909828201, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 7372800, + "rss_probe_seconds": 1.2014086639974266, + "rss_samples": 26, + "sampled_peak_rss_bytes": 1081114624, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0011203750036656857, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.01126008402207, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1779007499862928, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.197580498206662, + "rss_samples": 286, + "sampled_peak_rss_bytes": 105431040, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0011534159712027758, + "wall_cap_overshoot_seconds": 0.01126008402206935 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "Dumas", + "id": "Dumas/n150w60.002.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/Dumas/n150w60.002.txt", + "sha256": "6c9f4cb2f5a5fab2688ce7e4e745b2f275ac497d390219da4a32bc5a4145f37a", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.036111249995884, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.19129920899285935, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 163216, + "frontier_bytes": null, + "preprocessing_ms": 7.867, + "search_ms": 29992.251, + "startup_and_teardown_ms": 35.9612499958821 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.31326742420788, + "rss_samples": 282, + "sampled_peak_rss_bytes": 50479104, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.329769242554903e-07, + "wall_cap_overshoot_seconds": 0.036111249995883554 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 30.009797415987123, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.20241879098466597, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.491991879011039, + "rss_samples": 281, + "sampled_peak_rss_bytes": 243646464, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.004240125010255724, + "wall_cap_overshoot_seconds": 0.009797415987122804 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.040620707994094, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.18907020799815655, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 414533, + "inference_ms": 28793.246, + "propagation_ms": 24876.318 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 251650, + "frontier_bytes": null, + "preprocessing_ms": 5.187, + "search_ms": 29994.875, + "startup_and_teardown_ms": 40.52370799409255 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.322315449040616, + "rss_samples": 284, + "sampled_peak_rss_bytes": 5128192, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 6.250047590583563e-07, + "wall_cap_overshoot_seconds": 0.04062070799409412 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 30.027027667005314, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.21294666698668152, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.211673417972634, + "rss_samples": 285, + "sampled_peak_rss_bytes": 381501440, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.02396279198001139, + "wall_cap_overshoot_seconds": 0.02702766700531356 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.07414833299117, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.18042070799856447, + "measurements": { + "bound_calls": 160054, + "bound_ms": 4.555, + "inference_calls": 4967076, + "inference_ms": 26072.184, + "propagation_ms": 14164.318 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 4967086, + "frontier_bytes": null, + "preprocessing_ms": 5.167, + "search_ms": 29995.373, + "startup_and_teardown_ms": 73.55833299117148 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.248934875125997, + "rss_samples": 287, + "sampled_peak_rss_bytes": 13795328, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.7500285543501377e-07, + "wall_cap_overshoot_seconds": 0.07414833299117163 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 30.03231508299359, + "final_objective": 834, + "first_incumbent_reported_ms": 21805.019, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.19352712499676272, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": 834, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.222810874402057, + "rss_samples": 286, + "sampled_peak_rss_bytes": 908509184, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.02209499999298714, + "wall_cap_overshoot_seconds": 0.032315082993591204 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 3.0918997920234688, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1104803339985665, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 13123584, + "rss_probe_seconds": 1.3747366679599509, + "rss_samples": 30, + "sampled_peak_rss_bytes": 1086865408, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0010466669918969274, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.01672841599793, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1988467920164112, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.29219454084523, + "rss_samples": 286, + "sampled_peak_rss_bytes": 93831168, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0010593750048428774, + "wall_cap_overshoot_seconds": 0.016728415997931734 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "Dumas", + "id": "Dumas/n200w40.002.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/Dumas/n200w40.002.txt", + "sha256": "caef8dbbcdec934ed7fd2a49586633902aa40e445fde527f99de7a527ea90970", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.026023540995084, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.18754304200410843, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 36290, + "frontier_bytes": null, + "preprocessing_ms": 15.191, + "search_ms": 29985.972, + "startup_and_teardown_ms": 24.803540995082585 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.485392869653879, + "rss_samples": 281, + "sampled_peak_rss_bytes": 55672832, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 1.1250085663050413e-06, + "wall_cap_overshoot_seconds": 0.02602354099508375 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 30.011351207998814, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.20594808302121237, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.742289577843621, + "rss_samples": 277, + "sampled_peak_rss_bytes": 315211776, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.006315624981652945, + "wall_cap_overshoot_seconds": 0.011351207998814061 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.000848374998895, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1584972089913208, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.47041133811581, + "rss_samples": 281, + "sampled_peak_rss_bytes": 6864896, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0007492910081055015, + "wall_cap_overshoot_seconds": 0.0008483749988954514 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 30.007931500003906, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.36664374999236315, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.916355669178301, + "rss_samples": 275, + "sampled_peak_rss_bytes": 336789504, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.005666248966008425, + "wall_cap_overshoot_seconds": 0.007931500003905967 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.040619584004162, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.2856435839785263, + "measurements": { + "bound_calls": 75264, + "bound_ms": 2.356, + "inference_calls": 4364522, + "inference_ms": 25729.811, + "propagation_ms": 12542.539 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 4364531, + "frontier_bytes": null, + "preprocessing_ms": 10.631, + "search_ms": 29989.372, + "startup_and_teardown_ms": 40.571584004163014 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 14.10968837243854, + "rss_samples": 273, + "sampled_peak_rss_bytes": 13729792, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 5.420006345957518e-07, + "wall_cap_overshoot_seconds": 0.04061958400416188 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 30.00588087498909, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.19535887500387616, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.189507658971706, + "rss_samples": 285, + "sampled_peak_rss_bytes": 276168704, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.005733208992751315, + "wall_cap_overshoot_seconds": 0.005880874989088625 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 3.425299750000704, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.18257712500053458, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 13680640, + "rss_probe_seconds": 1.5595731239882298, + "rss_samples": 32, + "sampled_peak_rss_bytes": 1087422464, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0016882499912753701, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.001535666990094, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.18925004100310616, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.051408672181424, + "rss_samples": 287, + "sampled_peak_rss_bytes": 75546624, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0010878749890252948, + "wall_cap_overshoot_seconds": 0.0015356669900938869 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "Dumas", + "id": "variants/Dumas/customer-width-half/n40w100.004.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/variants/Dumas/customer-width-half/n40w100.004.txt", + "sha256": "4b2621859ded3a5eaa885ece60b028459d36f7645945a8854c2d33fe8da9b99b", + "variant": "customer-width-half" + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 0.10768966699833982, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0026066669961437583, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.311, + "search_ms": 0.006, + "startup_and_teardown_ms": 107.36066699833982 + }, + "proof": "infeasible", + "proof_reported_ms": 0.329, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.04445899999700487, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.7090060282498598e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 0.10219795900047757, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0022845420171506703, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.3, + "search_ms": 0.006, + "startup_and_teardown_ms": 101.87895900047756 + }, + "proof": "infeasible", + "proof_reported_ms": 0.319, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.042234916996676475, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.2499804142862558e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 0.10446225001942366, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0023504580021835864, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.021, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.306, + "search_ms": 0.005, + "startup_and_teardown_ms": 104.13225001942367 + }, + "proof": "infeasible", + "proof_reported_ms": 0.33, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.04412062501069158, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.5830155462026596e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 0.106221915979404, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.002400624973233789, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.029, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.335, + "search_ms": 0.004, + "startup_and_teardown_ms": 105.86291597940401 + }, + "proof": "infeasible", + "proof_reported_ms": 0.359, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.04320604100939818, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 9.170034900307655e-07, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 0.10369808299583383, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0023722499900031835, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.021, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.31, + "search_ms": 0.005, + "startup_and_teardown_ms": 103.36708299583383 + }, + "proof": "infeasible", + "proof_reported_ms": 0.331, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.04263020897633396, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.4580145943909883e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 0.10676374999457039, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.0023608749906998128, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1, + "inference_ms": 0.022, + "propagation_ms": 0.0 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 0, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 0, + "frontier_bytes": null, + "preprocessing_ms": 0.311, + "search_ms": 0.007, + "startup_and_teardown_ms": 106.42774999457039 + }, + "proof": "infeasible", + "proof_reported_ms": 0.336, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.04377537497202866, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.2080126907676458e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 3.6965426249953452, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10995470799389295, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 983040, + "rss_probe_seconds": 1.6585042910301127, + "rss_samples": 36, + "sampled_peak_rss_bytes": 1074724864, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0013840010215062648, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.003286874998594, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.18480424999142997, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.229996045178268, + "rss_samples": 284, + "sampled_peak_rss_bytes": 333152256, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0010907500400207937, + "wall_cap_overshoot_seconds": 0.003286874998593703 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "GendreauDumasExtended", + "id": "GendreauDumasExtended/n40w120.002.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/GendreauDumasExtended/n40w120.002.txt", + "sha256": "58657c1af619f05b0f5b0cc7d8a7acfb07d80f202b10a5b0ff49c6e6fd54494d", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.010464999999385, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.2095235830056481, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 5701643, + "frontier_bytes": null, + "preprocessing_ms": 0.646, + "search_ms": 29999.36, + "startup_and_teardown_ms": 10.416999999386462 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.18808120599715, + "rss_samples": 287, + "sampled_peak_rss_bytes": 5783552, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 2.9199873097240925e-07, + "wall_cap_overshoot_seconds": 0.010464999999385327 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 22.43913416599389, + "final_objective": 453, + "first_incumbent_reported_ms": 3281.012, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.2152101660030894, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": 453, + "30000": 453, + "5000": 474 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 70402048, + "rss_probe_seconds": 9.92195624433225, + "rss_samples": 214, + "sampled_peak_rss_bytes": 1144143872, + "scale": 1, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.018255125993164256, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.00108295798418, + "final_objective": 524, + "first_incumbent_reported_ms": 7.575, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.34895120799774304, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 544, + "1000": 536, + "15000": 528, + "30000": 524, + "5000": 529 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 14.087813618971268, + "rss_samples": 273, + "sampled_peak_rss_bytes": 3653632, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0005632909887935966, + "wall_cap_overshoot_seconds": 0.001082957984181121 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 12.895656584005337, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.17035754199605435, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 1114112, + "rss_probe_seconds": 5.809693161980249, + "rss_samples": 121, + "sampled_peak_rss_bytes": 1074855936, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.020748167007695884, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.012022957991576, + "final_objective": 450, + "first_incumbent_reported_ms": 11.67, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.18672220900771208, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 500, + "1000": 450, + "15000": 450, + "30000": 450, + "5000": 450 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.314045577106299, + "rss_samples": 283, + "sampled_peak_rss_bytes": 79249408, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0019035409786738455, + "wall_cap_overshoot_seconds": 0.012022957991575822 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 7.431797124998411, + "final_objective": 450, + "first_incumbent_reported_ms": 10.845, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.20060716700390913, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 488, + "1000": 450, + "15000": 450, + "30000": 450, + "5000": 450 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 5537792, + "rss_probe_seconds": 3.294437674077926, + "rss_samples": 71, + "sampled_peak_rss_bytes": 1079279616, + "scale": 1, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.01980724901659414, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 2.9579934159992263, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.16274425000301562, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 25411584, + "rss_probe_seconds": 1.3492425430158619, + "rss_samples": 28, + "sampled_peak_rss_bytes": 1099153408, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0010328750067856163, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.01119566700072, + "final_objective": 445, + "first_incumbent_reported_ms": 53.93075000029057, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.22831058301380835, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 456, + "1000": 456, + "15000": 445, + "30000": 445, + "5000": 450 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.367811409989372, + "rss_samples": 283, + "sampled_peak_rss_bytes": 218578944, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0011100829869974405, + "wall_cap_overshoot_seconds": 0.011195667000720277 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "GendreauDumasExtended", + "id": "GendreauDumasExtended/n80w140.002.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/GendreauDumasExtended/n80w140.002.txt", + "sha256": "5164c8948942b184550b9ac0e3fa342eb7a2a322c422cf7487e99aae06a05a4e", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.003927499987185, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1865467500174418, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.07763476180844, + "rss_samples": 289, + "sampled_peak_rss_bytes": 15761408, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0009087919897865504, + "wall_cap_overshoot_seconds": 0.003927499987185001 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 30.007977749977726, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.19832591697922908, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.172115496185143, + "rss_samples": 285, + "sampled_peak_rss_bytes": 387514368, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.006115041993325576, + "wall_cap_overshoot_seconds": 0.00797774997772649 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.014407083013793, + "final_objective": 635, + "first_incumbent_reported_ms": 371.568, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.21115083299810067, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 11826664, + "inference_ms": 21691.087, + "propagation_ms": 16323.85 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 81, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 8048968, + "frontier_bytes": null, + "preprocessing_ms": 1.144, + "search_ms": 29998.868, + "startup_and_teardown_ms": 14.35408301379357 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": 655, + "15000": 642, + "30000": 635, + "5000": 644 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.388507869647583, + "rss_samples": 283, + "sampled_peak_rss_bytes": 4407296, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 1.00000761449337e-06, + "wall_cap_overshoot_seconds": 0.014407083013793454 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 20.31243925000308, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.20584920901455916, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 2801664, + "rss_probe_seconds": 9.319632625993108, + "rss_samples": 187, + "sampled_peak_rss_bytes": 1076543488, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.03194975000224076, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.010299916000804, + "final_objective": 559, + "first_incumbent_reported_ms": 316.828, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.20744562498293817, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": 563, + "15000": 559, + "30000": 559, + "5000": 559 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.512263996031834, + "rss_samples": 279, + "sampled_peak_rss_bytes": 20774912, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0010124169930350035, + "wall_cap_overshoot_seconds": 0.010299916000803933 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 13.256164874997921, + "final_objective": 556, + "first_incumbent_reported_ms": 125.313, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.17122283298522234, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": 563, + "15000": 556, + "30000": 556, + "5000": 562 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 4096000, + "rss_probe_seconds": 6.088980708940653, + "rss_samples": 122, + "sampled_peak_rss_bytes": 1077837824, + "scale": 1, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.04237583398935385, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 4.493698915990535, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.29509929099003784, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 26001408, + "rss_probe_seconds": 2.4249444140295964, + "rss_samples": 36, + "sampled_peak_rss_bytes": 1099743232, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0017028339789249003, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.007309707987588, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.28511008399073035, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.645775247801794, + "rss_samples": 276, + "sampled_peak_rss_bytes": 105873408, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.001318291004281491, + "wall_cap_overshoot_seconds": 0.007309707987587899 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "GendreauDumasExtended", + "id": "GendreauDumasExtended/n100w160.002.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/GendreauDumasExtended/n100w160.002.txt", + "sha256": "283b26d40c702183a4aa873a500ae04d695b424d628c34945a61ecdee8d263bf", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.059386415989138, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.3407315830118023, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 2738472, + "frontier_bytes": null, + "preprocessing_ms": 8.062, + "search_ms": 29991.943, + "startup_and_teardown_ms": 58.92441598913865 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 14.521149037464056, + "rss_samples": 264, + "sampled_peak_rss_bytes": 20152320, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 5.829788278788328e-07, + "wall_cap_overshoot_seconds": 0.059386415989138186 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 30.010171582980547, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.22249379201093689, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 14.428244292765157, + "rss_samples": 262, + "sampled_peak_rss_bytes": 307003392, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.006369124981574714, + "wall_cap_overshoot_seconds": 0.0101715829805471 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.010244707984384, + "final_objective": 802, + "first_incumbent_reported_ms": 12349.864, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.2676453339809086, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 7448448, + "inference_ms": 23818.044, + "propagation_ms": 19013.957 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 101, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 4933458, + "frontier_bytes": null, + "preprocessing_ms": 3.285, + "search_ms": 29996.718, + "startup_and_teardown_ms": 9.633707984383364 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": 822, + "30000": 802, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 14.546018035296584, + "rss_samples": 264, + "sampled_peak_rss_bytes": 5177344, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 1.4579854905605316e-06, + "wall_cap_overshoot_seconds": 0.01024470798438415 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 24.24421645802795, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.20101970800897107, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 3670016, + "rss_probe_seconds": 11.009220367937814, + "rss_samples": 222, + "sampled_peak_rss_bytes": 1077411840, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.019987040985142812, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.044691291986965, + "final_objective": 646, + "first_incumbent_reported_ms": 539.771, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1902196660230402, + "measurements": { + "bound_calls": 551796, + "bound_ms": 13.83, + "inference_calls": 2863119, + "inference_ms": 26846.43, + "propagation_ms": 19895.086 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 101, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 2863128, + "frontier_bytes": null, + "preprocessing_ms": 2.043, + "search_ms": 29997.978, + "startup_and_teardown_ms": 44.642291986965574 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": 654, + "15000": 646, + "30000": 646, + "5000": 654 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.50524178679916, + "rss_samples": 281, + "sampled_peak_rss_bytes": 14090240, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.7500285543501377e-07, + "wall_cap_overshoot_seconds": 0.04469129198696464 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 14.278315625007963, + "final_objective": 633, + "first_incumbent_reported_ms": 540.149, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.20759191701654345, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": 669, + "15000": 633, + "30000": 633, + "5000": 665 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 1835008, + "rss_probe_seconds": 6.310296500188997, + "rss_samples": 135, + "sampled_peak_rss_bytes": 1075576832, + "scale": 1, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.01849587500328198, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 3.1672361250093672, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.138443665986415, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 34078720, + "rss_probe_seconds": 1.4236162529559806, + "rss_samples": 30, + "sampled_peak_rss_bytes": 1107820544, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0011598340061027557, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.009844666987192, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.20139962498797104, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.142970831191633, + "rss_samples": 285, + "sampled_peak_rss_bytes": 107331584, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0011298330209683627, + "wall_cap_overshoot_seconds": 0.009844666987191886 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "GendreauDumasExtended", + "id": "GendreauDumasExtended/n60w180.002.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/GendreauDumasExtended/n60w180.002.txt", + "sha256": "9df672deec8baf12725a256a7023303d650d86c599db9539f3179a8b80cc454d", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.002502999996068, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.18231020800885744, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.06287158789928, + "rss_samples": 286, + "sampled_peak_rss_bytes": 8732672, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.000878791994182393, + "wall_cap_overshoot_seconds": 0.002502999996067956 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 30.027572000020882, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.16538045802735724, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.364850719837705, + "rss_samples": 284, + "sampled_peak_rss_bytes": 626606080, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.018099166976753622, + "wall_cap_overshoot_seconds": 0.027572000020882115 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.019422542012762, + "final_objective": 553, + "first_incumbent_reported_ms": 9859.396, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1337515840132255, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 9274005, + "inference_ms": 24218.245, + "propagation_ms": 19026.75 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 61, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 6490964, + "frontier_bytes": null, + "preprocessing_ms": 0.726, + "search_ms": 30000.325, + "startup_and_teardown_ms": 18.341542012763966 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": 558, + "30000": 553, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.9779705357505, + "rss_samples": 290, + "sampled_peak_rss_bytes": 3981312, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.7500285543501377e-07, + "wall_cap_overshoot_seconds": 0.01942254201276228 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 17.796830999985104, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.19528104100027122, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 2113536, + "rss_probe_seconds": 8.211477466975339, + "rss_samples": 162, + "sampled_peak_rss_bytes": 1075855360, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.02548270800616592, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.01654112499091, + "final_objective": 444, + "first_incumbent_reported_ms": 90.747, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.25141445800545625, + "measurements": { + "bound_calls": 899983, + "bound_ms": 22.549, + "inference_calls": 4441000, + "inference_ms": 26669.47, + "propagation_ms": 20570.565 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 61, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 4441011, + "frontier_bytes": null, + "preprocessing_ms": 0.718, + "search_ms": 29999.315, + "startup_and_teardown_ms": 16.475124990909535 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 554, + "1000": 455, + "15000": 450, + "30000": 444, + "5000": 455 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.017912495939527, + "rss_samples": 290, + "sampled_peak_rss_bytes": 30375936, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.7500285543501377e-07, + "wall_cap_overshoot_seconds": 0.01654112499090843 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 11.316027833003318, + "final_objective": 445, + "first_incumbent_reported_ms": 492.358, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11994454098748975, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": 549, + "15000": 445, + "30000": 445, + "5000": 482 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 4964352, + "rss_probe_seconds": 4.790468254941516, + "rss_samples": 111, + "sampled_peak_rss_bytes": 1078706176, + "scale": 1, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.013820332998875529, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 2.700862333003897, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10611333302222192, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 25362432, + "rss_probe_seconds": 1.1745822959637735, + "rss_samples": 27, + "sampled_peak_rss_bytes": 1099104256, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0018886670004576445, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.0270344580058, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1679124590009451, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.77097994403448, + "rss_samples": 294, + "sampled_peak_rss_bytes": 157728768, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0012517069990281016, + "wall_cap_overshoot_seconds": 0.027034458005800843 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "GendreauDumasExtended", + "id": "GendreauDumasExtended/n20w200.002.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/GendreauDumasExtended/n20w200.002.txt", + "sha256": "d835d26fdc88ae53e9a58735ed9572981c477546ee1d5ea37c6d9eaf21b6db46", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 0.8533188749861438, + "final_objective": 203, + "first_incumbent_reported_ms": 0.87, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.108709332998842, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 37478, + "frontier_bytes": null, + "preprocessing_ms": 0.372, + "search_ms": 784.663, + "startup_and_teardown_ms": 68.26487498614381 + }, + "proof": "optimal", + "proof_reported_ms": 785.054, + "quality_at_reported_ms": { + "100": 238, + "1000": 203, + "15000": 203, + "30000": 203, + "5000": 203 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.38120337497093715, + "rss_samples": 8, + "sampled_peak_rss_bytes": 4603904, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.4580145943909883e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 2.6913569169992115, + "final_objective": 203, + "first_incumbent_reported_ms": 1.442, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.10869199997978285, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 125138, + "frontier_bytes": null, + "preprocessing_ms": 0.384, + "search_ms": 2667.206, + "startup_and_teardown_ms": 23.749916999211564 + }, + "proof": "optimal", + "proof_reported_ms": 2667.607, + "quality_at_reported_ms": { + "100": 266, + "1000": 248, + "15000": 203, + "30000": 203, + "5000": 203 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 1.1420175819657743, + "rss_samples": 26, + "sampled_peak_rss_bytes": 106823680, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.1250085663050413e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.037608375016134, + "final_objective": 260, + "first_incumbent_reported_ms": 1.013, + "gap_to_proved_optimum": 0.28078817733990147, + "maximum_sample_gap_seconds": 0.14832695902441628, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 12354523, + "inference_ms": 24457.389, + "propagation_ms": 18841.522 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 21, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 8698946, + "frontier_bytes": null, + "preprocessing_ms": 0.271, + "search_ms": 29999.731, + "startup_and_teardown_ms": 37.57437501613458 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 295, + "1000": 290, + "15000": 264, + "30000": 260, + "5000": 270 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.701216118875891, + "rss_samples": 295, + "sampled_peak_rss_bytes": 3096576, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 4.169996827840805e-07, + "wall_cap_overshoot_seconds": 0.03760837501613423 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 4.6452268330031075, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10690720801358111, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 22560768, + "rss_probe_seconds": 1.9611071650579106, + "rss_samples": 46, + "sampled_peak_rss_bytes": 1096302592, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.014419042010558769, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.04063279100228, + "final_objective": 203, + "first_incumbent_reported_ms": 1.533, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.23062658301205374, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 210, + "1000": 210, + "15000": 203, + "30000": 203, + "5000": 203 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.0638102904486, + "rss_samples": 289, + "sampled_peak_rss_bytes": 406831104, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.007734165992587805, + "wall_cap_overshoot_seconds": 0.04063279100228101 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 3.7796915840008296, + "final_objective": 203, + "first_incumbent_reported_ms": 1.691, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.11114012499456294, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 205, + "1000": 203, + "15000": 203, + "30000": 203, + "5000": 203 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 2736128, + "rss_probe_seconds": 1.6351139991020318, + "rss_samples": 37, + "sampled_peak_rss_bytes": 1076477952, + "scale": 1, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0159233340236824, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 5.262212500005262, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11129025000263937, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 5275648, + "rss_probe_seconds": 2.287508002948016, + "rss_samples": 51, + "sampled_peak_rss_bytes": 1079017472, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0010747910127975047, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.032178000023123, + "final_objective": 203, + "first_incumbent_reported_ms": 18.94545799586922, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.1644862090179231, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 203, + "1000": 203, + "15000": 203, + "30000": 203, + "5000": 203 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.048757165961433, + "rss_samples": 289, + "sampled_peak_rss_bytes": 296321024, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.001042416988639161, + "wall_cap_overshoot_seconds": 0.03217800002312288 + } + ], + "reference_kind": "matching campaign optimality proof", + "reference_scaled_cost": 203 + }, + { + "case": { + "family": "GendreauDumasExtended", + "id": "variants/GendreauDumasExtended/customer-width-half/n40w200.004.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/variants/GendreauDumasExtended/customer-width-half/n40w200.004.txt", + "sha256": "06b46d0300b0a32c004227f24e3fbc47692e56216a134695e0d7f9f2a81c1a6a", + "variant": "customer-width-half" + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.049459750007372, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.14183941698865965, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 3543999, + "frontier_bytes": null, + "preprocessing_ms": 0.627, + "search_ms": 29999.375, + "startup_and_teardown_ms": 49.41375000737389 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.840495369280688, + "rss_samples": 294, + "sampled_peak_rss_bytes": 8011776, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 5.410111043602228e-07, + "wall_cap_overshoot_seconds": 0.04945975000737235 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 17.138094583002385, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10869716698653065, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 1327104, + "rss_probe_seconds": 7.412543039186858, + "rss_samples": 166, + "sampled_peak_rss_bytes": 1075068928, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.015507333009736612, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 0.30410654097795486, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10000641597434878, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 15419, + "inference_ms": 188.696, + "propagation_ms": 164.232 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 9184, + "frontier_bytes": null, + "preprocessing_ms": 0.375, + "search_ms": 198.445, + "startup_and_teardown_ms": 105.25954097795486 + }, + "proof": "infeasible", + "proof_reported_ms": 198.847, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.12747770801070146, + "rss_samples": 3, + "sampled_peak_rss_bytes": 3080192, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.4999823179095984e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 0.10057208299986087, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.00224658299703151, + "measurements": { + "bound_calls": 282, + "bound_ms": 0.007, + "inference_calls": 9184, + "inference_ms": 14.247, + "propagation_ms": 8.502 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 9184, + "frontier_bytes": null, + "preprocessing_ms": 0.397, + "search_ms": 18.261, + "startup_and_teardown_ms": 81.88208299986087 + }, + "proof": "infeasible", + "proof_reported_ms": 18.69, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.041305166989332065, + "rss_samples": 1, + "sampled_peak_rss_bytes": 2801664, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 7.080088835209608e-07, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 0.09487079101381823, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.00219612501678057, + "measurements": { + "bound_calls": 1136, + "bound_ms": 0.03, + "inference_calls": 29486, + "inference_ms": 51.763, + "propagation_ms": 32.966 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 29493, + "frontier_bytes": null, + "preprocessing_ms": 0.373, + "search_ms": 64.738, + "startup_and_teardown_ms": 29.724791013818233 + }, + "proof": "infeasible", + "proof_reported_ms": 65.146, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.04053941700840369, + "rss_samples": 1, + "sampled_peak_rss_bytes": 2752512, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 1.334003172814846e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 0.10375516701606102, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.002229583013104275, + "measurements": { + "bound_calls": 282, + "bound_ms": 0.007, + "inference_calls": 9184, + "inference_ms": 14.228, + "propagation_ms": 8.641 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 9184, + "frontier_bytes": null, + "preprocessing_ms": 0.382, + "search_ms": 18.245, + "startup_and_teardown_ms": 85.08816701606102 + }, + "proof": "infeasible", + "proof_reported_ms": 18.667, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.04098399999202229, + "rss_samples": 1, + "sampled_peak_rss_bytes": 2785280, + "scale": null, + "status": "ok", + "termination": "infeasible", + "termination_reap_seconds": 7.920025382190943e-07, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 3.41222216602182, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.125015166006051, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 18087936, + "rss_probe_seconds": 1.5253969590121415, + "rss_samples": 33, + "sampled_peak_rss_bytes": 1091829760, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0012864999880548567, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.032472542021424, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.12364662499749102, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.347742042096797, + "rss_samples": 287, + "sampled_peak_rss_bytes": 386334720, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0013041670026723295, + "wall_cap_overshoot_seconds": 0.03247254202142358 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "SolomonPotvinBengio", + "id": "SolomonPotvinBengio/rc_201.2.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/SolomonPotvinBengio/rc_201.2.txt", + "sha256": "295faab3002d7c8e94d0fe136875801ee23c22c66532e8042322ff981e511948", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 0.410639416979393, + "final_objective": 71153740, + "first_incumbent_reported_ms": 1.33, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.1049169169855304, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 26999, + "frontier_bytes": null, + "preprocessing_ms": 0.726, + "search_ms": 390.049, + "startup_and_teardown_ms": 19.845416979393008 + }, + "proof": "optimal", + "proof_reported_ms": 390.794, + "quality_at_reported_ms": { + "100": 71731900, + "1000": 71153740, + "15000": 71153740, + "30000": 71153740, + "5000": 71153740 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.17584549999446608, + "rss_samples": 4, + "sampled_peak_rss_bytes": 4407296, + "scale": 100000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.4170072972774506e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 1.2657366250059567, + "final_objective": 71153740, + "first_incumbent_reported_ms": 3.799, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.11089899999205954, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 80106, + "frontier_bytes": null, + "preprocessing_ms": 0.413, + "search_ms": 1207.255, + "startup_and_teardown_ms": 58.04762500595666 + }, + "proof": "optimal", + "proof_reported_ms": 1207.689, + "quality_at_reported_ms": { + "100": 71153740, + "1000": 71153740, + "15000": 71153740, + "30000": 71153740, + "5000": 71153740 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.5752194160595536, + "rss_samples": 12, + "sampled_peak_rss_bytes": 76578816, + "scale": 100000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 7.920025382190943e-07, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 0.5157731249928474, + "final_objective": 71153740, + "first_incumbent_reported_ms": 4.478, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.10899562502163462, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 118538, + "inference_ms": 352.245, + "propagation_ms": 284.964 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 26, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 74071, + "frontier_bytes": null, + "preprocessing_ms": 0.287, + "search_ms": 408.454, + "startup_and_teardown_ms": 107.01312499284745 + }, + "proof": "optimal", + "proof_reported_ms": 408.76, + "quality_at_reported_ms": { + "100": 71153740, + "1000": 71153740, + "15000": 71153740, + "30000": 71153740, + "5000": 71153740 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.21915612497832626, + "rss_samples": 5, + "sampled_peak_rss_bytes": 3309568, + "scale": 100000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 7.080088835209608e-07, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 0.205619000014849, + "final_objective": 71153740, + "first_incumbent_reported_ms": 129.332, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.10078712500398979, + "measurements": { + "bound_calls": 22501, + "bound_ms": 0.552, + "inference_calls": 80498, + "inference_ms": 82.374, + "propagation_ms": 53.405 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 26, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 80498, + "frontier_bytes": null, + "preprocessing_ms": 0.261, + "search_ms": 140.661, + "startup_and_teardown_ms": 64.678000014849 + }, + "proof": "optimal", + "proof_reported_ms": 140.941, + "quality_at_reported_ms": { + "100": null, + "1000": 71153740, + "15000": 71153740, + "30000": 71153740, + "5000": 71153740 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.08252095902571455, + "rss_samples": 2, + "sampled_peak_rss_bytes": 12517376, + "scale": 100000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 2.9199873097240925e-07, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 0.598743167007342, + "final_objective": 71153740, + "first_incumbent_reported_ms": 86.448, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.10483895899960771, + "measurements": { + "bound_calls": 61617, + "bound_ms": 1.581, + "inference_calls": 315640, + "inference_ms": 362.999, + "propagation_ms": 244.368 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 26, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 315652, + "frontier_bytes": null, + "preprocessing_ms": 0.271, + "search_ms": 547.214, + "startup_and_teardown_ms": 51.22216700734202 + }, + "proof": "optimal", + "proof_reported_ms": 547.521, + "quality_at_reported_ms": { + "100": 71294520, + "1000": 71153740, + "15000": 71153740, + "30000": 71153740, + "5000": 71153740 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.25541016599163413, + "rss_samples": 6, + "sampled_peak_rss_bytes": 13336576, + "scale": 100000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.1659867595881224e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 0.20814254198921844, + "final_objective": 71153740, + "first_incumbent_reported_ms": 37.689, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.10121412499574944, + "measurements": { + "bound_calls": 16526, + "bound_ms": 0.415, + "inference_calls": 73572, + "inference_ms": 81.86, + "propagation_ms": 53.623 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 26, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 73572, + "frontier_bytes": null, + "preprocessing_ms": 0.265, + "search_ms": 128.705, + "startup_and_teardown_ms": 79.14054198921843 + }, + "proof": "optimal", + "proof_reported_ms": 129.002, + "quality_at_reported_ms": { + "100": 71153740, + "1000": 71153740, + "15000": 71153740, + "30000": 71153740, + "5000": 71153740 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.08479395898757502, + "rss_samples": 2, + "sampled_peak_rss_bytes": 10452992, + "scale": 100000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.0420044418424368e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 3.486989833007101, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1066859170095995, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 22970368, + "rss_probe_seconds": 1.5091543729358818, + "rss_samples": 35, + "sampled_peak_rss_bytes": 1096712192, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.001076749962521717, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.00757045901264, + "final_objective": 71153740, + "first_incumbent_reported_ms": 8238.115250016563, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.10757849999936298, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": 71153740, + "30000": 71153740, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.679449427174404, + "rss_samples": 292, + "sampled_peak_rss_bytes": 390955008, + "scale": 100000, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.001067707984475419, + "wall_cap_overshoot_seconds": 0.00757045901264064 + } + ], + "reference_kind": "matching campaign optimality proof", + "reference_scaled_cost": 71153740 + }, + { + "case": { + "family": "SolomonPotvinBengio", + "id": "SolomonPotvinBengio/rc_203.3.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/SolomonPotvinBengio/rc_203.3.txt", + "sha256": "bb9e4dd0b4408e1ddf9f721952c1dfc4d0cdafc062f8ab1f888d120f1f9c8b11", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.00115475000348, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1056047499878332, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.529913789825514, + "rss_samples": 296, + "sampled_peak_rss_bytes": 5701632, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0005418330256361514, + "wall_cap_overshoot_seconds": 0.0011547500034794211 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 18.236029874999076, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10905908400309272, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 8863744, + "rss_probe_seconds": 7.675447831890779, + "rss_samples": 180, + "sampled_peak_rss_bytes": 1082605568, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.013872208975953981, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.0057740000193, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.18819512499612756, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.910696551931323, + "rss_samples": 292, + "sampled_peak_rss_bytes": 3407872, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0006219169881660491, + "wall_cap_overshoot_seconds": 0.005774000019300729 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 12.738796459016157, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.30346508300863206, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 4636672, + "rss_probe_seconds": 6.155158335022861, + "rss_samples": 114, + "sampled_peak_rss_bytes": 1078378496, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.018907791993115097, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.011688416998368, + "final_objective": 82959910, + "first_incumbent_reported_ms": 18791.655, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1684781250078231, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": 82959910, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.774507413298124, + "rss_samples": 291, + "sampled_peak_rss_bytes": 73908224, + "scale": 100000, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.001600999996298924, + "wall_cap_overshoot_seconds": 0.011688416998367757 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 11.11953095899662, + "final_objective": 86430790, + "first_incumbent_reported_ms": 849.391, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.40112983400467783, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": 86430790, + "15000": 86430790, + "30000": 86430790, + "5000": 86430790 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 393216, + "rss_probe_seconds": 5.261831883137347, + "rss_samples": 101, + "sampled_peak_rss_bytes": 1074135040, + "scale": 100000, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.019376748998183757, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 4.962712459004251, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1101456249889452, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 7077888, + "rss_probe_seconds": 2.162660086003598, + "rss_samples": 49, + "sampled_peak_rss_bytes": 1080819712, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0010914999875240028, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.01451762500801, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.26031562499701977, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.144758454785915, + "rss_samples": 286, + "sampled_peak_rss_bytes": 215482368, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.001912291016196832, + "wall_cap_overshoot_seconds": 0.014517625008011237 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "SolomonPotvinBengio", + "id": "SolomonPotvinBengio/rc_204.1.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/SolomonPotvinBengio/rc_204.1.txt", + "sha256": "c6f38edd2b570bb8e350fef72290c82fdfa83455f4a5a8fa0119c6c58fbba704", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.000903291016584, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.37015741600771435, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.397606046812143, + "rss_samples": 283, + "sampled_peak_rss_bytes": 7749632, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.000781457987613976, + "wall_cap_overshoot_seconds": 0.0009032910165842623 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 30.020301124983234, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.18454891699366271, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.14721039912547, + "rss_samples": 287, + "sampled_peak_rss_bytes": 552501248, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.009309457993367687, + "wall_cap_overshoot_seconds": 0.02030112498323433 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.01016287499806, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.21710145799443126, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1149796, + "inference_ms": 29219.891, + "propagation_ms": 27037.633 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 708069, + "frontier_bytes": null, + "preprocessing_ms": 0.647, + "search_ms": 29999.43, + "startup_and_teardown_ms": 10.02687499806052 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.250311670912197, + "rss_samples": 284, + "sampled_peak_rss_bytes": 3637248, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 6.67001586407423e-07, + "wall_cap_overshoot_seconds": 0.010162874998059124 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 12.870467708009528, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1393343750096392, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 5750784, + "rss_probe_seconds": 5.580880828900263, + "rss_samples": 125, + "sampled_peak_rss_bytes": 1079492608, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.01773737499024719, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.00916249997681, + "final_objective": 93736607, + "first_incumbent_reported_ms": 4901.356, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.14010345897986554, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": 95067847, + "30000": 93736607, + "5000": 95067847 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.615845372056356, + "rss_samples": 294, + "sampled_peak_rss_bytes": 24018944, + "scale": 100000, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0009355829970445484, + "wall_cap_overshoot_seconds": 0.009162499976810068 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 11.470095666009001, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.23889620901900344, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 4308992, + "rss_probe_seconds": 5.0863927528844215, + "rss_samples": 109, + "sampled_peak_rss_bytes": 1078050816, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.016255916008958593, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 3.4851530419837218, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10677029198268428, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 20676608, + "rss_probe_seconds": 1.5035155430086888, + "rss_samples": 35, + "sampled_peak_rss_bytes": 1094418432, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0010911669814959168, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.00659433400142, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.14952462498331442, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.647283536905888, + "rss_samples": 296, + "sampled_peak_rss_bytes": 159318016, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0011473750055301934, + "wall_cap_overshoot_seconds": 0.006594334001420066 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "SolomonPotvinBengio", + "id": "SolomonPotvinBengio/rc_206.1.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/SolomonPotvinBengio/rc_206.1.txt", + "sha256": "83b9a4e3178a907c27c37f16856b11a312a4322983b1c503d1f0bfda03ea9cce", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 0.10349300000234507, + "final_objective": 1178479, + "first_incumbent_reported_ms": 0.263, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0022797090059611946, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 7, + "frontier_bytes": null, + "preprocessing_ms": 0.181, + "search_ms": 0.164, + "startup_and_teardown_ms": 103.14400000234507 + }, + "proof": "optimal", + "proof_reported_ms": 0.349, + "quality_at_reported_ms": { + "100": 1178479, + "1000": 1178479, + "15000": 1178479, + "30000": 1178479, + "5000": 1178479 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.040663041989319026, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 4.1601015254855156e-07, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 0.10155104100704193, + "final_objective": 1178479, + "first_incumbent_reported_ms": 0.287, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0024624580109957606, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 10, + "frontier_bytes": null, + "preprocessing_ms": 0.192, + "search_ms": 0.502, + "startup_and_teardown_ms": 100.84804100704193 + }, + "proof": "optimal", + "proof_reported_ms": 0.703, + "quality_at_reported_ms": { + "100": 1178479, + "1000": 1178479, + "15000": 1178479, + "30000": 1178479, + "5000": 1178479 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.04214304199558683, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 5.00003807246685e-07, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 0.1028538340178784, + "final_objective": 1178479, + "first_incumbent_reported_ms": 0.152, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0022334590030368418, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 20, + "inference_ms": 0.067, + "propagation_ms": 0.017 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 4, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 18, + "frontier_bytes": null, + "preprocessing_ms": 0.12, + "search_ms": 0.121, + "startup_and_teardown_ms": 102.6038340178784 + }, + "proof": "optimal", + "proof_reported_ms": 0.25, + "quality_at_reported_ms": { + "100": 1178479, + "1000": 1178479, + "15000": 1178479, + "30000": 1178479, + "5000": 1178479 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.04047979100141674, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.333013642579317e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 0.09944329198333435, + "final_objective": 1178479, + "first_incumbent_reported_ms": 0.176, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.002122499980032444, + "measurements": { + "bound_calls": 18, + "bound_ms": 0.0, + "inference_calls": 22, + "inference_ms": 0.07, + "propagation_ms": 0.016 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 4, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 22, + "frontier_bytes": null, + "preprocessing_ms": 0.12, + "search_ms": 0.122, + "startup_and_teardown_ms": 99.19129198333435 + }, + "proof": "optimal", + "proof_reported_ms": 0.252, + "quality_at_reported_ms": { + "100": 1178479, + "1000": 1178479, + "15000": 1178479, + "30000": 1178479, + "5000": 1178479 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.041511000017635524, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 5.829788278788328e-07, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 0.10527733399067074, + "final_objective": 1178479, + "first_incumbent_reported_ms": 0.172, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.002962958998978138, + "measurements": { + "bound_calls": 39, + "bound_ms": 0.001, + "inference_calls": 47, + "inference_ms": 0.077, + "propagation_ms": 0.022 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 4, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 50, + "frontier_bytes": null, + "preprocessing_ms": 0.123, + "search_ms": 0.175, + "startup_and_teardown_ms": 104.97133399067074 + }, + "proof": "optimal", + "proof_reported_ms": 0.306, + "quality_at_reported_ms": { + "100": 1178479, + "1000": 1178479, + "15000": 1178479, + "30000": 1178479, + "5000": 1178479 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.041801457991823554, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 2.9199873097240925e-07, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 0.10315512501983903, + "final_objective": 1178479, + "first_incumbent_reported_ms": 0.165, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0020510830217972398, + "measurements": { + "bound_calls": 14, + "bound_ms": 0.0, + "inference_calls": 18, + "inference_ms": 0.071, + "propagation_ms": 0.016 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 4, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 18, + "frontier_bytes": null, + "preprocessing_ms": 0.12, + "search_ms": 0.115, + "startup_and_teardown_ms": 102.91112501983903 + }, + "proof": "optimal", + "proof_reported_ms": 0.244, + "quality_at_reported_ms": { + "100": 1178479, + "1000": 1178479, + "15000": 1178479, + "30000": 1178479, + "5000": 1178479 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.04055045801214874, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 5.420006345957518e-07, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 0.10279837498092093, + "final_objective": 1178479, + "first_incumbent_reported_ms": 10.552166000707075, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.003698916989378631, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": 10, + "frontier_bytes": null, + "preprocessing_ms": 9.053540998138487, + "search_ms": 17.887833004351705, + "startup_and_teardown_ms": 75.85612498223782 + }, + "proof": "optimal", + "proof_reported_ms": 26.94224999868311, + "quality_at_reported_ms": { + "100": 1178479, + "1000": 1178479, + "15000": 1178479, + "30000": 1178479, + "5000": 1178479 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.040636208024807274, + "rss_samples": 1, + "sampled_peak_rss_bytes": 5996544, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.2910168152302504e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 0.10024779100785963, + "final_objective": 1178479, + "first_incumbent_reported_ms": 9.29758299025707, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.004159333009738475, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": 21, + "frontier_bytes": null, + "preprocessing_ms": 9.154125000350177, + "search_ms": 8.102374995360151, + "startup_and_teardown_ms": 82.9908330051694 + }, + "proof": "optimal", + "proof_reported_ms": 17.256958002690226, + "quality_at_reported_ms": { + "100": 1178479, + "1000": 1178479, + "15000": 1178479, + "30000": 1178479, + "5000": 1178479 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.04314516598242335, + "rss_samples": 1, + "sampled_peak_rss_bytes": 7503872, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.1250085663050413e-06, + "wall_cap_overshoot_seconds": 0 + } + ], + "reference_kind": "matching campaign optimality proof", + "reference_scaled_cost": 1178479 + }, + { + "case": { + "family": "SolomonPotvinBengio", + "id": "SolomonPotvinBengio/rc_208.1.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/SolomonPotvinBengio/rc_208.1.txt", + "sha256": "c698430e387cd23da8c27611b769e8c72e7497e74c6e4e35bf5cda241cee82f4", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.036039667000296, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1348498340230435, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 3538284, + "frontier_bytes": null, + "preprocessing_ms": 0.644, + "search_ms": 29999.357, + "startup_and_teardown_ms": 36.00466700029574 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.008788165927399, + "rss_samples": 290, + "sampled_peak_rss_bytes": 6569984, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.7500285543501377e-07, + "wall_cap_overshoot_seconds": 0.036039667000295594 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 30.01336766598979, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.16599154099822044, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.945403451099992, + "rss_samples": 290, + "sampled_peak_rss_bytes": 718389248, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.010796166985528544, + "wall_cap_overshoot_seconds": 0.013367665989790112 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.021177749993512, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11742658299044706, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 1392488, + "inference_ms": 29134.998, + "propagation_ms": 26630.251 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 864253, + "frontier_bytes": null, + "preprocessing_ms": 0.64, + "search_ms": 29999.374, + "startup_and_teardown_ms": 21.11374999351392 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.93624940875452, + "rss_samples": 291, + "sampled_peak_rss_bytes": 3407872, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.7500285543501377e-07, + "wall_cap_overshoot_seconds": 0.021177749993512407 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 16.032925915991655, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11302041698945686, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 3096576, + "rss_probe_seconds": 6.785286668105982, + "rss_samples": 156, + "sampled_peak_rss_bytes": 1076838400, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.015275875019142404, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.008908083982533, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.22574449999956414, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.983506455959287, + "rss_samples": 290, + "sampled_peak_rss_bytes": 33669120, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0013363340112846345, + "wall_cap_overshoot_seconds": 0.008908083982532844 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 20.55668045798666, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11047070901258849, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 2392064, + "rss_probe_seconds": 8.62361238093581, + "rss_samples": 203, + "sampled_peak_rss_bytes": 1076133888, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.015581625018967316, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 3.450767666014144, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10931579099269584, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 20725760, + "rss_probe_seconds": 1.5032778740278445, + "rss_samples": 34, + "sampled_peak_rss_bytes": 1094467584, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.001064834010321647, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.01926241698675, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1306466670066584, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.79386434177286, + "rss_samples": 293, + "sampled_peak_rss_bytes": 223756288, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.00105945800896734, + "wall_cap_overshoot_seconds": 0.019262416986748576 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "SolomonPotvinBengio", + "id": "variants/SolomonPotvinBengio/customer-width-half/rc_207.4.txt", + "path": "/private/tmp/open-search-tsptw-selected-network/variants/SolomonPotvinBengio/customer-width-half/rc_207.4.txt", + "sha256": "a2caea8e25d9073122648ef14cd71922089b3c9f9af847350663487f806548ba", + "variant": "customer-width-half" + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 0.10342529098852538, + "final_objective": 1196388, + "first_incumbent_reported_ms": 0.296, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0020768329850398004, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 25, + "frontier_bytes": null, + "preprocessing_ms": 0.191, + "search_ms": 0.323, + "startup_and_teardown_ms": 102.90729098852538 + }, + "proof": "optimal", + "proof_reported_ms": 0.518, + "quality_at_reported_ms": { + "100": 1196388, + "1000": 1196388, + "15000": 1196388, + "30000": 1196388, + "5000": 1196388 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.040750040992861614, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.7499842215329409e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 0.1008820410061162, + "final_objective": 1196388, + "first_incumbent_reported_ms": 0.333, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0022088330006226897, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 45, + "frontier_bytes": null, + "preprocessing_ms": 0.195, + "search_ms": 0.382, + "startup_and_teardown_ms": 100.3010410061162 + }, + "proof": "optimal", + "proof_reported_ms": 0.581, + "quality_at_reported_ms": { + "100": 1196388, + "1000": 1196388, + "15000": 1196388, + "30000": 1196388, + "5000": 1196388 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.04083720801281743, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 6.250047590583563e-07, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 0.10350129200378433, + "final_objective": 1196388, + "first_incumbent_reported_ms": 0.168, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0020743329951073974, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 312, + "inference_ms": 0.332, + "propagation_ms": 0.19 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 6, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 242, + "frontier_bytes": null, + "preprocessing_ms": 0.127, + "search_ms": 0.537, + "startup_and_teardown_ms": 102.82629200378433 + }, + "proof": "optimal", + "proof_reported_ms": 0.675, + "quality_at_reported_ms": { + "100": 1196388, + "1000": 1196388, + "15000": 1196388, + "30000": 1196388, + "5000": 1196388 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.0408384169859346, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.5830155462026596e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 0.09655675000976771, + "final_objective": 1196388, + "first_incumbent_reported_ms": 0.492, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0022034159919712692, + "measurements": { + "bound_calls": 220, + "bound_ms": 0.005, + "inference_calls": 308, + "inference_ms": 0.234, + "propagation_ms": 0.101 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 6, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 308, + "frontier_bytes": null, + "preprocessing_ms": 0.127, + "search_ms": 0.49, + "startup_and_teardown_ms": 95.9307500097677 + }, + "proof": "optimal", + "proof_reported_ms": 0.626, + "quality_at_reported_ms": { + "100": 1196388, + "1000": 1196388, + "15000": 1196388, + "30000": 1196388, + "5000": 1196388 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.04102216599858366, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.1250085663050413e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 0.10330762501689605, + "final_objective": 1196388, + "first_incumbent_reported_ms": 0.205, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0021524160110857338, + "measurements": { + "bound_calls": 491, + "bound_ms": 0.011, + "inference_calls": 670, + "inference_ms": 0.603, + "propagation_ms": 0.347 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 6, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 676, + "frontier_bytes": null, + "preprocessing_ms": 0.129, + "search_ms": 1.1, + "startup_and_teardown_ms": 102.06962501689605 + }, + "proof": "optimal", + "proof_reported_ms": 1.238, + "quality_at_reported_ms": { + "100": 1196388, + "1000": 1196388, + "15000": 1196388, + "30000": 1196388, + "5000": 1196388 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.040539583016652614, + "rss_samples": 1, + "sampled_peak_rss_bytes": 2621440, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.2920063454657793e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 0.10486320799100213, + "final_objective": 1196388, + "first_incumbent_reported_ms": 0.202, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0020737909944728017, + "measurements": { + "bound_calls": 140, + "bound_ms": 0.004, + "inference_calls": 235, + "inference_ms": 0.223, + "propagation_ms": 0.101 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 6, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 235, + "frontier_bytes": null, + "preprocessing_ms": 0.126, + "search_ms": 0.435, + "startup_and_teardown_ms": 104.29420799100212 + }, + "proof": "optimal", + "proof_reported_ms": 0.569, + "quality_at_reported_ms": { + "100": 1196388, + "1000": 1196388, + "15000": 1196388, + "30000": 1196388, + "5000": 1196388 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": null, + "rss_probe_seconds": 0.04218545797630213, + "rss_samples": 1, + "sampled_peak_rss_bytes": null, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 2.9199873097240925e-07, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 0.10588887499761768, + "final_objective": 1196388, + "first_incumbent_reported_ms": 9.85141601995565, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.003947624994907528, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": 56, + "frontier_bytes": null, + "preprocessing_ms": 9.327291016234085, + "search_ms": 8.596083003794774, + "startup_and_teardown_ms": 87.96466697822325 + }, + "proof": "optimal", + "proof_reported_ms": 17.924208019394428, + "quality_at_reported_ms": { + "100": 1196388, + "1000": 1196388, + "15000": 1196388, + "30000": 1196388, + "5000": 1196388 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.04135958300321363, + "rss_samples": 1, + "sampled_peak_rss_bytes": 6373376, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.0830117389559746e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 0.10685120802372694, + "final_objective": 1196388, + "first_incumbent_reported_ms": 9.779332991456613, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.003804083011345938, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": 173, + "frontier_bytes": null, + "preprocessing_ms": 9.603542013792321, + "search_ms": 9.153916005743667, + "startup_and_teardown_ms": 88.09312502853572 + }, + "proof": "optimal", + "proof_reported_ms": 18.758082995191216, + "quality_at_reported_ms": { + "100": 1196388, + "1000": 1196388, + "15000": 1196388, + "30000": 1196388, + "5000": 1196388 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.04246158298337832, + "rss_samples": 1, + "sampled_peak_rss_bytes": 6258688, + "scale": 10000, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.3750104699283838e-06, + "wall_cap_overshoot_seconds": 0 + } + ], + "reference_kind": "matching campaign optimality proof", + "reference_scaled_cost": 1196388 + }, + { + "case": { + "family": "AFG", + "id": "AFG/rbg019d.tw", + "path": "/private/tmp/open-search-tsptw-selected-network/AFG/rbg019d.tw", + "sha256": "b0e917e603b43d49243db0d82390977ffa963a97e50dad6c67d6dbde88dbc017", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 0.10192462499253452, + "final_objective": 1356, + "first_incumbent_reported_ms": 1.355, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.0021958329889457673, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 5048, + "frontier_bytes": null, + "preprocessing_ms": 0.35, + "search_ms": 43.024, + "startup_and_teardown_ms": 58.53962499253452 + }, + "proof": "optimal", + "proof_reported_ms": 43.385, + "quality_at_reported_ms": { + "100": 1356, + "1000": 1356, + "15000": 1356, + "30000": 1356, + "5000": 1356 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.04428187501616776, + "rss_samples": 1, + "sampled_peak_rss_bytes": 3489792, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.3750104699283838e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 0.2092591669934336, + "final_objective": 1356, + "first_incumbent_reported_ms": 1.556, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.09957983298227191, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 14516, + "frontier_bytes": null, + "preprocessing_ms": 0.305, + "search_ms": 151.678, + "startup_and_teardown_ms": 57.262166993433596 + }, + "proof": "optimal", + "proof_reported_ms": 151.997, + "quality_at_reported_ms": { + "100": 1358, + "1000": 1356, + "15000": 1356, + "30000": 1356, + "5000": 1356 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 0.08844674902502447, + "rss_samples": 2, + "sampled_peak_rss_bytes": 7520256, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 5.830079317092896e-07, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.00274745799834, + "final_objective": 1356, + "first_incumbent_reported_ms": 0.333, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.11118012500810437, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 1356, + "1000": 1356, + "15000": 1356, + "30000": 1356, + "5000": 1356 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.581849451147718, + "rss_samples": 296, + "sampled_peak_rss_bytes": 3047424, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0005398749781306833, + "wall_cap_overshoot_seconds": 0.0027474579983390868 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 2.6687876250070985, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10749429100542329, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 9945088, + "rss_probe_seconds": 1.1476785000704695, + "rss_samples": 27, + "sampled_peak_rss_bytes": 1083686912, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.014613292034482583, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 20.705441958009033, + "final_objective": 1356, + "first_incumbent_reported_ms": 0.691, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.12496362501406111, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 1362, + "1000": 1362, + "15000": 1356, + "30000": 1356, + "5000": 1356 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 13320192, + "rss_probe_seconds": 8.819952173886122, + "rss_samples": 203, + "sampled_peak_rss_bytes": 1087062016, + "scale": 1, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.01569362499867566, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 3.000766665994888, + "final_objective": 1356, + "first_incumbent_reported_ms": 0.721, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.18252054101321846, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 1362, + "1000": 1362, + "15000": 1356, + "30000": 1356, + "5000": 1356 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 24723456, + "rss_probe_seconds": 1.326494919048855, + "rss_samples": 29, + "sampled_peak_rss_bytes": 1098465280, + "scale": 1, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.015778583008795977, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 6.555909957998665, + "final_objective": 1356, + "first_incumbent_reported_ms": 5361.879083007807, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.10813620800036006, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": 1811384, + "frontier_bytes": null, + "preprocessing_ms": 13.39108298998326, + "search_ms": 5358.393750007963, + "startup_and_teardown_ms": 1184.1240409994498 + }, + "proof": "optimal", + "proof_reported_ms": 5371.785916999215, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": 1356, + "30000": 1356, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 2.800088376941858, + "rss_samples": 64, + "sampled_peak_rss_bytes": 746258432, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.6249832697212696e-06, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 14.393940042005852, + "final_objective": 1356, + "first_incumbent_reported_ms": 14302.140542014968, + "gap_to_proved_optimum": 0.0, + "maximum_sample_gap_seconds": 0.11031112499767914, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": 4711127, + "frontier_bytes": null, + "preprocessing_ms": 13.810417003696784, + "search_ms": 14295.393667009193, + "startup_and_teardown_ms": 84.73470900207758 + }, + "proof": "optimal", + "proof_reported_ms": 14309.205333003774, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": 1356, + "30000": 1356, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 6.1300843739300035, + "rss_samples": 140, + "sampled_peak_rss_bytes": 283590656, + "scale": 1, + "status": "ok", + "termination": "optimal", + "termination_reap_seconds": 1.8330174498260021e-06, + "wall_cap_overshoot_seconds": 0 + } + ], + "reference_kind": "matching campaign optimality proof", + "reference_scaled_cost": 1356 + }, + { + "case": { + "family": "AFG", + "id": "AFG/rbg050a.tw", + "path": "/private/tmp/open-search-tsptw-selected-network/AFG/rbg050a.tw", + "sha256": "afe0b9925a08ced51ec460620ef84425960b23208f55004eb20aa86b8a748117", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.01221899999655, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.12796004198025912, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 4861819, + "frontier_bytes": null, + "preprocessing_ms": 0.864, + "search_ms": 29999.139, + "startup_and_teardown_ms": 12.173999996550265 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.678211132850265, + "rss_samples": 296, + "sampled_peak_rss_bytes": 7208960, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.7500285543501377e-07, + "wall_cap_overshoot_seconds": 0.012218999996548519 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 30.023519665992353, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.13962708399049006, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.856372329464648, + "rss_samples": 293, + "sampled_peak_rss_bytes": 463650816, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.013409498991677538, + "wall_cap_overshoot_seconds": 0.02351966599235311 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.01026362500852, + "final_objective": 3298, + "first_incumbent_reported_ms": 1.457, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11869691699394025, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 19608549, + "inference_ms": 20047.545, + "propagation_ms": 12937.378 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 51, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 13369316, + "frontier_bytes": null, + "preprocessing_ms": 0.513, + "search_ms": 29999.488, + "startup_and_teardown_ms": 10.231625008520496 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 3336, + "1000": 3335, + "15000": 3312, + "30000": 3298, + "5000": 3314 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.662812998285517, + "rss_samples": 296, + "sampled_peak_rss_bytes": 3260416, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 1.2920063454657793e-06, + "wall_cap_overshoot_seconds": 0.010263625008519739 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 10.915858584019588, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.16260733298258856, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 2326528, + "rss_probe_seconds": 4.731039620208321, + "rss_samples": 106, + "sampled_peak_rss_bytes": 1076068352, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.015157999994698912, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.00117787500494, + "final_objective": 3000, + "first_incumbent_reported_ms": 38.655, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.12690420899889432, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 3042, + "1000": 3024, + "15000": 3000, + "30000": 3000, + "5000": 3000 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.587887082016096, + "rss_samples": 298, + "sampled_peak_rss_bytes": 27230208, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0009442919981665909, + "wall_cap_overshoot_seconds": 0.0011778750049415976 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 8.917424541985383, + "final_objective": 3016, + "first_incumbent_reported_ms": 115.02, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1100452080136165, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": 3016, + "15000": 3016, + "30000": 3016, + "5000": 3016 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 819200, + "rss_probe_seconds": 3.701313251105603, + "rss_samples": 89, + "sampled_peak_rss_bytes": 1074561024, + "scale": 1, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.01338050002232194, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 2.466757000016514, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10325070799444802, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 17989632, + "rss_probe_seconds": 1.039231956092408, + "rss_samples": 25, + "sampled_peak_rss_bytes": 1091731456, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0009837079560384154, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.01118404098088, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.13753579100011848, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.579625175014371, + "rss_samples": 298, + "sampled_peak_rss_bytes": 240140288, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0011169160134159029, + "wall_cap_overshoot_seconds": 0.011184040980879217 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "AFG", + "id": "AFG/rbg067a.tw", + "path": "/private/tmp/open-search-tsptw-selected-network/AFG/rbg067a.tw", + "sha256": "bbdf79985a587fd25ba8cb371830ffca424f4f315f0a93397ffd21dbd5786586", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.009676999994554, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.12273445798200555, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 1852009, + "frontier_bytes": null, + "preprocessing_ms": 1.316, + "search_ms": 29998.711, + "startup_and_teardown_ms": 9.606999994553917 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.820169095153688, + "rss_samples": 292, + "sampled_peak_rss_bytes": 8486912, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 5.420006345957518e-07, + "wall_cap_overshoot_seconds": 0.009676999994553626 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 30.02999091599486, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1285554580099415, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 366024, + "frontier_bytes": null, + "preprocessing_ms": 1.277, + "search_ms": 29998.759, + "startup_and_teardown_ms": 29.880915994857787 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.927215793752111, + "rss_samples": 292, + "sampled_peak_rss_bytes": 376356864, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.7500285543501377e-07, + "wall_cap_overshoot_seconds": 0.02999091599485837 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.03707541601034, + "final_objective": 4771, + "first_incumbent_reported_ms": 2.215, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10769583398359828, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 19141699, + "inference_ms": 18940.254, + "propagation_ms": 12121.446 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 68, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 13137929, + "frontier_bytes": null, + "preprocessing_ms": 0.824, + "search_ms": 29999.177, + "startup_and_teardown_ms": 37.047416010340385 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 4796, + "1000": 4785, + "15000": 4777, + "30000": 4771, + "5000": 4785 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.553604576911312, + "rss_samples": 298, + "sampled_peak_rss_bytes": 3440640, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 5.00003807246685e-07, + "wall_cap_overshoot_seconds": 0.03707541601033881 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 21.131908166978974, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1558021669916343, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 1835008, + "rss_probe_seconds": 8.93703116310644, + "rss_samples": 209, + "sampled_peak_rss_bytes": 1075576832, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.017545917013194412, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.012369416974252, + "final_objective": 4713, + "first_incumbent_reported_ms": 17.513, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.12765695797861554, + "measurements": { + "bound_calls": 1042214, + "bound_ms": 27.065, + "inference_calls": 11924107, + "inference_ms": 22320.71, + "propagation_ms": 14030.161 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 68, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 11924119, + "frontier_bytes": null, + "preprocessing_ms": 0.848, + "search_ms": 29999.154, + "startup_and_teardown_ms": 12.331416974251624 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 4740, + "1000": 4713, + "15000": 4713, + "30000": 4713, + "5000": 4713 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.612441622972256, + "rss_samples": 297, + "sampled_peak_rss_bytes": 68403200, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.3329997677356005e-06, + "wall_cap_overshoot_seconds": 0.01236941697425209 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 8.99168383399956, + "final_objective": 4713, + "first_incumbent_reported_ms": 17.734, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1079583749815356, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 4740, + "1000": 4713, + "15000": 4713, + "30000": 4713, + "5000": 4713 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 3309568, + "rss_probe_seconds": 3.703243500785902, + "rss_samples": 90, + "sampled_peak_rss_bytes": 1077051392, + "scale": 1, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.01654633303405717, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 2.7796628750220407, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.13373437500558794, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 28721152, + "rss_probe_seconds": 1.229573415912455, + "rss_samples": 27, + "sampled_peak_rss_bytes": 1102462976, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0009923749894369394, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.007091457984643, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10681270901113749, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.476137705525616, + "rss_samples": 298, + "sampled_peak_rss_bytes": 364331008, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0010450410190969706, + "wall_cap_overshoot_seconds": 0.007091457984643057 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "AFG", + "id": "AFG/rbg125a.tw", + "path": "/private/tmp/open-search-tsptw-selected-network/AFG/rbg125a.tw", + "sha256": "067b6739985430c2924247f14e0ccce8024d43f751803cb585f29b32f70d6092", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.00720399999409, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.12211662498884834, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 1360321, + "frontier_bytes": null, + "preprocessing_ms": 4.43, + "search_ms": 29995.571, + "startup_and_teardown_ms": 7.1319999940897105 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.712694829708198, + "rss_samples": 292, + "sampled_peak_rss_bytes": 20611072, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 1.167005393654108e-06, + "wall_cap_overshoot_seconds": 0.007203999994089827 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 30.007596832991112, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11004562498419546, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.757084202341503, + "rss_samples": 293, + "sampled_peak_rss_bytes": 310329344, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.004652124975109473, + "wall_cap_overshoot_seconds": 0.007596832991112024 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.00743387499824, + "final_objective": 8679, + "first_incumbent_reported_ms": 8.303, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1189530840201769, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 16385904, + "inference_ms": 16786.264, + "propagation_ms": 10878.01 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 126, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 11480829, + "frontier_bytes": null, + "preprocessing_ms": 3.12, + "search_ms": 29996.884, + "startup_and_teardown_ms": 7.3988749982418085 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 8741, + "1000": 8679, + "15000": 8679, + "30000": 8679, + "5000": 8679 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.797595707059372, + "rss_samples": 293, + "sampled_peak_rss_bytes": 4341760, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 1.1250085663050413e-06, + "wall_cap_overshoot_seconds": 0.007433874998241663 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 30.04173308398458, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1530769580276683, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.714049090136541, + "rss_samples": 296, + "sampled_peak_rss_bytes": 665485312, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.010060875007184222, + "wall_cap_overshoot_seconds": 0.041733083984581754 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.027062209002906, + "final_objective": 8073, + "first_incumbent_reported_ms": 74.736, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.13895795898861252, + "measurements": { + "bound_calls": 432268, + "bound_ms": 11.411, + "inference_calls": 7959820, + "inference_ms": 23052.75, + "propagation_ms": 13523.057 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 126, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 7959830, + "frontier_bytes": null, + "preprocessing_ms": 3.075, + "search_ms": 29997.003, + "startup_and_teardown_ms": 26.944209002907883 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 8242, + "1000": 8073, + "15000": 8073, + "30000": 8073, + "5000": 8073 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.013457487715641, + "rss_samples": 291, + "sampled_peak_rss_bytes": 25788416, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.3300602808594704e-07, + "wall_cap_overshoot_seconds": 0.027062209002906457 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 15.118087208014913, + "final_objective": 8074, + "first_incumbent_reported_ms": 76.452, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.12514537500101142, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 8242, + "1000": 8079, + "15000": 8074, + "30000": 8074, + "5000": 8074 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 1343488, + "rss_probe_seconds": 6.429284629150061, + "rss_samples": 149, + "sampled_peak_rss_bytes": 1075085312, + "scale": 1, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.022372124978573993, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 2.855545208993135, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11616970901377499, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 27312128, + "rss_probe_seconds": 1.2350888730143197, + "rss_samples": 28, + "sampled_peak_rss_bytes": 1101053952, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.001116708997869864, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.013317625009222, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1990495419886429, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.306130960350856, + "rss_samples": 284, + "sampled_peak_rss_bytes": 149422080, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0010607500153128058, + "wall_cap_overshoot_seconds": 0.013317625009221956 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "AFG", + "id": "AFG/rbg233.tw", + "path": "/private/tmp/open-search-tsptw-selected-network/AFG/rbg233.tw", + "sha256": "6883e6a2c33d94a3a233d21a26c5732d5838c2cac7679fbb3fd19fea6f55321f", + "variant": null + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.010133333998965, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11067949997959659, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 678552, + "frontier_bytes": null, + "preprocessing_ms": 20.059, + "search_ms": 29979.973, + "startup_and_teardown_ms": 10.06033399896478 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.713779165962478, + "rss_samples": 293, + "sampled_peak_rss_bytes": 66633728, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 9.999785106629133e-07, + "wall_cap_overshoot_seconds": 0.0101333339989651 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 30.010175709001487, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11393937500542961, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 29850, + "frontier_bytes": null, + "preprocessing_ms": 20.155, + "search_ms": 29980.498, + "startup_and_teardown_ms": 9.465709001487994 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.996743970434181, + "rss_samples": 290, + "sampled_peak_rss_bytes": 82788352, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 1.0829826351255178e-06, + "wall_cap_overshoot_seconds": 0.010175709001487121 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.0473625829909, + "final_objective": 16339, + "first_incumbent_reported_ms": 37.446, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.12944412499200553, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 14242609, + "inference_ms": 13255.043, + "propagation_ms": 8265.451 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 232, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 10039773, + "frontier_bytes": null, + "preprocessing_ms": 15.24, + "search_ms": 29984.761, + "startup_and_teardown_ms": 47.30658299089919 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": 16354, + "1000": 16339, + "15000": 16339, + "30000": 16339, + "5000": 16339 + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.72970081778476, + "rss_samples": 296, + "sampled_peak_rss_bytes": 7815168, + "scale": 1, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 5.420006345957518e-07, + "wall_cap_overshoot_seconds": 0.04736258299089968 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 30.0152564579912, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11093774999608286, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.480116842983989, + "rss_samples": 298, + "sampled_peak_rss_bytes": 313688064, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.00516050000442192, + "wall_cap_overshoot_seconds": 0.015256457991199568 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.005573749978794, + "final_objective": 15365, + "first_incumbent_reported_ms": 20956.286, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.11591874997247942, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": 15365, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.589391385263298, + "rss_samples": 297, + "sampled_peak_rss_bytes": 21577728, + "scale": 1, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0008081250125542283, + "wall_cap_overshoot_seconds": 0.005573749978793785 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 30.007787084003212, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.15724620799301192, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.503750090952963, + "rss_samples": 298, + "sampled_peak_rss_bytes": 303611904, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.005140042019775137, + "wall_cap_overshoot_seconds": 0.00778708400321193 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 3.3761225839843974, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10455804201774299, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 27754496, + "rss_probe_seconds": 1.4277250829618424, + "rss_samples": 34, + "sampled_peak_rss_bytes": 1101496320, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.0010162079997826368, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.009220667008776, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.22692862499388866, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.895225370273693, + "rss_samples": 291, + "sampled_peak_rss_bytes": 133005312, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.001097790984204039, + "wall_cap_overshoot_seconds": 0.009220667008776218 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + }, + { + "case": { + "family": "AFG", + "id": "variants/AFG/customer-width-half/rbg193.tw", + "path": "/private/tmp/open-search-tsptw-selected-network/variants/AFG/customer-width-half/rbg193.tw", + "sha256": "9daa682fcbbdabb66c57e36267d08185ab54996face3f19c4a140721c261af50", + "variant": "customer-width-half" + }, + "measurements": [ + { + "config": "closed-dom-size-bab", + "end_to_end_wall_seconds": 30.010265375021845, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.13995991699630395, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 1368192, + "frontier_bytes": null, + "preprocessing_ms": 12.498, + "search_ms": 29987.524, + "startup_and_teardown_ms": 10.195375021845393 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.676993204542669, + "rss_samples": 294, + "sampled_peak_rss_bytes": 26017792, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 1.5420082490891218e-06, + "wall_cap_overshoot_seconds": 0.010265375021845102 + }, + { + "config": "closed-dom-size-cabs", + "end_to_end_wall_seconds": 30.020533832983347, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.15531908298726194, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 106651, + "frontier_bytes": null, + "preprocessing_ms": 12.92, + "search_ms": 29987.248, + "startup_and_teardown_ms": 20.306832983347704 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.213320330251008, + "rss_samples": 286, + "sampled_peak_rss_bytes": 274792448, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.3300602808594704e-07, + "wall_cap_overshoot_seconds": 0.020533832983346656 + }, + { + "config": "transition-bab-lazy-unary", + "end_to_end_wall_seconds": 30.03338204199099, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.13848624998354353, + "measurements": { + "bound_calls": 0, + "bound_ms": 0.0, + "inference_calls": 405643, + "inference_ms": 29127.186, + "propagation_ms": 27181.595 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 264726, + "frontier_bytes": null, + "preprocessing_ms": 10.171, + "search_ms": 29990.025, + "startup_and_teardown_ms": 33.08204199099055 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.145740917010698, + "rss_samples": 288, + "sampled_peak_rss_bytes": 6569984, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.7500285543501377e-07, + "wall_cap_overshoot_seconds": 0.03338204199098982 + }, + { + "config": "transition-astar-lazy-unary", + "end_to_end_wall_seconds": 30.01008649999858, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1856585830100812, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 13.518257714895299, + "rss_samples": 281, + "sampled_peak_rss_bytes": 109395968, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.002974000002723187, + "wall_cap_overshoot_seconds": 0.010086499998578802 + }, + { + "config": "transition-cabs-lazy-unary", + "end_to_end_wall_seconds": 30.013776208012132, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.10869320898200385, + "measurements": { + "bound_calls": 59690, + "bound_ms": 1.667, + "inference_calls": 3889884, + "inference_ms": 26378.605, + "propagation_ms": 20160.236 + }, + "metrics": { + "allocation": { + "intvar_handle_bytes": 8, + "prepared_root_positions": 1, + "root_positions": 0, + "solution_positions": 0, + "space_bytes": 488 + }, + "clone_bytes": null, + "descriptor_bytes": 0, + "descriptor_count": 0, + "expansions": 3889892, + "frontier_bytes": null, + "preprocessing_ms": 10.053, + "search_ms": 29990.009, + "startup_and_teardown_ms": 13.677208012133633 + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.644431705935858, + "rss_samples": 296, + "sampled_peak_rss_bytes": 11452416, + "scale": null, + "status": "ok", + "termination": "stopped", + "termination_reap_seconds": 3.7500285543501377e-07, + "wall_cap_overshoot_seconds": 0.013776208012131974 + }, + { + "config": "transition-column-lazy-unary", + "end_to_end_wall_seconds": 30.02666233299533, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1622463749954477, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.746950505796121, + "rss_samples": 296, + "sampled_peak_rss_bytes": 221511680, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.004025207977974787, + "wall_cap_overshoot_seconds": 0.026662332995329052 + }, + { + "config": "didppy-astar", + "end_to_end_wall_seconds": 3.3473540830018464, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.1381226250086911, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 4358144, + "rss_probe_seconds": 1.4774487100658007, + "rss_samples": 32, + "sampled_peak_rss_bytes": 1078099968, + "scale": null, + "status": "rss-limit", + "termination": "rss-limit", + "termination_reap_seconds": 0.001922750030644238, + "wall_cap_overshoot_seconds": 0 + }, + { + "config": "didppy-cabs", + "end_to_end_wall_seconds": 30.002122042002156, + "final_objective": null, + "first_incumbent_reported_ms": null, + "gap_to_proved_optimum": null, + "maximum_sample_gap_seconds": 0.12697291700169444, + "measurements": null, + "metrics": { + "allocation": null, + "clone_bytes": null, + "descriptor_bytes": null, + "descriptor_count": null, + "expansions": null, + "frontier_bytes": null, + "preprocessing_ms": null, + "search_ms": null, + "startup_and_teardown_ms": null + }, + "proof": null, + "proof_reported_ms": null, + "quality_at_reported_ms": { + "100": null, + "1000": null, + "15000": null, + "30000": null, + "5000": null + }, + "quality_usable": true, + "repetition": 0, + "rss_overshoot_bytes": 0, + "rss_probe_seconds": 12.722748461907031, + "rss_samples": 294, + "sampled_peak_rss_bytes": 173129728, + "scale": null, + "status": "timeout", + "termination": "timeout", + "termination_reap_seconds": 0.0009973749984055758, + "wall_cap_overshoot_seconds": 0.002122042002156377 + } + ], + "reference_kind": "unavailable; campaign best is not an optimum", + "reference_scaled_cost": null + } + ], + "clocks": "publication checkpoints use solver-reported milliseconds; launch-inclusive publication timestamps are unavailable", + "family_groups": { + "AFG/canonical": { + "closed-dom-size-bab": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 0, + "samples": 5 + }, + "closed-dom-size-cabs": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 2, + "samples": 5 + }, + "didppy-astar": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 4, + "samples": 5 + }, + "didppy-cabs": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 4, + "samples": 5 + }, + "transition-astar-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 5, + "samples": 5 + }, + "transition-bab-lazy-unary": { + "failures": 0, + "feasible": 5, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 5 + }, + "transition-cabs-lazy-unary": { + "failures": 0, + "feasible": 5, + "infeasible": 0, + "optimal": 0, + "resource_stops": 3, + "samples": 5 + }, + "transition-column-lazy-unary": { + "failures": 0, + "feasible": 4, + "infeasible": 0, + "optimal": 0, + "resource_stops": 5, + "samples": 5 + } + }, + "AFG/variant": { + "closed-dom-size-bab": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + }, + "closed-dom-size-cabs": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + }, + "didppy-astar": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 1 + }, + "didppy-cabs": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 1 + }, + "transition-astar-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 1 + }, + "transition-bab-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + }, + "transition-cabs-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + }, + "transition-column-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 1 + } + }, + "Dumas/canonical": { + "closed-dom-size-bab": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 1, + "samples": 5 + }, + "closed-dom-size-cabs": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 3, + "samples": 5 + }, + "didppy-astar": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 5, + "samples": 5 + }, + "didppy-cabs": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 0, + "resource_stops": 5, + "samples": 5 + }, + "transition-astar-lazy-unary": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 4, + "samples": 5 + }, + "transition-bab-lazy-unary": { + "failures": 0, + "feasible": 3, + "infeasible": 0, + "optimal": 1, + "resource_stops": 1, + "samples": 5 + }, + "transition-cabs-lazy-unary": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 2, + "samples": 5 + }, + "transition-column-lazy-unary": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 1, + "resource_stops": 4, + "samples": 5 + } + }, + "Dumas/variant": { + "closed-dom-size-bab": { + "failures": 0, + "feasible": 0, + "infeasible": 1, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + }, + "closed-dom-size-cabs": { + "failures": 0, + "feasible": 0, + "infeasible": 1, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + }, + "didppy-astar": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 1 + }, + "didppy-cabs": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 1 + }, + "transition-astar-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 1, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + }, + "transition-bab-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 1, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + }, + "transition-cabs-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 1, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + }, + "transition-column-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 1, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + } + }, + "GendreauDumasExtended/canonical": { + "closed-dom-size-bab": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 2, + "samples": 5 + }, + "closed-dom-size-cabs": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 1, + "resource_stops": 4, + "samples": 5 + }, + "didppy-astar": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 5, + "samples": 5 + }, + "didppy-cabs": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 0, + "resource_stops": 5, + "samples": 5 + }, + "transition-astar-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 5, + "samples": 5 + }, + "transition-bab-lazy-unary": { + "failures": 0, + "feasible": 5, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 5 + }, + "transition-cabs-lazy-unary": { + "failures": 0, + "feasible": 5, + "infeasible": 0, + "optimal": 0, + "resource_stops": 3, + "samples": 5 + }, + "transition-column-lazy-unary": { + "failures": 0, + "feasible": 5, + "infeasible": 0, + "optimal": 0, + "resource_stops": 5, + "samples": 5 + } + }, + "GendreauDumasExtended/variant": { + "closed-dom-size-bab": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + }, + "closed-dom-size-cabs": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 1 + }, + "didppy-astar": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 1 + }, + "didppy-cabs": { + "failures": 0, + "feasible": 0, + "infeasible": 0, + "optimal": 0, + "resource_stops": 1, + "samples": 1 + }, + "transition-astar-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 1, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + }, + "transition-bab-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 1, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + }, + "transition-cabs-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 1, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + }, + "transition-column-lazy-unary": { + "failures": 0, + "feasible": 0, + "infeasible": 1, + "optimal": 0, + "resource_stops": 0, + "samples": 1 + } + }, + "SolomonPotvinBengio/canonical": { + "closed-dom-size-bab": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 2, + "resource_stops": 2, + "samples": 5 + }, + "closed-dom-size-cabs": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 2, + "resource_stops": 3, + "samples": 5 + }, + "didppy-astar": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 4, + "samples": 5 + }, + "didppy-cabs": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 1, + "resource_stops": 4, + "samples": 5 + }, + "transition-astar-lazy-unary": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 2, + "resource_stops": 3, + "samples": 5 + }, + "transition-bab-lazy-unary": { + "failures": 0, + "feasible": 2, + "infeasible": 0, + "optimal": 2, + "resource_stops": 1, + "samples": 5 + }, + "transition-cabs-lazy-unary": { + "failures": 0, + "feasible": 4, + "infeasible": 0, + "optimal": 2, + "resource_stops": 3, + "samples": 5 + }, + "transition-column-lazy-unary": { + "failures": 0, + "feasible": 3, + "infeasible": 0, + "optimal": 2, + "resource_stops": 3, + "samples": 5 + } + }, + "SolomonPotvinBengio/variant": { + "closed-dom-size-bab": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 0, + "samples": 1 + }, + "closed-dom-size-cabs": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 0, + "samples": 1 + }, + "didppy-astar": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 0, + "samples": 1 + }, + "didppy-cabs": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 0, + "samples": 1 + }, + "transition-astar-lazy-unary": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 0, + "samples": 1 + }, + "transition-bab-lazy-unary": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 0, + "samples": 1 + }, + "transition-cabs-lazy-unary": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 0, + "samples": 1 + }, + "transition-column-lazy-unary": { + "failures": 0, + "feasible": 1, + "infeasible": 0, + "optimal": 1, + "resource_stops": 0, + "samples": 1 + } + } + }, + "formulation_contrasts": { + "bab": { + "baseline": "closed-dom-size-bab", + "counts": { + "transition-bab-lazy-unary": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 16, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 8 + }, + "final_objective": { + "failed": 0, + "loss": 1, + "missing": 0, + "tie": 5, + "unavailable": 8, + "win": 10 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 0, + "unavailable": 18, + "win": 4 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 4, + "missing": 0, + "tie": 0, + "unavailable": 19, + "win": 1 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 4, + "unavailable": 11, + "win": 7 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 3, + "unavailable": 13, + "win": 6 + }, + "quality_at_15000_reported_ms": { + "failed": 0, + "loss": 1, + "missing": 0, + "tie": 5, + "unavailable": 9, + "win": 9 + }, + "quality_at_30000_reported_ms": { + "failed": 0, + "loss": 1, + "missing": 0, + "tie": 5, + "unavailable": 8, + "win": 10 + }, + "quality_at_5000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 4, + "unavailable": 11, + "win": 7 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 1, + "missing": 0, + "tie": 0, + "unavailable": 3, + "win": 20 + } + } + }, + "note": "Costs are paired only within the same case and scale. Checked timeout/RSS incumbents count as feasible quality, never proof. Native reported clocks start at main entry and include preprocessing; outer wall clocks measure launch through reap. Missing metrics are unavailable; lower values win. Cases mark canonical data and deterministic window variants separately.", + "reported_time_checkpoints_ms": [ + 100, + 1000, + 5000, + 15000, + 30000 + ] + }, + "cabs": { + "baseline": "closed-dom-size-cabs", + "counts": { + "transition-cabs-lazy-unary": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 13, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 11 + }, + "final_objective": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 6, + "unavailable": 8, + "win": 10 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 0, + "unavailable": 17, + "win": 5 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 3, + "missing": 0, + "tie": 0, + "unavailable": 19, + "win": 2 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 1, + "missing": 0, + "tie": 4, + "unavailable": 11, + "win": 8 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 3, + "unavailable": 13, + "win": 6 + }, + "quality_at_15000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 6, + "unavailable": 10, + "win": 8 + }, + "quality_at_30000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 6, + "unavailable": 8, + "win": 10 + }, + "quality_at_5000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 6, + "unavailable": 10, + "win": 8 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 3, + "missing": 0, + "tie": 0, + "unavailable": 3, + "win": 18 + } + } + }, + "note": "Costs are paired only within the same case and scale. Checked timeout/RSS incumbents count as feasible quality, never proof. Native reported clocks start at main entry and include preprocessing; outer wall clocks measure launch through reap. Missing metrics are unavailable; lower values win. Cases mark canonical data and deterministic window variants separately.", + "reported_time_checkpoints_ms": [ + 100, + 1000, + 5000, + 15000, + 30000 + ] + } + }, + "missing_metrics": "frontier/clone buffers and phases after kill are unavailable; allocation sizeof excludes dynamic storage", + "same_model": { + "closed": { + "baseline": "closed-dom-size-bab", + "counts": { + "closed-dom-size-cabs": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 13, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 11 + }, + "final_objective": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 6, + "unavailable": 17, + "win": 1 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 6, + "missing": 0, + "tie": 0, + "unavailable": 18, + "win": 0 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 6, + "missing": 0, + "tie": 0, + "unavailable": 17, + "win": 1 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 1, + "missing": 0, + "tie": 5, + "unavailable": 18, + "win": 0 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 3, + "unavailable": 18, + "win": 1 + }, + "quality_at_15000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 6, + "unavailable": 17, + "win": 1 + }, + "quality_at_30000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 6, + "unavailable": 17, + "win": 1 + }, + "quality_at_5000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 6, + "unavailable": 17, + "win": 1 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 21, + "missing": 0, + "tie": 0, + "unavailable": 3, + "win": 0 + } + } + }, + "note": "Costs are paired only within the same case and scale. Checked timeout/RSS incumbents count as feasible quality, never proof. Native reported clocks start at main entry and include preprocessing; outer wall clocks measure launch through reap. Missing metrics are unavailable; lower values win. Cases mark canonical data and deterministic window variants separately.", + "reported_time_checkpoints_ms": [ + 100, + 1000, + 5000, + 15000, + 30000 + ] + }, + "didppy": { + "baseline": "didppy-astar", + "counts": { + "didppy-cabs": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 23, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 1 + }, + "final_objective": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 3, + "unavailable": 17, + "win": 4 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 1, + "missing": 0, + "tie": 0, + "unavailable": 21, + "win": 2 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 0, + "unavailable": 21, + "win": 1 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 2, + "unavailable": 19, + "win": 3 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 2, + "unavailable": 20, + "win": 2 + }, + "quality_at_15000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 3, + "unavailable": 17, + "win": 4 + }, + "quality_at_30000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 3, + "unavailable": 17, + "win": 4 + }, + "quality_at_5000_reported_ms": { + "failed": 0, + "loss": 0, + "missing": 0, + "tie": 2, + "unavailable": 19, + "win": 3 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 1, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 23 + } + } + }, + "note": "Costs are paired only within the same case and scale. Checked timeout/RSS incumbents count as feasible quality, never proof. Native reported clocks start at main entry and include preprocessing; outer wall clocks measure launch through reap. Missing metrics are unavailable; lower values win. Cases mark canonical data and deterministic window variants separately.", + "reported_time_checkpoints_ms": [ + 100, + 1000, + 5000, + 15000, + 30000 + ] + }, + "transition": { + "baseline": "transition-bab-lazy-unary", + "counts": { + "transition-astar-lazy-unary": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 3, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 21 + }, + "final_objective": { + "failed": 0, + "loss": 12, + "missing": 0, + "tie": 4, + "unavailable": 8, + "win": 0 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 4, + "missing": 0, + "tie": 0, + "unavailable": 20, + "win": 0 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 0, + "unavailable": 18, + "win": 4 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 10, + "missing": 0, + "tie": 3, + "unavailable": 11, + "win": 0 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 9, + "missing": 0, + "tie": 2, + "unavailable": 13, + "win": 0 + }, + "quality_at_15000_reported_ms": { + "failed": 0, + "loss": 11, + "missing": 0, + "tie": 4, + "unavailable": 9, + "win": 0 + }, + "quality_at_30000_reported_ms": { + "failed": 0, + "loss": 12, + "missing": 0, + "tie": 4, + "unavailable": 8, + "win": 0 + }, + "quality_at_5000_reported_ms": { + "failed": 0, + "loss": 9, + "missing": 0, + "tie": 3, + "unavailable": 11, + "win": 1 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 20, + "missing": 0, + "tie": 0, + "unavailable": 3, + "win": 1 + } + }, + "transition-cabs-lazy-unary": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 9, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 15 + }, + "final_objective": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 5, + "unavailable": 6, + "win": 11 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 10, + "missing": 0, + "tie": 0, + "unavailable": 10, + "win": 4 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 4, + "missing": 0, + "tie": 0, + "unavailable": 18, + "win": 2 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 3, + "missing": 0, + "tie": 3, + "unavailable": 9, + "win": 9 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 3, + "missing": 0, + "tie": 2, + "unavailable": 12, + "win": 7 + }, + "quality_at_15000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 5, + "unavailable": 8, + "win": 9 + }, + "quality_at_30000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 5, + "unavailable": 6, + "win": 11 + }, + "quality_at_5000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 4, + "unavailable": 8, + "win": 10 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 20, + "missing": 0, + "tie": 0, + "unavailable": 3, + "win": 1 + } + }, + "transition-column-lazy-unary": { + "end_to_end_wall_seconds": { + "failed": 0, + "loss": 4, + "missing": 0, + "tie": 0, + "unavailable": 0, + "win": 20 + }, + "final_objective": { + "failed": 0, + "loss": 3, + "missing": 0, + "tie": 5, + "unavailable": 6, + "win": 10 + }, + "first_incumbent_reported_ms": { + "failed": 0, + "loss": 9, + "missing": 0, + "tie": 0, + "unavailable": 11, + "win": 4 + }, + "proof_reported_ms": { + "failed": 0, + "loss": 1, + "missing": 0, + "tie": 0, + "unavailable": 18, + "win": 5 + }, + "quality_at_1000_reported_ms": { + "failed": 0, + "loss": 3, + "missing": 0, + "tie": 3, + "unavailable": 8, + "win": 10 + }, + "quality_at_100_reported_ms": { + "failed": 0, + "loss": 3, + "missing": 0, + "tie": 3, + "unavailable": 13, + "win": 5 + }, + "quality_at_15000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 5, + "unavailable": 8, + "win": 9 + }, + "quality_at_30000_reported_ms": { + "failed": 0, + "loss": 3, + "missing": 0, + "tie": 5, + "unavailable": 6, + "win": 10 + }, + "quality_at_5000_reported_ms": { + "failed": 0, + "loss": 2, + "missing": 0, + "tie": 4, + "unavailable": 8, + "win": 10 + }, + "sampled_peak_rss_bytes": { + "failed": 0, + "loss": 20, + "missing": 0, + "tie": 0, + "unavailable": 3, + "win": 1 + } + } + }, + "note": "Costs are paired only within the same case and scale. Checked timeout/RSS incumbents count as feasible quality, never proof. Native reported clocks start at main entry and include preprocessing; outer wall clocks measure launch through reap. Missing metrics are unavailable; lower values win. Cases mark canonical data and deterministic window variants separately.", + "reported_time_checkpoints_ms": [ + 100, + 1000, + 5000, + 15000, + 30000 + ] + } + } + }, + "completed": 192, + "configurations": { + "closed-dom-size-bab": [ + "--formulation", + "closed", + "--engine", + "bab", + "--circuit-propagation", + "dom", + "--cost-branching", + "size" + ], + "closed-dom-size-cabs": [ + "--formulation", + "closed", + "--engine", + "cabs", + "--circuit-propagation", + "dom", + "--cost-branching", + "size" + ], + "didppy-astar": [ + "--engine", + "astar" + ], + "didppy-cabs": [ + "--engine", + "cabs" + ], + "transition-astar-lazy-unary": [ + "--formulation", + "transition", + "--engine", + "astar", + "--bound", + "prefix", + "--residual-propagation", + "unary", + "--state-pruning", + "off" + ], + "transition-bab-lazy-unary": [ + "--formulation", + "transition", + "--engine", + "bab", + "--bound", + "prefix", + "--residual-propagation", + "unary", + "--state-pruning", + "off" + ], + "transition-cabs-lazy-unary": [ + "--formulation", + "transition", + "--engine", + "cabs", + "--bound", + "prefix", + "--residual-propagation", + "unary", + "--state-pruning", + "off" + ], + "transition-column-lazy-unary": [ + "--formulation", + "transition", + "--engine", + "column", + "--bound", + "prefix", + "--residual-propagation", + "unary", + "--state-pruning", + "off" + ] + }, + "expected": 192, + "freeze_sha256": "6242f01b4103c750fe68a0775c436236f5f89a5ba4fa18ae74b9fbf4a4fca334", + "groups": { + "closed-dom-size-bab": { + "actual_launch_wall_seconds": 512.2057630829222, + "completed": 24, + "failed": 5, + "feasible": 6, + "infeasible": 1, + "median_wall_seconds": 30.010133333998965, + "optimal": 6, + "ordinary_stopped": 12, + "proved": 7, + "resource_terminated": 5, + "sampled_peak_rss_bytes": 66633728, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 19, + "rss-limit": 0, + "timeout": 5 + } + }, + "closed-dom-size-cabs": { + "actual_launch_wall_seconds": 482.67461045391974, + "completed": 24, + "failed": 13, + "feasible": 7, + "infeasible": 1, + "median_wall_seconds": 1.2657366250059567, + "optimal": 6, + "ordinary_stopped": 4, + "proved": 7, + "resource_terminated": 13, + "sampled_peak_rss_bytes": 1144143872, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 11, + "rss-limit": 3, + "timeout": 10 + } + }, + "didppy-astar": { + "actual_launch_wall_seconds": 79.3430839170178, + "completed": 24, + "failed": 21, + "feasible": 3, + "infeasible": 0, + "median_wall_seconds": 0.10588887499761768, + "optimal": 3, + "ordinary_stopped": 0, + "proved": 3, + "resource_terminated": 21, + "sampled_peak_rss_bytes": 1107820544, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 3, + "rss-limit": 21, + "timeout": 0 + } + }, + "didppy-cabs": { + "actual_launch_wall_seconds": 644.8794791680702, + "completed": 24, + "failed": 21, + "feasible": 7, + "infeasible": 0, + "median_wall_seconds": 0.10685120802372694, + "optimal": 3, + "ordinary_stopped": 0, + "proved": 3, + "resource_terminated": 21, + "sampled_peak_rss_bytes": 390955008, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 3, + "rss-limit": 0, + "timeout": 21 + } + }, + "transition-astar-lazy-unary": { + "actual_launch_wall_seconds": 371.9007607510139, + "completed": 24, + "failed": 18, + "feasible": 4, + "infeasible": 2, + "median_wall_seconds": 0.10339699948963244, + "optimal": 4, + "ordinary_stopped": 0, + "proved": 6, + "resource_terminated": 18, + "sampled_peak_rss_bytes": 1096302592, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 6, + "rss-limit": 11, + "timeout": 7 + } + }, + "transition-bab-lazy-unary": { + "actual_launch_wall_seconds": 558.5932209150051, + "completed": 24, + "failed": 4, + "feasible": 16, + "infeasible": 2, + "median_wall_seconds": 30.012335354011157, + "optimal": 4, + "ordinary_stopped": 14, + "proved": 6, + "resource_terminated": 4, + "sampled_peak_rss_bytes": 7815168, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 20, + "rss-limit": 0, + "timeout": 4 + } + }, + "transition-cabs-lazy-unary": { + "actual_launch_wall_seconds": 546.0430212079373, + "completed": 24, + "failed": 11, + "feasible": 16, + "infeasible": 2, + "median_wall_seconds": 30.012369416974252, + "optimal": 4, + "ordinary_stopped": 7, + "proved": 6, + "resource_terminated": 11, + "sampled_peak_rss_bytes": 1087062016, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 13, + "rss-limit": 1, + "timeout": 10 + } + }, + "transition-column-lazy-unary": { + "actual_launch_wall_seconds": 292.6677561669785, + "completed": 24, + "failed": 18, + "feasible": 15, + "infeasible": 2, + "median_wall_seconds": 0.10581347899278626, + "optimal": 4, + "ordinary_stopped": 0, + "proved": 6, + "resource_terminated": 18, + "sampled_peak_rss_bytes": 1098465280, + "status_counts": { + "execution-error": 0, + "invalid-output": 0, + "measurement-error": 0, + "ok": 6, + "rss-limit": 14, + "timeout": 4 + } + } + }, + "identities": { + "checker_sha256": "91bff4e5c1630f187eb816dbc2094507e362b6e349197e6680c2cbcee2338778", + "didppy": { + "adapter_sha256": "8361a24a494a698be1650f4d1560030abf707fea114da381f1e317deeb36c3d8", + "launcher": "uv", + "version": "0.11.1" + }, + "manifest_sha256": "946fb8cccbe4b580cdf1c4f29a0d85bd7524a84bd1431a6b3408a5736d8a7e87", + "native": { + "libraries": { + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodedriver.6.4.0.dylib": "d4332d9873d657cf1d58e825842dea0026a51a302894210745f5c5260077aaa4", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodeflatzinc.6.4.0.dylib": "dfbaa6f5c97951771ee2ea90f5d85dff9b6fb6ef4617404142894b2b8e45232f", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodefloat.6.4.0.dylib": "68b1aec0512dded3b80599cfbee34d8f301f50d60560ba5e8b2c06752efb20e7", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodeint.6.4.0.dylib": "635412b4c0cde700e0ec4dffa12b59b44e70b9f4252657ea42b4106ad078ccff", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodekernel.6.4.0.dylib": "2a7aced55c554a1449b007c127afb1e869ee7f58cdc22c634065b074f2597301", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodeminimodel.6.4.0.dylib": "48e26109dbbb45f8fef29262f3c3299d0486d2737de85f43fc65c66511c91f23", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodemodel.6.4.0.dylib": "b6be32f2f100c1b7fae00a361d48dfef82eb94eae5fd1eb643bc3d6ed057e0fa", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodesearch.6.4.0.dylib": "6c87b891455bad523659806c4920ff66f990fa960136c0fcb6e731d8c27bb01c", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodeset.6.4.0.dylib": "f07b55be6931f62c1c9821f1578bec89bb08568e6a4bf03c1f6981f0c9b7ae8e", + "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/libgecodesupport.6.4.0.dylib": "6cfefbf1ceb061ee0eb42917e8e5eb3203019e133dbfb50a78ee2d4cdefcc2a9" + }, + "path": "/Users/zayenz/.codex/worktrees/2d06/gecode/build/open-search/bin/tsptw", + "sha256": "7359159c0e6525dcc2e0865abc7aceecfee5059c797b7918cebcf6e137e23862" + }, + "runner_sha256": "402e19eeeafcc8b3f517dcd85c3a6866b86be02515dd101f27afa48f1b2b8032", + "sources": { + "examples/tsptw-data.hh": "5737e87ec75569c333088b66e59d15c42b0b12c873a4f35d9ca39105093f8d66", + "examples/tsptw-transition.hh": "92973ba230e21bbac97c84669bbf4f32591849daf7b27afcc260d5aecaabdc02", + "examples/tsptw.cpp": "d999b1c0f3b82e8e3c377432f1b1282b90aec654134f5b8fa04ae7c19c8b209c", + "gecode/search.hh": "9df30a8e1f2c8869bef5fd35ebd457528532235ecd7bb25d30bafcbbcd726926", + "gecode/search/astar.cpp": "cae422071b256e3989038fd63bd74d4cdbaa3a2b04a97c2669c6a101cd2b3079", + "gecode/search/astar.hpp": "af350c1108b66712f67ad7bd19844c9362f4606e3623c36f4566ededbe522d15", + "gecode/search/bab.cpp": "00b4762f2401ea1f3c1e4ca761181f7771290252a94ec20c8cccc5bb39b348de", + "gecode/search/bab.hpp": "726f1e57ca1a18baf5c6d5645cb5dbffa0e6f76a03f26cdd797e5baf25abce6e", + "gecode/search/base.hpp": "fd1a98449e27876b48ee115185194416612e743e35a00a07a971450ff471daf7", + "gecode/search/build.hpp": "cfaf01a48a711de6a814a51cf4e514e44e4f98378a6dbca73a2ee172ad508bc6", + "gecode/search/cabs.cpp": "8b45fd4f04d6078cae12f6cdb4bc514fc752a4fe17b33aa3801a57489d813c2f", + "gecode/search/cabs.hpp": "6b6fcaa4e2d4388e3496629eb06b3e35532f93722a2a704d82fe17f40ec6c396", + "gecode/search/column.cpp": "e97dd93ea6c28a938e11cf73bc241358da6ab9c95e2433601c7b9e52f7e1ec97", + "gecode/search/column.hpp": "4447bbf1a16b19d5cb32190d5c383019560fa4eb94c41ce74e48a6edeebc4954", + "gecode/search/cpprofiler/connector.hpp": "a2883f1711a6be340602019a62ce284dfa9b40dc6ba0bc0aec0c0d66f1effe69", + "gecode/search/cpprofiler/message.hpp": "91ef4240babfcc6a818d28257d0060863eeab0db4f18d8fa66458cab37c39210", + "gecode/search/cpprofiler/tracer.cpp": "eacb488b6ee27bcfd360ee411ec582c4321f4f014dfd3590df928c174f575875", + "gecode/search/cutoff.cpp": "c61236c2b09a4d7b4d628b08a7db62cb2552864437d8b7b178dde0f5a81a558f", + "gecode/search/cutoff.hpp": "726743ce80885e63892fd6b3814ca79f0fa73195a7e2770a2b97a4e4c74a64fc", + "gecode/search/dfs.cpp": "6fa3f7879f89e1650c2c144af8d4e5489a938efcbfa32532b8ab9bbfd88d185e", + "gecode/search/dfs.hpp": "63336435572ca66b23301003ada2a10917de200366e633bcbde9409a7c3bcd5a", + "gecode/search/engine.cpp": "ba44ee580ef5a7217e391a80c41e2e68ee8b9785de8fbb9694503b7dd79fccb5", + "gecode/search/engine.hpp": "46599fe8f8957080687aa18f938f71c97e587990202101aa6fdc4cb8cc240b79", + "gecode/search/exception.cpp": "b80d71aa47a856233e1de383fa84a14ec6eecf7738abdcf8e22384e49b89d612", + "gecode/search/exception.hpp": "35a76f421d21e2a8a5b319f08ec887972df68d686569fe6d30034e7673676cf0", + "gecode/search/lds.cpp": "90484573667912bd8e2eab75c483013447f9286773a2f5161aa65d260a970f06", + "gecode/search/lds.hpp": "b383af2b362e9c7a777e9ab9221835c2a5c11ad715be915c7d25095cdcb7f002", + "gecode/search/nogoods.cpp": "5ce5baedeb544c74b51f864f06669fa7dc723dc56ce2fda7a5a4973d7e27ec05", + "gecode/search/nogoods.hpp": "1cb7b897ab7cfc08bb4f033b59875cbe4a70f74a8998e398dc9148093b280393", + "gecode/search/options.cpp": "aa61ca08b1100b59737aed5492720c0d11be0a23b80d40f3f9efb75616d93921", + "gecode/search/options.hpp": "993a8154ba05bb2f47caa1e8145f5542703d762ffdfad52b79114a95393cb6db", + "gecode/search/par/bab.hpp": "6cafb3e779d117f59fba625829cf52f28e049e098f9b3649cd5491e6d8731ca7", + "gecode/search/par/dfs.hpp": "e742a3b4d83e843659f78fd76f8e4a02a68e62f5e9108890737699ee2dcc4774", + "gecode/search/par/engine.hpp": "48d4bf00fe4f417b48ece3aeffc680cc17950679a685b98b2e1c3383e351494a", + "gecode/search/par/path.hpp": "ff9908e5967b96ca4ce5b947b8c4f21e64a0251a02bb4a7045eb10820f936edb", + "gecode/search/par/pbs.cpp": "aaa024723d45065aaeb8fdbd3b87c0ae31ca32a1eecfca9ab21b77cd8fe754a2", + "gecode/search/par/pbs.hpp": "4bd4bae09a74aee478151e9fe63ac0b484470e28198020c5293a569a4dc4a985", + "gecode/search/pbs.cpp": "cf953d42395e1d096df0d991271dde4ae836dee35af60b87e297953272093206", + "gecode/search/pbs.hpp": "2fa9855e31bb690f6765f057544a0f0e451425df45125121f9bc625442cc748c", + "gecode/search/portfolio-cost.hpp": "cb63a32144a15433e5ef8da072914f8af8089cd6589663d40c692d3ac1334904", + "gecode/search/portfolio.cpp": "299993af771946f6d8ce4bd2561eabf100484169b56b465f68973845260d8516", + "gecode/search/portfolio.hpp": "75e25d112bd3419d8481ec3e503eb7ff03942180fbbdd347fee3048ac540b44c", + "gecode/search/rbs.cpp": "196abc8674d19fff5ea8211cd010391cf4f9662a2cff549cb7deeae5ed14a2ea", + "gecode/search/rbs.hpp": "3f5f38d243241eb395e0af0b77a3a8acae9aebd74d76f86be72a92ab9d1f5413", + "gecode/search/sebs.hpp": "893ba3bf4a1a9c8cf3d776582035bfb3ee3111b80fe5a9fa58ed653ca4870f08", + "gecode/search/seq/bab.hpp": "0854fd52139352d4b9a33ba9870ca8bd1d9bad9f1534ee140a491f412df783b7", + "gecode/search/seq/dead.cpp": "3baad801a8a24833905c45225cbfad74a8146cd16fe248763b28edff986d57cd", + "gecode/search/seq/dfs.hpp": "1c4d152fdb3b2adb0eddd6b1b7055e4e6c2ddf7190d415b4b8973343ce1169b5", + "gecode/search/seq/lds.hpp": "dde6c5560518de0833f92d60fea56c05adcdd9700fd9fb163c12607f8c87842e", + "gecode/search/seq/path.hpp": "ca92e28836620497a953117f1ce03ebb8e5482f03e303a14ed498c96fc9c693f", + "gecode/search/seq/pbs.cpp": "3f17ed9e52371ceec419e1fcfd9dc744ac1c5c8e177a2e95ee037a9b1376b6d3", + "gecode/search/seq/pbs.hpp": "b8438a8219aaf2173cc9e8c546b117bde893a241032b9ad5831ca46c807784e8", + "gecode/search/seq/rbs.cpp": "21f9b927a71bde6ea9c1dba6ac653c20f37bcf89214a588d2ba94167ae5bfb5d", + "gecode/search/seq/rbs.hpp": "04e9204bbd602f8fe49eb772d20c0676099e2bfb054db69683eec83b5f1ac2b0", + "gecode/search/statistics.hpp": "df01cbe88dd100946f00b5a747abf32550e2f8c3639b4274d5914168c91acea7", + "gecode/search/stop.cpp": "184572f90f6f9a3d754d3701d862bff607c0d09bf39d3353fd4321a0e12fd3ac", + "gecode/search/stop.hpp": "2a30137766f158679a7dde8ac45453000f44f29a3ec81cabfe5a34d04953eea5", + "gecode/search/trace-recorder.hpp": "ab495236abcf3819d2bfca47be90326e5fff9efdcb623de3f492675077ca00cd", + "gecode/search/tracer.cpp": "27266acb328d5f799a9639cefa8c2dd04333635847a7399e9ba1712115e07880", + "gecode/search/tracer.hpp": "af3481c6d85c7cc9b12b19a5fd1dd04d878a6c5a2bd6dc8f54af8120c1ebe09a", + "gecode/search/traits.hpp": "63d51e78557be9f5b241f8ea6db7afe73b554a9eac7e848f5b9ad00a64144c5f", + "gecode/search/worker-control.cpp": "57e9a4e6c497fbd67e030a65adca1d658f5458fd0984bafe1956f466b2cf09f2" + } + }, + "limits": { + "rss_bytes": 1073741824, + "sample_interval_seconds": 0.05, + "seconds": 30.0, + "threads": 1 + }, + "machine": { + "machine": "arm64", + "processor": "arm", + "system": "macOS-26.6.2-arm64-arm-64bit-Mach-O" + }, + "schema_version": 1, + "unfinished": 0 +} diff --git a/examples/data/tsptw/manifest.json b/examples/data/tsptw/manifest.json new file mode 100644 index 0000000000..7f79ea8b0d --- /dev/null +++ b/examples/data/tsptw/manifest.json @@ -0,0 +1,443 @@ +{ + "schema_version": 1, + "selection": "Frozen before solver tuning: two canonical development and five canonical held-out cases per family, plus one separately marked window variant per family per split. Sizes and named window classes vary; selection does not use measured solver outcomes. Source instances do not cross splits, including variant bases.", + "sources": { + "Dumas": { + "archive_url": "https://lopez-ibanez.eu/files/TSPTW/Dumas.tar.gz", + "archive_sha256": "a58fc8a764f8b9be2ee9c31bc0120da952508cb6cda6d1442bae79636421b811", + "tour_url": "https://lopez-ibanez.eu/files/TSPTW/Dumas-best-known-traveltime.txt", + "tour_table_sha256": "0e4bd30b9105ab41852c5d89a9472910c65e918d03b05fd0317b47727fe3c41d" + }, + "GendreauDumasExtended": { + "archive_url": "https://lopez-ibanez.eu/files/TSPTW/GendreauDumasExtended.tar.gz", + "archive_sha256": "449e853bc4236b30f73a6ad8dcf10e96cb5fadaef4d31c5948703fa90fdeae7d", + "tour_url": "https://lopez-ibanez.eu/files/TSPTW/GendreauDumasExtended-best-known-traveltime.txt", + "tour_table_sha256": "2223d460bee9d39987a975196c698c6de7238a924dfc617b8ccf9ad4be4175ce" + }, + "SolomonPotvinBengio": { + "archive_url": "https://lopez-ibanez.eu/files/TSPTW/SolomonPotvinBengio.tar.gz", + "archive_sha256": "a8048561b55dfd824cb48c266c5dfc855f0e878aec1a176d0b62900dde7af892", + "tour_url": "https://lopez-ibanez.eu/files/TSPTW/SolomonPotvinBengio-best-known-traveltime.txt", + "tour_table_sha256": "4f39631176ddf9ffab1d3b75d8eea0e8cd8a4d4c5ac0cd094fc02c098d3bd043" + }, + "AFG": { + "archive_url": "https://lopez-ibanez.eu/files/TSPTW/AFG.tar.gz", + "archive_sha256": "98797c37e6d16234db2eb41941e600082ca9b274c3f938646ce89a3b9e08e860", + "tour_url": "https://lopez-ibanez.eu/files/TSPTW/AFG-best-known-traveltime.txt", + "tour_table_sha256": "62783054e6fb1cff020646cecbe3be469d0d9ccd03ed61809ea6cd98a80f1f8a" + } + }, + "tiny": [ + { + "path": "tiny/decimal.txt", + "sha256": "add8c302d37fc661ca9f8667b6b530f7e3be2b519d30b3df9b31a2e3c8094169", + "optimum": "1.5", + "feasible_tours": 1, + "proof": "exhaustive enumeration" + }, + { + "path": "tiny/folded-service.txt", + "sha256": "61f45ff0e7bd9453e61d02d00dc0680db3fc5e5fb7a0710c8465ac483cf806bb", + "optimum": "9", + "feasible_tours": 2, + "proof": "exhaustive enumeration" + }, + { + "path": "tiny/late-customer.txt", + "sha256": "c1438cc1fa564c8b22011684aab37ae461e5f598d5d714a758c885ba14713ef6", + "optimum": null, + "feasible_tours": 0, + "proof": "exhaustive enumeration" + }, + { + "path": "tiny/late-return.txt", + "sha256": "f44a2168bc9638d0c657235b2eb9df1bc08fcc0dca06e6729782ed20235dae62", + "optimum": null, + "feasible_tours": 0, + "proof": "exhaustive enumeration" + }, + { + "path": "tiny/waiting.txt", + "sha256": "9bf9bf7ff8a243500d75d09436a75a92bef276c1ea596d7d8af5bc272952220e", + "optimum": "5", + "feasible_tours": 1, + "proof": "exhaustive enumeration" + } + ], + "published": [ + { + "family": "Dumas", + "path": "published/Dumas/n20w20.001.txt", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748", + "tour_path": "published/Dumas/n20w20.001.txt.tour", + "tour_sha256": "bb65acd7d979b3225c9f0869cc15ed77222b23faa489a7a00b0199f37221fe74", + "published_cost": "378", + "exact_cost": "378", + "status": "published best-known tour; no optimality proof inferred" + }, + { + "family": "GendreauDumasExtended", + "path": "published/GendreauDumasExtended/n20w120.001.txt", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba", + "tour_path": "published/GendreauDumasExtended/n20w120.001.txt.tour", + "tour_sha256": "4b49c9807ac9a66ceb3f6d3857c66ee9e8929e0c37b69234d905426bad20dbb2", + "published_cost": "267", + "exact_cost": "267", + "status": "published best-known tour; no optimality proof inferred" + }, + { + "family": "SolomonPotvinBengio", + "path": "published/SolomonPotvinBengio/rc_201.1.txt", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f", + "tour_path": "published/SolomonPotvinBengio/rc_201.1.txt.tour", + "tour_sha256": "5b1695a48e6eefb47b79bbebcaf45558740e4229d02df8281911d1820c177e55", + "published_cost": "444.54", + "exact_cost": "444.5425", + "status": "published best-known tour; no optimality proof inferred" + }, + { + "family": "AFG", + "path": "published/AFG/rbg010a.tw", + "sha256": "4a8e0c37052047063b19d698487bae0ed5256bb22de4f675fca3868975f40ef3", + "tour_path": "published/AFG/rbg010a.tw.tour", + "tour_sha256": "70d011b502f29dd14297cc341cccdb80ad3b736c666b9bd88c209ad52cc5673d", + "published_cost": "671", + "exact_cost": "671", + "status": "published best-known tour; no optimality proof inferred" + } + ], + "cases": [ + { + "id": "Dumas/n20w20.001.txt", + "split": "development", + "family": "Dumas", + "member": "Dumas/n20w20.001.txt", + "path": "Dumas/n20w20.001.txt", + "source_sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748", + "sha256": "83c354a30228db900577252a845fe2671175cb5e621a9a1061a01f9b56aaa748" + }, + { + "id": "Dumas/n60w100.001.txt", + "split": "development", + "family": "Dumas", + "member": "Dumas/n60w100.001.txt", + "path": "Dumas/n60w100.001.txt", + "source_sha256": "35d3690fbb7c4eb31775e6d8e32c5a36068498ca69c6097aed3d7eeb2976c6bb", + "sha256": "35d3690fbb7c4eb31775e6d8e32c5a36068498ca69c6097aed3d7eeb2976c6bb" + }, + { + "id": "Dumas/n40w20.002.txt", + "split": "held-out", + "family": "Dumas", + "member": "Dumas/n40w20.002.txt", + "path": "Dumas/n40w20.002.txt", + "source_sha256": "460887b23bae03441567010f3b1524b68121ca74a739d66ef705d0852879e8c5", + "sha256": "460887b23bae03441567010f3b1524b68121ca74a739d66ef705d0852879e8c5" + }, + { + "id": "Dumas/n80w40.002.txt", + "split": "held-out", + "family": "Dumas", + "member": "Dumas/n80w40.002.txt", + "path": "Dumas/n80w40.002.txt", + "source_sha256": "b5d6dc6c794fd2daa6e362604ac31f2562f4d829af3110226fbc4496ab8f471a", + "sha256": "b5d6dc6c794fd2daa6e362604ac31f2562f4d829af3110226fbc4496ab8f471a" + }, + { + "id": "Dumas/n100w60.002.txt", + "split": "held-out", + "family": "Dumas", + "member": "Dumas/n100w60.002.txt", + "path": "Dumas/n100w60.002.txt", + "source_sha256": "0b00a2eac6bf37c0bc94120e072cea9bf4a7891e4b2edf17a6497d0ea3c74b8b", + "sha256": "0b00a2eac6bf37c0bc94120e072cea9bf4a7891e4b2edf17a6497d0ea3c74b8b" + }, + { + "id": "Dumas/n150w60.002.txt", + "split": "held-out", + "family": "Dumas", + "member": "Dumas/n150w60.002.txt", + "path": "Dumas/n150w60.002.txt", + "source_sha256": "6c9f4cb2f5a5fab2688ce7e4e745b2f275ac497d390219da4a32bc5a4145f37a", + "sha256": "6c9f4cb2f5a5fab2688ce7e4e745b2f275ac497d390219da4a32bc5a4145f37a" + }, + { + "id": "Dumas/n200w40.002.txt", + "split": "held-out", + "family": "Dumas", + "member": "Dumas/n200w40.002.txt", + "path": "Dumas/n200w40.002.txt", + "source_sha256": "caef8dbbcdec934ed7fd2a49586633902aa40e445fde527f99de7a527ea90970", + "sha256": "caef8dbbcdec934ed7fd2a49586633902aa40e445fde527f99de7a527ea90970" + }, + { + "id": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "split": "development", + "family": "Dumas", + "member": "Dumas/n20w100.003.txt", + "path": "variants/Dumas/customer-due-plus-100/n20w100.003.txt", + "variant": "customer-due-plus-100", + "source_sha256": "254226989b1e19547a9a312ec1ff4c0f7d84b98126316f55f0c9e892401c7704", + "sha256": "2aed1af8ef2756d0ecf902a6c0a9f9916893bb2a65ef9a284e3ff85848c1962d" + }, + { + "id": "variants/Dumas/customer-width-half/n40w100.004.txt", + "split": "held-out", + "family": "Dumas", + "member": "Dumas/n40w100.004.txt", + "path": "variants/Dumas/customer-width-half/n40w100.004.txt", + "variant": "customer-width-half", + "source_sha256": "1416dcdf07241cf49e5bc58eaf18df05ea0f4820015af4b6b70fd7db22ab1b3f", + "sha256": "4b2621859ded3a5eaa885ece60b028459d36f7645945a8854c2d33fe8da9b99b" + }, + { + "id": "GendreauDumasExtended/n20w120.001.txt", + "split": "development", + "family": "GendreauDumasExtended", + "member": "GendreauDumasExtended/n20w120.001.txt", + "path": "GendreauDumasExtended/n20w120.001.txt", + "source_sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba", + "sha256": "633d3be7abef529e7faf8bd6094a40f548d8310f41cb2f3014d21b550ebf5fba" + }, + { + "id": "GendreauDumasExtended/n60w200.001.txt", + "split": "development", + "family": "GendreauDumasExtended", + "member": "GendreauDumasExtended/n60w200.001.txt", + "path": "GendreauDumasExtended/n60w200.001.txt", + "source_sha256": "d8dd1073870ba85b8bf82085df4056c97234889c57baf03dc459ff0aa61256c5", + "sha256": "d8dd1073870ba85b8bf82085df4056c97234889c57baf03dc459ff0aa61256c5" + }, + { + "id": "GendreauDumasExtended/n40w120.002.txt", + "split": "held-out", + "family": "GendreauDumasExtended", + "member": "GendreauDumasExtended/n40w120.002.txt", + "path": "GendreauDumasExtended/n40w120.002.txt", + "source_sha256": "58657c1af619f05b0f5b0cc7d8a7acfb07d80f202b10a5b0ff49c6e6fd54494d", + "sha256": "58657c1af619f05b0f5b0cc7d8a7acfb07d80f202b10a5b0ff49c6e6fd54494d" + }, + { + "id": "GendreauDumasExtended/n80w140.002.txt", + "split": "held-out", + "family": "GendreauDumasExtended", + "member": "GendreauDumasExtended/n80w140.002.txt", + "path": "GendreauDumasExtended/n80w140.002.txt", + "source_sha256": "5164c8948942b184550b9ac0e3fa342eb7a2a322c422cf7487e99aae06a05a4e", + "sha256": "5164c8948942b184550b9ac0e3fa342eb7a2a322c422cf7487e99aae06a05a4e" + }, + { + "id": "GendreauDumasExtended/n100w160.002.txt", + "split": "held-out", + "family": "GendreauDumasExtended", + "member": "GendreauDumasExtended/n100w160.002.txt", + "path": "GendreauDumasExtended/n100w160.002.txt", + "source_sha256": "283b26d40c702183a4aa873a500ae04d695b424d628c34945a61ecdee8d263bf", + "sha256": "283b26d40c702183a4aa873a500ae04d695b424d628c34945a61ecdee8d263bf" + }, + { + "id": "GendreauDumasExtended/n60w180.002.txt", + "split": "held-out", + "family": "GendreauDumasExtended", + "member": "GendreauDumasExtended/n60w180.002.txt", + "path": "GendreauDumasExtended/n60w180.002.txt", + "source_sha256": "9df672deec8baf12725a256a7023303d650d86c599db9539f3179a8b80cc454d", + "sha256": "9df672deec8baf12725a256a7023303d650d86c599db9539f3179a8b80cc454d" + }, + { + "id": "GendreauDumasExtended/n20w200.002.txt", + "split": "held-out", + "family": "GendreauDumasExtended", + "member": "GendreauDumasExtended/n20w200.002.txt", + "path": "GendreauDumasExtended/n20w200.002.txt", + "source_sha256": "d835d26fdc88ae53e9a58735ed9572981c477546ee1d5ea37c6d9eaf21b6db46", + "sha256": "d835d26fdc88ae53e9a58735ed9572981c477546ee1d5ea37c6d9eaf21b6db46" + }, + { + "id": "variants/GendreauDumasExtended/customer-due-plus-100/n20w200.003.txt", + "split": "development", + "family": "GendreauDumasExtended", + "member": "GendreauDumasExtended/n20w200.003.txt", + "path": "variants/GendreauDumasExtended/customer-due-plus-100/n20w200.003.txt", + "variant": "customer-due-plus-100", + "source_sha256": "df1740f52be6e936fa914148c00b512093f0b3e154be8683149020dc1a90a9e8", + "sha256": "8c7655127d49bc58a9198198138894dd3fe6180d1036cc6f0adbe2a768b62824" + }, + { + "id": "variants/GendreauDumasExtended/customer-width-half/n40w200.004.txt", + "split": "held-out", + "family": "GendreauDumasExtended", + "member": "GendreauDumasExtended/n40w200.004.txt", + "path": "variants/GendreauDumasExtended/customer-width-half/n40w200.004.txt", + "variant": "customer-width-half", + "source_sha256": "247b7ebc59958f5f6880c4dddd6f88d51b122712200d9733ceaf957e4804a706", + "sha256": "06b46d0300b0a32c004227f24e3fbc47692e56216a134695e0d7f9f2a81c1a6a" + }, + { + "id": "SolomonPotvinBengio/rc_201.1.txt", + "split": "development", + "family": "SolomonPotvinBengio", + "member": "SolomonPotvinBengio/rc_201.1.txt", + "path": "SolomonPotvinBengio/rc_201.1.txt", + "source_sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f", + "sha256": "e0c1dfb4fb08559c01c3ab3193316269fa8a3a215d529e0cc893ef300659766f" + }, + { + "id": "SolomonPotvinBengio/rc_202.2.txt", + "split": "development", + "family": "SolomonPotvinBengio", + "member": "SolomonPotvinBengio/rc_202.2.txt", + "path": "SolomonPotvinBengio/rc_202.2.txt", + "source_sha256": "28f5bbe945a1d11efd7a0be418a6bc647a565a6a3189d702ed085a7e3309567d", + "sha256": "28f5bbe945a1d11efd7a0be418a6bc647a565a6a3189d702ed085a7e3309567d" + }, + { + "id": "SolomonPotvinBengio/rc_201.2.txt", + "split": "held-out", + "family": "SolomonPotvinBengio", + "member": "SolomonPotvinBengio/rc_201.2.txt", + "path": "SolomonPotvinBengio/rc_201.2.txt", + "source_sha256": "295faab3002d7c8e94d0fe136875801ee23c22c66532e8042322ff981e511948", + "sha256": "295faab3002d7c8e94d0fe136875801ee23c22c66532e8042322ff981e511948" + }, + { + "id": "SolomonPotvinBengio/rc_203.3.txt", + "split": "held-out", + "family": "SolomonPotvinBengio", + "member": "SolomonPotvinBengio/rc_203.3.txt", + "path": "SolomonPotvinBengio/rc_203.3.txt", + "source_sha256": "bb9e4dd0b4408e1ddf9f721952c1dfc4d0cdafc062f8ab1f888d120f1f9c8b11", + "sha256": "bb9e4dd0b4408e1ddf9f721952c1dfc4d0cdafc062f8ab1f888d120f1f9c8b11" + }, + { + "id": "SolomonPotvinBengio/rc_204.1.txt", + "split": "held-out", + "family": "SolomonPotvinBengio", + "member": "SolomonPotvinBengio/rc_204.1.txt", + "path": "SolomonPotvinBengio/rc_204.1.txt", + "source_sha256": "c6f38edd2b570bb8e350fef72290c82fdfa83455f4a5a8fa0119c6c58fbba704", + "sha256": "c6f38edd2b570bb8e350fef72290c82fdfa83455f4a5a8fa0119c6c58fbba704" + }, + { + "id": "SolomonPotvinBengio/rc_206.1.txt", + "split": "held-out", + "family": "SolomonPotvinBengio", + "member": "SolomonPotvinBengio/rc_206.1.txt", + "path": "SolomonPotvinBengio/rc_206.1.txt", + "source_sha256": "83b9a4e3178a907c27c37f16856b11a312a4322983b1c503d1f0bfda03ea9cce", + "sha256": "83b9a4e3178a907c27c37f16856b11a312a4322983b1c503d1f0bfda03ea9cce" + }, + { + "id": "SolomonPotvinBengio/rc_208.1.txt", + "split": "held-out", + "family": "SolomonPotvinBengio", + "member": "SolomonPotvinBengio/rc_208.1.txt", + "path": "SolomonPotvinBengio/rc_208.1.txt", + "source_sha256": "c698430e387cd23da8c27611b769e8c72e7497e74c6e4e35bf5cda241cee82f4", + "sha256": "c698430e387cd23da8c27611b769e8c72e7497e74c6e4e35bf5cda241cee82f4" + }, + { + "id": "variants/SolomonPotvinBengio/customer-due-plus-100/rc_205.1.txt", + "split": "development", + "family": "SolomonPotvinBengio", + "member": "SolomonPotvinBengio/rc_205.1.txt", + "path": "variants/SolomonPotvinBengio/customer-due-plus-100/rc_205.1.txt", + "variant": "customer-due-plus-100", + "source_sha256": "926f8fca348023eb526a9737151f0cfb18e80a8afa59caa022463777a890a9c6", + "sha256": "a9d2e771ada12cce6d1b43ee722596479ba3fa0bbb7d173ff662145587b7641f" + }, + { + "id": "variants/SolomonPotvinBengio/customer-width-half/rc_207.4.txt", + "split": "held-out", + "family": "SolomonPotvinBengio", + "member": "SolomonPotvinBengio/rc_207.4.txt", + "path": "variants/SolomonPotvinBengio/customer-width-half/rc_207.4.txt", + "variant": "customer-width-half", + "source_sha256": "12ea91a274a4ab6882bb015c53661205c64163b4ac608896648ca52a7dc78987", + "sha256": "a2caea8e25d9073122648ef14cd71922089b3c9f9af847350663487f806548ba" + }, + { + "id": "AFG/rbg010a.tw", + "split": "development", + "family": "AFG", + "member": "AFG/rbg010a.tw", + "path": "AFG/rbg010a.tw", + "source_sha256": "4a8e0c37052047063b19d698487bae0ed5256bb22de4f675fca3868975f40ef3", + "sha256": "4a8e0c37052047063b19d698487bae0ed5256bb22de4f675fca3868975f40ef3" + }, + { + "id": "AFG/rbg038a.tw", + "split": "development", + "family": "AFG", + "member": "AFG/rbg038a.tw", + "path": "AFG/rbg038a.tw", + "source_sha256": "1279df2651b9937f944e25a6c33182dd93f18ac849ebca663b7e069abceb46af", + "sha256": "1279df2651b9937f944e25a6c33182dd93f18ac849ebca663b7e069abceb46af" + }, + { + "id": "AFG/rbg019d.tw", + "split": "held-out", + "family": "AFG", + "member": "AFG/rbg019d.tw", + "path": "AFG/rbg019d.tw", + "source_sha256": "b0e917e603b43d49243db0d82390977ffa963a97e50dad6c67d6dbde88dbc017", + "sha256": "b0e917e603b43d49243db0d82390977ffa963a97e50dad6c67d6dbde88dbc017" + }, + { + "id": "AFG/rbg050a.tw", + "split": "held-out", + "family": "AFG", + "member": "AFG/rbg050a.tw", + "path": "AFG/rbg050a.tw", + "source_sha256": "afe0b9925a08ced51ec460620ef84425960b23208f55004eb20aa86b8a748117", + "sha256": "afe0b9925a08ced51ec460620ef84425960b23208f55004eb20aa86b8a748117" + }, + { + "id": "AFG/rbg067a.tw", + "split": "held-out", + "family": "AFG", + "member": "AFG/rbg067a.tw", + "path": "AFG/rbg067a.tw", + "source_sha256": "bbdf79985a587fd25ba8cb371830ffca424f4f315f0a93397ffd21dbd5786586", + "sha256": "bbdf79985a587fd25ba8cb371830ffca424f4f315f0a93397ffd21dbd5786586" + }, + { + "id": "AFG/rbg125a.tw", + "split": "held-out", + "family": "AFG", + "member": "AFG/rbg125a.tw", + "path": "AFG/rbg125a.tw", + "source_sha256": "067b6739985430c2924247f14e0ccce8024d43f751803cb585f29b32f70d6092", + "sha256": "067b6739985430c2924247f14e0ccce8024d43f751803cb585f29b32f70d6092" + }, + { + "id": "AFG/rbg233.tw", + "split": "held-out", + "family": "AFG", + "member": "AFG/rbg233.tw", + "path": "AFG/rbg233.tw", + "source_sha256": "6883e6a2c33d94a3a233d21a26c5732d5838c2cac7679fbb3fd19fea6f55321f", + "sha256": "6883e6a2c33d94a3a233d21a26c5732d5838c2cac7679fbb3fd19fea6f55321f" + }, + { + "id": "variants/AFG/customer-due-plus-100/rbg016a.tw", + "split": "development", + "family": "AFG", + "member": "AFG/rbg016a.tw", + "path": "variants/AFG/customer-due-plus-100/rbg016a.tw", + "variant": "customer-due-plus-100", + "source_sha256": "89c48dcf6ee48ca5fe3e2a2d66dce00e2242910374e43a6d7daee79a3860bd80", + "sha256": "eace798b57d686dd5d0406178060daa9aeb1777b7eab0e87de33bec696cb5384" + }, + { + "id": "variants/AFG/customer-width-half/rbg193.tw", + "split": "held-out", + "family": "AFG", + "member": "AFG/rbg193.tw", + "path": "variants/AFG/customer-width-half/rbg193.tw", + "variant": "customer-width-half", + "source_sha256": "e6a1e3f9c5c9ec87481e153454c76489257f4b61428c85bed81518f2780dae9c", + "sha256": "9daa682fcbbdabb66c57e36267d08185ab54996face3f19c4a140721c261af50" + } + ] +} diff --git a/examples/data/tsptw/published/AFG/rbg010a.tw b/examples/data/tsptw/published/AFG/rbg010a.tw new file mode 100644 index 0000000000..8bec350870 --- /dev/null +++ b/examples/data/tsptw/published/AFG/rbg010a.tw @@ -0,0 +1,24 @@ +11 +0 0 0 0 0 0 0 0 0 0 0 +71 0 85 85 85 96 85 85 97 85 77 +50 65 0 77 77 62 77 77 62 77 74 +64 74 88 0 88 80 88 88 82 88 87 +44 54 68 68 0 60 68 68 62 68 67 +51 75 51 51 51 0 51 51 80 51 65 +53 64 78 78 78 68 0 78 69 78 76 +51 67 69 69 69 75 69 0 75 69 63 +43 61 71 71 71 43 71 71 0 71 68 +53 64 78 78 78 68 78 78 69 0 80 +42 66 52 52 52 70 52 52 70 52 0 +0 9396 +0 865 +399 1299 +550 1450 +685 1585 +819 1719 +1208 2108 +1412 2312 +1701 2330 +2413 3313 +3798 4698 +# Sum of service times: 522 diff --git a/examples/data/tsptw/published/AFG/rbg010a.tw.tour b/examples/data/tsptw/published/AFG/rbg010a.tw.tour new file mode 100644 index 0000000000..8479b87ec0 --- /dev/null +++ b/examples/data/tsptw/published/AFG/rbg010a.tw.tour @@ -0,0 +1 @@ +3 1 2 5 4 6 8 7 9 10 diff --git a/examples/data/tsptw/published/Dumas/n20w20.001.txt b/examples/data/tsptw/published/Dumas/n20w20.001.txt new file mode 100644 index 0000000000..789c9a6572 --- /dev/null +++ b/examples/data/tsptw/published/Dumas/n20w20.001.txt @@ -0,0 +1,43 @@ +21 +0 19 17 34 7 20 10 17 28 15 23 29 23 29 21 20 9 16 21 13 12 +19 0 10 41 26 3 27 25 15 17 17 14 18 48 17 6 21 14 17 13 31 +17 10 0 47 23 13 26 15 25 22 26 24 27 44 7 5 23 21 25 18 29 +34 41 47 0 36 39 25 51 36 24 27 38 25 44 54 45 25 28 26 28 27 +7 26 23 36 0 27 11 17 35 22 30 36 30 22 25 26 14 23 28 20 10 +20 3 13 39 27 0 26 27 12 15 14 11 15 49 20 9 20 11 14 11 30 +10 27 26 25 11 26 0 26 31 14 23 32 22 25 31 28 6 17 21 15 4 +17 25 15 51 17 27 26 0 39 31 38 38 38 34 13 20 26 31 36 28 27 +28 15 25 36 35 12 31 39 0 17 9 2 11 56 32 21 24 13 11 15 35 +15 17 22 24 22 15 14 31 17 0 9 18 8 39 29 21 8 4 7 4 18 +23 17 26 27 30 14 23 38 9 9 0 11 2 48 33 23 17 7 2 10 27 +29 14 24 38 36 11 32 38 2 18 11 0 13 57 31 20 25 14 13 17 36 +23 18 27 25 30 15 22 38 11 8 2 13 0 47 34 24 16 7 2 10 26 +29 48 44 44 22 49 25 34 56 39 48 57 47 0 46 48 31 42 46 40 21 +21 17 7 54 25 20 31 13 32 29 33 31 34 46 0 11 29 28 32 25 33 +20 6 5 45 26 9 28 20 21 21 23 20 24 48 11 0 23 19 22 17 32 +9 21 23 25 14 20 6 26 24 8 17 25 16 31 29 23 0 11 15 9 10 +16 14 21 28 23 11 17 31 13 4 7 14 7 42 28 19 11 0 5 3 21 +21 17 25 26 28 14 21 36 11 7 2 13 2 46 32 22 15 5 0 8 25 +13 13 18 28 20 11 15 28 15 4 10 17 10 40 25 17 9 3 8 0 19 +12 31 29 27 10 30 4 27 35 18 27 36 26 21 33 32 10 21 25 19 0 +0 408 +62 68 +181 205 +306 324 +214 217 +51 61 +102 129 +175 186 +250 263 +3 23 +21 49 +79 90 +78 96 +140 154 +354 386 +42 63 +2 13 +24 42 +20 33 +9 21 +275 300 diff --git a/examples/data/tsptw/published/Dumas/n20w20.001.txt.tour b/examples/data/tsptw/published/Dumas/n20w20.001.txt.tour new file mode 100644 index 0000000000..b60af63faf --- /dev/null +++ b/examples/data/tsptw/published/Dumas/n20w20.001.txt.tour @@ -0,0 +1 @@ +16 9 19 17 18 10 5 15 1 11 12 6 13 7 2 4 8 20 3 14 diff --git a/examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt b/examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt new file mode 100644 index 0000000000..d98b26740b --- /dev/null +++ b/examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt @@ -0,0 +1,43 @@ +21 +0 19 17 34 7 20 10 17 28 15 23 29 23 29 21 20 9 16 21 13 12 +19 0 10 41 26 3 27 25 15 17 17 14 18 48 17 6 21 14 17 13 31 +17 10 0 47 23 13 26 15 25 22 26 24 27 44 7 5 23 21 25 18 29 +34 41 47 0 36 39 25 51 36 24 27 38 25 44 54 45 25 28 26 28 27 +7 26 23 36 0 27 11 17 35 22 30 36 30 22 25 26 14 23 28 20 10 +20 3 13 39 27 0 26 27 12 15 14 11 15 49 20 9 20 11 14 11 30 +10 27 26 25 11 26 0 26 31 14 23 32 22 25 31 28 6 17 21 15 4 +17 25 15 51 17 27 26 0 39 31 38 38 38 34 13 20 26 31 36 28 27 +28 15 25 36 35 12 31 39 0 17 9 2 11 56 32 21 24 13 11 15 35 +15 17 22 24 22 15 14 31 17 0 9 18 8 39 29 21 8 4 7 4 18 +23 17 26 27 30 14 23 38 9 9 0 11 2 48 33 23 17 7 2 10 27 +29 14 24 38 36 11 32 38 2 18 11 0 13 57 31 20 25 14 13 17 36 +23 18 27 25 30 15 22 38 11 8 2 13 0 47 34 24 16 7 2 10 26 +29 48 44 44 22 49 25 34 56 39 48 57 47 0 46 48 31 42 46 40 21 +21 17 7 54 25 20 31 13 32 29 33 31 34 46 0 11 29 28 32 25 33 +20 6 5 45 26 9 28 20 21 21 23 20 24 48 11 0 23 19 22 17 32 +9 21 23 25 14 20 6 26 24 8 17 25 16 31 29 23 0 11 15 9 10 +16 14 21 28 23 11 17 31 13 4 7 14 7 42 28 19 11 0 5 3 21 +21 17 25 26 28 14 21 36 11 7 2 13 2 46 32 22 15 5 0 8 25 +13 13 18 28 20 11 15 28 15 4 10 17 10 40 25 17 9 3 8 0 19 +12 31 29 27 10 30 4 27 35 18 27 36 26 21 33 32 10 21 25 19 0 +0 458 +12 118 +131 255 +256 374 +164 267 +1 111 +52 179 +125 236 +200 313 +0 73 +0 99 +29 140 +28 146 +90 204 +304 436 +0 113 +0 63 +0 92 +0 83 +0 71 +225 350 diff --git a/examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt.tour b/examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt.tour new file mode 100644 index 0000000000..35b2ed70c1 --- /dev/null +++ b/examples/data/tsptw/published/GendreauDumasExtended/n20w120.001.txt.tour @@ -0,0 +1 @@ +6 16 9 19 17 18 12 10 11 5 1 15 2 7 4 13 20 3 8 14 diff --git a/examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt b/examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt new file mode 100644 index 0000000000..2418609f7a --- /dev/null +++ b/examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt @@ -0,0 +1,41 @@ +20 +0 45.1774 35.0571 35 57.0088 55.7136 52.2015 52 50.2892 51.4782 38.0526 33.541 26.9258 39.0512 11.1803 13.0384 25.2982 42.9418 19.2354 35.4683 +55.1774 10 20.198 20.7703 69.1692 64.1202 64.9181 60.6063 63.2541 55.618 17.2801 28.6011 34.4131 35.807 46.1386 42.1403 52.0595 27.4642 36.9258 36.1725 +45.0571 20.198 10 12 66.648 62.2015 62 58.3839 60.1597 54.1475 13 23.9284 25.6205 33.5372 35.9615 32.0227 44.8281 28.2483 26.7631 32.4722 +45 20.7703 12 10 68.5235 64.1202 63.8516 60.2892 62 56.0977 13.6056 25.8114 24.1421 35.4951 35.4951 32.0227 46.1248 30.2237 26.2788 34.3516 +67.0088 69.1692 66.648 68.5235 10 15.831 15 18.6023 17 24.1421 67.7754 52.72 75.7647 43.541 70.208 64.037 41.7805 51.8808 68.1378 44.176 +65.7136 64.1202 62.2015 64.1202 15.831 10 15.3852 14 16.4031 18.6023 63.1413 48.3275 72.2013 38.7924 67.8705 61.4782 40.4631 46.7151 65.0091 39.8329 +62.2015 64.9181 62 63.8516 15 15.3852 10 15.3852 12 21.1803 63.2259 48.0789 70.8276 39.1548 65.2268 59.0408 36.9258 47.8021 63.1507 39.5466 +62 60.6063 58.3839 60.2892 18.6023 14 15.3852 10 15 15.831 59.3964 44.4819 68.2151 35.0799 63.9351 57.5184 36.8328 43.2866 61.0098 35.9615 +60.2892 63.2541 60.1597 62 17 16.4031 12 15 10 20.4403 61.4296 46.2491 68.8558 37.4591 63.2353 57.0425 35 46.2353 61.1566 37.7308 +61.4782 55.618 54.1475 56.0977 24.1421 18.6023 21.1803 15.831 20.4403 10 54.9222 40.4138 65 30.6155 62.2015 55.607 37.0185 38.178 58.3735 32.0907 +48.0526 17.2801 13 13.6056 67.7754 63.1413 63.2259 59.3964 61.4296 54.9222 10 25.2643 27.6918 34.3516 38.8617 35.02 47.2022 28 29.6469 33.7065 +43.541 28.6011 23.9284 25.8114 52.72 48.3275 48.0789 44.4819 46.2491 40.4138 25.2643 10 35.4951 20 38.2843 32.0227 33.7697 19.434 31.095 18.544 +36.9258 34.4131 25.6205 24.1421 75.7647 72.2013 70.8276 68.2151 68.8558 65 27.6918 35.4951 10 45.3553 25.8114 26.2788 48.0132 42.6956 19.2195 43.0606 +49.0512 35.807 33.5372 35.4951 43.541 38.7924 39.1548 35.0799 37.4591 30.6155 34.3516 20 45.3553 10 46.0555 39.4109 32.0227 19.434 40.0832 13.6056 +21.1803 46.1386 35.9615 35.4951 70.208 67.8705 65.2268 63.9351 63.2353 62.2015 38.8617 38.2843 25.8114 46.0555 10 16.7082 39.0689 47.5366 19.2195 42.7567 +23.0384 42.1403 32.0227 32.0227 64.037 61.4782 59.0408 57.5184 57.0425 55.607 35.02 32.0227 26.2788 39.4109 16.7082 10 33.5372 41.4006 17.2111 36.0768 +35.2982 52.0595 44.8281 46.1248 41.7805 40.4631 36.9258 36.8328 35 37.0185 47.2022 33.7697 48.0132 32.0227 39.0689 33.5372 10 40.2655 39.1548 29.0263 +52.9418 27.4642 28.2483 30.2237 51.8808 46.7151 47.8021 43.2866 46.2353 38.178 28 19.434 42.6956 19.434 47.5366 41.4006 40.2655 10 39.8329 21.4018 +29.2354 36.9258 26.7631 26.2788 68.1378 65.0091 63.1507 61.0098 61.1566 58.3735 29.6469 31.095 19.2195 40.0832 19.2195 17.2111 39.1548 39.8329 10 37.2029 +45.4683 36.1725 32.4722 34.3516 44.176 39.8329 39.5466 35.9615 37.7308 32.0907 33.7065 18.544 43.0606 13.6056 42.7567 36.0768 29.0263 21.4018 37.2029 10 +0 960 +335 455 +537 657 +375 495 +146 266 +149 269 +194 314 +246 366 +165 285 +80 200 +440 560 +326 446 +397 517 +39 159 +11 131 +566 686 +268 388 +268 388 +105 225 +344 464 diff --git a/examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt.tour b/examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt.tour new file mode 100644 index 0000000000..7f65605637 --- /dev/null +++ b/examples/data/tsptw/published/SolomonPotvinBengio/rc_201.1.txt.tour @@ -0,0 +1 @@ +14 18 13 9 5 4 6 8 7 16 19 11 17 1 10 3 12 2 15 diff --git a/examples/data/tsptw/tiny/decimal.txt b/examples/data/tsptw/tiny/decimal.txt new file mode 100644 index 0000000000..b9d37e8f20 --- /dev/null +++ b/examples/data/tsptw/tiny/decimal.txt @@ -0,0 +1,7 @@ +3 +0 0.25 2 +1 0 0.50 +0.75 3 0 +0 4 +1 1 +0 2 diff --git a/examples/data/tsptw/tiny/folded-service.txt b/examples/data/tsptw/tiny/folded-service.txt new file mode 100644 index 0000000000..89df0a4402 --- /dev/null +++ b/examples/data/tsptw/tiny/folded-service.txt @@ -0,0 +1,8 @@ +3 +0 1 3 +6 0 5 +3 4 0 +0 20 +0 10 +0 15 +# Outgoing arcs already include service: customer 1 = 4, customer 2 = 2. diff --git a/examples/data/tsptw/tiny/late-customer.txt b/examples/data/tsptw/tiny/late-customer.txt new file mode 100644 index 0000000000..5566375c34 --- /dev/null +++ b/examples/data/tsptw/tiny/late-customer.txt @@ -0,0 +1,7 @@ +3 +0 5 9 +4 0 1 +2 8 0 +0 20 +0 4 +0 12 diff --git a/examples/data/tsptw/tiny/late-return.txt b/examples/data/tsptw/tiny/late-return.txt new file mode 100644 index 0000000000..392addfbe4 --- /dev/null +++ b/examples/data/tsptw/tiny/late-return.txt @@ -0,0 +1,7 @@ +3 +0 2 9 +4 0 1 +2 8 0 +0 7 +5 10 +0 12 diff --git a/examples/data/tsptw/tiny/waiting.txt b/examples/data/tsptw/tiny/waiting.txt new file mode 100644 index 0000000000..a120d114ff --- /dev/null +++ b/examples/data/tsptw/tiny/waiting.txt @@ -0,0 +1,8 @@ +3 +0 2 9 +4 0 1 +2 8 0 +0 20 +5 10 +0 12 +# Asymmetric and nonmetric: 0->2 is more costly than 0->1->2. diff --git a/examples/tsptw-data.hh b/examples/tsptw-data.hh new file mode 100644 index 0000000000..5d9e0db35a --- /dev/null +++ b/examples/tsptw-data.hh @@ -0,0 +1,143 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +#ifndef GECODE_EXAMPLES_TSPTW_DATA_HH +#define GECODE_EXAMPLES_TSPTW_DATA_HH + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/// Canonical matrix data shared by the native TSPTW examples. +namespace TSPTW { + struct Instance { + int n, scale; + int cost_limit, time_limit; + std::vector cost, ready, due; + int arc(int from, int to) const { return cost[from*n+to]; } + }; + + /// Checked arithmetic for total bounds, without rounding or saturation. + inline std::int64_t add_cost(std::int64_t a, std::int64_t b) { + if (b<0 || a<0 || a>std::numeric_limits::max()-b) + throw std::overflow_error("TSPTW bound arithmetic overflow"); + return a+b; + } + + /// Paths may use any vertex, relaxing residual reachability on nonmetric data. + inline std::vector shortest_paths(const Instance& data) { + const int n=data.n; + std::vector distance(data.cost.begin(),data.cost.end()); + for (int i=0; i 1 && token[0] == '.') token.insert(0,"0"); + if (dot != std::string::npos) { + while (!token.empty() && token.back() == '0') token.pop_back(); + if (!token.empty() && token.back() == '.') token.pop_back(); + } + Decimal result = {0, 0}; + bool fraction = false, digit = false; + for (std::size_t i=0; i '9') + throw std::runtime_error("invalid nonnegative decimal: " + original); + digit = true; + if (result.value > (Gecode::Int::Limits::max-(c-'0'))/10) + throw std::runtime_error("decimal exceeds Gecode integer limits: " + original); + result.value = result.value*10 + c-'0'; + if (fraction) result.places++; + } + if (!digit) + throw std::runtime_error("invalid decimal: " + original); + return result; + } + } + + /// Read decimal text exactly; comments begin with #. No service is added. + inline Instance read(std::istream& input) { + std::vector tokens; + std::string line, token; + while (std::getline(input,line)) { + std::istringstream row(line.substr(0,line.find('#'))); + while (row >> token) tokens.push_back(token); + } + if (input.bad() || tokens.empty()) + throw std::runtime_error("cannot read TSPTW instance"); + if (tokens[0].find_first_not_of("0123456789") != std::string::npos) + throw std::runtime_error("node count must be an integer"); + const long long n = Detail::decimal(tokens[0]).value; + if (n < 2 || n*n > Gecode::Int::Limits::max || + static_cast(1+n*n+2*n) != tokens.size()) + throw std::runtime_error("invalid TSPTW dimensions"); + std::vector values; + int places = 0; + for (std::size_t i=1; i(n); + data.scale = 1; + for (int i=0; i Gecode::Int::Limits::max/10) + throw std::runtime_error("decimal scale exceeds Gecode integer limits"); + data.scale *= 10; + } + std::vector scaled; + for (std::size_t i=0; i Gecode::Int::Limits::max/10) + throw std::runtime_error("scaled value exceeds Gecode integer limits"); + value *= 10; + } + scaled.push_back(static_cast(value)); + } + data.cost.assign(scaled.begin(),scaled.begin()+n*n); + for (int i=0; i data.due[i]) + throw std::runtime_error("window ready time exceeds due time"); + } + if (data.ready[0] != 0) + throw std::runtime_error("nonzero start-depot ready time is unsupported"); + // Bound every n-arc tour and waiting-plus-travel arithmetic, including + // infeasible prefixes. Models can safely use these as IntVar domains. + const long long cost_limit = n * *std::max_element(data.cost.begin(),data.cost.end()); + const long long time_limit = cost_limit + *std::max_element(data.due.begin(),data.due.end()); + if (time_limit > Gecode::Int::Limits::max) + throw std::runtime_error("tour arithmetic exceeds Gecode integer limits"); + data.cost_limit = static_cast(cost_limit); + data.time_limit = static_cast(time_limit); + return data; + } + + inline Instance read(const std::string& path) { + std::ifstream input(path.c_str()); + if (!input) throw std::runtime_error("cannot open TSPTW instance: " + path); + return read(input); + } +} +#endif diff --git a/examples/tsptw-transition.hh b/examples/tsptw-transition.hh new file mode 100644 index 0000000000..9584b44675 --- /dev/null +++ b/examples/tsptw-transition.hh @@ -0,0 +1,355 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +#ifndef GECODE_EXAMPLES_TSPTW_TRANSITION_HH +#define GECODE_EXAMPLES_TSPTW_TRANSITION_HH + +#include "tsptw-data.hh" +#include +#include +#include +#include + +namespace TSPTW { + + /// One run owns these aggregates; clones and callbacks count repeated work. + struct Measurements { + std::uint64_t bound_calls=0, inference_calls=0; + double bound_ms=0, inference_ms=0, propagation_ms=0; + }; + + /// One concrete visit per search edge, with an explicit depot return. + class Transition : public Gecode::Space { + public: + enum Representation { LAZY, EAGER }; + enum BoundMode { PREFIX, MIN_ARCS }; + enum ResidualPropagation { OFF, CHEAP, UNARY }; + std::shared_ptr data; + std::vector unvisited; + int location=0, time=0, prefix_cost=0, depth=0; + bool returned=false; + + explicit Transition(std::shared_ptr instance, + Representation representation=LAZY, + ResidualPropagation propagation=OFF, + std::shared_ptr measurements=nullptr); + Transition(Transition& s); + Gecode::Space* copy(void) override { return new Transition(*this); } + Gecode::SpaceComparison compare(const Gecode::Space& s) const override; + void constrain(const Gecode::Space& s) override; + std::vector tour(void) const; + int cost(void) const { return prefix_cost; } + int allocated_positions(void) const { + return representation==LAZY ? lazy.prefix_size() : eager.size(); + } + Gecode::IntVar position(int p) const { + return representation==LAZY ? lazy[p] : eager[p]; + } + std::vector actions(void) const { + std::vector result; + if (returned) return result; + if (unvisited.empty()) { + if (applicable(0)) result.push_back(0); + } else { + for (int node : unvisited) + if (applicable(node)) result.push_back(node); + } + return result; + } + std::int64_t bound(BoundMode mode) const; + static Gecode::Search::HeuristicGuidance + guidance(BoundMode mode=PREFIX, + std::shared_ptr measurements=nullptr) { + return {Gecode::Search::HeuristicGuidance::MINIMIZE, + [mode,measurements](const Gecode::Space& s)->std::int64_t { + const auto start=std::chrono::steady_clock::now(); + const auto result=static_cast(s).bound(mode); + if (measurements) { + measurements->bound_calls++; + measurements->bound_ms+=std::chrono::duration( + std::chrono::steady_clock::now()-start).count(); + } + return result; + }}; + } + + /// Cost-aware Pareto labels for this model's history-independent rules. + static Gecode::Search::StatePruning + pareto_pruning(unsigned long int descriptor_limit=0) { + using Pruning=Gecode::Search::StatePruning; + return {[](const Gecode::Space& s)->std::optional { + const auto& state=static_cast(s); + Pruning::Descriptor descriptor{0,{state.location}, + {state.time,state.prefix_cost}}; + descriptor.key.insert(descriptor.key.end(), + state.unvisited.begin(),state.unvisited.end()); + for (std::int64_t value : descriptor.key) + descriptor.hash=descriptor.hash*31+std::hash{}(value); + return descriptor; + },[](const Pruning::Descriptor& a, const Pruning::Descriptor& b) { + if (a.label==b.label) return Pruning::EQUIVALENT; + if (a.label[0]<=b.label[0] && a.label[1]<=b.label[1]) + return Pruning::DOMINATES; + if (a.label[0]>=b.label[0] && a.label[1]>=b.label[1]) + return Pruning::DOMINATED; + return Pruning::INCOMPARABLE; + },descriptor_limit}; + } + + private: + class VisitBrancher; + Representation representation; + ResidualPropagation propagation; + std::shared_ptr> distance; + std::shared_ptr measurements; + std::vector residual_actions; + Gecode::OpenIntVarList lazy; + Gecode::IntVarArray eager; + int ceiling; + + bool applicable(int node) const { + return time+data->arc(location,node)<=data->due[node] && + prefix_cost+data->arc(location,node) actions; + VisitChoice(const Gecode::Brancher& b, int p, std::vector a) + : Choice(b,static_cast(a.size())), + position(p), actions(std::move(a)) {} + void archive(Gecode::Archive& e) const override { + Choice::archive(e); + e << position << static_cast(actions.size()); + for (int node : actions) e << node; + } + }; + public: + VisitBrancher(Gecode::Home home) : Brancher(home) {} + VisitBrancher(Gecode::Space& home, VisitBrancher& b) : Brancher(home,b) {} + bool status(const Gecode::Space& home) const override { + return !static_cast(home).returned; + } + bool prepare(Gecode::Space& home, Gecode::Search::Statistics& stat, + const Gecode::Search::Options&) override { + auto& s=static_cast(home); + (void) s.materialize(s.depth); + (void) s.status(stat); + return true; + } + const Gecode::Choice* choice(Gecode::Space& home) override { + const auto& s=static_cast(home); + return new VisitChoice(*this,s.depth,s.actions()); + } + const Gecode::Choice* choice(const Gecode::Space&, + Gecode::Archive& e) override { + int position; + unsigned int count; + e >> position >> count; + std::vector actions(count); + for (int& node : actions) e >> node; + return new VisitChoice(*this,position,std::move(actions)); + } + Gecode::ExecStatus commit(Gecode::Space& home, const Gecode::Choice& c, + unsigned int alternative) override { + auto& s=static_cast(home); + const auto& choice=static_cast(c); + const int node=choice.actions[alternative]; + const auto customer=std::find(s.unvisited.begin(),s.unvisited.end(),node); + if (s.returned || choice.position!=s.depth || !s.applicable(node) || + (node==0 ? !s.unvisited.empty() : customer==s.unvisited.end())) + return Gecode::ES_FAILED; + // BAB replay can skip prepare for historical choices. Allocating the + // recorded position has no visit effects, just as in prepare. + Gecode::IntVar next=s.materialize(choice.position); + if (home.failed() || Gecode::me_failed(Gecode::Int::IntView(next).eq(home,node))) + return Gecode::ES_FAILED; + const int arc=s.data->arc(s.location,node); + s.time=std::max(s.time+arc,s.data->ready[node]); + s.prefix_cost+=arc; + s.location=node; + s.depth++; + if (node==0) { + s.returned=true; + if (s.representation==LAZY) s.lazy.close(home); + } else { + s.unvisited.erase(customer); + } + // Rebuild from the new residual state, including historical BAB replay. + if (!s.refresh_inference() || (!s.returned && s.actions().empty())) + return Gecode::ES_FAILED; + return home.failed() ? Gecode::ES_FAILED : Gecode::ES_OK; + } + void print(const Gecode::Space&, const Gecode::Choice& c, + unsigned int alternative, std::ostream& out) const override { + const auto& choice=static_cast(c); + out << "visit[" << choice.position << "] = " << choice.actions[alternative]; + } + Gecode::Actor* copy(Gecode::Space& home) override { + return new (home) VisitBrancher(home,*this); + } + size_t dispose(Gecode::Space& home) override { + (void) Brancher::dispose(home); + return sizeof(*this); + } + }; + + inline Transition::Transition(std::shared_ptr instance, + Representation representation0, + ResidualPropagation propagation0, + std::shared_ptr measurements0) + : data(std::move(instance)), unvisited(data->n-1), + representation(representation0), propagation(propagation0), + measurements(std::move(measurements0)), ceiling(data->cost_limit+1) { + std::iota(unvisited.begin(),unvisited.end(),1); + if (propagation!=OFF) + distance=std::make_shared>( + shortest_paths(*data)); + if (representation==LAZY) + lazy=Gecode::OpenIntVarList(*this, + [](Gecode::Space& home, int) { + const auto& s=static_cast(home); + return Gecode::IntVar(home,0,s.data->n-1); + },data->n); + else + eager=Gecode::IntVarArray(*this,data->n,0,data->n-1); + (void) new (*this) VisitBrancher(*this); + if (!refresh_inference() || actions().empty()) fail(); + } + + inline Transition::Transition(Transition& s) + : Space(s), data(s.data), unvisited(s.unvisited), location(s.location), + time(s.time), prefix_cost(s.prefix_cost), depth(s.depth), + returned(s.returned), representation(s.representation), + propagation(s.propagation), distance(s.distance), measurements(s.measurements), + residual_actions(s.residual_actions), ceiling(s.ceiling) { + if (representation==LAZY) lazy.update(*this,s.lazy); + else eager.update(*this,s.eager); + } + + inline std::int64_t Transition::bound(BoundMode mode) const { + if (returned || mode==PREFIX) return prefix_cost; + if (unvisited.empty()) return add_cost(prefix_cost,data->arc(location,0)); + std::int64_t incoming=0, outgoing=0; + int enter=std::numeric_limits::max(); + int returning=std::numeric_limits::max(); + for (int u : unvisited) { + enter=std::min(enter,data->arc(location,u)); + returning=std::min(returning,data->arc(u,0)); + int leave=data->arc(u,0), arrive=data->arc(location,u); + for (int v : unvisited) if (u!=v) { + leave=std::min(leave,data->arc(u,v)); + arrive=std::min(arrive,data->arc(v,u)); + } + outgoing=add_cost(outgoing,leave); + incoming=add_cost(incoming,arrive); + } + outgoing=add_cost(outgoing,enter); + incoming=add_cost(incoming,returning); + return add_cost(prefix_cost,std::max(incoming,outgoing)); + } + + /// Every residual tour induces nonoverlapping minimum-outgoing intervals. + class ResidualSchedule : public Gecode::Space { + public: + Gecode::IntVarArray starts; + ResidualSchedule(const Instance& data, const std::vector& unvisited, + int location, int time, + const std::vector& distance) + : starts(*this,static_cast(unvisited.size()),0,data.time_limit) { + Gecode::IntArgs durations(starts.size()); + for (int k=0; k(data.ready[u], + add_cost(time,distance[location*data.n+u])); + const auto upper=std::min(data.due[u], + std::min(data.due[0]-distance[u*data.n], + data.due[0]-duration)); + if (lower>upper) { fail(); return; } + Gecode::dom(*this,starts[k],static_cast(lower),static_cast(upper)); + } + Gecode::unary(*this,starts,durations,Gecode::IPL_DEF); + } + ResidualSchedule(ResidualSchedule& s) : Space(s) { + starts.update(*this,s.starts); + } + Gecode::Space* copy(void) override { return new ResidualSchedule(*this); } + }; + + inline bool Transition::refresh_inference(void) { + if (propagation==OFF) return true; + const auto start=std::chrono::steady_clock::now(); + residual_actions.clear(); + bool feasible=true; + if (!returned && unvisited.empty()) { + if (time+data->arc(location,0)<=data->due[0]) residual_actions.push_back(0); + else feasible=false; + } else if (!returned && propagation==CHEAP) { + // Shortest paths may use unavailable vertices: this weakens the check + // but keeps it necessary for asymmetric and nonmetric travel matrices. + for (int u : unvisited) { + const auto earliest=std::max(data->ready[u], + add_cost(time,(*distance)[location*data->n+u])); + if (earliest>data->due[u] || + add_cost(earliest,(*distance)[u*data->n])>data->due[0]) { + feasible=false; + break; + } + } + } else if (!returned) { + ResidualSchedule schedule(*data,unvisited,location,time,*distance); + const auto propagation_start=std::chrono::steady_clock::now(); + feasible=schedule.status()!=Gecode::SS_FAILED; + if (measurements) + measurements->propagation_ms+=std::chrono::duration( + std::chrono::steady_clock::now()-propagation_start).count(); + if (feasible) + for (std::size_t k=0; karc(location,u),data->ready[u]); + if (schedule.starts[static_cast(k)].in(earliest)) + residual_actions.push_back(u); + } + } + if (measurements) { + measurements->inference_calls++; + measurements->inference_ms+=std::chrono::duration( + std::chrono::steady_clock::now()-start).count(); + } + return feasible; + } + + inline Gecode::SpaceComparison + Transition::compare(const Gecode::Space& s0) const { + const auto& s=static_cast(s0); + if (cost()==s.cost()) return Gecode::SC_EQUIVALENT; + return cost()(s0).cost()); + if (cost()>=ceiling || (!returned && actions().empty())) fail(); + } + + inline std::vector Transition::tour(void) const { + if (!returned || depth!=data->n) + throw std::runtime_error("incomplete transition solution"); + std::vector result; + for (int p=0; p + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "tsptw-data.hh" +#include "tsptw-transition.hh" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Gecode; + +// A costed circuit with a departure-to-return timing chain. Time slack +// remains unbranched: a fixed route has an earliest feasible schedule. +class TSPTWClosed : public Space { +public: + std::shared_ptr data; + IntVarArray successor, arc_cost, visit; + IntVar departure, returning, total; + + explicit TSPTWClosed(std::shared_ptr instance, + IntPropLevel circuit_propagation, bool size_branching) + : data(std::move(instance)), + successor(*this,data->n,0,data->n-1), + arc_cost(*this,data->n,0,data->cost_limit), + visit(*this,data->n-1,0,data->time_limit), + departure(*this,0,0), returning(*this,0,data->due[0]), + total(*this,0,data->cost_limit) { + const int n=data->n; + // Direct arcs need not bound paths in nonmetric data. Shortest paths + // relax the tour and give sound elapsed-time bounds in both directions. + const auto shortest=TSPTW::shortest_paths(*data); + std::vector lower(n,0), upper(n,data->due[0]); + for (int i=1; i(data->ready[i],shortest[i]); + const long long hi=std::min(data->due[i], + data->due[0]-shortest[i*n]); + if (lo>hi) { fail(); return; } + lower[i]=static_cast(lo); + upper[i]=static_cast(hi); + dom(*this,visit[i-1],lower[i],upper[i]); + } + circuit(*this,IntArgs(n*n,data->cost.data()),successor,arc_cost,total, + circuit_propagation); + for (int i=0; iarc(i,j); + if (static_cast(lower[i])+cost>upper[j]) { + rel(*this,successor[i],IRT_NQ,j); + continue; + } + BoolVar edge(*this,0,1); + rel(*this,successor[i],IRT_EQ,j,Reify(edge,RM_EQV)); + const IntVar from=i==0 ? departure : visit[i-1]; + const IntVar to=j==0 ? returning : visit[j-1]; + linear(*this,IntArgs({1,-1}),IntVarArgs({from,to}),IRT_LQ,-cost, + Reify(edge,RM_IMP),IPL_BND); + } + } + branch(*this,arc_cost,size_branching ? INT_VAR_SIZE_MIN() : + INT_VAR_REGRET_MAX_MAX(),INT_VAL_MIN()); + branch(*this,successor,INT_VAR_SIZE_MIN(),INT_VAL_MIN()); + } + + TSPTWClosed(TSPTWClosed& s) : Space(s), data(s.data) { + successor.update(*this,s.successor); + arc_cost.update(*this,s.arc_cost); + visit.update(*this,s.visit); + departure.update(*this,s.departure); + returning.update(*this,s.returning); + total.update(*this,s.total); + } + Space* copy(void) override { return new TSPTWClosed(*this); } + int cost(void) const { return total.val(); } + static Search::HeuristicGuidance guidance(void) { + return {Search::HeuristicGuidance::MINIMIZE, + [](const Space& s)->std::int64_t { + return static_cast(s).total.min(); + }}; + } + SpaceComparison compare(const Space& s0) const override { + const auto& s=static_cast(s0); + if (total.val()==s.total.val()) return SC_EQUIVALENT; + return total.val()(s0); + rel(*this,total,IRT_LE,s.total.val()); + } + std::vector tour(void) const { + std::vector result; + int node=0; + for (int i=1; in; i++) { + node=successor[node].val(); + if (node==0) throw std::runtime_error("incomplete circuit solution"); + result.push_back(node); + } + if (successor[node].val()!=0) + throw std::runtime_error("circuit solution does not return to depot"); + return result; + } +}; + +struct TourValue { + int cost, return_time; +}; + +// Check concrete routes independently of the CP timing and cost variables. +static TourValue evaluate_tour(const TSPTW::Instance& data, + const std::vector& tour) { + if (tour.size()!=static_cast(data.n-1)) + throw std::runtime_error("tour omits customers or has extra visits"); + std::vector seen(data.n,false); + for (int node : tour) { + if (node<=0 || node>=data.n || seen[node]) + throw std::runtime_error("tour has a duplicate or invalid customer"); + seen[node]=true; + } + long long time=0, cost=0; + int previous=0; + for (std::size_t i=0; i<=tour.size(); i++) { + const int node=i==tour.size() ? 0 : tour[i]; + const int arc=data.arc(previous,node); + time=std::max(time+arc,data.ready[node]); + cost+=arc; + if (time>data.due[node]) + throw std::runtime_error(node==0 ? "late return" : "late customer"); + previous=node; + } + // The reader bounds all n-arc tours and waiting-plus-travel arithmetic. + return {static_cast(cost),static_cast(time)}; +} + +static std::vector read_tour(const std::string& path) { + std::ifstream input(path); + if (!input) throw std::runtime_error("cannot open seed tour: "+path); + std::vector tour; + std::string token; + while (input>>token) { + if (token.find_first_not_of("0123456789")!=std::string::npos) + throw std::runtime_error("invalid seed tour IDs"); + tour.push_back(std::stoi(token)); + } + if (input.bad()) throw std::runtime_error("cannot read seed tour"); + return tour; +} + +static std::unique_ptr +make_seed(TSPTWClosed& root, const std::vector& tour, TourValue value) { + if (root.status()==SS_FAILED) + throw std::runtime_error("checked seed rejected by CP root"); + std::unique_ptr seed(static_cast(root.clone())); + int previous=0; + for (int node : tour) { + rel(*seed,seed->successor[previous],IRT_EQ,node); + previous=node; + } + rel(*seed,seed->successor[previous],IRT_EQ,0); + if (seed->status()!=SS_SOLVED || seed->total.val()!=value.cost) + throw std::runtime_error("checked seed rejected by CP model"); + return seed; +} + +static std::unique_ptr +make_seed(TSPTW::Transition& root, const std::vector& tour, TourValue value) { + if (root.status()==SS_FAILED) + throw std::runtime_error("checked seed rejected by transition root"); + std::unique_ptr seed( + static_cast(root.clone())); + Search::Statistics stat; + Search::Options options; + // Replay checked customer IDs and then the explicit return using the same + // decisions as search, without directly installing residual state. + for (std::size_t p=0; p<=tour.size(); p++) { + const int node=p==tour.size() ? 0 : tour[p]; + if (seed->status()!=SS_BRANCH || !seed->prepare(stat,options)) + throw std::runtime_error("checked seed rejected by transition model"); + const auto actions=seed->actions(); + const auto action=std::find(actions.begin(),actions.end(),node); + if (action==actions.end()) + throw std::runtime_error("checked seed has no applicable transition"); + std::unique_ptr choice(seed->choice()); + seed->commit(*choice,static_cast(action-actions.begin())); + } + if (seed->status()!=SS_SOLVED || seed->cost()!=value.cost) + throw std::runtime_error("checked seed rejected by transition model"); + return seed; +} + +static std::string exact_text(int value, int scale) { + std::string text=std::to_string(value/scale); + if (value%scale) { + std::string fraction=std::to_string(scale+value%scale).substr(1); + while (fraction.back()=='0') fraction.pop_back(); + text+='.'+fraction; + } + return text; +} + +static void write_tour(const std::vector& tour, TourValue value, int scale) { + std::cout << "\"tour\":["; + for (std::size_t i=0; i time_ms, node_limit; + Search::CABSOptions cabs; + Search::ColumnOptions column; + bool quota_set=false, width_set=false, frontier_set=false; + bool closed_settings_set=false; +}; + +static unsigned long long read_limit(const std::string& text) { + if (text.empty() || text.find_first_not_of("0123456789")!=std::string::npos) + throw std::runtime_error("limits must be nonnegative integers"); + return std::stoull(text); +} + +static RunOptions read_options(int argc, char* argv[]) { + if (argc<2) + throw std::runtime_error("usage: tsptw INSTANCE [--engine bab|rbs|astar|cabs|column] " + "[--formulation closed|transition|eager] [--state-pruning off|pareto] " + "[--bound prefix|min-arcs] [--residual-propagation off|unary] " + "[--circuit-propagation dom|val] [--cost-branching regret|size] " + "[--seed TOUR] [--time-ms N] [--node-limit N] " + "[--max-width N] [--frontier-limit N] [--initial-quota N] [--quota-growth N]"); + RunOptions options; + options.instance=argv[1]; + for (int i=2; istd::numeric_limits::max()) + throw std::runtime_error("search limit is too large"); + if (option=="--max-width") { + options.cabs.max_width=limit; options.width_set=true; + } else if (option=="--frontier-limit") { + options.cabs.frontier_limit=options.column.frontier_limit=limit; + options.frontier_set=true; + } else { + if (option=="--initial-quota") options.column.initial_quota=limit; + else options.column.quota_growth=limit; + options.quota_set=true; + } + } else throw std::runtime_error("unknown option: "+option); + } + if (options.engine!="bab" && options.engine!="astar" && + options.engine!="cabs" && options.engine!="column" && options.engine!="rbs") + throw std::runtime_error("engine must be bab, rbs, astar, cabs, or column"); + if (options.formulation!="closed" && options.formulation!="transition" && + options.formulation!="eager") + throw std::runtime_error("formulation must be closed, transition, or eager"); + if (options.circuit_propagation!="dom" && options.circuit_propagation!="val") + throw std::runtime_error("circuit propagation must be dom or val"); + if (options.cost_branching!="regret" && options.cost_branching!="size") + throw std::runtime_error("cost branching must be regret or size"); + if (options.formulation!="closed" && + (options.closed_settings_set || options.engine=="rbs")) + throw std::runtime_error("circuit/branching settings and rbs require closed"); + if (options.state_pruning!="off" && options.state_pruning!="pareto") + throw std::runtime_error("state pruning must be off or pareto"); + if (options.bound!="prefix" && options.bound!="min-arcs") + throw std::runtime_error("bound must be prefix or min-arcs"); + if (options.residual_propagation!="off" && options.residual_propagation!="cheap" && + options.residual_propagation!="unary") + throw std::runtime_error("residual propagation must be off, cheap or unary"); + if (options.formulation=="closed" && + (options.bound!="prefix" || options.residual_propagation!="off")) + throw std::runtime_error("bound and residual propagation switches require transition/eager"); + if (options.state_pruning=="pareto" && + (options.formulation=="closed" || options.engine=="bab")) + throw std::runtime_error("Pareto pruning requires transition/eager A*, CABS, or column"); + if (options.width_set && options.engine!="cabs") + throw std::runtime_error("width limits require cabs"); + if (options.frontier_set && options.engine!="cabs" && options.engine!="column") + throw std::runtime_error("frontier limits require cabs or column"); + if (options.quota_set && options.engine!="column") + throw std::runtime_error("quota settings require column"); + if (!options.column.initial_quota) + throw std::runtime_error("initial quota must be positive"); + return options; +} + +class LimitsStop : public Search::Stop { + std::unique_ptr time, nodes; +public: + explicit LimitsStop(const RunOptions& options) { + if (options.time_ms) time.reset(Search::Stop::time(*options.time_ms)); + if (options.node_limit) nodes.reset(Search::Stop::node(*options.node_limit)); + } + bool stop(const Search::Statistics& s, const Search::Options& o) override { + return (time && time->stop(s,o)) || (nodes && nodes->stop(s,o)); + } +}; + +template +static int run_search(const RunOptions& options, Model& root, + const Search::Options& search, + std::chrono::steady_clock::time_point start, + std::shared_ptr measurements=nullptr) { + auto elapsed=[start]() { + return std::chrono::duration( + std::chrono::steady_clock::now()-start).count(); + }; + const auto& data=root.data; + std::optional incumbent; + std::vector best_tour; + if (!options.seed.empty()) { + best_tour=read_tour(options.seed); + incumbent=evaluate_tour(*data,best_tour); + } + std::unique_ptr seed; + if (incumbent) seed=make_seed(root,best_tour,*incumbent); + int root_positions=0, prepared_positions=0, solution_positions=0; + if constexpr (std::is_same::value) { + if (seed) solution_positions=seed->allocated_positions(); + root_positions=root.allocated_positions(); + if (root.status()==SS_BRANCH) { + Search::Statistics stat; + // Record the first prepared prefix without committing a visit. + (void) root.prepare(stat,search); + } + prepared_positions=root.allocated_positions(); + } + const auto guidance=[&]() { + if constexpr (std::is_same::value) + return Model::guidance(options.bound=="prefix" ? Model::PREFIX : Model::MIN_ARCS, + measurements); + else return Model::guidance(); + }(); + Search::StatePruning pruning; + if constexpr (std::is_same::value) + if (options.state_pruning=="pareto") pruning=Model::pareto_pruning(); + const double preprocessing_ms=elapsed(); + const auto search_start=std::chrono::steady_clock::now(); + std::unique_ptr engine; + if (options.engine=="bab") engine.reset(Search::babengine(&root,search)); + else if (options.engine=="rbs") { + std::unique_ptr builder(rbs(search)); + engine.reset((*builder)(&root)); + } else if (options.engine=="astar") + engine.reset(Search::astarengine(&root,guidance,search,pruning)); + else if (options.engine=="column") + engine.reset(Search::columnengine(&root,guidance,search,options.column,pruning)); + else engine.reset(Search::cabsengine(&root,guidance,search,options.cabs,pruning)); + // Default clone ownership keeps root and seed independently owned here. + // The stop object was declared first and outlives every engine. + if (seed) engine->constrain(*seed); + std::cout << std::fixed << std::setprecision(3); + if (incumbent) { + std::cout << "{\"type\":\"solution\","; + write_tour(best_tour,*incumbent,data->scale); + std::cout << ",\"elapsed_ms\":" << elapsed() << '}' << std::endl; + } + while (std::unique_ptr solution{ + static_cast(engine->next())}) { + auto tour=solution->tour(); + const TourValue value=evaluate_tour(*data,tour); + if (value.cost!=solution->cost() || (incumbent && value.cost>=incumbent->cost)) + throw std::runtime_error("invalid or nonimproving model solution"); + incumbent=value; + best_tour=std::move(tour); + if constexpr (std::is_same::value) + solution_positions=solution->allocated_positions(); + std::cout << "{\"type\":\"solution\","; + write_tour(best_tour,value,data->scale); + std::cout << ",\"elapsed_ms\":" << elapsed() << '}' << std::endl; + } + const double search_ms=std::chrono::duration( + std::chrono::steady_clock::now()-search_start).count(); + const auto statistics=engine->statistics(); + const char* termination=engine->stopped() ? "stopped" : + incumbent ? "optimal" : "infeasible"; + std::cout << "{\"type\":\"summary\",\"engine\":\"" << options.engine + << "\",\"formulation\":\"" << options.formulation + << "\",\"state_pruning\":\"" << options.state_pruning + << "\",\"bound\":\"" << (options.formulation=="closed" ? "cp-total" : options.bound) + << "\",\"residual_propagation\":\"" << options.residual_propagation + << "\",\"termination\":\"" << termination << '"'; + if (options.formulation=="closed") + std::cout << ",\"circuit_propagation\":\"" << options.circuit_propagation + << "\",\"cost_branching\":\"" << options.cost_branching << '"'; + if (options.engine=="rbs") + std::cout << ",\"restart_cutoff\":\"luby\",\"restart_scale\":1000" + << ",\"nogoods_limit\":128"; + if (options.engine=="column") { + std::cout << ",\"initial_quota\":" << options.column.initial_quota + << ",\"quota_growth\":" << options.column.quota_growth + << ",\"frontier_limit\":" << options.column.frontier_limit; + } + std::cout << ",\"incumbent\":"; + if (incumbent) { + std::cout << '{'; + write_tour(best_tour,*incumbent,data->scale); + std::cout << '}'; + } else std::cout << "null"; + if constexpr (std::is_same::value) { + std::cout << ",\"allocation\":{\"space_bytes\":" << sizeof(Model) + << ",\"intvar_handle_bytes\":" << sizeof(IntVar) + << ",\"root_positions\":" << root_positions + << ",\"prepared_root_positions\":" << prepared_positions + << ",\"solution_positions\":" << solution_positions << '}'; + std::cout << ",\"measurements\":{\"bound_calls\":" << measurements->bound_calls + << ",\"bound_ms\":" << measurements->bound_ms + << ",\"inference_calls\":" << measurements->inference_calls + << ",\"inference_ms\":" << measurements->inference_ms + << ",\"propagation_ms\":" << measurements->propagation_ms << '}'; + } + std::cout << ",\"preprocessing_ms\":" << preprocessing_ms + << ",\"search_ms\":" << search_ms + << ",\"elapsed_ms\":" << elapsed() << ",\"statistics\":{\"node\":" + << statistics.node << ",\"fail\":" << statistics.fail + << ",\"depth\":" << statistics.depth << ",\"propagate\":" + << statistics.propagate << ",\"restart\":" << statistics.restart + << ",\"nogood\":" << statistics.nogood + << ",\"state_pruned\":" << statistics.state_pruned + << ",\"state_descriptors\":" << statistics.state_descriptors + << ",\"state_bytes\":" << statistics.state_bytes << "}}\n"; + return 0; +} + +static int run(const RunOptions& options) { + const auto start=std::chrono::steady_clock::now(); + LimitsStop stop(options); + auto data=std::make_shared(TSPTW::read(options.instance)); + Search::Options search; + search.clone=true; + search.threads=1; + search.stop=&stop; + if (options.engine=="rbs") { + search.cutoff=Search::Cutoff::luby(1000); + search.nogoods_limit=128; + } + if (options.formulation=="closed") { + TSPTWClosed root(data,options.circuit_propagation=="dom" ? IPL_DOM : IPL_VAL, + options.cost_branching=="size"); + return run_search(options,root,search,start); + } + auto measurements=std::make_shared(); + TSPTW::Transition root(data,options.formulation=="transition" ? + TSPTW::Transition::LAZY : TSPTW::Transition::EAGER, + options.residual_propagation=="off" ? + TSPTW::Transition::OFF : options.residual_propagation=="cheap" ? + TSPTW::Transition::CHEAP : TSPTW::Transition::UNARY, + measurements); + return run_search(options,root,search,start,measurements); +} + +int main(int argc, char* argv[]) { + try { + return run(read_options(argc,argv)); + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/gecode/kernel/core.cpp b/gecode/kernel/core.cpp index 63b03c7594..05f3b57ae5 100644 --- a/gecode/kernel/core.cpp +++ b/gecode/kernel/core.cpp @@ -118,7 +118,8 @@ namespace Gecode { VarImpDisposerBase* Space::vd[AllVarConf::idx_d]; #endif - Space::Space(void) : mm(ssd.data().sm), no_gpi(false) { + Space::Space(void) : mm(ssd.data().sm), no_gpi(false), + retained_branchers(false) { #ifdef GECODE_HAS_CBS var_id_counter = 0; #endif @@ -565,6 +566,34 @@ namespace Gecode { } + bool + Brancher::prepare(Space&, Search::Statistics&, const Search::Options&) { + return true; + } + + bool + Brancher::retained(void) const { return false; } + + void + Brancher::invalidate(Space&) {} + + bool + Space::prepare(Search::Statistics& stat, const Search::Options& options) { + return b_status == Brancher::cast(&bl) || + b_status->prepare(*this,stat,options); + } + + void + Space::invalidate_branchers(void) { + if (!retained_branchers || failed()) + return; + for (Brancher* b=Brancher::cast(bl.next()); + b != Brancher::cast(&bl); b=Brancher::cast(b->next())) + if (b->retained()) + b->invalidate(*this); + b_status = b_commit = Brancher::cast(bl.next()); + } + const Choice* Space::choice(void) { if (!stable()) @@ -576,9 +605,11 @@ namespace Gecode { while (b != Brancher::cast(&bl)) { Brancher* d = b; b = Brancher::cast(b->next()); - rfree(d,d->dispose(*this)); + if (failed() || !d->retained()) { + d->unlink(); + rfree(d,d->dispose(*this)); + } } - bl.init(); b_status = b_commit = Brancher::cast(&bl); return nullptr; } @@ -590,8 +621,10 @@ namespace Gecode { while (b != b_status) { Brancher* d = b; b = Brancher::cast(b->next()); - d->unlink(); - rfree(d,d->dispose(*this)); + if (!d->retained()) { + d->unlink(); + rfree(d,d->dispose(*this)); + } } // Make sure that b_commit does not point to a deleted brancher! b_commit = b_status; @@ -725,6 +758,7 @@ namespace Gecode { Space::Space(Space& s) : ssd(s.ssd), mm(ssd.data().sm,s.mm,s.pc.p.n_sub*sizeof(Propagator**)), + retained_branchers(s.retained_branchers), #ifdef GECODE_HAS_CBS var_id_counter(s.var_id_counter), #endif @@ -897,6 +931,11 @@ namespace Gecode { Space::constrain(const Space&) { } + SpaceComparison + Space::compare(const Space&) const { + throw SpaceNoComparison("Space::compare"); + } + bool Space::master(const MetaInfo& mi) { switch (mi.type()) { diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index fc29bf5b0f..4b8da0c627 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -53,6 +53,19 @@ namespace Gecode { + /** + * \brief Result of comparing two spaces according to their objectives + * + * Adding Space::compare changes the ordinary C++ ABI of Space by extending + * its virtual function table. + */ + enum SpaceComparison { + SC_BETTER, ///< The receiver has a better objective + SC_EQUIVALENT, ///< Both objectives are equivalent + SC_WORSE, ///< The receiver has a worse objective + SC_INCOMPARABLE ///< Neither objective dominates the other + }; + class Space; /** @@ -145,6 +158,7 @@ namespace Gecode { class AFC; class Choice; class Brancher; + namespace Search { class Statistics; class Options; } class Group; class PropagatorGroup; class BrancherGroup; @@ -1487,6 +1501,16 @@ namespace Gecode { * */ virtual bool status(const Space& home) const = 0; + /// Prepare a choice; false suspends search without changing its path. + GECODE_KERNEL_EXPORT + virtual bool prepare(Space& home, Search::Statistics& stat, + const Search::Options& options); + /// Whether an exhausted brancher must survive subsequent restrictions. + GECODE_KERNEL_EXPORT + virtual bool retained(void) const; + /// Invalidate previously established choices after strengthening. + GECODE_KERNEL_EXPORT + virtual void invalidate(Space& home); /** * \brief Return choice * @@ -1820,6 +1844,8 @@ namespace Gecode { * If equal to &bl, no brancher does exist. */ Brancher* b_status; + /// Whether any brancher requires notification on restriction posting. + bool retained_branchers; /** * \brief Points to the first brancher to be used for commit * @@ -2159,6 +2185,24 @@ namespace Gecode { * \ingroup TaskModelScript */ GECODE_KERNEL_EXPORT virtual void constrain(const Space& best); + /** + * \brief Compare this space's objective with \a other + * + * Comparison is read-only and receiver-relative. The model determines + * what objective information suffices for a sound result. The default + * implementation throws SpaceNoComparison; SC_INCOMPARABLE is an ordering + * result rather than missing information. + * + * Optimization models overriding constrain with a different objective + * must also override compare. SC_BETTER must be irreflexive and transitive, + * SC_WORSE its reverse, and SC_EQUIVALENT transitive and interchangeable + * when comparing a third solution. On objective-complete solutions, + * SC_BETTER must agree with the strict improvement restriction posted by + * constrain. A better incumbent's restriction must admit a subset of the + * solutions admitted by the old restriction. + */ + GECODE_KERNEL_EXPORT virtual SpaceComparison + compare(const Space& other) const; /** * \brief Master configuration function for meta search engines * @@ -2230,6 +2274,14 @@ namespace Gecode { */ GECODE_KERNEL_EXPORT SpaceStatus status(StatusStatistics& stat); + /// Prepare the current brancher before a search engine changes its path. + GECODE_KERNEL_EXPORT + bool prepare(Search::Statistics& stat, const Search::Options& options); + /// Enable restriction notifications for a retained brancher. + void retain_branchers(void) { retained_branchers = true; } + /// Notify retained branchers before posting a restriction or constraining. + GECODE_KERNEL_EXPORT + void invalidate_branchers(void); /// Query space status without collecting statistics SpaceStatus status(void); @@ -3440,7 +3492,9 @@ namespace Gecode { forceinline Home::Home(Space& s0, Propagator* p0, PropagatorGroup pg0, BrancherGroup bg0) - : s(s0), p(p0), pg(pg0), bg(bg0) {} + : s(s0), p(p0), pg(pg0), bg(bg0) { + s.invalidate_branchers(); + } forceinline Home::Home(const Home& h) : s(h.s), p(h.p), pg(h.pg), bg(h.bg) {} diff --git a/gecode/kernel/exception.cpp b/gecode/kernel/exception.cpp index e16c6cd61a..994d76f9c4 100644 --- a/gecode/kernel/exception.cpp +++ b/gecode/kernel/exception.cpp @@ -47,6 +47,9 @@ namespace Gecode { SpaceNoBrancher::SpaceNoBrancher(const char* l) : Exception(l,"Attempt to commit with no brancher") {} + SpaceNoComparison::SpaceNoComparison(const char* l) + : Exception(l,"Space does not support comparison") {} + SpaceIllegalAlternative::SpaceIllegalAlternative(const char* l) : Exception(l,"Attempt to commit with illegal alternative") {} diff --git a/gecode/kernel/exception.hpp b/gecode/kernel/exception.hpp index 792f4975e6..d118b10a9f 100644 --- a/gecode/kernel/exception.hpp +++ b/gecode/kernel/exception.hpp @@ -68,6 +68,13 @@ namespace Gecode { SpaceNoBrancher(const char* l); }; + /// %Exception: Comparison not implemented by space + class GECODE_KERNEL_EXPORT SpaceNoComparison : public Exception { + public: + /// Initialize with location \a l + SpaceNoComparison(const char* l); + }; + /// %Exception: Commit with illegal alternative class GECODE_KERNEL_EXPORT SpaceIllegalAlternative : public Exception { public: diff --git a/gecode/minimodel.hh b/gecode/minimodel.hh index 5b7bc145d0..79c778833b 100755 --- a/gecode/minimodel.hh +++ b/gecode/minimodel.hh @@ -2424,6 +2424,9 @@ namespace Gecode { /// Member function constraining according to decreasing cost GECODE_MINIMODEL_EXPORT virtual void constrain(const Space& best); + /// Compare integer costs (both costs must be assigned) + GECODE_MINIMODEL_EXPORT + virtual SpaceComparison compare(const Space& other) const; /// Return variable with current cost virtual IntVar cost(void) const = 0; }; @@ -2441,6 +2444,9 @@ namespace Gecode { /// Member function constraining according to increasing cost GECODE_MINIMODEL_EXPORT virtual void constrain(const Space& best); + /// Compare integer costs (both costs must be assigned) + GECODE_MINIMODEL_EXPORT + virtual SpaceComparison compare(const Space& other) const; /// Return variable with current cost virtual IntVar cost(void) const = 0; }; diff --git a/gecode/minimodel/optimize.cpp b/gecode/minimodel/optimize.cpp index 6442ada179..9e159b364f 100755 --- a/gecode/minimodel/optimize.cpp +++ b/gecode/minimodel/optimize.cpp @@ -35,6 +35,14 @@ namespace Gecode { + static SpaceComparison + compare_int(int x, int y, bool minimize) { + if (x == y) + return SC_EQUIVALENT; + return ((x < y) == minimize) ? SC_BETTER : SC_WORSE; + } + + void IntMinimizeSpace::constrain(const Space& _best) { const IntMinimizeSpace* best = @@ -45,6 +53,15 @@ namespace Gecode { } + SpaceComparison + IntMinimizeSpace::compare(const Space& other) const { + const IntMinimizeSpace* s = dynamic_cast(&other); + if (s == nullptr) + throw DynamicCastFailed("IntMinimizeSpace::compare"); + return compare_int(cost().val(),s->cost().val(),true); + } + + void IntMaximizeSpace::constrain(const Space& _best) { const IntMaximizeSpace* best = @@ -55,6 +72,15 @@ namespace Gecode { } + SpaceComparison + IntMaximizeSpace::compare(const Space& other) const { + const IntMaximizeSpace* s = dynamic_cast(&other); + if (s == nullptr) + throw DynamicCastFailed("IntMaximizeSpace::compare"); + return compare_int(cost().val(),s->cost().val(),false); + } + + void IntLexMinimizeSpace::constrain(const Space& _best) { const IntLexMinimizeSpace* best = diff --git a/gecode/search.hh b/gecode/search.hh index 13dfd9eec3..c92b4d9dc6 100755 --- a/gecode/search.hh +++ b/gecode/search.hh @@ -47,6 +47,11 @@ #include #include #include +#include +#include +#include +#include +#include #include @@ -160,6 +165,12 @@ namespace Gecode { namespace Search { unsigned long int restart; /// Number of no-goods posted unsigned long int nogood; + /// Number of arrivals or live candidates removed by sound state pruning + unsigned long long int state_pruned; + /// Peak number of retained state descriptors + unsigned long int state_descriptors; + /// Peak owned descriptor bytes, excluding container/allocator overhead + std::size_t state_bytes; /// Initialize Statistics(void); /// Reset @@ -1152,6 +1163,293 @@ namespace Gecode { #include +namespace Gecode { namespace Search { + + /** + * \brief Read-only guidance for scalar integer heuristic search + * + * The bound is an optimistic total objective value on a stable Space: + * a lower bound for minimization, an upper bound for maximization, and + * the exact objective on a solution. It must agree with Space::compare + * and Space::constrain. The engine adds no prefix cost. Engines define how + * they use the optional lower-first ranking; rank never justifies pruning. + * Callbacks and their captured + * dependencies must remain valid for all engine clones and must not post + * constraints or refer to another Space's local state. + * \ingroup TaskModelSearch + */ + class HeuristicGuidance { + public: + /// Objective direction + enum Sense { MINIMIZE, MAXIMIZE }; + /// Evaluate a propagated stable space + typedef std::function Evaluate; + /// Objective direction + Sense sense; + /// Optimistic total objective bound (required) + Evaluate bound; + /// Optional ranking (lower first) + Evaluate rank; + /// Initialize guidance + HeuristicGuidance(Sense sense, Evaluate bound, Evaluate rank=Evaluate()); + }; + + /// Fixed exploration order for native best-first search + enum BestFirstOrder { + BOUND_FIRST, ///< Optimistic bound, lower rank, then insertion order + RANK_FIRST ///< Lower rank, optimistic bound, then insertion order + }; + + /** + * \brief Optional model-justified exact-state and label pruning + * + * Descriptors own values only, never native handles or Spaces. Equal hashes + * select comparisons, but exact keys must also match. The author guarantees + * that a key includes every fact affecting residual feasibility and + * brancher/search progress. Equivalence/dominance must preserve an + * at-least-as-good completion, including prefix objective cost, and a route + * to a solution under repeated pruning. A sufficient argument preserves such + * a completion with no more remaining search transitions. Identical domains + * and costs at successive brancher stages alone are insufficient: a retained + * ancestor descriptor could suppress its only route to a goal. Include + * sufficient stage state in the key or decline extraction; no stage field + * is required or inferred. Relations are directional, consistent and + * transitive. An arbitrary heuristic bound does not establish a sound label + * relation. + * Callbacks read stable Spaces and follow HeuristicGuidance's lifetime rules. + * Both callbacks empty disable all extraction and retained descriptor work; + * supplying only one is invalid. Extraction may decline individual nodes. + * \ingroup TaskModelSearch + */ + class StatePruning { + public: + /// Owned exact grouping key and cost/resource label + struct Descriptor { + std::size_t hash; + std::vector key, label; + /// Object and vector capacity bytes, excluding container/allocator overhead + std::size_t storage(void) const { + return sizeof(Descriptor) + + (key.capacity()+label.capacity())*sizeof(std::int64_t); + } + }; + /// Relation of the first descriptor to the second, with identical keys + enum Relation { EQUIVALENT, DOMINATES, DOMINATED, INCOMPARABLE }; + /// Extract owned state, or decline when sufficient state is unavailable + typedef std::function(const Space&)> Extract; + /// Compare labels of descriptors with exactly equal grouping keys + typedef std::function Compare; + Extract extract; + Compare compare; + /// Metadata ceiling; zero is unlimited. Forgetting never removes search work. + unsigned long int descriptor_limit; + /// Initialize optional extraction, relation and metadata ceiling + StatePruning(Extract extract=Extract(), Compare compare=Compare(), + unsigned long int descriptor_limit=0); + }; + +}} + +namespace Gecode { + + /** + * \brief Sequential bound-ordered A* with improving solution publication + * + * Spaces must implement compatible scalar integer comparison and strict + * improvement constraints. A generated solution can precede a better one; + * continue next() until nullptr with stopped()==false to prove optimality of + * the best accepted incumbent, including external adoption, or infeasibility + * if no incumbent was accepted. Returned solutions belong to the caller. + * + * Equal bounds use lower rank, then insertion order. + * The frontier stores clones; c_d and a_d do not control recomputation. + * There is no hard memory guarantee and no-good extraction is empty. + * Only one expanded thread and no tracer are supported. Capacity-one + * worker control and ordinary resumable stops are supported. + * If guidance, state pruning, or comparison throws, destroy the engine before further use. + * \ingroup TaskModelSearch + */ + template + class AStar : public Search::Base { + public: + /// Initialize from space, guidance, and ordinary search options + AStar(T* s, const Search::HeuristicGuidance& guidance, + const Search::Options& o=Search::Options::def, + const Search::StatePruning& pruning=Search::StatePruning()); + /// Adopt a strictly improving compatible external incumbent + void constrain(const Space& incumbent); + /// Whether engine does best solution search + static const bool best = true; + }; + + /// Return an A* engine builder with copied guidance + template + SEB astar(const Search::HeuristicGuidance& guidance, + const Search::Options& o=Search::Options::def, + const Search::StatePruning& pruning=Search::StatePruning()); + + /** + * \brief Return a native best-first builder with copied guidance and fixed order + * + * Uses the same engine and contracts as AStar. BOUND_FIRST preserves AStar + * ordering; RANK_FIRST permits arbitrary, inadmissible lower-first ranking. + * Missing rank falls back to bound order. Ties use insertion order. Values + * are cached for each admitted stable node; reset preserves the order. + * Rank selects pending branching Spaces only and never prunes. Generated + * solutions are published immediately, so the first need not be optimal. + * Natural finite-tree exhaustion with stopped()==false proves closure under + * the bound, comparison, and optional sound StatePruning contracts. + * \ingroup TaskModelSearch + */ + template + SEB bestfirst(const Search::HeuristicGuidance& guidance, + Search::BestFirstOrder ordering, + const Search::Options& o=Search::Options::def, + const Search::StatePruning& pruning=Search::StatePruning()); + +} + +#include + +namespace Gecode { namespace Search { + + /// Intrinsic CABS resource limits; zero means unlimited + class CABSOptions { + public: + /// Maximum permitted width (passes use widths 1, 2, 4, ...) + unsigned long int max_width; + /// Quota of retained pending search Spaces, including the selected parent + unsigned long int frontier_limit; + /// Initialize width and frontier-node limits + CABSOptions(unsigned long int maximum_width=0, + unsigned long int frontier_nodes=0); + }; + +}} + +namespace Gecode { + + /** + * \brief Sequential complete anytime beam search with widening restarts + * + * Each native Choice commit advances one layer. Width starts at one and + * doubles after a completed pass if discarded bounds still permit improvement + * or no incumbent exists. A pass can finish without widening when every + * discarded bound excludes strict improvement of the accepted incumbent. + * Lower rank selects the beam; without rank, the optimistic total bound + * selects it. Equal priorities use generation order. Only admissible bounds + * justify pruning and completion. + * + * Returned solutions belong to the caller and strictly improve the retained + * incumbent. Continue next() until nullptr with stopped()==false to prove + * optimality of the best accepted incumbent, including external adoption, + * or infeasibility if none was accepted. Publication preserves unfinished + * passes. + * Ordinary stops and capacity-one worker control preserve continuation. + * Width/frontier limits and width overflow are incomplete stops latched + * until reset through the native Engine interface. + * + * The frontier quota includes current-layer entries, next-layer candidates, + * and the selected parent. It excludes the restart root, incumbent, returned + * solutions, temporary successor, and brancher trial Spaces. It is a node + * quota, without a byte or process-memory guarantee. Clones store the search; + * c_d/a_d do not enable replay and no-good extraction is empty. + * One expanded thread and no tracer are supported. Guidance, comparison, + * and constraints obey the same scalar integer contract as AStar. + * If guidance, state pruning, or comparison throws, destroy the engine before further use. + * \ingroup TaskModelSearch + */ + template + class CABS : public Search::Base { + public: + /// Initialize from space, guidance, ordinary options, and beam limits + CABS(T* s, const Search::HeuristicGuidance& guidance, + const Search::Options& o=Search::Options::def, + const Search::CABSOptions& limits=Search::CABSOptions(), + const Search::StatePruning& pruning=Search::StatePruning()); + /// Adopt a strictly improving compatible external incumbent + void constrain(const Space& incumbent); + /// Whether engine does best solution search + static const bool best = true; + }; + + /// Return a CABS engine builder with copied guidance and beam limits + template + SEB cabs(const Search::HeuristicGuidance& guidance, + const Search::Options& o=Search::Options::def, + const Search::CABSOptions& limits=Search::CABSOptions(), + const Search::StatePruning& pruning=Search::StatePruning()); + +} + +#include + +namespace Gecode { namespace Search { + + /// Expansion quotas and retained-frontier limit for sequential column search + class ColumnOptions { + public: + /// Positive quota of selected parents per depth in the first cycle + unsigned long int initial_quota; + /// Added after each full depth cycle; zero selects fixed-quota CBFS + unsigned long int quota_growth; + /// Retained pending Spaces including the unfinished parent; zero is unlimited + unsigned long int frontier_limit; + ColumnOptions(unsigned long int initial=1, unsigned long int growth=1, + unsigned long int frontier_nodes=0); + }; + +}} + +namespace Gecode { + + /** + * \brief Sequential progressive column search with fixed-quota CBFS mode + * + * Each cycle expands up to the quota from successive depth queues, including + * deeper successors generated in that cycle. Quota growth saturates safely. + * Deferred work remains queued. Lower-first rank orders each queue when + * supplied; otherwise the optimistic objective bound orders it. Only sound + * bounds and optional state dominance prune work. + * + * Publication, ordinary stops and capacity-one worker control preserve the + * full cycle and original Choice cursor. Returned solutions belong to the + * caller and strictly improve the incumbent. Continue next() to nullptr with + * stopped()==false to prove optimality of the best accepted incumbent, + * including external adoption, or infeasibility if none was accepted. + * A frontier-cap loss is incomplete and stays stopped until Engine::reset. + * + * The cap counts queued Spaces and the unfinished parent, excluding incumbent, + * returned solutions and temporary successors/trials. It is a node limit, + * without a byte or process-memory guarantee. Clones retain search work; + * c_d/a_d do not enable replay and no-good extraction is empty. One expanded + * thread and no tracer are supported. Guidance and comparison obey AStar's + * scalar integer contract. Destroy the engine if a callback throws. + * \ingroup TaskModelSearch + */ + template + class Column : public Search::Base { + public: + Column(T* s, const Search::HeuristicGuidance& guidance, + const Search::Options& o=Search::Options::def, + const Search::ColumnOptions& limits=Search::ColumnOptions(), + const Search::StatePruning& pruning=Search::StatePruning()); + /// Adopt a strictly improving compatible external incumbent + void constrain(const Space& incumbent); + static const bool best = true; + }; + + /// Return a column-search builder with copied guidance, quotas and pruning + template + SEB column(const Search::HeuristicGuidance& guidance, + const Search::Options& o=Search::Options::def, + const Search::ColumnOptions& limits=Search::ColumnOptions(), + const Search::StatePruning& pruning=Search::StatePruning()); + +} + +#include + namespace Gecode { /** diff --git a/gecode/search/astar.cpp b/gecode/search/astar.cpp new file mode 100644 index 0000000000..b7013a6f6e --- /dev/null +++ b/gecode/search/astar.cpp @@ -0,0 +1,244 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include + +namespace Gecode { namespace Search { + + Engine* + astarengine(Space* s, const HeuristicGuidance& g, const Options& o, + const StatePruning& p) { + return bestfirstengine(s,g,BOUND_FIRST,o,p); + } + + Engine* + bestfirstengine(Space* s, const HeuristicGuidance& g, BestFirstOrder ordering, + const Options& o, const StatePruning& p) { + Options expanded = o.expand(); + if (expanded.threads != 1.0) + throw UnsupportedAStarThreads("AStar"); + if (expanded.tracer != nullptr) + throw UnsupportedAStarTracer("AStar"); + if (!g.bound) + throw UninitializedAStarBound("AStar"); + if (static_cast(p.extract) != static_cast(p.compare)) + throw UninitializedStatePruning("AStar"); + return new WorkerToEngine(s,expanded, + Seq::AStar::Configuration{g,p,ordering}); + } + +}} + +namespace Gecode { namespace Search { namespace Seq { + + bool + AStar::Later::operator()(const Entry& a, const Entry& b) const { + if (ordering == RANK_FIRST && a.rank != b.rank) + return a.rank > b.rank; + if (a.bound != b.bound) + return sense == HeuristicGuidance::MINIMIZE ? + a.bound > b.bound : a.bound < b.bound; + if (a.rank != b.rank) + return a.rank > b.rank; + return a.order > b.order; + } + + AStar::AStar(Space* s, const Options& o, const Configuration& c) + : opt(o), guidance(c.guidance), pruning(c.pruning), ordering(c.ordering), + registry(pruning.extract ? + new StateRegistry(pruning.compare,pruning.descriptor_limit) : + nullptr), + order(0), incumbent_revision(0), + current_status(SS_FAILED), current_depth(0), current_revision(0), + counted(false), best_bound(0) { + initialize(s,o.clone); + } + + void + AStar::initialize(Space* s, bool clone) { + // Evaluate guidance only after construction has transferred ownership. + if (s == nullptr) { + fail++; + } else { + current_status = s->status(*this); + if (current_status == SS_FAILED) { + fail++; + if (!clone) delete s; + } else { + expansion.select(std::unique_ptr(clone ? s->clone() : s)); + } + } + } + + bool + AStar::improves(std::int64_t bound) const { + return !best || (guidance.sense == HeuristicGuidance::MINIMIZE ? + bound < best_bound : bound > best_bound); + } + + void + AStar::enqueue(std::unique_ptr s, unsigned long int depth, + unsigned long long int revision) { + const std::int64_t bound = guidance.bound(*s); + if (!improves(bound)) { + fail++; + return; + } + std::optional descriptor; + if (registry) { + descriptor = pruning.extract(*s); + if (descriptor && registry->subsumes(*descriptor)) { + state_pruned++; + return; + } + } + const std::int64_t rank = guidance.rank ? guidance.rank(*s) : 0; + frontier.push_back({std::move(s),bound,rank,depth,order++,revision}); + std::push_heap(frontier.begin(),frontier.end(),Later{guidance.sense,ordering}); + if (!descriptor) return; + // Admit fresh work before replacing labels, even after earlier expansion. + // Old pending Spaces retain their original Choice cursors and coverage. + registry->retain(std::move(*descriptor)); + state_descriptors = std::max(state_descriptors,registry->size()); + state_bytes = std::max(state_bytes,registry->storage()); + } + + Space* + AStar::publish(std::unique_ptr s) { + // Clean exhausted branchers, retaining completion branchers as usual. + (void) s->choice(); + if (best && !Search::better(*s,*best,"AStar::next")) + return nullptr; + const std::int64_t bound = guidance.bound(*s); + std::unique_ptr result(s->clone()); + best = std::move(s); + best_bound = bound; + incumbent_revision++; + return result.release(); + } + + Space* + AStar::next(void) { + start(); + while (true) { + if (stop_seq(opt)) return nullptr; + if (!expansion) { + if (frontier.empty()) return nullptr; + std::pop_heap(frontier.begin(),frontier.end(),Later{guidance.sense,ordering}); + Entry entry = std::move(frontier.back()); + frontier.pop_back(); + if (!improves(entry.bound)) { + fail++; + continue; + } + expansion.select(std::move(entry.space)); + current_depth = entry.depth; + current_revision = entry.incumbent_revision; + current_status = SS_BRANCH; + counted = true; + } + if (!counted) { + node++; + stack_depth(current_depth); + counted = true; + if (current_status == SS_SOLVED) { + if (Space* solution = publish(expansion.take())) return solution; + continue; + } + if (!improves(guidance.bound(expansion.space()))) { + fail++; + expansion.reset(); + continue; + } + } + const auto child_depth = current_depth+1; + ExpansionStep step = expansion.next(*this,opt,child_depth,current_revision, + best.get(),incumbent_revision); + if (step.state == ExpansionStep::SUSPENDED) return nullptr; + if (step.state == ExpansionStep::EXHAUSTED) continue; + switch (step.status) { + case SS_FAILED: + fail++; + break; + case SS_SOLVED: + if (Space* solution = publish(std::move(step.space))) return solution; + break; + case SS_BRANCH: + enqueue(std::move(step.space),child_depth,incumbent_revision); + break; + default: + GECODE_NEVER; + } + } + } + + Statistics + AStar::statistics(void) const { + return *this; + } + + void + AStar::constrain(const Space& b) { + if (best && !Search::better(b,*best,"AStar::constrain")) return; + const std::int64_t bound = guidance.bound(b); + std::unique_ptr incumbent(b.clone()); + best = std::move(incumbent); + best_bound = bound; + incumbent_revision++; + } + + void + AStar::reset(Space* s) { + expansion.reset(); + frontier.clear(); + if (registry) registry->clear(); + best.reset(); + order = 0; + incumbent_revision = current_revision = 0; + current_depth = 0; + counted = false; + Worker::reset(); + initialize(s,false); + } + + NoGoods& + AStar::nogoods(void) { + return NoGoods::eng; + } + +}}} + +// STATISTICS: search-other diff --git a/gecode/search/astar.hpp b/gecode/search/astar.hpp new file mode 100644 index 0000000000..84a0a690c3 --- /dev/null +++ b/gecode/search/astar.hpp @@ -0,0 +1,103 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +namespace Gecode { namespace Search { + + /// Create a native sequential A* engine + GECODE_SEARCH_EXPORT Engine* + astarengine(Space* s, const HeuristicGuidance& guidance, const Options& o, + const StatePruning& pruning=StatePruning()); + + /// Create a native sequential best-first engine with fixed exploration order + GECODE_SEARCH_EXPORT Engine* + bestfirstengine(Space* s, const HeuristicGuidance& guidance, + BestFirstOrder ordering, const Options& o, + const StatePruning& pruning=StatePruning()); + + /// A* and best-first engine builder + template + class AStarBuilder : public Builder { + HeuristicGuidance guidance; + StatePruning pruning; + BestFirstOrder ordering; + public: + AStarBuilder(const HeuristicGuidance& g, const Options& o, + const StatePruning& p=StatePruning(), + BestFirstOrder order=BOUND_FIRST) + : Builder(o,true), guidance(g), pruning(p), ordering(order) {} + Engine* operator()(Space* s) const override { + return bestfirstengine(dynamic_cast(s),guidance,ordering,opt,pruning); + } + }; + + inline + HeuristicGuidance::HeuristicGuidance(Sense s, Evaluate b, Evaluate r) + : sense(s), bound(std::move(b)), rank(std::move(r)) {} + + inline + StatePruning::StatePruning(Extract e, Compare c, unsigned long int limit) + : extract(std::move(e)), compare(std::move(c)), descriptor_limit(limit) {} + +}} + +namespace Gecode { + + template + inline + AStar::AStar(T* s, const Search::HeuristicGuidance& g, + const Search::Options& o, const Search::StatePruning& p) + : Search::Base(Search::astarengine(s,g,o,p)) {} + + template + inline void + AStar::constrain(const Space& incumbent) { + this->e->constrain(incumbent); + } + + template + inline SEB + astar(const Search::HeuristicGuidance& g, const Search::Options& o, + const Search::StatePruning& p) { + return new Search::AStarBuilder(g,o,p); + } + + template + inline SEB + bestfirst(const Search::HeuristicGuidance& g, Search::BestFirstOrder ordering, + const Search::Options& o, const Search::StatePruning& p) { + return new Search::AStarBuilder(g,o,p,ordering); + } + +} + +// STATISTICS: search-other diff --git a/gecode/search/branch-feedback.hh b/gecode/search/branch-feedback.hh new file mode 100644 index 0000000000..6d8fcf76a3 --- /dev/null +++ b/gecode/search/branch-feedback.hh @@ -0,0 +1,176 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef GECODE_SEARCH_BRANCH_FEEDBACK_HH +#define GECODE_SEARCH_BRANCH_FEEDBACK_HH + +#include + +namespace Gecode { namespace Search { + + /// Post-propagation outcome of a newly explored branch + enum class BranchOutcome { + FAILED, + SOLVED, + BRANCH + }; + + /// Shared receiver for internal branch feedback + class BranchFeedback : public SharedHandle { + public: + /// Receiver implementation + class Receiver : public SharedHandle::Object { + public: + /// Observe alternative \a alternative of stable choice \a id + virtual void observe(unsigned int id, unsigned int alternative, + const Space& space, + BranchOutcome outcome) noexcept = 0; + /// Destructor + virtual ~Receiver(void) {} + }; + + /// Construct an empty handle + BranchFeedback(void) {} + /// Construct a handle for \a receiver + explicit BranchFeedback(Receiver* receiver) + : SharedHandle(receiver) {} + /// Return the internal receiver + Receiver* receiver(void) const { + return static_cast(object()); + } + /// Report one branch outcome + void observe(unsigned int id, unsigned int alternative, + const Space& space, BranchOutcome outcome) const noexcept { + receiver()->observe(id,alternative,space,outcome); + } + }; + + /// Internal opt-in choice carrying stable feedback identity + class GECODE_VTABLE_EXPORT BranchFeedbackChoice : public Choice { + private: + BranchFeedback _feedback; + unsigned int _feedback_id; + protected: + /// Initialize for brancher \a brancher with \a alternatives + BranchFeedbackChoice(const Brancher& brancher, unsigned int alternatives, + const BranchFeedback& feedback, unsigned int id) + : Choice(brancher,alternatives), _feedback(feedback), + _feedback_id(id) {} + public: + /// Map search alternative to the stable semantic alternative + virtual unsigned int + feedback_alternative(unsigned int alternative) const noexcept { + return alternative; + } + /// Report the post-propagation outcome for \a alternative + void feedback(unsigned int alternative, const Space& space, + BranchOutcome outcome) const noexcept { + _feedback.observe(_feedback_id,feedback_alternative(alternative), + space,outcome); + } + /// Return receiver handle + const BranchFeedback& feedback(void) const { + return _feedback; + } + /// Return stable choice identity + unsigned int feedback_id(void) const { + return _feedback_id; + } + }; + + /// Retained feedback for a stolen alternative whose choice can be deleted + class PendingBranchFeedback { + private: + BranchFeedback _feedback; + unsigned int _id; + unsigned int _alternative; + public: + /// Construct an empty pending observation + PendingBranchFeedback(void) : _id(0U), _alternative(0U) {} + /// Whether an observation is pending + explicit operator bool(void) const { + return static_cast(_feedback); + } + /// Retain feedback from \a choice for \a alternative, if opted in + void set(const Choice& choice, unsigned int alternative) { + if (const BranchFeedbackChoice* feedback_choice = + dynamic_cast(&choice)) { + _feedback = feedback_choice->feedback(); + _id = feedback_choice->feedback_id(); + _alternative = feedback_choice->feedback_alternative(alternative); + } else { + clear(); + } + } + /// Report and clear the pending observation + void observe(const Space& space, BranchOutcome outcome) noexcept { + assert(_feedback); + BranchFeedback feedback(_feedback); + const unsigned int id = _id; + const unsigned int alternative = _alternative; + clear(); + feedback.observe(id,alternative,space,outcome); + } + /// Clear the pending observation + void clear(void) { + _feedback = BranchFeedback(); + } + }; + + /// Report \a outcome for an opt-in \a choice + forceinline void + branch_feedback(const Choice& choice, unsigned int alternative, + const Space& space, BranchOutcome outcome) noexcept { + if (const BranchFeedbackChoice* feedback_choice = + dynamic_cast(&choice)) + feedback_choice->feedback(alternative,space,outcome); + } + + /// Convert a space status to its branch-feedback outcome + forceinline BranchOutcome + branch_outcome(SpaceStatus status) { + switch (status) { + case SS_FAILED: return BranchOutcome::FAILED; + case SS_SOLVED: return BranchOutcome::SOLVED; + case SS_BRANCH: return BranchOutcome::BRANCH; + default: GECODE_NEVER; + } + GECODE_NEVER; + return BranchOutcome::FAILED; + } + +}} + +#endif + +// STATISTICS: search-other diff --git a/gecode/search/cabs.cpp b/gecode/search/cabs.cpp new file mode 100644 index 0000000000..311a2b1b47 --- /dev/null +++ b/gecode/search/cabs.cpp @@ -0,0 +1,357 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include +#include + +namespace Gecode { namespace Search { + + Engine* + cabsengine(Space* s, const HeuristicGuidance& g, const Options& o, + const CABSOptions& limits, const StatePruning& p) { + Options expanded = o.expand(); + if (expanded.threads != 1.0) + throw UnsupportedCABSThreads("CABS"); + if (expanded.tracer != nullptr) + throw UnsupportedCABSTracer("CABS"); + if (!g.bound) + throw UninitializedCABSBound("CABS"); + if (static_cast(p.extract) != static_cast(p.compare)) + throw UninitializedStatePruning("CABS"); + return new WorkerToEngine(s,expanded, + Seq::CABS::Configuration{g,limits,p}); + } + +}} + +namespace Gecode { namespace Search { namespace Seq { + + bool + CABS::Earlier::operator()(const Entry& a, const Entry& b) const { + if (ranked) { + if (a.rank != b.rank) return a.rank < b.rank; + } else if (a.bound != b.bound) { + return sense == HeuristicGuidance::MINIMIZE ? + a.bound < b.bound : a.bound > b.bound; + } + return a.order < b.order; + } + + CABS::CABS(Space* s, const Options& o, const Configuration& c) + : opt(o), guidance(c.guidance), limits(c.limits), pruning(c.pruning), + candidate_descriptors(0), candidate_bytes(0), width(1), order(0), + incumbent_revision(0), current_status(SS_FAILED), current_depth(0), + current_revision(0), counted(false), best_bound(0), + limited(false) { + initialize(s,o.clone); + } + + void + CABS::initialize(Space* s, bool clone) { + if (s == nullptr) { + fail++; + return; + } + current_status = s->status(*this); + if (current_status == SS_FAILED) { + fail++; + if (!clone) delete s; + return; + } + // Finish all construction-time clones before taking the caller's root. + std::unique_ptr restart_root(clone ? s->clone() : nullptr); + expansion.select(std::unique_ptr( + (clone ? restart_root.get() : s)->clone())); + root.reset(clone ? restart_root.release() : s); + } + + bool + CABS::improves(std::int64_t bound) const { + return !best || (guidance.sense == HeuristicGuidance::MINIMIZE ? + bound < best_bound : bound > best_bound); + } + + bool + CABS::retain(std::unique_ptr s, unsigned long int depth, + unsigned long long int revision) { + const std::int64_t bound = guidance.bound(*s); + if (!improves(bound)) { + fail++; + return true; + } + std::optional descriptor; + std::vector victims; + if (pruning.extract) { + descriptor = pruning.extract(*s); + if (descriptor) + for (std::size_t i=0; ihash != retained->hash || + descriptor->key != retained->key) continue; + const auto relation = pruning.compare(*descriptor,*retained); + if (relation == StatePruning::EQUIVALENT || + relation == StatePruning::DOMINATED) { + state_pruned++; + return true; + } + if (relation == StatePruning::DOMINATES) victims.push_back(i); + } + } + const std::int64_t rank = guidance.rank ? guidance.rank(*s) : 0; + Entry entry{std::move(s),bound,rank,depth,order++,revision, + std::move(descriptor)}; + Earlier earlier{guidance.sense,static_cast(guidance.rank)}; + if (!victims.empty()) { + // A dominator takes a victim's retained slot regardless of rank. Only + // after it survives may the other dominated live candidates disappear. + for (std::size_t victim : victims) + remove_descriptor(candidates[victim]); + candidates[victims.front()] = std::move(entry); + add_descriptor(candidates[victims.front()]); + for (std::size_t j=victims.size(); j>1; j--) + candidates.erase(candidates.begin()+victims[j-1]); + std::make_heap(candidates.begin(),candidates.end(),earlier); + state_pruned += victims.size(); + record_descriptors(); + return true; + } + if (candidates.size() == width) { + const bool replace = earlier(entry,candidates.front()); + const std::int64_t discarded = replace ? candidates.front().bound : entry.bound; + if (!discarded_bound || (guidance.sense == HeuristicGuidance::MINIMIZE ? + discarded < *discarded_bound : + discarded > *discarded_bound)) + discarded_bound = discarded; + if (!replace) return true; + // Replacement uses the discarded entry's retained slot. + std::pop_heap(candidates.begin(),candidates.end(),earlier); + remove_descriptor(candidates.back()); + candidates.back() = std::move(entry); + add_descriptor(candidates.back()); + std::push_heap(candidates.begin(),candidates.end(),earlier); + record_descriptors(); + return true; + } + const auto pending = layer.size() + candidates.size() + (expansion ? 1U : 0U); + if (limits.frontier_limit && pending >= limits.frontier_limit) { + limited = _stopped = true; + return false; + } + candidates.push_back(std::move(entry)); + add_descriptor(candidates.back()); + std::push_heap(candidates.begin(),candidates.end(),earlier); + record_descriptors(); + return true; + } + + void + CABS::add_descriptor(const Entry& entry) { + if (entry.descriptor) { + candidate_descriptors++; + candidate_bytes += entry.descriptor->storage(); + } + } + + void + CABS::remove_descriptor(const Entry& entry) { + if (entry.descriptor) { + candidate_descriptors--; + candidate_bytes -= entry.descriptor->storage(); + } + } + + void + CABS::record_descriptors(void) { + if (pruning.descriptor_limit && + candidate_descriptors > pruning.descriptor_limit) { + // Forget metadata only; every retained candidate still gets expanded. + for (auto& candidate : candidates) candidate.descriptor.reset(); + candidate_descriptors = 0; + candidate_bytes = 0; + return; + } + state_descriptors = std::max(state_descriptors,candidate_descriptors); + state_bytes = std::max(state_bytes,candidate_bytes); + } + + Space* + CABS::publish(std::unique_ptr s) { + (void) s->choice(); + if (best && !Search::better(*s,*best,"CABS::next")) return nullptr; + const std::int64_t bound = guidance.bound(*s); + std::unique_ptr result(s->clone()); + best = std::move(s); + best_bound = bound; + incumbent_revision++; + return result.release(); + } + + bool + CABS::advance(void) { + if (!candidates.empty()) { + layer.swap(candidates); + // Only live next-layer candidates can justify state pruning. + if (pruning.extract) + for (auto& entry : layer) entry.descriptor.reset(); + candidate_descriptors = 0; + candidate_bytes = 0; + Earlier earlier{guidance.sense,static_cast(guidance.rank)}; + // Keep the preferred node at the back for constant-time selection. + std::sort(layer.begin(),layer.end(), + [&](const Entry& a, const Entry& b) { return earlier(b,a); }); + return true; + } + if (!discarded_bound || !improves(*discarded_bound)) return false; + if (width > std::numeric_limits::max()/2 || + (limits.max_width && width*2 > limits.max_width)) { + limited = _stopped = true; + return false; + } + width *= 2; + restart++; + discarded_bound.reset(); + expansion.select(std::unique_ptr(root->clone())); + current_status = SS_BRANCH; + current_depth = 0; + current_revision = 0; + counted = false; + return true; + } + + Space* + CABS::next(void) { + if (limited) { + _stopped = true; + return nullptr; + } + start(); + while (true) { + if (stop_seq(opt)) return nullptr; + if (!expansion) { + if (layer.empty() && !advance()) return nullptr; + if (!expansion) { + Entry entry = std::move(layer.back()); + layer.pop_back(); + if (!improves(entry.bound)) { + fail++; + continue; + } + expansion.select(std::move(entry.space)); + current_depth = entry.depth; + current_revision = entry.incumbent_revision; + current_status = SS_BRANCH; + counted = true; + } + } + if (!counted) { + node++; + stack_depth(current_depth); + counted = true; + if (current_status == SS_SOLVED) { + if (Space* solution = publish(expansion.take())) return solution; + continue; + } + if (!improves(guidance.bound(expansion.space()))) { + fail++; + expansion.reset(); + continue; + } + } + const auto child_depth = current_depth+1; + ExpansionStep step = expansion.next(*this,opt,child_depth,current_revision, + best.get(),incumbent_revision); + if (step.state == ExpansionStep::SUSPENDED) return nullptr; + if (step.state == ExpansionStep::EXHAUSTED) continue; + switch (step.status) { + case SS_FAILED: + fail++; + break; + case SS_SOLVED: + if (Space* solution = publish(std::move(step.space))) return solution; + break; + case SS_BRANCH: + if (!retain(std::move(step.space),child_depth,incumbent_revision)) + return nullptr; + break; + default: + GECODE_NEVER; + } + } + } + + Statistics + CABS::statistics(void) const { + return *this; + } + + void + CABS::constrain(const Space& b) { + if (best && !Search::better(b,*best,"CABS::constrain")) return; + const std::int64_t bound = guidance.bound(b); + std::unique_ptr incumbent(b.clone()); + best = std::move(incumbent); + best_bound = bound; + incumbent_revision++; + } + + void + CABS::reset(Space* s) { + expansion.reset(); + layer.clear(); + candidates.clear(); + candidate_descriptors = 0; + candidate_bytes = 0; + root.reset(); + best.reset(); + width = 1; + order = 0; + incumbent_revision = current_revision = 0; + current_depth = 0; + discarded_bound.reset(); + counted = limited = false; + Worker::reset(); + start(); + initialize(s,false); + } + + NoGoods& + CABS::nogoods(void) { + return NoGoods::eng; + } + +}}} + +// STATISTICS: search-other diff --git a/gecode/search/cabs.hpp b/gecode/search/cabs.hpp new file mode 100644 index 0000000000..4a20c48a5d --- /dev/null +++ b/gecode/search/cabs.hpp @@ -0,0 +1,88 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +namespace Gecode { namespace Search { + + /// Create a native sequential CABS engine + GECODE_SEARCH_EXPORT Engine* + cabsengine(Space* s, const HeuristicGuidance& guidance, const Options& o, + const CABSOptions& limits=CABSOptions(), + const StatePruning& pruning=StatePruning()); + + /// CABS engine builder + template + class CABSBuilder : public Builder { + HeuristicGuidance guidance; + CABSOptions limits; + StatePruning pruning; + public: + CABSBuilder(const HeuristicGuidance& g, const Options& o, + const CABSOptions& l, const StatePruning& p=StatePruning()) + : Builder(o,true), guidance(g), limits(l), pruning(p) {} + Engine* operator()(Space* s) const override { + return cabsengine(dynamic_cast(s),guidance,opt,limits,pruning); + } + }; + + inline + CABSOptions::CABSOptions(unsigned long int maximum_width, + unsigned long int frontier_nodes) + : max_width(maximum_width), frontier_limit(frontier_nodes) {} + +}} + +namespace Gecode { + + template + inline + CABS::CABS(T* s, const Search::HeuristicGuidance& g, + const Search::Options& o, const Search::CABSOptions& limits, + const Search::StatePruning& p) + : Search::Base(Search::cabsengine(s,g,o,limits,p)) {} + + template + inline void + CABS::constrain(const Space& incumbent) { + this->e->constrain(incumbent); + } + + template + inline SEB + cabs(const Search::HeuristicGuidance& g, const Search::Options& o, + const Search::CABSOptions& limits, const Search::StatePruning& p) { + return new Search::CABSBuilder(g,o,limits,p); + } + +} + +// STATISTICS: search-other diff --git a/gecode/search/column.cpp b/gecode/search/column.cpp new file mode 100644 index 0000000000..f3ddb7108f --- /dev/null +++ b/gecode/search/column.cpp @@ -0,0 +1,284 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include +#include + +namespace Gecode { namespace Search { + + Engine* + columnengine(Space* s, const HeuristicGuidance& g, const Options& o, + const ColumnOptions& limits, const StatePruning& p) { + Options expanded = o.expand(); + if (expanded.threads != 1.0) + throw UnsupportedColumnThreads("Column"); + if (expanded.tracer != nullptr) + throw UnsupportedColumnTracer("Column"); + if (!g.bound) + throw UninitializedColumnBound("Column"); + if (!limits.initial_quota) + throw InvalidColumnQuota("Column"); + if (static_cast(p.extract) != static_cast(p.compare)) + throw UninitializedStatePruning("Column"); + return new WorkerToEngine(s,expanded, + Seq::Column::Configuration{g,limits,p}); + } + +}} + +namespace Gecode { namespace Search { namespace Seq { + + bool + Column::Later::operator()(const Entry& a, const Entry& b) const { + if (ranked) { + if (a.rank != b.rank) return a.rank > b.rank; + } else if (a.bound != b.bound) { + return sense == HeuristicGuidance::MINIMIZE ? + a.bound > b.bound : a.bound < b.bound; + } + return a.order > b.order; + } + + Column::Column(Space* s, const Options& o, const Configuration& c) + : opt(o), guidance(c.guidance), limits(c.limits), pruning(c.pruning), + registry(pruning.extract ? + new StateRegistry(pruning.compare,pruning.descriptor_limit) : + nullptr), + queued(0), cycle_depth(0), + quota(limits.initial_quota), expanded(0), limited(false), order(0), + incumbent_revision(0), + current_status(SS_FAILED), current_depth(0), current_revision(0), + counted(false), best_bound(0) { + initialize(s,o.clone); + } + + void + Column::initialize(Space* s, bool clone) { + // Evaluate guidance only after construction has transferred ownership. + if (s == nullptr) { + fail++; + } else { + current_status = s->status(*this); + if (current_status == SS_FAILED) { + fail++; + if (!clone) delete s; + } else { + expansion.select(std::unique_ptr(clone ? s->clone() : s)); + } + } + } + + bool + Column::improves(std::int64_t bound) const { + return !best || (guidance.sense == HeuristicGuidance::MINIMIZE ? + bound < best_bound : bound > best_bound); + } + + bool + Column::retain(std::unique_ptr s, unsigned long int depth, + unsigned long long int revision) { + const std::int64_t bound = guidance.bound(*s); + if (!improves(bound)) { + fail++; + return true; + } + std::optional descriptor; + if (registry) { + descriptor = pruning.extract(*s); + if (descriptor && registry->subsumes(*descriptor)) { + state_pruned++; + return true; + } + } + if (limits.frontier_limit && + queued + (expansion ? 1U : 0U) >= limits.frontier_limit) { + limited = _stopped = true; + return false; + } + const std::int64_t rank = guidance.rank ? guidance.rank(*s) : 0; + if (columns.size() <= depth) columns.resize(depth+1); + auto& column = columns[depth]; + column.push_back({std::move(s),bound,rank,depth,order++,revision}); + std::push_heap(column.begin(),column.end(), + Later{guidance.sense,static_cast(guidance.rank)}); + queued++; + if (!descriptor) return true; + // Admit fresh work before replacing labels, even after earlier expansion. + // Old pending Spaces retain their original Choice cursors and coverage. + registry->retain(std::move(*descriptor)); + state_descriptors = std::max(state_descriptors,registry->size()); + state_bytes = std::max(state_bytes,registry->storage()); + return true; + } + + Space* + Column::publish(std::unique_ptr s) { + // Clean exhausted branchers, retaining completion branchers as usual. + (void) s->choice(); + if (best && !Search::better(*s,*best,"Column::next")) + return nullptr; + const std::int64_t bound = guidance.bound(*s); + std::unique_ptr result(s->clone()); + best = std::move(s); + best_bound = bound; + incumbent_revision++; + return result.release(); + } + + bool + Column::select(void) { + while (queued) { + if (stop_seq(opt)) return false; + if (cycle_depth >= columns.size()) { + cycle_depth = 0; + expanded = 0; + const auto maximum = std::numeric_limits::max(); + quota += std::min(limits.quota_growth,maximum-quota); + } + auto& column = columns[cycle_depth]; + if (column.empty() || expanded == quota) { + cycle_depth++; + expanded = 0; + continue; + } + std::pop_heap(column.begin(),column.end(), + Later{guidance.sense,static_cast(guidance.rank)}); + Entry entry = std::move(column.back()); + column.pop_back(); + queued--; + if (!improves(entry.bound)) { + fail++; + continue; + } + expansion.select(std::move(entry.space)); + current_depth = entry.depth; + current_revision = entry.incumbent_revision; + current_status = SS_BRANCH; + counted = true; + expanded++; + return true; + } + return false; + } + + Space* + Column::next(void) { + if (limited) { + _stopped = true; + return nullptr; + } + start(); + while (true) { + if (stop_seq(opt)) return nullptr; + if (!expansion && !select()) return nullptr; + if (!counted) { + node++; + stack_depth(current_depth); + counted = true; + if (current_status == SS_SOLVED) { + if (Space* solution = publish(expansion.take())) return solution; + continue; + } + if (!improves(guidance.bound(expansion.space()))) { + fail++; + expansion.reset(); + continue; + } + expanded++; + } + const auto child_depth = current_depth+1; + ExpansionStep step = expansion.next(*this,opt,child_depth,current_revision, + best.get(),incumbent_revision); + if (step.state == ExpansionStep::SUSPENDED) return nullptr; + if (step.state == ExpansionStep::EXHAUSTED) continue; + switch (step.status) { + case SS_FAILED: + fail++; + break; + case SS_SOLVED: + if (Space* solution = publish(std::move(step.space))) return solution; + break; + case SS_BRANCH: + if (!retain(std::move(step.space),child_depth,incumbent_revision)) + return nullptr; + break; + default: + GECODE_NEVER; + } + } + } + + Statistics + Column::statistics(void) const { + return *this; + } + + void + Column::constrain(const Space& b) { + if (best && !Search::better(b,*best,"Column::constrain")) return; + const std::int64_t bound = guidance.bound(b); + std::unique_ptr incumbent(b.clone()); + best = std::move(incumbent); + best_bound = bound; + incumbent_revision++; + } + + void + Column::reset(Space* s) { + expansion.reset(); + columns.clear(); + queued = 0; + cycle_depth = expanded = 0; + quota = limits.initial_quota; + limited = false; + if (registry) registry->clear(); + best.reset(); + order = 0; + incumbent_revision = current_revision = 0; + current_depth = 0; + counted = false; + Worker::reset(); + initialize(s,false); + } + + NoGoods& + Column::nogoods(void) { + return NoGoods::eng; + } + +}}} + +// STATISTICS: search-other diff --git a/gecode/search/column.hpp b/gecode/search/column.hpp new file mode 100644 index 0000000000..243aff0b04 --- /dev/null +++ b/gecode/search/column.hpp @@ -0,0 +1,90 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +namespace Gecode { namespace Search { + + /// Create a native sequential column search engine + GECODE_SEARCH_EXPORT Engine* + columnengine(Space* s, const HeuristicGuidance& guidance, const Options& o, + const ColumnOptions& limits=ColumnOptions(), + const StatePruning& pruning=StatePruning()); + + /// Column-search engine builder + template + class ColumnBuilder : public Builder { + HeuristicGuidance guidance; + ColumnOptions limits; + StatePruning pruning; + public: + ColumnBuilder(const HeuristicGuidance& g, const Options& o, + const ColumnOptions& l, const StatePruning& p=StatePruning()) + : Builder(o,true), guidance(g), limits(l), pruning(p) {} + Engine* operator()(Space* s) const override { + return columnengine(dynamic_cast(s),guidance,opt,limits,pruning); + } + }; + + inline + ColumnOptions::ColumnOptions(unsigned long int initial, + unsigned long int growth, + unsigned long int frontier_nodes) + : initial_quota(initial), quota_growth(growth), + frontier_limit(frontier_nodes) {} + +}} + +namespace Gecode { + + template + inline + Column::Column(T* s, const Search::HeuristicGuidance& g, + const Search::Options& o, const Search::ColumnOptions& limits, + const Search::StatePruning& p) + : Search::Base(Search::columnengine(s,g,o,limits,p)) {} + + template + inline void + Column::constrain(const Space& incumbent) { + this->e->constrain(incumbent); + } + + template + inline SEB + column(const Search::HeuristicGuidance& g, const Search::Options& o, + const Search::ColumnOptions& limits, const Search::StatePruning& p) { + return new Search::ColumnBuilder(g,o,limits,p); + } + +} + +// STATISTICS: search-other diff --git a/gecode/search/completion.hh b/gecode/search/completion.hh new file mode 100644 index 0000000000..797d1a47ae --- /dev/null +++ b/gecode/search/completion.hh @@ -0,0 +1,142 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +#ifndef GECODE_SEARCH_COMPLETION_HH +#define GECODE_SEARCH_COMPLETION_HH + +#include +#include + +namespace Gecode { namespace Search { + + /// Brancher-owned existential DFS, advanced at the native choice boundary. + class Completion : public Brancher { + struct Pending : Stop { + std::unique_ptr> engine; + Statistics published; + Statistics* caller; + const Options* options; + + Pending(void) : caller(nullptr), options(nullptr) {} + + Statistics delta(const Statistics& s) const { + Statistics d=s; + d.node-=published.node; d.fail-=published.fail; + d.propagate-=published.propagate; + d.restart-=published.restart; d.nogood-=published.nogood; + // Depth is the maximum constituent depth, as in operator+=. + return d; + } + bool stop(const Statistics& s, const Options&) override { + if (!options || !options->stop) return false; + Statistics total=*caller; + total+=delta(s); + return options->stop->stop(total,*options); + } + void publish(void) { + if (engine && caller) { + Statistics s=engine->statistics(); + *caller+=delta(s); + published=s; + } + } + }; + Pending* pending; + enum Result { UNSET, FOUND, EXHAUSTED } result; + unsigned int revision; + protected: + bool done; + bool suppressed; + + Completion(Home home) + : Brancher(home), pending(nullptr), result(UNSET), revision(0), + done(false), suppressed(false) { + static_cast(home).retain_branchers(); + } + Completion(Space& home, Completion& b) + : Brancher(home,b), pending(nullptr), result(b.result), + revision(b.revision), done(b.done), suppressed(b.suppressed) {} + + /// Clone the trial root and post its ordinary completion branching. + virtual Space* trial(Space& home)=0; + + class CompletionChoice : public Choice { + public: + bool fail; + unsigned int revision; + CompletionChoice(const Brancher& b, bool f, unsigned int r) + : Choice(b,1), fail(f), revision(r) {} + size_t size(void) const { return sizeof(*this); } + void archive(Archive& e) const override { + Choice::archive(e); e.put(fail); e.put(revision); + } + }; + + public: + bool retained(void) const override { return !suppressed; } + void invalidate(Space&) override { + delete pending; pending=nullptr; + result=UNSET; done=false; revision++; + } + bool prepare(Space& home, Statistics& stat, + const Options& options) override { + if (result != UNSET) return true; + if (options.stop && options.stop->stop(stat,options)) return false; + if (!pending) pending=new Pending; + pending->caller=&stat; + pending->options=&options; + try { + if (!pending->engine) { + Options o=options; + o.clone=false; o.threads=1; o.stop=pending; o.tracer=nullptr; + // The trial runs inside the owning worker's engine. Its control is + // already bound there and cannot be attached to a second engine. + o.worker_control=WorkerControl(); + // Suppression belongs only to this trial clone, never to its source. + suppressed=true; + std::unique_ptr root; + try { root.reset(trial(home)); } + catch (...) { suppressed=false; throw; } + suppressed=false; + pending->engine.reset(new DFS(root.get(),o)); + root.release(); + } + std::unique_ptr solution(pending->engine->next()); + pending->publish(); + if (solution) result=FOUND; + else if (!pending->engine->stopped()) result=EXHAUSTED; + pending->caller=nullptr; pending->options=nullptr; + } catch (...) { + pending->publish(); + pending->caller=nullptr; pending->options=nullptr; + throw; + } + if (result == UNSET) return false; + delete pending; pending=nullptr; + return true; + } + const Choice* choice(Space& home) override { + Statistics stat; + Options options; + (void) prepare(home,stat,options); + return new CompletionChoice(*this,result==EXHAUSTED,revision); + } + const Choice* choice(const Space&, Archive& e) override { + bool fail; unsigned int r; + e >> fail >> r; + return new CompletionChoice(*this,fail,r); + } + ExecStatus commit(Space&, const Choice& c, unsigned int) override { + const CompletionChoice& ch=static_cast(c); + if (ch.fail) return ES_FAILED; + // A strengthened replay must return to preparation before being solved. + if (revision == ch.revision) { done=true; result=FOUND; } + return ES_OK; + } + size_t dispose(Space& home) override { + delete pending; pending=nullptr; + (void) Brancher::dispose(home); + return sizeof(*this); + } + }; +}} + +#endif diff --git a/gecode/search/exception.cpp b/gecode/search/exception.cpp index 7b36ff7f01..736ac1d06c 100644 --- a/gecode/search/exception.cpp +++ b/gecode/search/exception.cpp @@ -34,6 +34,43 @@ #include namespace Gecode { namespace Search { + Incomparable::Incomparable(const char* l) + : Exception(l,"Incumbent objectives are incomparable") {} + + UninitializedStatePruning::UninitializedStatePruning(const char* l) + : Exception(l,"State pruning requires extraction and label comparison") {} + + UnsupportedColumnThreads::UnsupportedColumnThreads(const char* l) + : Exception(l,"Column requires exactly one expanded search thread") {} + + UnsupportedColumnTracer::UnsupportedColumnTracer(const char* l) + : Exception(l,"Column does not support search tracers") {} + + UninitializedColumnBound::UninitializedColumnBound(const char* l) + : Exception(l,"Column total-bound guidance is missing") {} + + InvalidColumnQuota::InvalidColumnQuota(const char* l) + : Exception(l,"Column initial quota must be positive") {} + + UnsupportedCABSThreads::UnsupportedCABSThreads(const char* l) + : Exception(l,"CABS requires exactly one expanded search thread") {} + + UnsupportedCABSTracer::UnsupportedCABSTracer(const char* l) + : Exception(l,"CABS does not support search tracers") {} + + UninitializedCABSBound::UninitializedCABSBound(const char* l) + : Exception(l,"CABS total-bound guidance is missing") {} + + UnsupportedAStarThreads::UnsupportedAStarThreads(const char* l) + : Exception(l,"A* requires exactly one expanded search thread") {} + + UnsupportedAStarTracer::UnsupportedAStarTracer(const char* l) + : Exception(l,"A* does not support search tracers") {} + + UninitializedAStarBound::UninitializedAStarBound(const char* l) + : Exception(l,"A* total-bound guidance is missing") {} + + UninitializedCutoff::UninitializedCutoff(const char* l) : Exception(l,"Cutoff for restart-based search is missing") {} diff --git a/gecode/search/exception.hpp b/gecode/search/exception.hpp index 26a6c0e6e9..90a280653d 100644 --- a/gecode/search/exception.hpp +++ b/gecode/search/exception.hpp @@ -80,6 +80,66 @@ namespace Gecode { namespace Search { /// Initialize with location \a l WorkerControlInUse(const char* l); }; + /// %Exception: Column requires exactly one expanded thread + class GECODE_SEARCH_EXPORT UnsupportedColumnThreads : public Exception { + public: + UnsupportedColumnThreads(const char* l); + }; + /// %Exception: Column does not support search tracers + class GECODE_SEARCH_EXPORT UnsupportedColumnTracer : public Exception { + public: + UnsupportedColumnTracer(const char* l); + }; + /// %Exception: Column requires total-bound guidance + class GECODE_SEARCH_EXPORT UninitializedColumnBound : public Exception { + public: + UninitializedColumnBound(const char* l); + }; + /// %Exception: Column requires a positive initial quota + class GECODE_SEARCH_EXPORT InvalidColumnQuota : public Exception { + public: + InvalidColumnQuota(const char* l); + }; + /// %Exception: CABS requires exactly one expanded thread + class GECODE_SEARCH_EXPORT UnsupportedCABSThreads : public Exception { + public: + UnsupportedCABSThreads(const char* l); + }; + /// %Exception: CABS does not support search tracers + class GECODE_SEARCH_EXPORT UnsupportedCABSTracer : public Exception { + public: + UnsupportedCABSTracer(const char* l); + }; + /// %Exception: CABS requires total-bound guidance + class GECODE_SEARCH_EXPORT UninitializedCABSBound : public Exception { + public: + UninitializedCABSBound(const char* l); + }; + /// %Exception: A* requires exactly one expanded thread + class GECODE_SEARCH_EXPORT UnsupportedAStarThreads : public Exception { + public: + UnsupportedAStarThreads(const char* l); + }; + /// %Exception: A* does not support search tracers + class GECODE_SEARCH_EXPORT UnsupportedAStarTracer : public Exception { + public: + UnsupportedAStarTracer(const char* l); + }; + /// %Exception: A* requires total-bound guidance + class GECODE_SEARCH_EXPORT UninitializedAStarBound : public Exception { + public: + UninitializedAStarBound(const char* l); + }; + /// %Exception: State pruning requires both extraction and label comparison + class GECODE_SEARCH_EXPORT UninitializedStatePruning : public Exception { + public: + UninitializedStatePruning(const char* l); + }; + /// %Exception: Incumbent objectives are incomparable + class GECODE_SEARCH_EXPORT Incomparable : public Exception { + public: + Incomparable(const char* l); + }; //@} }} diff --git a/gecode/search/seq/astar.hh b/gecode/search/seq/astar.hh new file mode 100644 index 0000000000..cd38be9df6 --- /dev/null +++ b/gecode/search/seq/astar.hh @@ -0,0 +1,104 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef GECODE_SEARCH_SEQ_ASTAR_HH +#define GECODE_SEARCH_SEQ_ASTAR_HH + +#include +#include +#include +#include +#include + +namespace Gecode { namespace Search { namespace Seq { + + /// Sequential tree search with owned stable frontier clones + class AStar : public Worker { + public: + struct Configuration { + HeuristicGuidance guidance; + StatePruning pruning; + BestFirstOrder ordering; + }; + private: + struct Entry { + std::unique_ptr space; + std::int64_t bound, rank; + unsigned long int depth; + unsigned long long int order, incumbent_revision; + }; + /// Heap comparison with fixed exploration order + struct Later { + HeuristicGuidance::Sense sense; + BestFirstOrder ordering; + bool operator()(const Entry& a, const Entry& b) const; + }; + Options opt; + HeuristicGuidance guidance; + StatePruning pruning; + BestFirstOrder ordering; + /// Metadata survives expansion; pending Spaces and Choices stay untouched + std::unique_ptr registry; + std::vector frontier; + unsigned long long int order; + /// Revision of the currently accepted incumbent + unsigned long long int incumbent_revision; + /// Selected parent, also retained across interrupted preparation + ChoiceExpansion expansion; + SpaceStatus current_status; + unsigned long int current_depth; + /// Incumbent restriction already inherited by the selected source + unsigned long long int current_revision; + bool counted; + std::unique_ptr best; + std::int64_t best_bound; + + bool improves(std::int64_t bound) const; + void enqueue(std::unique_ptr s, unsigned long int depth, + unsigned long long int revision); + Space* publish(std::unique_ptr s); + void initialize(Space* s, bool clone); + public: + AStar(Space* s, const Options& o, const Configuration& c); + Space* next(void); + Statistics statistics(void) const; + void constrain(const Space& b); + void reset(Space* s); + NoGoods& nogoods(void); + }; + +}}} + +#endif + +// STATISTICS: search-seq diff --git a/gecode/search/seq/cabs.hh b/gecode/search/seq/cabs.hh new file mode 100644 index 0000000000..8761bdd686 --- /dev/null +++ b/gecode/search/seq/cabs.hh @@ -0,0 +1,114 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef GECODE_SEARCH_SEQ_CABS_HH +#define GECODE_SEARCH_SEQ_CABS_HH + +#include +#include +#include +#include + +namespace Gecode { namespace Search { namespace Seq { + + /// Sequential layered beams, widening while discarded work can improve + class CABS : public Worker { + public: + /// Additional configuration carried through the sequential engine adapter + struct Configuration { + HeuristicGuidance guidance; + CABSOptions limits; + StatePruning pruning; + }; + private: + struct Entry { + std::unique_ptr space; + std::int64_t bound, rank; + unsigned long int depth; + unsigned long long int order, incumbent_revision; + std::optional descriptor; + }; + /// Better priority first; a candidate heap therefore exposes its worst node + struct Earlier { + HeuristicGuidance::Sense sense; + bool ranked; + bool operator()(const Entry& a, const Entry& b) const; + }; + Options opt; + HeuristicGuidance guidance; + CABSOptions limits; + StatePruning pruning; + /// Descriptors owned by the live next-layer candidates + unsigned long int candidate_descriptors; + std::size_t candidate_bytes; + unsigned long int width; + unsigned long long int order, incumbent_revision; + /// Never restricted; every wider pass regenerates omitted alternatives + std::unique_ptr root; + std::vector layer, candidates; + /// Source, preparation, and original alternative cursor survive next() + ChoiceExpansion expansion; + SpaceStatus current_status; + unsigned long int current_depth; + unsigned long long int current_revision; + bool counted; + std::unique_ptr best; + std::int64_t best_bound; + /// Optimistic bound over all width-discarded candidates in this pass + std::optional discarded_bound; + /// Intrinsic resource stops persist until reset + bool limited; + + bool improves(std::int64_t bound) const; + bool retain(std::unique_ptr s, unsigned long int depth, + unsigned long long int revision); + void add_descriptor(const Entry& entry); + void remove_descriptor(const Entry& entry); + void record_descriptors(void); + Space* publish(std::unique_ptr s); + bool advance(void); + void initialize(Space* s, bool clone); + public: + CABS(Space* s, const Options& o, const Configuration& c); + Space* next(void); + Statistics statistics(void) const; + void constrain(const Space& b); + void reset(Space* s); + NoGoods& nogoods(void); + }; + +}}} + +#endif + +// STATISTICS: search-seq diff --git a/gecode/search/seq/choice-expansion.hh b/gecode/search/seq/choice-expansion.hh new file mode 100644 index 0000000000..d1bd9e3d5b --- /dev/null +++ b/gecode/search/seq/choice-expansion.hh @@ -0,0 +1,133 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef GECODE_SEARCH_SEQ_CHOICE_EXPANSION_HH +#define GECODE_SEARCH_SEQ_CHOICE_EXPANSION_HH + +#include +#include +#include +#include + +namespace Gecode { namespace Search { namespace Seq { + + /// One propagated child, interrupted preparation, or exhausted choice + struct ExpansionStep { + enum State { CHILD, SUSPENDED, EXHAUSTED }; + State state; + std::unique_ptr space; + SpaceStatus status; + }; + + /// Own a selected parent and its original-choice continuation + class ChoiceExpansion { + private: + // Declaration order also destroys the choice before its source. + std::unique_ptr parent; + std::unique_ptr choice; + unsigned int alternative; + public: + ChoiceExpansion(void) : alternative(0) {} + + explicit operator bool(void) const { return static_cast(parent); } + Space& space(void) const { return *parent; } + + /// Take ownership of a stable selected source + void select(std::unique_ptr s) { + assert(!parent && !choice); + parent = std::move(s); + alternative = 0; + } + + /// Take a solved root, which has no saved choice + std::unique_ptr take(void) { + assert(!choice); + return std::move(parent); + } + + /// Clear continuation before deleting its source + void reset(void) { + choice.reset(); + parent.reset(); + alternative = 0; + } + + /// Commit and propagate one original alternative without changing siblings + ExpansionStep next(Worker& worker, const Options& opt, + unsigned long int child_depth, + unsigned long long int source_revision, + const Space* incumbent, + unsigned long long int incumbent_revision) { + assert(parent); + if (!choice) { + if (!worker.prepare(*parent,opt)) + return {ExpansionStep::SUSPENDED,nullptr,SS_FAILED}; + choice.reset(parent->choice()); + alternative = 0; + } + if (alternative == choice->alternatives()) { + reset(); + return {ExpansionStep::EXHAUSTED,nullptr,SS_FAILED}; + } + std::unique_ptr child; + // Keep the final choice alive through commit, cut, status and feedback, + // including exceptional cleanup, after its source becomes the child. + std::unique_ptr final_choice; + const Choice* decision = choice.get(); + if (alternative+1 == choice->alternatives()) { + child = std::move(parent); + final_choice = std::move(choice); + } else { + child.reset(parent->clone()); + } + child->commit(*decision,alternative); + if (incumbent && source_revision != incumbent_revision) { + // Repeating an inherited cut would restart completion branchers. + child->invalidate_branchers(); + child->constrain(*incumbent); + } + worker.node++; + worker.stack_depth(child_depth); + const SpaceStatus status = child->status(worker); + branch_feedback(*decision,alternative,*child,branch_outcome(status)); + alternative++; + // The final source is already retired before retained-node admission. + return {ExpansionStep::CHILD,std::move(child),status}; + } + }; + +}}} + +#endif + +// STATISTICS: search-seq diff --git a/gecode/search/seq/column.hh b/gecode/search/seq/column.hh new file mode 100644 index 0000000000..1f8d58dc0e --- /dev/null +++ b/gecode/search/seq/column.hh @@ -0,0 +1,110 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef GECODE_SEARCH_SEQ_COLUMN_HH +#define GECODE_SEARCH_SEQ_COLUMN_HH + +#include +#include +#include +#include +#include + +namespace Gecode { namespace Search { namespace Seq { + + /// Sequential depth queues with retained alternatives and progressive quotas + class Column : public Worker { + public: + struct Configuration { + HeuristicGuidance guidance; + ColumnOptions limits; + StatePruning pruning; + }; + private: + struct Entry { + std::unique_ptr space; + std::int64_t bound, rank; + unsigned long int depth; + unsigned long long int order, incumbent_revision; + }; + /// Heap comparison: lower rank, or optimistic bound without ranking + struct Later { + HeuristicGuidance::Sense sense; + bool ranked; + bool operator()(const Entry& a, const Entry& b) const; + }; + Options opt; + HeuristicGuidance guidance; + ColumnOptions limits; + StatePruning pruning; + /// Metadata survives expansion; pending Spaces and Choices stay untouched + std::unique_ptr registry; + std::vector> columns; + std::size_t queued; + /// Cycle state survives publication, stops and worker parking + unsigned long int cycle_depth, quota, expanded; + /// A resource loss cannot become a completeness claim on the next call + bool limited; + unsigned long long int order; + /// Revision of the currently accepted incumbent + unsigned long long int incumbent_revision; + /// Selected parent, also retained across interrupted preparation + ChoiceExpansion expansion; + SpaceStatus current_status; + unsigned long int current_depth; + /// Incumbent restriction already inherited by the selected source + unsigned long long int current_revision; + bool counted; + std::unique_ptr best; + std::int64_t best_bound; + + bool improves(std::int64_t bound) const; + bool retain(std::unique_ptr s, unsigned long int depth, + unsigned long long int revision); + bool select(void); + Space* publish(std::unique_ptr s); + void initialize(Space* s, bool clone); + public: + Column(Space* s, const Options& o, const Configuration& c); + Space* next(void); + Statistics statistics(void) const; + void constrain(const Space& b); + void reset(Space* s); + NoGoods& nogoods(void); + }; + +}}} + +#endif + +// STATISTICS: search-seq diff --git a/gecode/search/seq/state-registry.hh b/gecode/search/seq/state-registry.hh new file mode 100644 index 0000000000..fb90e704be --- /dev/null +++ b/gecode/search/seq/state-registry.hh @@ -0,0 +1,105 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef GECODE_SEARCH_SEQ_STATE_REGISTRY_HH +#define GECODE_SEARCH_SEQ_STATE_REGISTRY_HH + +#include +#include +#include +#include +#include + +namespace Gecode { namespace Search { namespace Seq { + + /// Persistent descriptor metadata, independent of pending search work + class StateRegistry { + private: + StatePruning::Compare compare; + unsigned long int limit; + std::unordered_map> buckets; + unsigned long int descriptors; + std::size_t descriptor_bytes; + public: + StateRegistry(const StatePruning::Compare& c, unsigned long int l) + : compare(c), limit(l), descriptors(0), descriptor_bytes(0) {} + + /// Whether an equivalent or better retained label subsumes this descriptor + bool subsumes(const StatePruning::Descriptor& descriptor) const { + const auto bucket = buckets.find(descriptor.hash); + if (bucket != buckets.end()) + for (const auto& retained : bucket->second) { + if (descriptor.key != retained.key) continue; + const auto relation = compare(descriptor,retained); + if (relation == StatePruning::EQUIVALENT || + relation == StatePruning::DOMINATED) + return true; + } + return false; + } + + /// Retain admitted metadata, replacing dominated labels and forgetting at limit + void retain(StatePruning::Descriptor descriptor) { + auto& bucket = buckets[descriptor.hash]; + bucket.erase(std::remove_if(bucket.begin(),bucket.end(), + [&](const StatePruning::Descriptor& retained) { + if (descriptor.key != retained.key || + compare(descriptor,retained) != StatePruning::DOMINATES) + return false; + descriptors--; + descriptor_bytes -= retained.storage(); + return true; + }),bucket.end()); + if (limit && descriptors >= limit) clear(); + descriptor_bytes += descriptor.storage(); + buckets[descriptor.hash].push_back(std::move(descriptor)); + descriptors++; + } + + /// Forget metadata without affecting search coverage + void clear(void) { + buckets.clear(); + descriptors = 0; + descriptor_bytes = 0; + } + + unsigned long int size(void) const { return descriptors; } + std::size_t storage(void) const { return descriptor_bytes; } + }; + +}}} + +#endif + +// STATISTICS: search-seq diff --git a/gecode/search/statistics.hpp b/gecode/search/statistics.hpp index b6f3dfde14..758a3309f3 100755 --- a/gecode/search/statistics.hpp +++ b/gecode/search/statistics.hpp @@ -39,12 +39,14 @@ namespace Gecode { namespace Search { Statistics::reset(void) { StatusStatistics::reset(); fail=0ULL; node=0ULL; depth=0UL; restart=0UL; nogood=0UL; + state_pruned=0ULL; state_descriptors=0UL; state_bytes=0; } forceinline Statistics::Statistics(void) : fail(0ULL), node(0ULL), - depth(0UL), restart(0UL), nogood(0UL) {} + depth(0UL), restart(0UL), nogood(0UL), + state_pruned(0ULL), state_descriptors(0UL), state_bytes(0) {} forceinline Statistics& Statistics::operator +=(const Statistics& s) { @@ -54,6 +56,9 @@ namespace Gecode { namespace Search { depth = std::max(depth,s.depth); restart += s.restart; nogood += s.nogood; + state_pruned += s.state_pruned; + state_descriptors = std::max(state_descriptors,s.state_descriptors); + state_bytes = std::max(state_bytes,s.state_bytes); return *this; } diff --git a/gecode/search/support.hh b/gecode/search/support.hh index fe25d487b2..4cd83a3e3c 100644 --- a/gecode/search/support.hh +++ b/gecode/search/support.hh @@ -38,6 +38,23 @@ namespace Gecode { namespace Search { + /// Whether incoming solution \a s should replace incumbent \a b + forceinline bool + better(const Space& s, const Space& b, const char* l) { + switch (s.compare(b)) { + case SC_BETTER: + return true; + case SC_EQUIVALENT: + case SC_WORSE: + return false; + case SC_INCOMPARABLE: + throw Incomparable(l); + default: + GECODE_NEVER; + } + return false; + } + /// Clone space \a s depending on options \a o forceinline Space* snapshot(Space* s, const Options& o); @@ -51,6 +68,9 @@ namespace Gecode { namespace Search { public: /// Initialization WorkerToEngine(Space* s, const Options& o); + /// Initialization with additional worker configuration + template + WorkerToEngine(Space* s, const Options& o, const Configuration& configuration); /// Return next solution (nullptr, if none exists or search has been stopped) virtual Space* next(void); /// Return statistics @@ -77,6 +97,11 @@ namespace Gecode { namespace Search { WorkerToEngine::WorkerToEngine(Space* s, const Options& o) : Engine(o,1U), w(s,o) {} template + template + WorkerToEngine::WorkerToEngine(Space* s, const Options& o, + const Configuration& configuration) + : Engine(o,1U), w(s,o,configuration) {} + template Space* WorkerToEngine::next(void) { return w.next(); diff --git a/gecode/search/worker.hh b/gecode/search/worker.hh index 892cd85a59..dc277b3ad3 100644 --- a/gecode/search/worker.hh +++ b/gecode/search/worker.hh @@ -56,6 +56,8 @@ namespace Gecode { namespace Search { bool stop(const Options& o); /// Check stop and wait for admission of a sequential worker bool stop_seq(Options& o); + /// Prepare a brancher before changing the search path. + bool prepare(Space& s, const Options& o); /// Check whether engine has been stopped bool stopped(void) const; /// Reset statistics with root depth \a d @@ -97,6 +99,13 @@ namespace Gecode { namespace Search { return true; } + forceinline bool + Worker::prepare(Space& s, const Options& o) { + bool prepared = s.prepare(*this,o); + _stopped |= !prepared; + return prepared; + } + forceinline bool Worker::stopped(void) const { return _stopped; diff --git a/misc/test-tsptw-benchmark.py b/misc/test-tsptw-benchmark.py new file mode 100644 index 0000000000..629949bc87 --- /dev/null +++ b/misc/test-tsptw-benchmark.py @@ -0,0 +1,284 @@ +"""Focused checks for record reuse and false proof/parser hazards.""" +import importlib.util +import copy +import contextlib +import io +from unittest.mock import Mock, patch +import json +from pathlib import Path +import tempfile +import unittest + +spec = importlib.util.spec_from_file_location('benchmark', Path(__file__).with_name('tsptw-benchmark.py')) +benchmark = importlib.util.module_from_spec(spec) +spec.loader.exec_module(benchmark) + + +class Records(unittest.TestCase): + def test_case_selector_cannot_leak_held_out_cases_into_development(self): + case = 'Dumas/n20w20.001.txt' + selected = benchmark.select_cases('development', Path('/tmp/selected'), case) + self.assertEqual([row['id'] for row in selected], [case]) + for split, names in (('held-out', case), ('tiny', case), + ('development', case+','+case), ('development', 'Dumas/n40w20.002.txt')): + with self.subTest(split=split, names=names), self.assertRaises(ValueError): + benchmark.select_cases(split, Path('/tmp/selected'), names) + selected = benchmark.select_cases('development', Path('/tmp/selected'), case+',tiny/late-return.txt') + self.assertEqual([row['id'] for row in selected], [case, 'tiny/late-return.txt']) + self.assertEqual(len(benchmark.select_cases('development', Path('/tmp/selected'))), 12) + with self.assertRaises(ValueError): + benchmark.select_cases('development', Path('/tmp/selected'), 'tiny/../late-return.txt') + + def test_task_budget_preserves_verification_and_cleanup(self): + with tempfile.TemporaryDirectory() as directory: + ledger = Path(directory)/'budget.jsonl' + header = dict(event='budget', scope='task', task_id='open-search-028', + total_seconds=300, verification_reserve_seconds=60, cleanup_reserve_seconds=20) + ledger.write_text(json.dumps(header)+'\n') + attempt = benchmark.admit_budget(ledger, 'correctness', 15, 171) + with self.assertRaisesRegex(ValueError, 'unreaped'): + benchmark.admit_budget(ledger, 'development', 2, 0) + benchmark.budget_event(ledger, dict(event='reap', id=attempt, wall_seconds=200)) + with self.assertRaisesRegex(ValueError, 'task allowance'): + benchmark.admit_budget(ledger, 'development', 21, 0) + with self.assertRaisesRegex(ValueError, 'excludes'): + benchmark.admit_budget(ledger, 'held-out', 1, 0) + with self.assertRaisesRegex(ValueError, 'verification allowance'): + benchmark.admit_budget(ledger, 'verification', 61, 0) + attempt = benchmark.admit_budget(ledger, 'verification', 15, 0) + benchmark.budget_event(ledger, dict(event='reap', id=attempt, wall_seconds=15)) + self.assertEqual(benchmark.budget_state(ledger), (dict(correctness=200, verification=15), {})) + attempt = benchmark.admit_budget(ledger, 'verification', 45, 0) + benchmark.budget_event(ledger, dict(event='unresolved', id=attempt, wall_seconds=3)) + with self.assertRaisesRegex(ValueError, 'unreaped'): + benchmark.admit_budget(ledger, 'verification', 1, 0) + + def test_summary_uses_saved_exact_transition_arguments(self): + # The historical display name deliberately differs from the frozen arguments. + row = dict(config='transition-astar', arguments=benchmark.CONFIGS['transition-astar-lazy-min-arcs']) + summary = dict(type='summary', engine='astar', formulation='transition', bound='min-arcs', + residual_propagation='off', state_pruning='off', termination='infeasible', incumbent=None, + elapsed_ms=2, preprocessing_ms=1, search_ms=1, statistics=dict(node=1)) + benchmark.validate_summary(summary, [], row) + for field, wrong in (('bound', 'prefix'), ('residual_propagation', 'unary'), ('state_pruning', 'pareto')): + with self.subTest(field=field), self.assertRaisesRegex(ValueError, field): + benchmark.validate_summary(dict(summary, **{field:wrong}), [], row) + column = dict(summary, engine='column', bound='prefix', initial_quota=1, quota_growth=1, frontier_limit=0) + with self.assertRaisesRegex(ValueError, 'initial_quota'): + benchmark.validate_summary(dict(column, initial_quota=2), [], dict(config='transition-column')) + + def test_malformed_terminal_record_is_unfinished(self): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory)/'row.json' + path.write_text(json.dumps(dict(row={}, status='ok', wall_seconds=float('nan'), incumbents=[], diagnostic=''))) + self.assertIsNone(benchmark.completed(path, {})) + path.write_text('{') + self.assertIsNone(benchmark.completed(path, {})) + + def test_malformed_saved_contents_do_not_reuse_or_count_as_proof(self): + checked = benchmark.checker.check_tour( + benchmark.checker.read_instance(benchmark.DATA/'tiny/waiting.txt'), [1, 2]) + incumbent = dict(tour=[1, 2], **{k: checked[k] for k in ('cost', 'scaled_cost', 'scale', 'return_time')}) + trace = dict(incumbent, type='solution', checked=True, elapsed_ms=1) + summary = dict(type='summary', engine='bab', formulation='closed', termination='optimal', + incumbent=incumbent, elapsed_ms=2, preprocessing_ms=0.5, search_ms=1.5, statistics=dict(node=3)) + metrics = dict(preprocessing_ms=0.5, search_ms=1.5, startup_and_teardown_ms=1, + expansions=3, descriptor_count=None, descriptor_bytes=None, allocation=None, + frontier_bytes=None, clone_bytes=None) + row = dict(config='closed-bab', argv=['solver', 'waiting.txt']) + valid = dict(row=row, status='ok', wall_seconds=0.003, diagnostic='', incumbents=[trace], + final_objective=5, metrics=metrics, proof='optimal', summary=summary, + stop_reason='optimal', returncode=0, peak_rss_bytes=None, argv=row['argv']) + mutations = [ + ('incumbents', [42]), ('summary', {}), ('final_objective', 'not-an-objective'), + ('metrics', dict(metrics, search_ms='not-a-measurement')), + ('metrics', dict(metrics, search_ms=float('nan'))), ('final_objective', 6), + ('proof', 'infeasible'), ('summary', dict(summary, incumbent=dict(incumbent, tour=[2, 1]))), + ('argv', ['solver', 'different.txt']), + ] + with tempfile.TemporaryDirectory() as directory: + path = Path(directory)/'row.json' + path.write_text(json.dumps(valid)) + self.assertIsNotNone(benchmark.completed(path, row)) + for field, replacement in mutations: + with self.subTest(field=field, replacement=replacement): + value = copy.deepcopy(valid) + value[field] = replacement + path.write_text(json.dumps(value)) + self.assertIsNone(benchmark.completed(path, row)) + + def test_malformed_live_measurements_are_reusable_terminal_failures(self): + instance = benchmark.DATA/'tiny/waiting.txt' + checked = benchmark.checker.check_tour(benchmark.checker.read_instance(instance), [1, 2]) + incumbent = dict(tour=[1, 2], **{k: checked[k] for k in ('cost', 'scaled_cost', 'scale', 'return_time')}) + solution = dict(incumbent, type='solution', elapsed_ms=1) + summary = dict(type='summary', engine='astar', formulation='transition', termination='optimal', + incumbent=incumbent, elapsed_ms=2, preprocessing_ms=0.5, search_ms=1.5, + statistics=dict(node=3, state_descriptors=0, state_bytes=0), + allocation=dict(space_bytes=1, intvar_handle_bytes=1, root_positions=0, + prepared_root_positions=1, solution_positions=3), + measurements=dict(bound_calls=1, bound_ms=0.1, inference_calls=0, inference_ms=0, propagation_ms=0)) + row = dict(id='sample', config='transition-astar', case=dict(path=str(instance), sha256=benchmark.digest(instance))) + config = dict(configs=benchmark.CONFIGS, native=dict(path='unused'), seconds=1, + rss_bytes=1024**3, sample_interval_seconds=0.05) + for section, field in (('statistics', 'node'), ('statistics', 'state_bytes'), + ('allocation', 'space_bytes'), ('measurements', 'bound_ms')): + with self.subTest(section=section, field=field), tempfile.TemporaryDirectory() as directory: + malformed = copy.deepcopy(summary) + malformed[section][field] = 'not-a-measurement' + def capture(argv, destination, *limits): + destination.with_suffix('.stdout').write_text(json.dumps(solution)+'\n'+json.dumps(malformed)+'\n') + destination.with_suffix('.stderr').write_text('') + return dict(status='ok', returncode=0, wall_seconds=0.003, diagnostic='', peak_rss_bytes=None) + samples = Path(directory) + with patch.object(benchmark, 'capture', side_effect=capture), contextlib.redirect_stdout(io.StringIO()): + benchmark.collect(config, row, samples) + result = benchmark.completed(samples/'sample.json', row) + self.assertIsNotNone(result) + self.assertEqual(result['status'], 'invalid-output') + self.assertIsNone(result['proof']) + self.assertEqual(result['final_objective'], 5) + self.assertEqual(len(result['incumbents']), 1) + self.assertIsNone(result['summary']) + self.assertIn(field, result['diagnostic']) + + def test_failed_cleanup_preserves_error_streams_and_blocks_resume(self): + for failure in ('kill', 'wait'): + with self.subTest(failure=failure), tempfile.TemporaryDirectory() as directory: + samples = Path(directory) + ledger = samples/'budget.jsonl' + ledger.write_text(json.dumps(dict(event='budget', total_seconds=7200))+'\n') + process = Mock(pid=321, returncode=None) + process.poll.return_value = None + original = RuntimeError('original probe failure') + cleanup = PermissionError('cleanup permission denied') + def spawn(*args, **kwargs): + kwargs['stdout'].write(b'partial output\n') + kwargs['stderr'].write(b'partial diagnostic\n') + return process + process.wait.side_effect = cleanup if failure == 'wait' else None + with patch.object(benchmark.subprocess, 'Popen', side_effect=spawn), \ + patch.object(benchmark.subprocess, 'run', side_effect=original), \ + patch.object(benchmark.os, 'killpg', side_effect=cleanup if failure == 'kill' else None), \ + patch.object(benchmark.time, 'perf_counter', side_effect=range(20)): + with self.assertRaises(RuntimeError) as raised: + benchmark.capture(['solver'], samples/'sample', 100, 1000, 0.05, + budget=dict(ledger=str(ledger), phase='correctness'), remaining_nominal_seconds=0) + self.assertIs(raised.exception, original) + self.assertIn('cleanup permission denied', original.__notes__[0]) + self.assertEqual((samples/'sample.stdout').read_text(), 'partial output\n') + self.assertEqual((samples/'sample.stderr').read_text(), 'partial diagnostic\n') + used, pending = benchmark.budget_state(ledger) + self.assertEqual(len(pending), 1) + launch = json.loads((samples/'sample.launch.json').read_text()) + self.assertEqual(used['correctness'], launch['wall_seconds']) + self.assertGreater(used['correctness'], 0) + self.assertEqual(next(iter(pending.values()))['pid'], 321) + self.assertNotIn('"event": "reap"', ledger.read_text()) + with patch.object(benchmark.subprocess, 'Popen') as spawn: + with self.assertRaisesRegex(ValueError, '321'): + benchmark.capture(['solver'], samples/'other', 1, 1000, 0.05) + with self.assertRaisesRegex(ValueError, '321'): + benchmark.collect({}, {}, samples) + spawn.assert_not_called() + with self.assertRaisesRegex(ValueError, '321'): + benchmark.admit_budget(ledger, 'correctness', 1, 0) + + def test_confirmed_exit_and_failed_spawn_close_launch(self): + for mode in ('exit', 'kill', 'spawn-failure'): + with self.subTest(mode=mode), tempfile.TemporaryDirectory() as directory: + samples = Path(directory) + ledger = samples/'budget.jsonl' + ledger.write_text(json.dumps(dict(event='budget', total_seconds=7200))+'\n') + process = Mock(pid=321, returncode=0) + process.poll.return_value = None if mode == 'kill' else 0 + with patch.object(benchmark.subprocess, 'Popen', + side_effect=OSError('spawn failed') if mode == 'spawn-failure' else None, + return_value=process), patch.object(benchmark.os, 'killpg'), \ + patch.object(benchmark.time, 'perf_counter', side_effect=range(20)): + result = benchmark.capture(['solver'], samples/'sample', 0.1, 1000, 0.05, + budget=dict(ledger=str(ledger), phase='correctness'), remaining_nominal_seconds=0) + self.assertEqual(result['status'], 'execution-error' if mode == 'spawn-failure' else + 'timeout' if mode == 'kill' else 'ok') + self.assertFalse((samples/'sample.launch.json').exists()) + used, pending = benchmark.budget_state(ledger) + self.assertFalse(pending) + self.assertGreater(used['correctness'], 0) + + def test_recovery_requires_absence_and_keeps_measured_accounting(self): + with tempfile.TemporaryDirectory() as directory: + samples = Path(directory) + ledger = samples/'budget.jsonl' + ledger.write_text('\n'.join(json.dumps(row) for row in ( + dict(event='budget', total_seconds=7200), + dict(event='launch', id='attempt', phase='correctness'), + dict(event='unresolved', id='attempt', wall_seconds=5)))+'\n') + marker = samples/'sample.launch.json' + launch = dict(id='attempt', pid=321, pgid=321, argv=['solver'], wall_seconds=5, started_at=100, + budget=dict(ledger=str(ledger), phase='correctness')) + benchmark.write_json(marker, launch) + for error in (None, PermissionError('cannot inspect PID')): + with patch.object(benchmark.os, 'kill', side_effect=error) as probe, \ + patch.object(benchmark.os, 'killpg') as signal_group: + with self.assertRaises((ValueError, PermissionError)): + benchmark.recover(samples) + probe.assert_called_once_with(321, 0) + signal_group.assert_not_called() + self.assertTrue(marker.exists()) + with patch.object(benchmark.os, 'kill', side_effect=ProcessLookupError), \ + patch.object(benchmark.time, 'time', return_value=110), \ + patch.object(benchmark.os, 'killpg') as signal_group: + benchmark.recover(samples) + signal_group.assert_not_called() + self.assertFalse(marker.exists()) + used, pending = benchmark.budget_state(ledger) + self.assertEqual(used, dict(correctness=10)) + recovery = json.loads(ledger.read_text().splitlines()[-1]) + self.assertEqual(recovery['observed_wall_seconds'], 5) + self.assertIn('exit time unobserved', recovery['timing']) + self.assertFalse(pending) + benchmark.require_reaped(samples) + # Historical reaps retain their original accounting interpretation. + ledger.write_text('\n'.join(json.dumps(row) for row in ( + dict(event='budget', total_seconds=7200), + dict(event='launch', id='old', phase='correctness'), + dict(event='reap', id='old', wall_seconds=7)))+'\n') + self.assertEqual(benchmark.budget_state(ledger), (dict(correctness=7), {})) + + def test_infeasibility_proof_rejects_any_checked_tour_for_same_case(self): + case = dict(id='waiting.txt') + config = dict(cases=[case], seconds=1, configs={}) + infeasible = dict(row=dict(case=case), proof='infeasible', incumbents=[], final_objective=None) + for status in ('ok', 'timeout', 'rss-limit', 'invalid-output'): + with self.subTest(status=status): + feasible = dict(row=dict(case=case), proof=None, status=status, + incumbents=[dict(checked=True, scaled_cost=5)], final_objective=5) + with self.assertRaisesRegex(ValueError, 'infeasibility proof for waiting.txt'): + benchmark.comparison_report(config, [infeasible, feasible]) + + def test_checker_keeps_valid_trace_before_rejecting_false_proof(self): + data = benchmark.checker.read_instance(benchmark.DATA/'tiny/waiting.txt') + solution = dict(type='solution', elapsed_ms=1, tour=[1, 2], **{k: v for k, v in + benchmark.checker.check_tour(data, [1, 2]).items() if k != 'visits'}) + with tempfile.TemporaryDirectory() as directory: + path = Path(directory)/'output' + path.write_text(json.dumps(solution)+'\n'+json.dumps(dict(type='summary', termination='infeasible', incumbent=None))+'\n') + trace, summary, errors = benchmark.checked_output(path, data) + self.assertEqual(len(trace), 1) + self.assertTrue(errors) + + def test_checks_later_incumbents_after_an_invalid_line(self): + data = benchmark.checker.read_instance(benchmark.DATA/'tiny/waiting.txt') + value = benchmark.checker.check_tour(data, [1, 2]) + solution = dict(type='solution', elapsed_ms=1, tour=[1, 2], **{k: v for k, v in value.items() if k != 'visits'}) + with tempfile.TemporaryDirectory() as directory: + path = Path(directory)/'output' + path.write_text('bad json\n'+json.dumps(solution)+'\n') + trace, _, errors = benchmark.checked_output(path, data) + self.assertTrue(errors) + self.assertEqual(len(trace), 1) + self.assertTrue(trace[0]['checked']) + + +if __name__ == '__main__': + unittest.main() diff --git a/misc/tsptw-acquire.py b/misc/tsptw-acquire.py new file mode 100644 index 0000000000..2974e17af3 --- /dev/null +++ b/misc/tsptw-acquire.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Acquire the frozen TSPTW selection explicitly, checking manifest hashes.""" +import argparse +import hashlib +import io +import json +from pathlib import Path +import runpy +import sys +import tarfile +from urllib.request import urlopen + +CHECKER = runpy.run_path(str(Path(__file__).with_name("tsptw-check.py"))) +ROOT = Path(__file__).resolve().parents[1] +MANIFEST = ROOT / "examples/data/tsptw/manifest.json" + + +def window_variant(content, variant): + tokens = " ".join(line.split("#", 1)[0] for line in + content.decode().splitlines()).split() + n = int(tokens[0]) + from fractions import Fraction + for i in range(1, n): + ready = Fraction(tokens[1+n*n+2*i]) + due_index = 1+n*n+2*i+1 + due = Fraction(tokens[due_index]) + due = due+100 if variant == "customer-due-plus-100" else ready+(due-ready)/2 + tokens[due_index] = CHECKER["exact_text"](due) + rows = [str(n)] + rows += [" ".join(tokens[1+i*n:1+(i+1)*n]) for i in range(n)] + rows += [" ".join(tokens[1+n*n+2*i:1+n*n+2*i+2]) for i in range(n)] + return ("\n".join(rows)+"\n").encode() + + +def verify_hash(content, expected, name): + if hashlib.sha256(content).hexdigest() != expected: + raise ValueError(f"content hash mismatch: {name}") + + +def acquire(destination, archive_directory=None): + manifest = json.loads(MANIFEST.read_text()) + archives = {} + for family, source in manifest["sources"].items(): + archive_name = source["archive_url"].rsplit("/", 1)[1] + content = ((archive_directory/archive_name).read_bytes() if archive_directory + else urlopen(source["archive_url"], timeout=60).read()) + verify_hash(content, source["archive_sha256"], archive_name) + archives[family] = tarfile.open(fileobj=io.BytesIO(content), mode="r:gz") + try: + for case in manifest["cases"]: + content = archives[case["family"]].extractfile(case["member"]).read() + verify_hash(content, case["source_sha256"], case["member"]) + if "variant" in case: + content = window_variant(content, case["variant"]) + verify_hash(content, case["sha256"], case["id"]) + path = destination/case["path"] + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(content) + CHECKER["read_instance"](path) + finally: + for archive in archives.values(): + archive.close() + print(f"acquired and validated {len(manifest['cases'])} selected cases in {destination}") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("destination", type=Path) + parser.add_argument("--archives", type=Path, help="use already downloaded archives offline") + args = parser.parse_args() + try: + acquire(args.destination, args.archives) + except (OSError, ValueError, tarfile.TarError) as error: + print(str(error), file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/misc/tsptw-benchmark.py b/misc/tsptw-benchmark.py new file mode 100755 index 0000000000..b156b4a789 --- /dev/null +++ b/misc/tsptw-benchmark.py @@ -0,0 +1,902 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.11" +# dependencies = [] +# /// +"""Serial, named TSPTW comparisons. Paths resolve from cwd; one writer per run.""" +import argparse +from fractions import Fraction +import hashlib +import importlib.util +import json +import math +import os +from pathlib import Path +import platform +import re +import signal +import statistics +import subprocess +import sys +import time + +ROOT = Path(__file__).resolve().parent.parent +DATA = ROOT / 'examples/data/tsptw' +ADAPTER = Path(__file__).with_name('tsptw-didppy.py').resolve() +SPEC = importlib.util.spec_from_file_location('tsptw_checker', Path(__file__).with_name('tsptw-check.py')) +checker = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(checker) +CONFIGS = { + 'closed-bab': ['--formulation', 'closed', '--engine', 'bab'], + 'closed-cabs': ['--formulation', 'closed', '--engine', 'cabs'], + 'transition-bab': ['--formulation', 'transition', '--engine', 'bab'], + 'transition-astar': ['--formulation', 'transition', '--engine', 'astar'], + 'transition-cabs': ['--formulation', 'transition', '--engine', 'cabs'], + 'transition-column': ['--formulation', 'transition', '--engine', 'column'], + 'didppy-astar': ['--engine', 'astar'], + 'didppy-cabs': ['--engine', 'cabs'], +} +DEFAULT_CONFIGS = tuple(CONFIGS) +CONFIGS.update({ + 'closed-val-bab': ['--formulation', 'closed', '--engine', 'bab', '--circuit-propagation', 'val'], + 'closed-size-bab': ['--formulation', 'closed', '--engine', 'bab', '--cost-branching', 'size'], + 'closed-rbs-regret': ['--formulation', 'closed', '--engine', 'rbs'], + 'closed-rbs-size': ['--formulation', 'closed', '--engine', 'rbs', '--cost-branching', 'size'], + 'closed-dom-size-bab': ['--formulation', 'closed', '--engine', 'bab', '--circuit-propagation', 'dom', '--cost-branching', 'size'], + 'closed-dom-size-cabs': ['--formulation', 'closed', '--engine', 'cabs', '--circuit-propagation', 'dom', '--cost-branching', 'size'], +}) +for name, formulation, bound, residual, pruning in ( + ('lazy-prefix', 'transition', 'prefix', 'off', 'off'), + ('eager-prefix', 'eager', 'prefix', 'off', 'off'), + ('lazy-pareto', 'transition', 'prefix', 'off', 'pareto'), + ('lazy-min-arcs', 'transition', 'min-arcs', 'off', 'off'), + ('lazy-unary', 'transition', 'prefix', 'unary', 'off')): + CONFIGS['transition-astar-'+name] = ['--formulation', formulation, '--engine', 'astar', + '--bound', bound, '--residual-propagation', residual, '--state-pruning', pruning] +for engine in ('bab', 'cabs', 'column'): + CONFIGS['transition-'+engine+'-lazy-unary'] = ['--formulation', 'transition', '--engine', engine, + '--bound', 'prefix', '--residual-propagation', 'unary', '--state-pruning', 'off'] +for engine in ('bab', 'cabs'): + CONFIGS['transition-'+engine+'-lazy-cheap'] = ['--formulation', 'transition', '--engine', engine, + '--bound', 'prefix', '--residual-propagation', 'cheap', '--state-pruning', 'off'] +TERMINAL = {'ok', 'timeout', 'rss-limit', 'execution-error', 'invalid-output', 'measurement-error'} +QUALITY_STATUSES = {'ok', 'timeout', 'rss-limit'} +CHECKPOINTS_MS = (100, 1000, 5000, 15000, 30000) + + +def digest(path): + return hashlib.sha256(Path(path).read_bytes()).hexdigest() + + +def write_json(path, value): + temporary = path.with_suffix(path.suffix + '.tmp') + temporary.write_text(json.dumps(value, indent=2, sort_keys=True, allow_nan=False) + '\n') + temporary.replace(path) + + +def budget_header(path): + header = json.loads(Path(path).read_text().splitlines()[0]) + if header.get('scope') == 'task': + if (header.get('event') != 'budget' or not header.get('task_id') or + any(not nonnegative_number(header.get(field)) for field in + ('total_seconds', 'verification_reserve_seconds', 'cleanup_reserve_seconds')) or + header['total_seconds'] <= header['verification_reserve_seconds']+header['cleanup_reserve_seconds']): + raise ValueError('invalid task budget header') + elif header.get('event') != 'budget' or header.get('total_seconds') != 7200: + raise ValueError('expected the retained 7200-second evaluation ledger') + return header + + +def budget_state(path): + budget_header(path) + rows = [json.loads(line) for line in Path(path).read_text().splitlines()] + used, pending, charged = {}, {}, {} + for row in rows[1:]: + if row['event'] == 'launch': + pending[row['id']] = row + elif row['event'] == 'process': + pending[row['id']].update(row['process']) + elif row['event'] in ('unresolved', 'reap'): + launch = pending[row['id']] + elapsed = row['wall_seconds'] + used[launch['phase']] = used.get(launch['phase'], 0) + elapsed-charged.get(row['id'], 0) + charged[row['id']] = elapsed + if row['event'] == 'reap': + pending.pop(row['id']) + return used, pending + + +def budget_event(path, row): + with Path(path).open('a') as output: + output.write(json.dumps(row, allow_nan=False)+'\n') + output.flush() + os.fsync(output.fileno()) + + +def admit_budget(path, phase, seconds, remaining_nominal_seconds): + header = budget_header(path) + used, pending = budget_state(path) + if pending: + raise ValueError('unreaped budget entry requires inspection: '+json.dumps(pending, sort_keys=True)) + if header.get('scope') == 'task': + if phase not in ('correctness', 'development', 'verification'): + raise ValueError('task solver budget excludes compilation and held-out work') + allowance = header['verification_reserve_seconds'] + if phase == 'verification' and used.get(phase, 0)+seconds > allowance: + raise ValueError('task verification allowance would be exceeded') + reserve = max(0, allowance-used.get('verification', 0)-(seconds if phase == 'verification' else 0)) + if sum(used.values())+seconds+remaining_nominal_seconds+reserve+header['cleanup_reserve_seconds'] > header['total_seconds']: + raise ValueError('remaining nominal work and reserves exceed the task allowance') + identity = str(time.time_ns()) + budget_event(path, dict(event='launch', id=identity, phase=phase, + cap_seconds=seconds, remaining_nominal_seconds=remaining_nominal_seconds)) + return identity + pre_freeze = sum(value for name, value in used.items() if name in ('compilation', 'correctness', 'development')) + if phase in ('compilation', 'correctness', 'development') and pre_freeze+seconds > 400: + raise ValueError('pre-freeze measured work would exceed 400 seconds') + if phase == 'verification' and used.get(phase, 0)+seconds > 300: + raise ValueError('verification allowance would exceed 300 seconds') + verification_reserve = max(0, 300-used.get('verification', 0)-(seconds if phase == 'verification' else 0)) + if sum(used.values())+seconds+remaining_nominal_seconds+verification_reserve+600 > 7200: + raise ValueError('remaining nominal work and reserves exceed the shared allowance') + identity = str(time.time_ns()) + budget_event(path, dict(event='launch', id=identity, phase=phase, + cap_seconds=seconds, remaining_nominal_seconds=remaining_nominal_seconds)) + return identity + + +def select_cases(split, selected_root, case_ids=None): + if case_ids and split != 'development': + raise ValueError('case-ids is a development-only selector') + if split == 'tiny': + return [dict(id=p.name, path=str(p), sha256=digest(p)) for p in sorted((DATA/'tiny').glob('*.txt'))] + manifest = json.loads((DATA/'manifest.json').read_text()) + cases = [dict(id=c['id'], path=str((selected_root/c['path']).resolve()), sha256=c['sha256'], + family=c['family'], variant=c.get('variant')) + for c in manifest['cases'] if c['split'] == split] + if case_ids: + names = case_ids.split(',') + controls = {f'tiny/{p.name}': p for p in (DATA/'tiny').glob('*.txt')} + cases.extend(dict(id=name, path=str(controls[name]), sha256=digest(controls[name]), + family='tiny', variant='tiny control') for name in names if name in controls) + if len(names) != len(set(names)) or not set(names) <= {c['id'] for c in cases}: + raise ValueError('case-ids must name distinct development cases or existing tiny controls') + cases = [c for c in cases if c['id'] in names] + return cases + + +def experiment(args): + names = args.configs.split(',') if args.configs else list(DEFAULT_CONFIGS[:6] if args.split == 'development' else DEFAULT_CONFIGS) + if len(set(names)) != len(names) or any(n not in CONFIGS for n in names): + raise ValueError('configs must be distinct names from: ' + ', '.join(CONFIGS)) + repetitions = args.repetitions if args.repetitions is not None else (2 if args.split == 'development' else 1) + seconds = args.seconds if args.seconds is not None else (30 if args.split == 'held-out' else 15 if args.split == 'development' else 5) + if repetitions < 1 or not math.isfinite(seconds) or seconds <= 0: + raise ValueError('repetitions and seconds must be positive') + if args.split == 'development' and (len(names) > 6 or repetitions > 2 or seconds > 15): + raise ValueError('development is bounded to six configurations, two repeats, 15 seconds') + if args.split == 'held-out' and (repetitions != 1 or seconds > 30): + raise ValueError('held-out is bounded to one repeat and 30 seconds') + if args.compilation_seconds is not None and (not math.isfinite(args.compilation_seconds) or args.compilation_seconds < 0): + raise ValueError('compilation seconds must be finite and nonnegative') + if args.command == 'run' and args.max_samples is not None and args.max_samples < 0: + raise ValueError('max-samples must be nonnegative') + solver = args.solver.resolve() + config = dict(schema_version=1, split=args.split, configs={n: CONFIGS[n] for n in names}, + cases=select_cases(args.split, args.selected_root.resolve(), args.case_ids), repetitions=repetitions, + seconds=seconds, rss_bytes=1024**3, sample_interval_seconds=0.05, + threads=1, native=dict(path=str(solver), sha256=digest(solver), + libraries={str(p.resolve()): digest(p) for p in sorted(solver.parent.parent.glob('libgecode*.6.4.0.dylib'))}), + didppy=dict(version='0.11.1', adapter_sha256=digest(ADAPTER), launcher=args.uv), + runner_sha256=digest(__file__), checker_sha256=digest(SPEC.origin), + machine=dict(system=platform.platform(), machine=platform.machine(), processor=platform.processor()), + sources={str(p.relative_to(ROOT)): digest(p) for p in + [ROOT/'examples/tsptw.cpp', ROOT/'examples/tsptw-transition.hh', ROOT/'examples/tsptw-data.hh', + ROOT/'gecode/search.hh', *sorted((ROOT/'gecode/search').rglob('*.cpp')), + *sorted((ROOT/'gecode/search').rglob('*.hpp'))]}, + manifest_sha256=digest(DATA/'manifest.json'), + compilation=dict(status='prebuilt; measured builds belong to the shared ledger when supplied', seconds=args.compilation_seconds), + budget=dict(ledger=str(args.budget_ledger.resolve()), phase=args.budget_phase or + ('held-out' if args.split == 'held-out' else 'development' if args.split == 'development' else 'correctness')) + if args.budget_ledger else None, + timing='launch through reap; checks after timing; watchdog includes startup and preprocessing') + if config['budget'] and budget_header(config['budget']['ledger']).get('scope') == 'task': + if config['budget']['phase'] not in ('correctness', 'development', 'verification') or args.split == 'held-out': + raise ValueError('task solver budget excludes held-out work') + config['compilation']['status'] = 'prebuilt; measured builds reported separately from task solver budget' + rows = [] + for repetition in range(repetitions): + order = names if repetition % 2 == 0 else names[::-1] + for case_index, case in enumerate(config['cases']): + for name in order: + row = dict(id=f'{repetition:02d}-{case_index:03d}-{name}', case=case, + config=name, repetition=repetition, arguments=CONFIGS[name]) + row['argv'] = command(config, row) + rows.append(row) + config['rows'] = rows + return config + + +def preview(config): + seconds = len(config['rows'])*config['seconds'] + value = dict(samples=len(config['rows']), solver_budget_seconds=seconds, + compilation_seconds=config['compilation']['seconds'], + development_ceiling_seconds=2700, held_out_ceiling_seconds=7200, + full_campaign=dict(development_samples=6*12*2, development_solver_seconds=2160, + held_out_samples=8*24, held_out_solver_seconds=5760, + held_out_remaining_for_ablations_seconds=1440, + full_development_plus_held_out_seconds=7920, + budget_scope='development tuning has its own 45-minute ceiling; evaluation has a separate 120-minute ceiling including its development ablations and compilation; held-out leaves 24 minutes inside evaluation'), + configurations=list(config['configs']), cases=[c['id'] for c in config['cases']], + timing=config['timing']) + if config.get('budget'): + value['budget'] = budget_header(config['budget']['ledger']) + if value['budget'].get('scope') == 'task': + for key in ('development_ceiling_seconds', 'held_out_ceiling_seconds', 'full_campaign'): + value.pop(key) + print(json.dumps(value, indent=2)) + + +def nonnegative_number(value, *, integer=False): + return (not isinstance(value, bool) and isinstance(value, int if integer else (int, float)) + and math.isfinite(value) and value >= 0) + + +def validate_incumbent(value): + """Check a saved incumbent's own fields without needing the original input.""" + if not isinstance(value, dict): + raise ValueError('invalid incumbent record') + tour = value['tour'] + if (not isinstance(tour, list) or not tour or + any(not nonnegative_number(v, integer=True) or v == 0 for v in tour) or + set(tour) != set(range(1, len(tour)+1))): + raise ValueError('invalid saved tour') + if not nonnegative_number(value['scaled_cost'], integer=True): + raise ValueError('invalid saved scaled cost') + scale = value['scale'] + if not nonnegative_number(scale, integer=True) or scale == 0: + raise ValueError('invalid saved scale') + for key in ('cost', 'return_time'): + if not isinstance(value[key], str) or not re.fullmatch(r'[0-9]+(?:\.[0-9]+)?', value[key]): + raise ValueError('invalid saved ' + key) + if Fraction(value['cost'])*scale != value['scaled_cost']: + raise ValueError('saved cost and scaling differ') + + +def validate_saved_trace(trace): + if not isinstance(trace, list): + raise ValueError('invalid incumbent trace') + previous_cost, previous_time = None, -1 + for incumbent in trace: + validate_incumbent(incumbent) + if incumbent['type'] != 'solution' or incumbent['checked'] is not True: + raise ValueError('incumbent lacks independent check') + elapsed = incumbent['elapsed_ms'] + if not nonnegative_number(elapsed) or elapsed < previous_time: + raise ValueError('invalid saved incumbent time') + if previous_cost is not None and incumbent['scaled_cost'] >= previous_cost: + raise ValueError('nonimproving saved incumbent') + previous_cost, previous_time = incumbent['scaled_cost'], elapsed + + +def validate_allocation(allocation): + if not isinstance(allocation, dict): + raise ValueError('invalid allocation measurements') + for key in ('space_bytes', 'intvar_handle_bytes', 'root_positions', + 'prepared_root_positions', 'solution_positions'): + if not nonnegative_number(allocation[key], integer=True): + raise ValueError('invalid allocation measurement ' + key) + + +def validate_summary(summary, trace, row): + if not isinstance(summary, dict) or summary['type'] != 'summary': + raise ValueError('invalid summary') + options = row['arguments'] if 'arguments' in row else CONFIGS[row['config']] + engine = options[options.index('--engine')+1] + formulation = 'didppy' if row['config'].startswith('didppy-') else options[options.index('--formulation')+1] + if summary['engine'] != engine or summary['formulation'] != formulation: + raise ValueError('summary engine/formulation differs') + if formulation != 'didppy': + for option, field, default in (('--bound', 'bound', 'cp-total' if formulation == 'closed' else 'prefix'), + ('--residual-propagation', 'residual_propagation', 'off'), + ('--state-pruning', 'state_pruning', 'off')): + requested = options[options.index(option)+1] if option in options else default + if summary.get(field, default if row['config'] in DEFAULT_CONFIGS else None) != requested: + raise ValueError('summary differs from requested ' + field) + if engine == 'column': + for option, field, default in (('--initial-quota', 'initial_quota', 1), + ('--quota-growth', 'quota_growth', 1), ('--frontier-limit', 'frontier_limit', 0)): + requested = int(options[options.index(option)+1]) if option in options else default + if summary.get(field, default if row['config'] in DEFAULT_CONFIGS else None) != requested: + raise ValueError('summary differs from requested ' + field) + if formulation == 'closed': + for option, field, default in (('--circuit-propagation', 'circuit_propagation', 'dom'), + ('--cost-branching', 'cost_branching', 'regret')): + requested = options[options.index(option)+1] if option in options else default + # Older default records predate these fields; explicit candidates require them. + if summary.get(field, default if row['config'] in DEFAULT_CONFIGS else None) != requested: + raise ValueError('summary differs from requested ' + field) + if engine == 'rbs' and any(summary.get(k) != v for k, v in + (('restart_cutoff', 'luby'), ('restart_scale', 1000), ('nogoods_limit', 128))): + raise ValueError('summary differs from fixed restart settings') + if formulation == 'didppy' and summary['version'] != '0.11.1': + raise ValueError('DIDPPy version differs') + termination = summary['termination'] + if termination not in ('optimal', 'infeasible', 'stopped'): + raise ValueError('invalid termination') + incumbent = summary['incumbent'] + if bool(trace) != (incumbent is not None): + raise ValueError('summary and trace differ') + if incumbent is not None: + validate_incumbent(incumbent) + if any(incumbent[k] != trace[-1][k] for k in ('tour', 'cost', 'scaled_cost', 'scale', 'return_time')): + raise ValueError('summary differs from final incumbent') + if termination == 'optimal' and not trace or termination == 'infeasible' and trace: + raise ValueError('proof contradicts incumbent') + for key in ('elapsed_ms', 'preprocessing_ms', 'search_ms'): + if not nonnegative_number(summary[key]): + raise ValueError('invalid summary timing ' + key) + if trace and summary['elapsed_ms'] < trace[-1]['elapsed_ms']: + raise ValueError('summary precedes final incumbent') + statistics = summary['statistics'] + if not isinstance(statistics, dict) or 'node' not in statistics: + raise ValueError('missing expansion statistics') + for key, value in statistics.items(): + if not nonnegative_number(value, integer=True): + raise ValueError('invalid summary statistic ' + key) + if 'allocation' in summary: + validate_allocation(summary['allocation']) + if 'measurements' in summary: + measurements = summary['measurements'] + if not isinstance(measurements, dict): + raise ValueError('invalid summary measurements') + for key in ('bound_calls', 'bound_ms', 'inference_calls', 'inference_ms', 'propagation_ms'): + if not nonnegative_number(measurements[key], integer=key.endswith('_calls')): + raise ValueError('invalid summary measurement ' + key) + + +def completed(path, row): + if not path.exists(): + return None + try: + value = json.loads(path.read_text()) + if value['row'] != row or value['status'] not in TERMINAL: + raise ValueError('record identity/status differs') + if 'argv' in row and value['argv'] != row['argv']: + raise ValueError('saved command differs from the frozen row') + if not nonnegative_number(value['wall_seconds']): + raise ValueError('invalid wall time') + if not isinstance(value['diagnostic'], str): + raise ValueError('invalid diagnostic') + trace = value['incumbents'] + validate_saved_trace(trace) + objective = value['final_objective'] + if objective is not None and not nonnegative_number(objective, integer=True): + raise ValueError('invalid final objective') + if objective != (trace[-1]['scaled_cost'] if trace else None): + raise ValueError('final objective differs from saved trace') + metrics = value['metrics'] + if not isinstance(metrics, dict): + raise ValueError('invalid metrics') + for key in ('preprocessing_ms', 'search_ms', 'startup_and_teardown_ms', 'expansions', + 'descriptor_count', 'descriptor_bytes', 'frontier_bytes', 'clone_bytes'): + metric = metrics[key] + integer = key not in ('preprocessing_ms', 'search_ms', 'startup_and_teardown_ms') + if metric is not None and not nonnegative_number(metric, integer=integer): + raise ValueError('invalid saved metric ' + key) + allocation = metrics['allocation'] + if allocation is not None: + validate_allocation(allocation) + proof = value['proof'] + if proof not in (None, 'optimal', 'infeasible'): + raise ValueError('invalid proof') + summary = value['summary'] + if summary is not None: + validate_summary(summary, trace, row) + for metric, field in (('preprocessing_ms', 'preprocessing_ms'), ('search_ms', 'search_ms')): + if metrics[metric] != summary[field]: + raise ValueError('metric differs from saved summary ' + metric) + for metric, source in (('expansions', 'node'), ('descriptor_count', 'state_descriptors'), ('descriptor_bytes', 'state_bytes')): + if metrics[metric] != summary['statistics'].get(source): + raise ValueError('metric differs from summary statistic ' + source) + if metrics['allocation'] != summary.get('allocation'): + raise ValueError('allocation differs from summary') + if value['status'] == 'ok': + if summary is None or value['returncode'] != 0: + raise ValueError('successful row lacks successful summary/exit') + expected_proof = summary['termination'] if summary['termination'] in ('optimal', 'infeasible') else None + if proof != expected_proof or value['stop_reason'] != summary['termination']: + raise ValueError('proof/stop reason differs from saved summary') + elif proof is not None or value['stop_reason'] != value['status']: + raise ValueError('failed row claims proof or wrong stop reason') + if value['returncode'] is not None and (isinstance(value['returncode'], bool) or not isinstance(value['returncode'], int)): + raise ValueError('invalid exit code') + rss = value['peak_rss_bytes'] + if rss is not None and (not nonnegative_number(rss, integer=True) or rss == 0): + raise ValueError('invalid RSS') + return value + except (ValueError, KeyError, TypeError, OverflowError) as error: + print(f'unfinished record {path}: {error}', file=sys.stderr) + return None + + +def command(config, row): + options = config['configs'][row['config']] + if row['config'].startswith('didppy-'): + argv = [config['didppy']['launcher'], 'run', '--script', str(ADAPTER)] + else: + argv = [config['native']['path']] + return argv + [row['case']['path']] + options + ['--time-ms', str(round(config['seconds']*1000))] + + +def kill_group(process): + try: + os.killpg(process.pid, signal.SIGKILL) + except ProcessLookupError: + pass + process.wait() + + +def require_reaped(samples): + pending = sorted(samples.glob('*.launch.json')) + if pending: + raise ValueError('unconfirmed child exit; use recover after inspecting '+ + '; '.join(str(path)+': '+path.read_text().strip() for path in pending)) + + +def recover(samples): + """Confirm child absence and charge through inspection, without claiming its exit time.""" + for path in sorted(samples.glob('*.launch.json')): + launch = json.loads(path.read_text()) + pid = launch.get('pid') + if not isinstance(pid, int) or pid <= 0: + raise ValueError('launch has no confirmed child identity: '+str(path)) + try: + os.kill(pid, 0) + except ProcessLookupError: + pass + else: + raise ValueError('child PID still exists; inspect before recovery: '+json.dumps(launch)) + budget = launch.get('budget') + if budget: + _, pending = budget_state(budget['ledger']) + if launch['id'] in pending: + budget_event(budget['ledger'], dict(event='reap', id=launch['id'], + wall_seconds=max(launch['wall_seconds'], time.time()-launch['started_at']), + observed_wall_seconds=launch['wall_seconds'], returncode=None, + status='recovered-absent', timing='launch-through-absence upper bound; exit time unobserved')) + path.unlink() + + +def capture(argv, destination, seconds, rss_limit, interval, *, budget=None, + remaining_nominal_seconds=5760): + require_reaped(destination.parent) + identity = None + if budget: + identity = admit_budget(budget['ledger'], budget['phase'], seconds, remaining_nominal_seconds) + budget_event(budget['ledger'], dict(event='command', id=identity, argv=argv)) + started = time.perf_counter() + marker = destination.with_suffix('.launch.json') + launch = dict(id=identity, argv=argv, pid=None, pgid=None, budget=budget, + started_at=time.time(), wall_seconds=0) + write_json(marker, launch) + peak, process, reason, exited = None, None, None, False + observations, max_gap, probe_seconds, termination_seconds = 0, 0.0, 0.0, 0.0 + previous = started + try: + with destination.with_suffix('.stdout').open('wb') as stdout, destination.with_suffix('.stderr').open('wb') as stderr: + try: + environment = dict(os.environ, OMP_NUM_THREADS='1', OPENBLAS_NUM_THREADS='1', MKL_NUM_THREADS='1') + process = subprocess.Popen(argv, stdout=stdout, stderr=stderr, cwd=ROOT, + start_new_session=True, env=environment) + except OSError as error: + reason = 'execution-error' + return dict(returncode=None, status=reason, diagnostic=str(error), + wall_seconds=time.perf_counter()-started, peak_rss_bytes=None, + rss_samples=0, maximum_sample_gap_seconds=None, rss_probe_seconds=0, + termination_reap_seconds=0, wall_cap_overshoot_seconds=0, rss_overshoot_bytes=None) + try: + launch.update(pid=process.pid, pgid=process.pid) + write_json(marker, launch) + if budget: + budget_event(budget['ledger'], dict(event='process', id=identity, + process=dict(pid=process.pid, pgid=process.pid, argv=argv))) + while process.poll() is None: + now = time.perf_counter() + if now-started >= seconds: + reason = 'timeout' + else: + # Include uv's interpreter child; launcher RSS alone is misleading. + probe_start = time.perf_counter() + probe = subprocess.run(['ps', '-axo', 'pid=,pgid=,rss='], capture_output=True, text=True) + probe_seconds += time.perf_counter()-probe_start + if probe.returncode: + reason = 'measurement-error' + else: + rss = sum(int(fields[2])*1024 for line in probe.stdout.splitlines() + if len(fields := line.split()) == 3 and int(fields[1]) == process.pid) + if rss: + peak = max(peak or 0, rss) + observations += 1 + max_gap = max(max_gap, now-previous) + previous = now + if rss > rss_limit: + reason = 'rss-limit' + if reason: + termination_start = time.perf_counter() + kill_group(process) + termination_seconds += time.perf_counter()-termination_start + exited = True + break + time.sleep(min(interval, max(0, seconds-(time.perf_counter()-started)))) + if not exited: + termination_start = time.perf_counter() + process.wait() + termination_seconds += time.perf_counter()-termination_start + exited = True + except BaseException as error: + try: + kill_group(process) + exited = True + except BaseException as cleanup_error: + error.add_note('child cleanup failed: '+str(cleanup_error)+ + '; owned process: '+json.dumps(launch)) + raise + elapsed = time.perf_counter()-started + return dict(returncode=process.returncode, status=reason or ('ok' if process.returncode == 0 else 'execution-error'), + diagnostic=reason or '', wall_seconds=elapsed, peak_rss_bytes=peak, + rss_samples=observations, maximum_sample_gap_seconds=max_gap if observations else None, + rss_probe_seconds=probe_seconds, termination_reap_seconds=termination_seconds, + wall_cap_overshoot_seconds=max(0, elapsed-seconds), + rss_overshoot_bytes=max(0, peak-rss_limit) if peak is not None else None) + finally: + launch['wall_seconds'] = time.perf_counter()-started + # No child was created, or wait confirmed exit. A failed kill/wait never closes work. + closed = process is None or exited + write_json(marker, launch) + if budget: + budget_event(budget['ledger'], dict(event='reap' if closed else 'unresolved', id=identity, + wall_seconds=launch['wall_seconds'], + returncode=process.returncode if exited else None, + status='interrupted' if sys.exc_info()[0] else reason or + ('ok' if process and process.returncode == 0 else 'execution-error'))) + if closed: + marker.unlink() + + +def checked_output(path, data): + trace = [] + summary = None + errors = [] + previous_cost = None + previous_time = -1 + for line_number, line in enumerate(path.read_text().splitlines(), 1): + try: + event = json.loads(line) + if summary is not None: + errors.append('output after final summary') + if event['type'] == 'solution': + value = checker.check_tour(data, event['tour'], event['cost']) + for key in ('scaled_cost', 'scale', 'return_time'): + if value[key] != event[key]: + raise ValueError('incorrect solution ' + key) + elapsed = event['elapsed_ms'] + if isinstance(elapsed, bool) or not isinstance(elapsed, (int, float)) or not math.isfinite(elapsed) or elapsed < previous_time: + raise ValueError('invalid incumbent time') + if previous_cost is not None and value['scaled_cost'] >= previous_cost: + raise ValueError('nonimproving incumbent') + candidate = dict(event, checked=True) + validate_saved_trace([candidate]) + previous_cost, previous_time = value['scaled_cost'], elapsed + trace.append(candidate) + elif event['type'] == 'summary': + if summary is not None: + raise ValueError('duplicate summary') + summary = event + if event['termination'] not in ('optimal', 'infeasible', 'stopped'): + raise ValueError('unknown solver termination') + incumbent = event['incumbent'] + if bool(incumbent) != bool(trace): + raise ValueError('summary does not match published incumbent') + if incumbent: + checker.check_tour(data, incumbent['tour'], incumbent['cost']) + if any(incumbent[k] != trace[-1][k] for k in ('tour', 'cost', 'scaled_cost', 'scale', 'return_time')): + raise ValueError('summary differs from final incumbent') + if event['termination'] == 'optimal' and not trace or event['termination'] == 'infeasible' and trace: + raise ValueError('proof contradicts incumbent') + for key in ('elapsed_ms', 'preprocessing_ms', 'search_ms'): + value = event.get(key) + if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value) or value < 0: + raise ValueError('invalid summary timing ' + key) + else: + raise ValueError('unknown output event') + except (ValueError, KeyError, TypeError) as error: + errors.append(f'line {line_number}: {error}') + return trace, summary, errors + + +def collect(config, row, samples, remaining_nominal_seconds=5760): + require_reaped(samples) + case_path = Path(row['case']['path']) + if digest(case_path) != row['case']['sha256']: + raise ValueError('input digest mismatch: ' + str(case_path)) + data = checker.read_instance(case_path) + destination = samples/row['id'] + # Partial streams survive retries. Completed streams are never touched. + for suffix in ('.stdout', '.stderr', '.json'): + path = destination.with_suffix(suffix) + if path.exists(): + path.rename(path.with_name(path.name + f'.partial-{time.time_ns()}')) + argv = command(config, row) + budget_arguments = dict(budget=config['budget'], remaining_nominal_seconds=remaining_nominal_seconds) if config.get('budget') else {} + result = capture(argv, destination, config['seconds'], config['rss_bytes'], config['sample_interval_seconds'], **budget_arguments) + trace, summary = [], None + result['process_termination'] = result['status'] + try: + trace, summary, errors = checked_output(destination.with_suffix('.stdout'), data) + if errors: + raise ValueError('; '.join(errors)) + if result['status'] == 'ok' and summary is None: + raise ValueError('missing final summary') + if summary is not None: + validate_summary(summary, trace, row) + except (ValueError, KeyError, TypeError, OverflowError) as error: + result['status'] = 'invalid-output' + result['diagnostic'] = str(error) + valid = result['status'] == 'ok' + if result['status'] == 'invalid-output': + summary = None + result.update(row=row, argv=argv, incumbents=trace, summary=summary, + proof=summary['termination'] if valid and summary['termination'] in ('optimal', 'infeasible') else None, + final_objective=trace[-1]['scaled_cost'] if trace else None, + stop_reason=result['status'] if not valid else summary['termination'], + metrics=dict(preprocessing_ms=summary.get('preprocessing_ms') if summary else None, + search_ms=summary.get('search_ms') if summary else None, + startup_and_teardown_ms=max(0, result['wall_seconds']*1000-summary['elapsed_ms']) if summary else None, + expansions=summary.get('statistics', {}).get('node') if summary else None, + descriptor_count=summary.get('statistics', {}).get('state_descriptors') if summary else None, + descriptor_bytes=summary.get('statistics', {}).get('state_bytes') if summary else None, + allocation=summary.get('allocation') if summary else None, + frontier_bytes=None, clone_bytes=None), + unavailable='frontier and clone footprint are not instrumented; allocation sizeof excludes buffers; phases unavailable after kill; RSS is sampled process-group sum with overshoot') + write_json(destination.with_suffix('.json'), result) + print(row['id'], result['status'], result['proof'], result['final_objective'], flush=True) + + +def paired_sample(record, checkpoints): + if record is None: + return dict(status='missing', quality_usable=False) + usable = record['status'] in QUALITY_STATUSES + trace = record['incumbents'] if usable else [] + quality = {str(ms): next((v['scaled_cost'] for v in reversed(trace) + if v['elapsed_ms'] <= ms), None) for ms in checkpoints} + return dict(status=record['status'], quality_usable=usable, + final_objective=trace[-1]['scaled_cost'] if trace else None, + scale=trace[-1]['scale'] if trace else None, + first_incumbent_reported_ms=trace[0]['elapsed_ms'] if trace else None, + quality_at_reported_ms=quality, proof=record['proof'], termination=record['stop_reason'], + proof_reported_ms=record['summary']['elapsed_ms'] if record['proof'] else None, + end_to_end_wall_seconds=record['wall_seconds'], sampled_peak_rss_bytes=record['peak_rss_bytes']) + + +def compare_pair(baseline, candidate, field, checkpoint=None): + if 'missing' in (baseline['status'], candidate['status']): + return 'missing' + if not baseline['quality_usable'] or not candidate['quality_usable']: + return 'failed' + left, right = baseline[field], candidate[field] + if checkpoint is not None: + left, right = left[checkpoint], right[checkpoint] + if left is None and right is None: + return 'unavailable' + if field in ('final_objective', 'quality_at_reported_ms'): + if left is None: + return 'win' + if right is None: + return 'loss' + elif left is None or right is None: + return 'unavailable' + return 'win' if right < left else 'loss' if right > left else 'tie' + + +def paired_analysis(config, records, baseline_name=None, candidates=None): + baseline_name = baseline_name or next((name for name in ('closed-dom-size-bab', 'closed-size-bab', 'closed-bab') + if name in config['configs']), None) + if baseline_name is None: + return None + checkpoints = [ms for ms in CHECKPOINTS_MS if ms <= config['seconds']*1000] + by_key = {(row['case']['id'], row['repetition'], row['config']): record + for row, record in zip(config['rows'], records)} + pairs, counts = [], {} + fields = ('final_objective', 'first_incumbent_reported_ms', 'proof_reported_ms', + 'end_to_end_wall_seconds', 'sampled_peak_rss_bytes') + for name in candidates if candidates is not None else config['configs']: + if name == baseline_name: + continue + tallies = {field: dict.fromkeys(('win', 'tie', 'loss', 'missing', 'failed', 'unavailable'), 0) + for field in (*fields, *(f'quality_at_{ms}_reported_ms' for ms in checkpoints))} + for case in config['cases']: + for repetition in range(config['repetitions']): + baseline = paired_sample(by_key.get((case['id'], repetition, baseline_name)), checkpoints) + candidate = paired_sample(by_key.get((case['id'], repetition, name)), checkpoints) + comparisons = {field: compare_pair(baseline, candidate, field) for field in fields} + for ms in checkpoints: + comparisons[f'quality_at_{ms}_reported_ms'] = compare_pair( + baseline, candidate, 'quality_at_reported_ms', str(ms)) + for field, outcome in comparisons.items(): + tallies[field][outcome] += 1 + pairs.append(dict(case=case, repetition=repetition, candidate=name, + baseline=baseline, measured=candidate, comparisons=comparisons)) + counts[name] = tallies + return dict(baseline=baseline_name, reported_time_checkpoints_ms=checkpoints, counts=counts, pairs=pairs, + note='Costs are paired only within the same case and scale. Checked timeout/RSS incumbents count as feasible quality, never proof. Native reported clocks start at main entry and include preprocessing; outer wall clocks measure launch through reap. Missing metrics are unavailable; lower values win. Cases mark canonical data and deterministic window variants separately.') + + +def comparison_report(config, records): + checkpoints = [ms for ms in CHECKPOINTS_MS if ms <= config['seconds']*1000] + cases = [] + for case in config['cases']: + selected = [r for r in records if r and r['row']['case']['id'] == case['id']] + if any(r['proof'] == 'infeasible' for r in selected) and any(r['incumbents'] for r in selected): + raise ValueError('checked tour contradicts an infeasibility proof for '+case['id']) + proven = {r['final_objective'] for r in selected if r['proof'] == 'optimal'} + if len(proven) > 1: + raise ValueError('conflicting optimality proofs for '+case['id']) + reference = next(iter(proven), None) + measured = [] + for record in selected: + sample = paired_sample(record, checkpoints) + objective = sample['final_objective'] + if reference is not None and objective is not None and objective < reference: + raise ValueError('checked candidate contradicts an optimality proof') + sample.update(config=record['row']['config'], repetition=record['row']['repetition'], + gap_to_proved_optimum=float(Fraction(objective-reference, reference)) + if objective is not None and reference else 0 if objective == reference == 0 else None, + metrics=record['metrics'], measurements=record['summary'].get('measurements') if record['summary'] else None, + rss_samples=record.get('rss_samples'), maximum_sample_gap_seconds=record.get('maximum_sample_gap_seconds'), + rss_probe_seconds=record.get('rss_probe_seconds'), termination_reap_seconds=record.get('termination_reap_seconds'), + wall_cap_overshoot_seconds=record.get('wall_cap_overshoot_seconds'), rss_overshoot_bytes=record.get('rss_overshoot_bytes')) + measured.append(sample) + cases.append(dict(case=case, reference_scaled_cost=reference, + reference_kind='matching campaign optimality proof' if reference is not None else 'unavailable; campaign best is not an optimum', + measurements=measured)) + same_model = {} + for formulation, preferred in (('closed', ('closed-dom-size-bab', 'closed-size-bab', 'closed-bab')), + ('transition', tuple(n for n in config['configs'] if n.startswith('transition-bab'))), + ('didppy', ('didppy-astar',))): + names = [name for name, arguments in config['configs'].items() + if ('didppy' if name.startswith('didppy-') else arguments[arguments.index('--formulation')+1]) == formulation] + baseline = next((name for name in preferred if name in names), None) + if baseline: + paired = paired_analysis(config, records, baseline, names) + same_model[formulation] = {key: value for key, value in paired.items() if key != 'pairs'} + formulation_contrasts = {} + for engine in ('bab', 'cabs'): + names = {arguments[arguments.index('--formulation')+1]: name + for name, arguments in config['configs'].items() + if not name.startswith('didppy-') and arguments[arguments.index('--engine')+1] == engine} + if 'closed' in names and 'transition' in names: + paired = paired_analysis(config, records, names['closed'], [names['transition']]) + formulation_contrasts[engine] = {key: value for key, value in paired.items() if key != 'pairs'} + family_groups = {} + for case in cases: + key = case['case'].get('family', 'tiny')+'/'+('variant' if case['case'].get('variant') else 'canonical') + group = family_groups.setdefault(key, {}) + for record in case['measurements']: + counts = group.setdefault(record['config'], dict(samples=0, feasible=0, optimal=0, infeasible=0, resource_stops=0, failures=0)) + counts['samples'] += 1 + counts['feasible'] += record['final_objective'] is not None + counts['optimal'] += record['proof'] == 'optimal' + counts['infeasible'] += record['proof'] == 'infeasible' + counts['resource_stops'] += record['status'] in ('timeout', 'rss-limit') + counts['failures'] += record['status'] not in QUALITY_STATUSES + return dict(same_model=same_model, formulation_contrasts=formulation_contrasts, + family_groups=family_groups, cases=cases, + clocks='publication checkpoints use solver-reported milliseconds; launch-inclusive publication timestamps are unavailable', + missing_metrics='frontier/clone buffers and phases after kill are unavailable; allocation sizeof excludes dynamic storage') + + +def analyze(directory, output=None): + config = json.loads((directory/'config.json').read_text()) + records = [completed(directory/'samples'/f"{row['id']}.json", row) for row in config['rows']] + groups = {} + for name in config['configs']: + selected = [r for r in records if r and r['row']['config'] == name] + good = [r for r in selected if r['status'] == 'ok'] + groups[name] = dict(completed=len(selected), failed=len(selected)-len(good), + status_counts={status:sum(r['status'] == status for r in selected) for status in sorted(TERMINAL)}, + feasible=sum(bool(r['incumbents']) and r['status'] in QUALITY_STATUSES for r in selected), + resource_terminated=sum(r['status'] in ('timeout', 'rss-limit') for r in selected), + proved=sum(r['proof'] is not None for r in good), + optimal=sum(r['proof'] == 'optimal' for r in good), + infeasible=sum(r['proof'] == 'infeasible' for r in good), + ordinary_stopped=sum(r['stop_reason'] == 'stopped' for r in good), + actual_launch_wall_seconds=sum(r['wall_seconds'] for r in selected), + median_wall_seconds=statistics.median(r['wall_seconds'] for r in good) if good else None, + sampled_peak_rss_bytes=max((r['peak_rss_bytes'] for r in selected if r['peak_rss_bytes'] is not None), default=None)) + report = dict(expected=len(records), completed=sum(r is not None for r in records), + unfinished=sum(r is None for r in records), groups=groups, paired=paired_analysis(config, records), + comparisons=comparison_report(config, records), + note='medians include only valid process runs; incomplete/failed work is counted; objectives and checked traces remain in individual rows') + write_json(directory/'analysis.json', report) + if output: + compact = dict(schema_version=1, campaign=directory.name, freeze_sha256=digest(directory/'config.json'), + expected=report['expected'], completed=report['completed'], unfinished=report['unfinished'], + configurations=config['configs'], limits=dict(seconds=config['seconds'], threads=config['threads'], + rss_bytes=config['rss_bytes'], sample_interval_seconds=config['sample_interval_seconds']), + machine=config['machine'], identities={key:config.get(key) for key in + ('native', 'didppy', 'runner_sha256', 'checker_sha256', 'manifest_sha256', 'sources')}, + groups=groups, comparisons=report['comparisons']) + write_json(output, compact) + print(json.dumps(report, indent=2)) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + commands = parser.add_subparsers(dest='command', required=True) + for name in ('preview', 'run'): + sub = commands.add_parser(name) + sub.add_argument('--split', choices=['tiny', 'development', 'held-out'], default='tiny') + sub.add_argument('--configs', help='comma-separated fixed configuration names') + sub.add_argument('--case-ids', help='comma-separated development IDs or tiny/.txt controls; held-out always uses the full split') + sub.add_argument('--budget-ledger', type=Path, help='shared evaluation or task solver ledger') + sub.add_argument('--budget-phase', choices=['development', 'correctness', 'held-out', 'verification']) + sub.add_argument('--solver', type=Path, default=ROOT/'build/open-search/bin/tsptw') + sub.add_argument('--uv', default='uv') + sub.add_argument('--selected-root', type=Path, default=DATA/'selected') + sub.add_argument('--repetitions', type=int) + sub.add_argument('--seconds', type=float) + sub.add_argument('--compilation-seconds', type=float, default=None, help='separately measured prebuild time; omitted means unavailable') + if name == 'run': + sub.add_argument('--name', required=True) + sub.add_argument('--results-root', type=Path, default=ROOT/'build/tsptw-results') + sub.add_argument('--max-samples', type=int, help='collect at most this many missing rows; full config stays fixed') + sub = commands.add_parser('recover', help='confirm recorded child absence before resuming; never signals a PID') + sub.add_argument('--name', required=True) + sub.add_argument('--results-root', type=Path, default=ROOT/'build/tsptw-results') + sub = commands.add_parser('analyze') + sub.add_argument('--name', required=True) + sub.add_argument('--results-root', type=Path, default=ROOT/'build/tsptw-results') + sub.add_argument('--output', type=Path, help='also write the compact reproducible comparison') + args = parser.parse_args() + try: + if args.command != 'preview' and not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.-]*', args.name): + raise ValueError('name must be a single safe path component') + if args.command == 'recover': + recover(args.results_root/args.name/'samples') + return + if args.command == 'analyze': + analyze(args.results_root/args.name, args.output) + return + config = experiment(args) + preview(config) + if args.command == 'preview': + return + directory = args.results_root/args.name + config_path = directory/'config.json' + if config_path.exists() and json.loads(config_path.read_text()) != config: + raise ValueError('configuration differs; choose a new run name') + directory.mkdir(parents=True, exist_ok=True) + samples = directory/'samples' + samples.mkdir(exist_ok=True) + if not config_path.exists(): + write_json(config_path, config) + remaining = sum(completed(samples/f"{row['id']}.json", row) is None for row in config['rows']) + count = 0 + for row in config['rows']: + if completed(samples/f"{row['id']}.json", row): + continue + if args.max_samples is not None and count >= args.max_samples: + break + budget = config.get('budget') or {} + task_budget = budget and budget_header(budget['ledger']).get('scope') == 'task' + nominal = max(0, (remaining-1)*config['seconds']) if task_budget or budget.get('phase') == 'held-out' else (0 if budget.get('phase') == 'verification' else 5760) + collect(config, row, samples, nominal) + count += 1 + remaining -= 1 + except (OSError, ValueError) as error: + parser.exit(1, '\n'.join([str(error), *getattr(error, '__notes__', [])])+'\n') + + +if __name__ == '__main__': + main() diff --git a/misc/tsptw-cadds-study.cpp b/misc/tsptw-cadds-study.cpp new file mode 100644 index 0000000000..8874f11c34 --- /dev/null +++ b/misc/tsptw-cadds-study.cpp @@ -0,0 +1,441 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +// Standalone sequential CADDS study; no production engine or Model dependency. +#include "examples/tsptw-transition.hh" +#include +#include +#include +#include +#include + +using namespace Gecode; +using Owned = std::unique_ptr; +using Clock = std::chrono::steady_clock; + +// Constant-time counts include seeds, pending/active/in-flight states, +// incumbents, and publications owned by the caller. These are not byte counts. +class State : public TSPTW::Transition { +public: + static inline unsigned long live=0, peak=0, clones=0; + void created() { peak=std::max(peak,++live); } + State(std::shared_ptr data, + std::shared_ptr measurements) + : Transition(std::move(data),LAZY,OFF,std::move(measurements)) { created(); } + State(State& s) : Transition(s) { clones++; created(); } + Space* copy() override { return new State(*this); } + ~State() override { --live; } +}; + +struct Step { + enum Kind { SOLUTION, SUSPENDED, EXHAUSTED } kind; + Owned solution; +}; +struct Entry { + Owned space; + std::int64_t bound, rank; + unsigned long long tie, revision; +}; +struct Fragment { + unsigned depth; + std::vector sources, staged; + size_t source=0; + unsigned page_survivors=0; + unsigned long long source_revision=0; + // Destroy the Choice continuation before the sources. + Search::Seq::ChoiceExpansion expansion; + explicit Fragment(unsigned d) : depth(d) {} +}; + +// advance: finish the active continuation, classify each child, finish the +// frozen fragment, install its successors, then select the next deepest layer. +// The cursor control instead descends after each page of B surviving children. +class Study { + bool cadds, optimize; + unsigned width; + Search::HeuristicGuidance guidance; + Search::Options options; + Search::Worker worker; + Owned incumbent, root_goal; + std::int64_t incumbent_bound=0; + unsigned long long revision=0, tie=0; + std::vector> layers; + std::unique_ptr active; + std::vector> stack; + unsigned long pending=0, staged=0, sources=0; + + Entry entry(Owned s, std::int64_t bound) { + const auto rank=guidance.rank ? guidance.rank(*s) : bound; + return {std::move(s),bound,rank,tie++,revision}; + } + void sample() { + peak_pending=std::max(peak_pending,pending); + peak_staged=std::max(peak_staged,staged); + peak_sources=std::max(peak_sources,sources); + } + Owned publish(Owned s) { + if (optimize) { + if (incumbent && s->compare(*incumbent)!=SC_BETTER) return {}; + incumbent.reset(s->clone()); + incumbent_bound=guidance.bound(*s); + revision++; + } + return s; + } + bool select_fragment() { + size_t d=layers.size(); + while (d && layers[d-1].empty()) --d; + if (!d) return false; + --d; + auto& layer=layers[d]; + const size_t count=std::min(width,layer.size()); + std::partial_sort(layer.begin(),layer.begin()+count,layer.end(), + [](const Entry& a,const Entry& b) { + return a.rank(static_cast(d)); + for (size_t k=0;ksources.push_back(std::move(layer[k])); + layer.erase(layer.begin(),layer.begin()+count); + pending-=count; sources+=count; fragments++; sample(); + return true; + } + void finish_fragment() { + const unsigned d=active->depth+1; + if (layers.size()<=d) layers.resize(d+1); + // The deepest-layer rule makes this layer empty at fragment start. + if (!layers[d].empty()) throw std::runtime_error("nonempty successor layer"); + pending+=active->staged.size(); staged-=active->staged.size(); + layers[d]=std::move(active->staged); + active.reset(); sample(); + } + void descend_page(Fragment& f) { + auto child=std::make_unique(f.depth+1); + staged-=f.staged.size(); sources+=f.staged.size(); + child->sources=std::move(f.staged); f.page_survivors=0; + stack.push_back(std::move(child)); fragments++; sample(); + } +public: + unsigned long commits=0, pruned=0, suspensions=0, fragments=0; + unsigned long peak_pending=0, peak_staged=0, peak_sources=0; + + Study(Owned root, bool full_fragment, bool optimization, unsigned b, + Search::HeuristicGuidance g, Search::Options opt={}) + : cadds(full_fragment),optimize(optimization),width(b), + guidance(std::move(g)),options(opt) { + if (!width) throw std::runtime_error("zero width"); + if (guidance.sense!=Search::HeuristicGuidance::MINIMIZE) + throw std::runtime_error("study supports minimization"); + worker.node++; + const auto status=root->status(worker); + if (status==SS_FAILED) { worker.fail++; return; } + if (status==SS_SOLVED) { root_goal=std::move(root); return; } + const auto bound=guidance.bound(*root); + if (cadds) { + layers.resize(1); layers[0].push_back(entry(std::move(root),bound)); pending=1; + } else { + auto f=std::make_unique(0); + f->sources.push_back(entry(std::move(root),bound)); sources=1; + stack.push_back(std::move(f)); + } + sample(); + } + Search::Statistics statistics() const { return worker; } + bool done() const { + return !root_goal && !active && stack.empty() && pending==0; + } + Step advance(unsigned commit_quota=std::numeric_limits::max()) { + worker.start(); + if (root_goal) return {Step::SOLUTION,publish(std::move(root_goal))}; + unsigned work=0; + for (;;) { + if (cadds) { + if (!active && !select_fragment()) return {Step::EXHAUSTED,{}}; + } else if (stack.empty()) return {Step::EXHAUSTED,{}}; + Fragment& f=cadds ? *active : *stack.back(); + // A published goal can fill the cursor page without a branching child. + if (!cadds && f.page_survivors==width) { + if (!f.staged.empty()) descend_page(f); else f.page_survivors=0; + continue; + } + if (!f.expansion) { + while (f.source=incumbent_bound) { + selected.space.reset(); sources--; pruned++; continue; + } + f.source_revision=selected.revision; + f.expansion.select(std::move(selected.space)); + } + if (worker.stop(options) || work==commit_quota) { + suspensions++; return {Step::SUSPENDED,{}}; + } + const bool owned_source=static_cast(f.expansion); + auto child=f.expansion.next(worker,options,f.depth+1,f.source_revision, + optimize ? incumbent.get() : nullptr,revision); + if (owned_source && !f.expansion) { sources--; sample(); } + if (child.state==Search::Seq::ExpansionStep::SUSPENDED) { + suspensions++; return {Step::SUSPENDED,{}}; + } + if (child.state==Search::Seq::ExpansionStep::EXHAUSTED) continue; + commits++; work++; + if (child.status==SS_FAILED) { worker.fail++; continue; } + const auto bound=guidance.bound(*child.space); + if (optimize && incumbent && bound>=incumbent_bound) { + pruned++; continue; + } + if (!cadds) f.page_survivors++; + if (child.status==SS_SOLVED) { + if (auto result=publish(std::move(child.space))) + return {Step::SOLUTION,std::move(result)}; + } else { + f.staged.push_back(entry(std::move(child.space),bound)); staged++; sample(); + } + } + } +}; + +static void numbers(const std::vector& v) { + std::cout<<'['; + for (size_t k=0;k path; + static inline std::vector expanded; + class Branch : public Brancher { + bool prepared=false; + class Decision : public Choice { + public: + std::vector actions; + Decision(const Brancher& b,std::vector a) + : Choice(b,static_cast(a.size())),actions(std::move(a)) {} + void archive(Archive& e) const override { + Choice::archive(e); e<(actions.size()); + for (int a:actions) e<(h).node<10; + } + bool prepare(Space&,Search::Statistics&,const Search::Options&) override { + if (!prepared) { prepared=true; return false; } + return true; + } + const Choice* choice(Space& h) override { + const auto& s=static_cast(h); + std::vector a; + if (s.node==0) { a={9,1,2}; if (s.ceiling<=9) a.erase(a.begin()); } + else if (s.node==1) a={3,10}; + else if (s.node==2) a={4}; + else if (s.node==3) a={7}; + else a={5}; + expanded.push_back(s.node); + return new Decision(*this,std::move(a)); + } + const Choice* choice(const Space&,Archive& e) override { + unsigned n; e>>n; std::vector a(n); for (int& v:a) e>>v; + return new Decision(*this,std::move(a)); + } + ExecStatus commit(Space& h,const Choice& c,unsigned a) override { + auto& s=static_cast(h); + const int action=static_cast(c).actions[a]; + s.path.push_back(action); prepared=false; + if (action==10) return ES_FAILED; + if (action>=1 && action<=4) { s.node=action; return ES_OK; } + s.node=10; s.cost=action==9?9:(action==5?5:3); + return s.cost(s).cost; + return cost(s).cost; + if (node>=10 && cost>=ceiling) fail(); + } +}; + +static void witnesses() { + Search::HeuristicGuidance g{Search::HeuristicGuidance::MINIMIZE, + [](const Space& s)->std::int64_t { + const auto& w=static_cast(s); return w.node>=10?w.cost:0; + },[](const Space& s)->std::int64_t { + const auto& w=static_cast(s); return w.node==1 || w.node==3?100:0; + }}; + std::vector schedule; + struct Observation { bool cadds; unsigned width; std::vector costs; }; + std::vector observations; + unsigned pauses=0; + for (bool cadds:{true,false}) for (unsigned width:{1u,4u}) { + Witness::expanded.clear(); + Study coverage(std::make_unique(),cadds,false,width,g); + std::set> goals; + for (;;) { + auto step=coverage.advance(1); + if (step.kind==Step::EXHAUSTED) break; + if (step.kind==Step::SUSPENDED) { pauses++; continue; } + goals.insert(static_cast(*step.solution).path); + } + require(goals==std::set>{{9},{1,3,7},{2,4,5}},"coverage"); + require(coverage.statistics().fail==1 && coverage.done(),"exhaustion"); + if (cadds && width==4) { + schedule=Witness::expanded; + const auto second=std::find(schedule.begin(),schedule.end(),1); + const auto successor=std::find(schedule.begin(),schedule.end(),4); + require(second{0,2,1,4,3},"fragment witness order"); + } + Study optimization(std::make_unique(),cadds,true,width,g); + std::vector costs; + for (;;) { + auto step=optimization.advance(1); + if (step.kind==Step::EXHAUSTED) break; + if (step.kind==Step::SOLUTION) costs.push_back(static_cast(*step.solution).cost); + } + require(!costs.empty() && costs.front()==9 && costs.back()==3 && + std::adjacent_find(costs.begin(),costs.end(),std::less_equal())==costs.end(), + "strict improvement or misleading rank"); + observations.push_back({cadds,width,std::move(costs)}); + } + Search::NodeStop stop(0); Search::Options options; options.stop=&stop; + Study resumed(std::make_unique(),true,false,4,g,options); + require(resumed.advance().kind==Step::SUSPENDED && !resumed.done(),"ordinary stop"); + stop.limit(1000); + require(resumed.advance().kind==Step::SUSPENDED,"preparation suspension"); + // Stop after commits while the original root continuation is still active. + require(resumed.advance(1).kind==Step::SOLUTION,"shallow goal"); + stop.limit(resumed.statistics().node-1); + require(resumed.advance().kind==Step::SUSPENDED && !resumed.done(),"midfragment stop"); + stop.limit(1000); + unsigned returns=1; + for (;;) { + auto step=resumed.advance(1); + if (step.kind==Step::EXHAUSTED) break; + if (step.kind==Step::SOLUTION) returns++; + } + require(returns==3 && resumed.done(),"stop resume lost work"); + auto failed=std::make_unique(); failed->fail(); + Study empty(std::move(failed),true,false,4,g); + require(empty.advance().kind==Step::EXHAUSTED,"failed root"); + Study solved(std::make_unique(10),true,true,4,g); + require(solved.advance().kind==Step::SOLUTION && + solved.advance().kind==Step::EXHAUSTED,"solved root"); + // Directly establish the fresh-choice index shift, using clone-related Spaces. + Search::Statistics stat; Search::Options opt; + auto source=std::make_unique(); (void)source->status(); + require(!source->prepare(stat,opt) && source->prepare(stat,opt),"prepare"); + std::unique_ptr original(source->choice()); + auto cut=std::unique_ptr(static_cast(source->clone())); + cut->ceiling=6; (void)cut->status(); + std::unique_ptr fresh(cut->choice()); + auto wrong=std::unique_ptr(static_cast(cut->clone())); + wrong->commit(*fresh,1); (void)wrong->status(); + auto right=std::unique_ptr(static_cast(source->clone())); + right->commit(*original,1); right->ceiling=6; (void)right->status(); + require(wrong->path==std::vector{2} && right->path==std::vector{1},"choice index shift"); + std::cout<<"{\"kind\":\"witness\",\"coverage_configurations\":4,\"goals\":3," + <<"\"prepare_and_slice_suspensions\":"<(Clock::now()-start).count(); +} +static void solution(const State& s,bool coverage,Clock::time_point start) { + std::cout<<"{\"kind\":\""<<(coverage?"coverage":"solution") + <<"\",\"scaled_cost\":"<(TSPTW::read(argv[5])); + auto measurements=std::make_shared(); + auto guidance=TSPTW::Transition::guidance(TSPTW::Transition::MIN_ARCS,measurements); + Search::TimeStop stop(time_ms); Search::Options opt; + opt.clone=false; opt.threads=1; if (time_ms) opt.stop=&stop; + Search::Statistics counts; + bool stopped=false; + int best=-1; unsigned publications=0; + unsigned long commits=0,pending=0,staged=0,sources=0,fragments=0,pruned=0; + { + if (engine=="cabs") { + CABS search(new State(data,measurements),guidance,opt); + while (std::unique_ptr s{search.next()}) { + best=s->cost(); publications++; solution(*s,false,start); + } + stopped=search.stopped(); counts=search.statistics(); + } else { + Study study(std::make_unique(data,measurements),engine=="cadds",mode=="opt",width,guidance,opt); + for (;;) { + auto step=study.advance(); + if (step.kind==Step::EXHAUSTED) break; + if (step.kind==Step::SUSPENDED) { stopped=true; break; } + auto& s=static_cast(*step.solution); + best=best<0?s.cost():std::min(best,s.cost()); publications++; + solution(s,mode=="coverage",start); + } + counts=study.statistics(); commits=study.commits; pending=study.peak_pending; + staged=study.peak_staged; sources=study.peak_sources; + fragments=study.fragments; pruned=study.pruned; + } + } + require(State::live==0,"leaked live states"); + const char* termination=stopped?"stopped":(mode=="coverage"?"exhausted":(best<0?"infeasible":"optimal")); + std::cout<<"{\"kind\":\"summary\",\"engine\":\""<scale<<",\"best_scaled_cost\":"; + if (best<0) std::cout<<"null"; else std::cout<bound_calls + <<",\"bound_ms\":"<bound_ms<<",\"inference_calls\":"<inference_calls + <<",\"inference_ms\":"<inference_ms<<",\"propagation_ms\":"<propagation_ms + <<"}}\n"; + } catch (const std::exception& e) { std::cerr<= trace[-1]['scaled_cost']: + raise ValueError('incumbents do not strictly improve') + previous_time = elapsed + tour = tuple(record['tour']) + if row['mode'] == 'coverage' and tour in tours: + raise ValueError('duplicate coverage tour') + tours.add(tour) + trace.append(dict(scaled_cost=cost, elapsed_ms=elapsed, tour=record['tour'], exact_cost=result['cost'])) + elif record.get('kind') == 'summary' and summary is None: + summary = record + else: + raise ValueError('unexpected native record') + if capture['status'] == 'ok' and summary is None: + raise ValueError('successful run has no summary') + if summary: + best = min((item['scaled_cost'] for item in trace), default=None) + if (summary.get('best_scaled_cost') != best or summary.get('publications') != len(trace) or + summary.get('scale') != data[2] or summary.get('elapsed_ms', -1) < previous_time): + raise ValueError('summary disagrees with publications') + termination = summary.get('termination') + if termination not in ('optimal', 'infeasible', 'stopped', 'exhausted'): + raise ValueError('unknown termination') + if (termination == 'optimal' and best is None) or (termination == 'infeasible' and best is not None): + raise ValueError('contradictory proof') + if row['case']['id'].startswith('tiny/'): + expected = checker.enumerate_tours(data) + optimum = expected['optimum']['scaled_cost'] if expected['optimum'] else None + if termination == 'stopped' or best != optimum: + raise ValueError('tiny optimum/exhaustion mismatch') + if row['mode'] == 'coverage' and (termination != 'exhausted' or tours != feasible_tours(data)): + raise ValueError('tiny complete tour identities mismatch') + return dict(trace=trace, summary=summary, checked_publications=len(trace)) + + +def load_sample(samples, row): + path = samples / (row['id']+'.json') + if not path.exists(): + return None + record = json.loads(path.read_text()) + if record.get('row') != row or record.get('capture', {}).get('status') not in benchmark.TERMINAL: + raise ValueError('invalid saved terminal sample: '+str(path)) + return record + + +def run(args): + config = experiment(args) + if args.dry_run: + print(json.dumps(dict(config=config, nominal_seconds=sum(r['cap_seconds'] for r in config['rows']), + ledger=str(LEDGER), budget=HEADER), indent=2)) + return + directory = args.results_root / args.name + directory.mkdir(parents=True, exist_ok=True) + saved = directory / 'config.json' + if saved.exists() and json.loads(saved.read_text()) != config: + raise ValueError('material configuration changed; use a new experiment name') + if not saved.exists(): + benchmark.write_json(saved, config) + if not LEDGER.exists(): + with LEDGER.open('x') as output: + output.write(json.dumps(HEADER)+'\n') + if benchmark.budget_header(LEDGER) != HEADER: + raise ValueError('unexpected task budget header') + samples = directory / 'samples' + samples.mkdir(exist_ok=True) + benchmark.require_reaped(samples) + selected = args.only.split(',') if args.only else None + if selected and any(identity not in {row['id'] for row in config['rows']} for identity in selected): + raise ValueError('unknown selected row') + launched = 0 + for index, row in enumerate(config['rows']): + if selected and row['id'] not in selected: + continue + if load_sample(samples, row): + continue + if args.limit is not None and launched >= args.limit: + break + remaining = sum(other['cap_seconds'] for other in config['rows'][index+1:] + if (not selected or other['id'] in selected) and not load_sample(samples, other)) + # Correctness must admit the entire not-yet-launched frozen development matrix. + if args.phase == 'correctness': + remaining += 60 + attempt = 1 + destination = samples / row['id'] + while destination.with_suffix('.stdout').exists() or destination.with_suffix('.stderr').exists(): + attempt += 1 + destination = samples / (row['id']+f'-attempt{attempt}') + capture = benchmark.capture(row['argv'], destination, row['cap_seconds'], + config['rss_bytes'], config['sample_interval_seconds'], + budget=dict(ledger=str(LEDGER), phase=args.phase), remaining_nominal_seconds=remaining) + record = dict(row=row, capture=capture, stream=destination.name) + try: + record['checked'] = checked_output(destination.with_suffix('.stdout'), row, capture) + except ValueError as error: + record['capture'] = dict(capture, status='invalid-output', validation_error=str(error)) + benchmark.write_json(samples / (row['id']+'.json'), record) + launched += 1 + print(json.dumps(dict(id=row['id'], status=record['capture']['status'], wall_seconds=capture['wall_seconds'])), flush=True) + + +def analyze(args): + directory = args.results_root / args.name + config = json.loads((directory / 'config.json').read_text()) + rows, missing = [], [] + for row in config['rows']: + record = load_sample(directory / 'samples', row) + if record is None: + missing.append(row['id']) + continue + # Recheck raw publications independently on every offline analysis. + checked = checked_output(directory / 'samples' / (record['stream']+'.stdout'), row, record['capture']) + capture, trace, summary = record['capture'], checked['trace'], checked['summary'] + best = min(trace, key=lambda item: item['scaled_cost']) if trace else None + rows.append(dict(id=row['id'], config=row['config'], case=row['case'], repetition=row['repetition'], + argv=row['argv'], status=capture['status'], summary=summary, witness=checked.get('witness'), + checked_publications=len(trace), first_publication_ms=trace[0]['elapsed_ms'] if trace else None, + best_publication_ms=best['elapsed_ms'] if best else None, best=best, + incumbent_trace=[dict(scaled_cost=t['scaled_cost'], elapsed_ms=t['elapsed_ms']) for t in trace], + end_to_end_wall_seconds=capture['wall_seconds'], sampled_peak_rss_bytes=capture['peak_rss_bytes'], + rss_samples=capture['rss_samples'], maximum_sample_gap_seconds=capture['maximum_sample_gap_seconds'], + rss_probe_seconds=capture['rss_probe_seconds'], termination_reap_seconds=capture['termination_reap_seconds'], + wall_cap_overshoot_seconds=capture['wall_cap_overshoot_seconds'], rss_overshoot_bytes=capture['rss_overshoot_bytes'])) + result = dict(schema_version=1, experiment=args.name, configuration=config, expected_rows=len(config['rows']), + completed_rows=len(rows), missing_rows=missing, rows=rows) + output = args.output or directory / 'analysis.json' + benchmark.write_json(output, result) + print(json.dumps(dict(output=str(output), completed=len(rows), missing=len(missing), + checked_publications=sum(r['checked_publications'] for r in rows)))) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + commands = parser.add_subparsers(dest='command', required=True) + for name in ('run', 'analyze', 'recover'): + sub = commands.add_parser(name) + sub.add_argument('--name', required=True) + sub.add_argument('--results-root', type=Path, default=Path('/private/tmp/open-search-030-study/results')) + if name == 'run': + sub.add_argument('--binary', type=Path, required=True) + sub.add_argument('--suite', choices=('witness', 'tiny', 'development'), default='development') + sub.add_argument('--phase', choices=('correctness', 'development', 'verification'), default='development') + sub.add_argument('--dry-run', action='store_true') + sub.add_argument('--limit', type=int) + sub.add_argument('--only', help='comma-separated frozen row IDs for independent verification') + if name == 'analyze': + sub.add_argument('--output', type=Path) + args = parser.parse_args() + if not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.-]*', args.name): + parser.error('name must be one safe path component') + try: + if args.command == 'run': + run(args) + elif args.command == 'analyze': + analyze(args) + else: + benchmark.recover(args.results_root / args.name / 'samples') + except (OSError, ValueError) as error: + print(str(error), file=sys.stderr) + return 1 + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/misc/tsptw-check.py b/misc/tsptw-check.py new file mode 100644 index 0000000000..92b20fdf91 --- /dev/null +++ b/misc/tsptw-check.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +"""Check canonical TSPTW tours using exact rational arithmetic, without Gecode.""" +import argparse +from fractions import Fraction +import itertools +import json +from pathlib import Path +import re +import sys + +INTEGER_MAX = 2147483646 # Gecode::Int::Limits::max + + +def read_instance(path): + tokens = " ".join(line.split("#", 1)[0] for line in + Path(path).read_text().splitlines()).split() + if not tokens or not re.fullmatch(r"[0-9]+", tokens[0]): + raise ValueError("node count must be an integer") + n = int(tokens[0]) + if n < 2 or n*n > INTEGER_MAX or len(tokens) != 1+n*n+2*n: + raise ValueError("invalid TSPTW dimensions") + if any(not re.fullmatch(r"(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)", x) + for x in tokens[1:]): + raise ValueError("invalid nonnegative decimal") + values = [Fraction(x) for x in tokens[1:]] + # The common power of ten is deliberately computed from text, independently + # of the native reader's mantissa conversion. Trailing zeroes need no scale. + places = max(len(x.rstrip("0").split(".")[1]) if "." in x else 0 + for x in tokens[1:]) + scale = 10**places + if scale > INTEGER_MAX or any(x*scale > INTEGER_MAX for x in values): + raise ValueError("scaled value exceeds Gecode integer limits") + matrix = [values[i*n:(i+1)*n] for i in range(n)] + windows = [tuple(values[n*n+2*i:n*n+2*i+2]) for i in range(n)] + if any(ready > due for ready, due in windows): + raise ValueError("window ready time exceeds due time") + if windows[0][0] != 0: + raise ValueError("nonzero start-depot ready time is unsupported") + cost_limit = n*max(values[:n*n]) + time_limit = cost_limit+max(due for _, due in windows) + if time_limit*scale > INTEGER_MAX: + raise ValueError("tour arithmetic exceeds Gecode integer limits") + return matrix, windows, scale + + +def exact_text(value): + """Render a finite decimal Fraction without rounding.""" + denominator = value.denominator + places = 0 + while (10**places) % denominator: + places += 1 + integer = value.numerator * (10**places // denominator) + if not places: + return str(integer) + text = str(integer).zfill(places+1) + return (text[:-places]+"."+text[-places:]).rstrip("0").rstrip(".") + + +def check_tour(data, tour, claimed_cost=None): + matrix, windows, scale = data + n = len(matrix) + if len(tour) != n-1: + raise ValueError("tour omits customers or has extra visits") + if set(tour) != set(range(1, n)): + raise ValueError("tour has a duplicate or invalid customer") + time = Fraction(0) + cost = Fraction(0) + previous = 0 + visits = [] + for node in [*tour, 0]: + arrival = time+matrix[previous][node] + time = max(arrival, windows[node][0]) + cost += matrix[previous][node] + if time > windows[node][1]: + raise ValueError("late return" if node == 0 else f"late customer {node}") + visits.append({"node": node, "arrival": exact_text(arrival), + "waiting": exact_text(time-arrival), "time": exact_text(time)}) + previous = node + if claimed_cost is not None and cost != Fraction(claimed_cost): + raise ValueError(f"incorrect cost: recomputed {exact_text(cost)}") + return {"cost": exact_text(cost), "scaled_cost": int(cost*scale), + "return_time": exact_text(time), "scale": scale, "visits": visits} + + +def enumerate_tours(data): + n = len(data[0]) + if n > 10: + raise ValueError("tiny enumeration is limited to nine customers") + best = None + feasible = 0 + for tour in itertools.permutations(range(1, n)): + try: + result = check_tour(data, tour) + except ValueError: + continue + feasible += 1 + if best is None or result["scaled_cost"] < best["scaled_cost"]: + best = {**result, "tour": list(tour)} + return {"feasible_tours": feasible, "optimum": best, "proof": "exhaustive enumeration"} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + commands = parser.add_subparsers(dest="command", required=True) + check = commands.add_parser("check") + check.add_argument("instance", type=Path) + check.add_argument("tour", type=Path, help="whitespace customer IDs; omit both depot visits") + check.add_argument("--cost", help="exact original-unit arc cost, including return") + enumerate_command = commands.add_parser("enumerate") + enumerate_command.add_argument("instance", type=Path) + args = parser.parse_args() + try: + data = read_instance(args.instance) + if args.command == "check": + tour = [int(x) for x in args.tour.read_text().split()] + result = check_tour(data, tour, args.cost) + else: + result = enumerate_tours(data) + print(json.dumps(result)) + except (OSError, ValueError) as error: + print(str(error), file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/misc/tsptw-didppy.py b/misc/tsptw-didppy.py new file mode 100755 index 0000000000..e5987d18fa --- /dev/null +++ b/misc/tsptw-didppy.py @@ -0,0 +1,86 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.11" +# dependencies = ["didppy==0.11.1"] +# /// +"""Exact travel-cost TSPTW reference; single-threaded DIDPPy CAASDy (A*) / CABS.""" +import argparse +import importlib.metadata +import importlib.util +import json +from pathlib import Path +import time + +START = time.perf_counter() +import didppy as dp + +spec = importlib.util.spec_from_file_location('checker', Path(__file__).with_name('tsptw-check.py')) +checker = importlib.util.module_from_spec(spec) +spec.loader.exec_module(checker) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('instance', type=Path) + parser.add_argument('--engine', choices=['astar', 'cabs'], required=True) + parser.add_argument('--time-ms', type=int, required=True) + args = parser.parse_args() + data = checker.read_instance(args.instance) + matrix, windows, scale = data + arcs = [[int(v*scale) for v in row] for row in matrix] + ready = [int(a*scale) for a, _ in windows] + due = [int(b*scale) for _, b in windows] + n = len(arcs) + model = dp.Model() + customers = model.add_object_type(number=n) + unvisited = model.add_set_var(object_type=customers, target=list(range(1, n))) + location = model.add_element_var(object_type=customers, target=0) + clock = model.add_int_resource_var(target=0, less_is_better=True) + travel = model.add_int_table(arcs) + for j in range(1, n): + arrival = dp.max(clock + travel[location, j], ready[j]) + model.add_transition(dp.Transition(name=str(j), + cost=travel[location, j] + dp.IntExpr.state_cost(), + preconditions=[unvisited.contains(j), arrival <= due[j]], + effects=[(unvisited, unvisited.remove(j)), (location, j), (clock, arrival)])) + model.add_transition(dp.Transition(name='return', + cost=travel[location, 0] + dp.IntExpr.state_cost(), + preconditions=[unvisited.is_empty(), location != 0, clock + travel[location, 0] <= due[0]], + effects=[(location, 0), (clock, clock + travel[location, 0])])) + model.add_base_case([unvisited.is_empty(), location == 0]) + # The notebook's direct-arc residual deadline constraint is unsound for + # nonmetric input. Only actual visits and the explicit return check time. + incoming = model.add_int_table([min(arcs[i][j] for i in range(n) if i != j) for j in range(n)]) + outgoing = model.add_int_table([min(arcs[i][j] for j in range(n) if i != j) for i in range(n)]) + # Every unfinished tour needs one departure from its current location and + # all remaining customers, plus one arrival at each customer and the depot. + unfinished = ~(unvisited.is_empty() & (location == 0)) + model.add_dual_bound(unfinished.if_then_else(incoming[unvisited] + incoming[0], 0)) + model.add_dual_bound(unfinished.if_then_else(outgoing[unvisited] + outgoing[location], 0)) + preprocess_ms = (time.perf_counter()-START)*1000 + solver_type = dp.CAASDy if args.engine == 'astar' else dp.CABS + search_start = time.perf_counter() + solver = solver_type(model, quiet=True, time_limit=max(0.000001, args.time_ms/1000-preprocess_ms/1000)) + incumbent = None + while True: + solution, terminated = solver.search_next() + if solution.cost is not None and (incumbent is None or solution.cost < incumbent['scaled_cost']): + tour = [int(t.name) for t in solution.transitions if t.name != 'return'] + value = checker.check_tour(data, tour) + if value['scaled_cost'] != solution.cost: + raise ValueError('DIDPPy objective differs from checked arc sum') + incumbent = {k: value[k] for k in ('cost', 'scaled_cost', 'scale', 'return_time')} + incumbent['tour'] = tour + print(json.dumps(dict(type='solution', elapsed_ms=(time.perf_counter()-START)*1000, **incumbent)), flush=True) + if terminated: + break + termination = 'optimal' if solution.is_optimal else 'infeasible' if solution.is_infeasible else 'stopped' + print(json.dumps(dict(type='summary', engine=args.engine, formulation='didppy', + version=importlib.metadata.version('didppy'), termination=termination, incumbent=incumbent, + elapsed_ms=(time.perf_counter()-START)*1000, preprocessing_ms=preprocess_ms, + search_ms=(time.perf_counter()-search_start)*1000, + statistics=dict(node=solution.expanded, generated=solution.generated))), flush=True) + + +if __name__ == '__main__': + main() diff --git a/misc/tsptw-read.cpp b/misc/tsptw-read.cpp new file mode 100644 index 0000000000..b8b94969bb --- /dev/null +++ b/misc/tsptw-read.cpp @@ -0,0 +1,14 @@ +// Small CLI for exercising the native example input reader. +#include "examples/tsptw-data.hh" +#include +int main(int argc, char* argv[]) { + if (argc != 2) { std::cerr << "usage: tsptw-read INSTANCE\n"; return 2; } + try { + TSPTW::Instance d = TSPTW::read(argv[1]); + std::cout << d.n << ' ' << d.scale << ' ' << d.cost_limit << ' ' + << d.time_limit << '\n'; + for (std::size_t i=0; i 0 and allocation["intvar_handle_bytes"] > 0 + assert allocation["root_positions"] == (0 if formulation == "transition" else n) + assert allocation["prepared_root_positions"] in ({0, 1} if formulation == "transition" else {n}) + assert allocation["solution_positions"] == (n if summary["incumbent"] else 0) + measurements = summary["measurements"] + assert all(isinstance(x, (int, float)) and x >= 0 for x in measurements.values()) + assert measurements["propagation_ms"] <= measurements["inference_ms"] + if propagation == "off": + assert measurements["inference_calls"] == 0 + assert measurements["inference_ms"] == measurements["propagation_ms"] == 0 + assert summary["termination"] in {"optimal", "infeasible", "stopped"} + assert all(isinstance(x, int) and x >= 0 for x in summary["statistics"].values()) + last = None + elapsed = 0 + for record in records[:-1]: + assert record["type"] == "solution", record + assert all(type(node) is int for node in record["tour"]), record + checked = CHECKER["check_tour"](data, record["tour"], record["cost"]) + for field in ("scaled_cost", "scale", "cost", "return_time"): + assert record[field] == checked[field], (path, engine, field, record) + assert last is None or checked["scaled_cost"] < last["scaled_cost"], records + assert record["elapsed_ms"] >= elapsed + elapsed = record["elapsed_ms"] + last = {field: record[field] for field in + ("tour", "scaled_cost", "scale", "cost", "return_time")} + assert summary["elapsed_ms"] >= elapsed + assert summary["incumbent"] == last, records + if seed_result: + assert records[0]["tour"] == tour + assert records[0]["scaled_cost"] == seed_result["scaled_cost"] + if summary["termination"] == "optimal": + assert last is not None, records + if summary["termination"] == "infeasible": + assert last is None, records + return summary, records[:-1] + + +def validate_pruning(solver, path, require_pruning=False, options=(), + formulations=("transition", "eager")): + optimum = CHECKER["enumerate_tours"](CHECKER["read_instance"](path))["optimum"] + for engine in ("astar", "cabs"): + lazy_tours = None + for formulation in formulations: + for pruning in ("off", "pareto"): + summary, solutions = run_solver(solver, path, engine, + (*options, "--state-pruning", pruning), formulation=formulation) + assert summary["termination"] == ("optimal" if optimum else "infeasible") + if optimum: + assert summary["incumbent"]["scaled_cost"] == optimum["scaled_cost"] + if require_pruning and pruning == "pareto": + assert summary["statistics"]["state_pruned"] > 0, summary + assert summary["statistics"]["state_descriptors"] > 0, summary + assert summary["statistics"]["state_bytes"] > 0, summary + if pruning == "pareto": + tours = [record["tour"] for record in solutions] + if formulation == "transition": + lazy_tours = tours + else: + assert tours == lazy_tours, (path, engine, lazy_tours, tours) + + +def validate_strengthening(solver, path, configurations): + optimum = CHECKER["enumerate_tours"](CHECKER["read_instance"](path))["optimum"] + for bound, propagation in configurations: + for engine in ("bab", "astar", "cabs"): + summary, _ = run_solver(solver, path, engine, + ("--bound", bound, "--residual-propagation", propagation), + formulation="transition") + assert summary["termination"] == ("optimal" if optimum else "infeasible") + if optimum: + assert summary["incumbent"]["scaled_cost"] == optimum["scaled_cost"] + + +def validate_column(solver, path, options=(), seed=None, formulations=("closed", "transition")): + optimum = CHECKER["enumerate_tours"](CHECKER["read_instance"](path))["optimum"] + for growth in ("0", "1"): + for formulation in formulations: + summary, _ = run_solver(solver, path, "column", + (*options, "--initial-quota", "1", "--quota-growth", growth), + seed=seed, formulation=formulation) + assert summary["initial_quota"] == 1 and summary["quota_growth"] == int(growth) + assert summary["termination"] == ("optimal" if optimum else "infeasible") + if optimum: + assert summary["incumbent"]["scaled_cost"] == optimum["scaled_cost"] + + +def validate_solver(solver, manifest): + engines = ("bab", "astar", "cabs") + formulations = ("closed", "transition", "eager") + for case in manifest["tiny"]: + path = DATA/case["path"] + optimum = CHECKER["enumerate_tours"](CHECKER["read_instance"](path))["optimum"] + validate_pruning(solver, path) + validate_column(solver, path) + validate_strengthening(solver, path, (("min-arcs", "off"), ("min-arcs", "unary"))) + for engine in engines: + lazy_tours = None + for formulation in formulations: + summary, solutions = run_solver(solver, path, engine, formulation=formulation) + assert summary["termination"] == ("optimal" if optimum else "infeasible") + if optimum: + assert summary["incumbent"]["scaled_cost"] == optimum["scaled_cost"] + tours = [record["tour"] for record in solutions] + if formulation == "transition": + lazy_tours = tours + elif formulation == "eager": + assert tours == lazy_tours, (case, engine, lazy_tours, tours) + with tempfile.TemporaryDirectory(prefix="tsptw-solver-") as directory: + directory = Path(directory) + nonmetric = directory/"nonmetric.txt" + nonmetric.write_text((DATA/"tiny/waiting.txt").read_text().replace("0 12", "0 6")) + assert CHECKER["enumerate_tours"](CHECKER["read_instance"](nonmetric))["optimum"]["cost"] == "5" + validate_strengthening(solver, nonmetric, (("min-arcs", "unary"),)) + validate_strengthening(solver, DATA/"tiny/waiting.txt", (("prefix", "unary"),)) + seed = directory/"seed.tour" + folded = DATA/"tiny/folded-service.txt" + for formulation in formulations: + for engine in engines: + summary, _ = run_solver(solver, nonmetric, engine, formulation=formulation) + assert summary["termination"] == "optimal" and summary["incumbent"]["cost"] == "5" + seed.write_text("2 1\n") + summary, solutions = run_solver(solver, folded, engine, seed=seed, formulation=formulation) + assert summary["termination"] == "optimal" and summary["incumbent"]["cost"] == "9" + assert solutions[0]["cost"] == "13" and len(solutions) >= 2 + seed.write_text("1 2\n") + summary, solutions = run_solver(solver, folded, engine, seed=seed, formulation=formulation) + assert summary["termination"] == "optimal" and len(solutions) == 1 + for instance, tour in [(folded, "1 1\n"), (DATA/"tiny/waiting.txt", "2 1\n"), + (folded, "1 2 999999999999999999999\n")]: + seed.write_text(tour) + result = subprocess.run([str(solver), str(instance), "--engine", engine, + "--formulation", formulation, "--seed", str(seed)], + capture_output=True, text=True) + assert result.returncode == 1 and not result.stdout, result + # A cheap prefix can publish an expensive return before better tours. + improving = directory/"improving.txt" + improving.write_text("4\n0 1 2 3\n1 0 1 4\n1 3 0 1\n20 1 3 0\n" + "0 100\n5 100\n0 100\n0 100\n") + optimum = CHECKER["enumerate_tours"](CHECKER["read_instance"](improving))["optimum"] + for engine in engines: + lazy_tours = None + for formulation in ("transition", "eager"): + summary, solutions = run_solver(solver, improving, engine, formulation=formulation) + assert summary["termination"] == "optimal" + assert summary["incumbent"]["scaled_cost"] == optimum["scaled_cost"] + assert len(solutions) >= 2 + tours = [record["tour"] for record in solutions] + if lazy_tours is None: + lazy_tours = tours + else: + assert tours == lazy_tours, (engine, lazy_tours, tours) + validate_pruning(solver, improving) + validate_column(solver, improving, formulations=("transition",)) + seed.write_text("2 1\n") + validate_column(solver, folded, seed=seed) + # Many paths reach equal residual states before any complete tour. + duplicates = directory/"duplicates.txt" + duplicates.write_text("6\n" + "\n".join( + " ".join("0" if i == j else "1" for j in range(6)) for i in range(6)) + + "\n" + "0 100\n"*6) + validate_pruning(solver, duplicates, require_pruning=True) + # Waiting makes arrival time differ from prefix cost. Different visit + # orders give incomparable labels; enumerate every tour independently. + pareto = directory/"pareto.txt" + pareto.write_text("5\n0 3 1 7 4\n4 0 5 1 3\n2 1 0 4 6\n" + "3 4 2 0 1\n1 2 3 5 0\n" + "0 100\n15 100\n0 100\n0 100\n5 100\n") + validate_pruning(solver, pareto) + validate_column(solver, pareto, ("--state-pruning", "pareto", + "--bound", "min-arcs", "--residual-propagation", "unary"), + formulations=("transition",)) + validate_pruning(solver, pareto, options=("--bound", "min-arcs", + "--residual-propagation", "unary"), formulations=("transition",)) + for formulation, engine in (("closed", "astar"), ("transition", "bab")): + result = subprocess.run([str(solver), str(duplicates), "--engine", engine, + "--formulation", formulation, "--state-pruning", "pareto"], + capture_output=True, text=True) + assert result.returncode == 1 and not result.stdout, result + # Equal-cost routes still retain decisions after cost branching. + branching = directory/"branching.txt" + branching.write_text("5\n" + "\n".join( + " ".join("0" if i == j else "1" for j in range(5)) for i in range(5)) + + "\n" + "0 100\n"*5) + branching_optimum = CHECKER["enumerate_tours"]( + CHECKER["read_instance"](branching))["optimum"]["scaled_cost"] + for formulation in formulations: + for engine in engines: + summary, _ = run_solver(solver, branching, engine, ("--node-limit", "0"), + formulation=formulation) + assert summary["termination"] == "stopped" + assert summary["statistics"]["node"] > 0 + for limit in ("--max-width", "--frontier-limit"): + summary, _ = run_solver(solver, branching, "cabs", (limit, "1"), + formulation=formulation) + # A ceiling stops only when further retained work or widening + # is needed. Tight total bounds can prove this equal-cost tree + # complete before hitting the ceiling. + assert summary["termination"] in ("stopped", "optimal"), summary + if summary["termination"] == "optimal": + assert summary["incumbent"]["scaled_cost"] == branching_optimum + for formulation, growth, limit in (("closed", "1", "--node-limit"), + ("transition", "0", "--frontier-limit")): + summary, _ = run_solver(solver, branching, "column", + ("--quota-growth", growth, limit, "0" if limit == "--node-limit" else "1"), + formulation=formulation) + assert summary["termination"] == "stopped", summary + for options in (("--initial-quota", "0"), ("--max-width", "1")): + result = subprocess.run([str(solver), str(branching), "--engine", "column", *options], + capture_output=True, text=True) + assert result.returncode == 1 and not result.stdout, result + complete = stopped = 0 + for case in manifest["published"]: + proven_cost = None + for formulation in formulations: + for engine in engines: + summary, _ = run_solver(solver, DATA/case["path"], engine, + ("--time-ms", "5000"), DATA/case["tour_path"], formulation) + assert Fraction(summary["incumbent"]["cost"]) <= Fraction(case["exact_cost"]) + assert summary["termination"] != "infeasible" + if summary["termination"] == "optimal": + cost = summary["incumbent"]["scaled_cost"] + assert proven_cost is None or cost == proven_cost + proven_cost = cost + complete += 1 + else: + stopped += 1 + print(f"passed closed/lazy/eager BAB/A*/CABS: tiny optima/infeasibility, " + f"matching transition tours, focused progressive/fixed column checks, " + f"Pareto off/on enumeration, nonmetric filtering, " + f"minimum-arc bounds and residual unary propagation, " + f"checked seeds and stops; " + f"canonical runs: {complete} proven, {stopped} stopped (5 seconds per run)") + + +def validate(reader, selected, solver): + manifest = json.loads((DATA/"manifest.json").read_text()) + cases = manifest["cases"] + assert Counter(c["split"] for c in cases) == {"development": 12, "held-out": 24} + assert len({c["id"] for c in cases}) == len(cases) + assert len({c["path"] for c in cases}) == len(cases) + for key in (lambda c: (c["family"], c["member"]), lambda c: c["sha256"]): + dev = {key(c) for c in cases if c["split"] == "development"} + held = {key(c) for c in cases if c["split"] == "held-out"} + assert not dev & held, "split overlap" + for family in manifest["sources"]: + assert Counter(c["split"] for c in cases if c["family"] == family) == { + "development": 3, "held-out": 6} + checked = 0 + for case in [*manifest["tiny"], *manifest["published"]]: + path = DATA/case["path"] + assert hashlib.sha256(path.read_bytes()).hexdigest() == case["sha256"] + validate_native(reader, path) + data = CHECKER["read_instance"](path) + if "tour_path" in case: + tour_path = DATA/case["tour_path"] + assert hashlib.sha256(tour_path.read_bytes()).hexdigest() == case["tour_sha256"] + tour = [int(x) for x in tour_path.read_text().split()] + result = CHECKER["check_tour"](data, tour, case["exact_cost"]) + displayed = case["published_cost"] + places = len(displayed.split(".")[1]) if "." in displayed else 0 + assert abs(Fraction(result["cost"])-Fraction(displayed)) <= Fraction(1, 2*10**places) + else: + result = CHECKER["enumerate_tours"](data) + assert result["feasible_tours"] == case["feasible_tours"] + optimum = result["optimum"] + assert (None if optimum is None else optimum["cost"]) == case["optimum"] + checked += 1 + waiting = CHECKER["read_instance"](DATA/"tiny/waiting.txt") + result = CHECKER["check_tour"](waiting, [1, 2], "5") + assert result["visits"][0]["waiting"] == "3" and result["return_time"] == "8" + reject(lambda: CHECKER["check_tour"](waiting, [1, 1]), "duplicate") + reject(lambda: CHECKER["check_tour"](waiting, [1]), "omits") + reject(lambda: CHECKER["check_tour"](waiting, [1, 3]), "invalid") + reject(lambda: CHECKER["check_tour"](waiting, [1, 2], "8"), "incorrect cost") + for case, message in [("late-customer", "late customer"), ("late-return", "late return")]: + data = CHECKER["read_instance"](DATA/f"tiny/{case}.txt") + reject(lambda: CHECKER["check_tour"](data, [1, 2]), message) + with tempfile.TemporaryDirectory(prefix="tsptw-validate-") as directory: + path = Path(directory)/"input.txt" + good = "2\n0 1\n1 0\n0 10\n0 10\n" + bad_inputs = [good.replace("2\n", "3\n", 1), good+"1\n", + good.replace("0 1", "0 -1", 1), good.replace("0 1", "0 1.0.00", 1), + good.replace("0 1", "0 1e1", 1), good.replace("0 1", "0 .", 1), + good.replace("0 10", "11 10", 1), + good.replace("0 10", "1 10", 1), good.replace("0 1", "0 2147483647", 1), + good.replace("0 1", "0 1073741824", 1), + good.replace("0 1", "0 0.0000000001", 1), + good.replace("0 10", "0 2147483646", 1)] + for content in bad_inputs: + path.write_text(content) + try: + CHECKER["read_instance"](path) + except ValueError: + pass + else: + raise AssertionError(f"Python accepted invalid data: {content}") + assert subprocess.run([str(reader), str(path)], capture_output=True).returncode == 1 + path.write_text(good.replace("0 1", "0 1.000", 1)+"# optional comment\n") + validate_native(reader, path) + for zero in (".0", ".00"): + path.write_text(good.replace("0 1", f"{zero} 1", 1)) + validate_native(reader, path) + tour_path = Path(directory)/"tour.txt" + tour_path.write_text("1 2\n") + command = ["python3", str(Path(__file__).with_name("tsptw-check.py")), "check", + str(DATA/"tiny/waiting.txt"), str(tour_path), "--cost", "5"] + assert subprocess.run(command, capture_output=True).returncode == 0 + tour_path.write_text("1 1\n") + assert subprocess.run(command, capture_output=True).returncode == 1 + if selected: + for case in cases: + path = selected/case["path"] + assert hashlib.sha256(path.read_bytes()).hexdigest() == case["sha256"] + validate_native(reader, path) + checked += 1 + print(f"passed: {checked} instances, five tiny enumerations, four published tours, " + "reader rejection cases, checker failures, CLI smoke, and disjoint 12/24 selection") + if solver: + validate_solver(solver, manifest) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--reader", type=Path, required=True, help="compiled misc/tsptw-read.cpp") + parser.add_argument("--selected", type=Path, help="also validate explicitly acquired 36-case corpus") + parser.add_argument("--solver", type=Path, help="also validate the native tsptw executable") + args = parser.parse_args() + validate(args.reader.resolve(), args.selected, args.solver.resolve() if args.solver else None) diff --git a/test/search.cpp b/test/search.cpp index 70cf9d8cc0..711c0c9aac 100644 --- a/test/search.cpp +++ b/test/search.cpp @@ -38,13 +38,20 @@ #include #include #include +#include +#include #include "test/test.hh" +#include "examples/tsptw-transition.hh" #include #include #include #include +#include +#include +#include +#include #include #include @@ -83,6 +90,1815 @@ namespace Test { using namespace Gecode; using namespace Gecode::Int; + /// A real nested existential search using the production completion owner. + class CompletionSpace; + class TestCompletion : public Gecode::Search::Completion { + int first, count; + public: + TestCompletion(Home home, int f, int n) + : Completion(home), first(f), count(n) { + home.notice(*this,AP_DISPOSE,false); + } + TestCompletion(Space& home, TestCompletion& b) + : Completion(home,b), first(b.first), count(b.count) {} + bool status(const Space&) const override { return !done && !suppressed; } + Space* trial(Space& home) override; + Actor* copy(Space& home) override { + return new (home) TestCompletion(home,*this); + } + size_t dispose(Space& home) override { + home.ignore(*this,AP_DISPOSE,false); + (void) Completion::dispose(home); + return sizeof(*this); + } + }; + + class CompletionSpace : public Space { + protected: + std::shared_ptr> constraints; + public: + IntVarArray x; + CompletionSpace(bool sat, bool nested, bool outer=false, + std::shared_ptr> constraints0={}, + bool completion=true) + : constraints(std::move(constraints0)), x(*this,sat ? 4 : 5,1,4) { + for (int i=0;ifetch_add(1,std::memory_order_relaxed); + // Direct view restriction deliberately bypasses posting Home. + for (int i=0;i s( + static_cast(home.clone())); + IntVarArgs args(count); + for (int i=0;ix[first+i]; + branch(*s,args,INT_VAR_NONE(),INT_VAL_MIN()); + return s.release(); + } + + class CompletionNative : public Base { + static bool same(const Gecode::Search::Statistics& a, + const Gecode::Search::Statistics& b) { + return a.node==b.node && a.fail==b.fail && a.propagate==b.propagate && + a.depth==b.depth && a.restart==b.restart && a.nogood==b.nogood; + } + class Slice : public Gecode::Search::Stop { + public: + std::atomic limit{2}; + bool failures=false; + std::atomic valid{true}; + std::atomic calls{0}; + bool stop(const Gecode::Search::Statistics& s, + const Gecode::Search::Options& o) override { + calls.fetch_add(1,std::memory_order_relaxed); + if (o.c_d!=5 || o.a_d!=2 || o.stop!=this) + valid.store(false,std::memory_order_relaxed); + return (failures ? s.fail : s.node)>= + limit.load(std::memory_order_relaxed); + } + }; + class ObjectiveSpace : public CompletionSpace { + public: + IntVar cost; + ObjectiveSpace(bool completion, int fixed=-1, + std::shared_ptr> constraints={}) + : CompletionSpace(true,false,false,std::move(constraints),completion), + cost(*this,0,2) { + if (fixed >= 0) + rel(*this,cost,IRT_EQ,fixed); + else + branch(*this,cost,INT_VAL_MAX()); + } + ObjectiveSpace(ObjectiveSpace& s) : CompletionSpace(s) { + cost.update(*this,s.cost); + } + Space* copy(void) override { return new ObjectiveSpace(*this); } + SpaceComparison compare(const Space& s0) const override { + const ObjectiveSpace& s=dynamic_cast(s0); + if (cost.val() < s.cost.val()) + return SC_BETTER; + if (cost.val() > s.cost.val()) + return SC_WORSE; + return SC_EQUIVALENT; + } + void constrain(const Space& s0) override { + if (constraints) + constraints->fetch_add(1,std::memory_order_relaxed); + const ObjectiveSpace& s=static_cast(s0); + if (s.cost.val() > 0) { + rel(*this,cost,IRT_LE,s.cost.val()-1); + } else { + // The strict-better alternative is deliberately infeasible, but + // direct restriction leaves that hidden completion unresolved. + for (int i=0;i + static bool heuristic(void) { + using Guidance=Gecode::Search::HeuristicGuidance; + Guidance guidance(Guidance::MINIMIZE,[](const Space& s) -> std::int64_t { + return static_cast(s).cost.min(); + }); + Gecode::Search::Options o; o.clone=false; o.c_d=5; o.a_d=2; + std::unique_ptr full(Kind==2 ? + Gecode::Search::columnengine(new ObjectiveSpace(true),guidance,o) : Kind==1 ? + Gecode::Search::cabsengine(new ObjectiveSpace(true),guidance,o) : + Gecode::Search::astarengine(new ObjectiveSpace(true),guidance,o)); + int optimum=3; + while (std::unique_ptr s{ + static_cast(full->next())}) optimum=s->cost.val(); + if (optimum!=0 || full->stopped()) return false; + const auto total=full->statistics(); + optimum=3; + Slice stop; o.stop=&stop; + std::unique_ptr sliced(Kind==2 ? + Gecode::Search::columnengine(new ObjectiveSpace(true),guidance,o) : Kind==1 ? + Gecode::Search::cabsengine(new ObjectiveSpace(true),guidance,o) : + Gecode::Search::astarengine(new ObjectiveSpace(true),guidance,o)); + unsigned int interruptions=0; + for (unsigned int i=0;i<400;i++) { + if (std::unique_ptr s{ + static_cast(sliced->next())}) { + optimum=s->cost.val(); continue; + } + if (!sliced->stopped()) { + if (optimum!=0 || !interruptions || !same(total,sliced->statistics())) + return false; + // Adoption during retained preparation cuts committed clones, + // including hidden completion state that must be revalidated. + stop.limit.store(2,std::memory_order_relaxed); + std::unique_ptr adopted( + Kind==2 ? Gecode::Search::columnengine(new ObjectiveSpace(true),guidance,o) : + Kind==1 ? Gecode::Search::cabsengine(new ObjectiveSpace(true),guidance,o) : + Gecode::Search::astarengine(new ObjectiveSpace(true),guidance,o)); + if (adopted->next() || !adopted->stopped()) return false; + Gecode::Search::Options witness_options; witness_options.clone=false; + Gecode::DFS dfs(new ObjectiveSpace(true,0),witness_options); + std::unique_ptr witness(dfs.next()); + if (!witness) return false; + adopted->constrain(*witness); + const auto before=adopted->statistics(); + if (adopted->next() || !adopted->stopped() || + !same(before,adopted->statistics())) return false; + stop.limit.store(10000,std::memory_order_relaxed); + return !adopted->next() && !adopted->stopped() && + adopted->statistics().fail>before.fail; + } + interruptions++; + const auto before=sliced->statistics(); + if (sliced->next() || !sliced->stopped() || + !same(before,sliced->statistics())) return false; + stop.limit.fetch_add(2,std::memory_order_relaxed); + } + return false; + } + static bool replay(void) { + CompletionSpace root(true,false); + Gecode::Search::Options o; + o.c_d=5; o.a_d=2; + Gecode::Search::Statistics stat; + Slice stop; o.stop=&stop; + if (root.status()!=SS_BRANCH || root.prepare(stat,o)) return false; + std::unique_ptr clone( + static_cast(root.clone())); + // Restricting a pending source must discard its obsolete trial. + root.invalidate_branchers(); + root.constrain(root); + if (root.status()!=SS_BRANCH) return false; + stop.limit.store(10000,std::memory_order_relaxed); + if (!clone->prepare(stat,o)) return false; + std::unique_ptr ch(clone->choice()); + Archive archive; ch->archive(archive); + std::unique_ptr archived(clone->choice(archive)); + std::unique_ptr replayed( + static_cast(clone->clone())); + replayed->commit(*archived,0); + if (replayed->status()!=SS_SOLVED) return false; + (void) replayed->choice(); + // Retention through solved cleanup and strengthening without failure. + replayed->invalidate_branchers(); + replayed->constrain(root); + if (replayed->status()!=SS_BRANCH) return false; + replayed->commit(*ch,0); + if (replayed->status()!=SS_BRANCH) return false; + Slice small; o.stop=&small; + Gecode::Search::Statistics revalidation; + if (replayed->prepare(revalidation,o)) return false; + small.limit.store(10000,std::memory_order_relaxed); + if (!replayed->prepare(revalidation,o)) return false; + std::unique_ptr failed(replayed->choice()); + replayed->commit(*failed,0); + if (replayed->status()!=SS_FAILED) return false; + // The independent clone's unchanged established choice needs no DFS. + clone->commit(*ch,0); + return clone->status()==SS_SOLVED; + } + public: + CompletionNative(void) : Base("Search::Completion::Native", TestTags::check()) {} + bool run(void) override { + return replay() && heuristic<0>() && heuristic<1>() && heuristic<2>(); + } + } completionNative; + + /// The example's real n-ary transitions survive cloning and continuation. + class TSPTWTransitionTest : public Base { + using Transition=TSPTW::Transition; + static std::shared_ptr instance(void) { + std::istringstream input( + "4\n0 1 2 3\n1 0 1 4\n1 3 0 1\n20 1 3 0\n" + "0 100\n5 100\n0 100\n0 100\n"); + return std::make_shared(TSPTW::read(input)); + } + static bool visit(Transition& s, int node) { + Gecode::Search::Options options; + Gecode::Search::Statistics stat; + if (s.status()!=SS_BRANCH || !s.prepare(stat,options)) return false; + const auto actions=s.actions(); + const auto action=std::find(actions.begin(),actions.end(),node); + if (action==actions.end()) return false; + std::unique_ptr choice(s.choice()); + s.commit(*choice,static_cast(action-actions.begin())); + return !s.failed(); + } + static bool replay(Transition::Representation representation, + Transition::ResidualPropagation propagation=Transition::OFF) { + const auto data=instance(); + Transition root(data,representation,propagation); + Gecode::Search::Options options; + Gecode::Search::Statistics stat; + const bool lazy=representation==Transition::LAZY; + if (root.status()!=SS_BRANCH || root.allocated_positions()!=(lazy ? 0 : 4)) + return false; + std::unique_ptr absent(static_cast(root.clone())); + if (!root.prepare(stat,options) || root.allocated_positions()!=(lazy ? 1 : 4) || + root.position(0).assigned() || root.depth || root.location || + root.time || root.cost() || root.unvisited!=std::vector({1,2,3})) + return false; + std::unique_ptr original(root.choice()); + Archive archive; original->archive(archive); + std::unique_ptr restored(root.choice(archive)); + if (restored->alternatives()!=3) return false; + absent->commit(*restored,0); + if (absent->status()!=SS_BRANCH || absent->depth!=1 || absent->location!=1 || + absent->time!=5 || absent->cost()!=1 || absent->returned || + absent->unvisited!=std::vector({2,3}) || + absent->position(0).val()!=1 || root.position(0).assigned()) + return false; + std::unique_ptr next(static_cast(absent->clone())); + if (!absent->prepare(stat,options) || absent->depth!=1 || absent->cost()!=1 || + absent->allocated_positions()!=(lazy ? 2 : 4) || absent->position(1).assigned()) + return false; + original.reset(absent->choice()); + Archive prefix_archive; original->archive(prefix_archive); + restored.reset(absent->choice(prefix_archive)); + next->commit(*restored,0); + if (next->status()!=SS_BRANCH || next->depth!=2 || next->cost()!=2 || + next->time!=6 || next->position(1).val()!=2 || absent->position(1).assigned()) + return false; + + Transition optimum(data,representation,propagation); + for (int node : {2,3,1,0}) if (!visit(optimum,node)) return false; + if (optimum.status()!=SS_SOLVED || optimum.cost()!=5) return false; + std::unique_ptr cut(static_cast(absent->clone())); + cut->constrain(optimum); + if (cut->status()!=SS_BRANCH || cut->actions()!=std::vector({2})) return false; + // The original second alternative still means customer 3 after a cut + // shrinks the fresh choice; it cannot silently become another action. + cut->commit(*restored,1); + if (cut->status()!=SS_FAILED) return false; + if (!visit(*next,3) || next->returned || next->cost()!=3 || + next->status()!=SS_BRANCH || next->actions()!=std::vector({0}) || + !visit(*next,0)) return false; + return next->status()==SS_SOLVED && next->returned && next->depth==4 && + next->cost()==23 && next->time==27 && next->allocated_positions()==4 && + next->tour()==std::vector({1,2,3}) && root.depth==0 && absent->depth==1; + } + static bool continuation(Transition::Representation representation, int kind) { + const auto data=instance(); + Transition root(data,representation), incumbent(data,representation); + for (int node : {3,1,2,0}) if (!visit(incumbent,node)) return false; + if (incumbent.status()!=SS_SOLVED || incumbent.cost()!=6) return false; + Gecode::Search::NodeStop stop(10000); + Gecode::Search::Options options; + options.threads=1; options.stop=&stop; + // Force BAB to replay edges between stored prefix clones. + options.c_d=5; options.a_d=2; + Gecode::Search::WorkerControl control(1U); +#ifdef GECODE_HAS_THREADS + options.worker_control=control; +#endif + std::unique_ptr engine(kind==0 ? + Gecode::Search::babengine(&root,options) : kind==1 ? + Gecode::Search::astarengine(&root,Transition::guidance(),options) : kind==3 ? + Gecode::Search::columnengine(&root,Transition::guidance(),options) : + Gecode::Search::cabsengine(&root,Transition::guidance(),options)); + std::unique_ptr first(static_cast(engine->next())); + if (!first || first->cost()!=23 || first->tour()!=std::vector({1,2,3})) + return false; + const auto before=engine->statistics().node; + stop.limit(before-1); + if (engine->next() || !engine->stopped() || engine->statistics().node!=before) + return false; + stop.limit(10000); + engine->constrain(incumbent); + std::unique_ptr better; +#ifdef GECODE_HAS_THREADS + control.request(0U); + std::atomic entered(false), done(false); + std::thread caller([&] { + entered.store(true,std::memory_order_release); + better.reset(static_cast(engine->next())); + done.store(true,std::memory_order_release); + }); + while (!entered.load(std::memory_order_acquire)) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + const bool parked=!done.load(std::memory_order_acquire) && + engine->statistics().node==before; + control.request(1U); + caller.join(); + if (!parked) return false; +#else + better.reset(static_cast(engine->next())); +#endif + return better && better->cost()==5 && better->tour()==std::vector({2,3,1}) && + !engine->next() && !engine->stopped() && engine->statistics().depth==4; + } + struct Suffix { + std::int64_t cost; + int first; + }; + // Numeric permutations use no bound, transition applicability or CP model. + static std::vector completions(const Transition& state) { + if (state.returned) return {{state.prefix_cost,-1}}; + std::vector result; + auto suffix=state.unvisited; + do { + std::int64_t time=state.time, cost=state.prefix_cost; + int location=state.location; + bool feasible=true; + for (std::size_t p=0; p<=suffix.size(); p++) { + const int node=p==suffix.size() ? 0 : suffix[p]; + const int arc=state.data->arc(location,node); + time=std::max(time+arc,state.data->ready[node]); + cost+=arc; + if (time>state.data->due[node]) { feasible=false; break; } + location=node; + } + if (feasible) result.push_back({cost,suffix.empty() ? 0 : suffix.front()}); + } while (std::next_permutation(suffix.begin(),suffix.end())); + return result; + } + static bool residuals(Transition& state, const std::vector& prefix, + bool& filtered, bool& rejected) { + const auto suffixes=completions(state); + const auto bound=state.bound(Transition::MIN_ARCS); + if (boundarc(state.location,0)) return false; + for (const auto& suffix : suffixes) if (bound>suffix.cost) return false; + + for (auto propagation : {Transition::CHEAP,Transition::UNARY}) { + Transition inferred(state.data,Transition::LAZY,propagation); + bool survived=inferred.status()!=SS_FAILED; + for (int node : prefix) { + if (!survived) break; + survived=visit(inferred,node); + } + if (!survived) { + if (propagation==Transition::CHEAP) rejected=true; + if (!suffixes.empty()) return false; + } else { + if (inferred.unvisited!=state.unvisited || inferred.location!=state.location || + inferred.time!=state.time || inferred.cost()!=state.cost()) return false; + const auto actions=inferred.actions(); + if (actions.size() choice(state.choice()); + for (std::size_t a=0; a child(static_cast(state.clone())); + child->commit(*choice,static_cast(a)); + auto next=prefix; + next.push_back(actions[a]); + // Even a failed committed residual state must have no feasible suffix. + if (!residuals(*child,next,filtered,rejected)) return false; + } + return true; + } + static bool bounds_and_inference(void) { + bool filtered=false, rejected=false; + // Waiting/nonmetric and unequal/incomparable (t,g) labels; tight unary + // filtering and failure; zero-duration intervals; pending/finished return. + for (const std::string& text : { + std::string("4\n0 1 2 3\n1 0 1 4\n1 3 0 1\n20 1 3 0\n" + "0 100\n5 100\n0 100\n0 100\n"), + std::string("5\n0 3 1 7 4\n4 0 5 1 3\n2 1 0 4 6\n" + "3 4 2 0 1\n1 2 3 5 0\n" + "0 100\n15 100\n0 100\n0 100\n5 100\n"), + std::string("3\n0 2 9\n9 0 2\n1 9 0\n0 6\n5 5\n0 7\n"), + // Direct 0->2 misses due 6 and, after waiting, direct 1->0 + // misses depot due 8; visiting 1,2 still returns at 8, cost 5. + std::string("3\n0 2 9\n4 0 1\n2 8 0\n0 8\n5 10\n0 6\n"), + std::string("3\n0 0 0\n3 0 3\n3 3 0\n0 10\n0 0\n0 4\n"), + std::string("3\n0 0 0\n3 0 3\n3 3 0\n0 20\n0 2\n0 2\n"), + std::string("3\n0 0 0\n0 0 0\n0 0 0\n0 0\n0 0\n0 0\n")}) { + std::istringstream input(text); + auto data=std::make_shared(TSPTW::read(input)); + Transition root(data); + if (!residuals(root,{},filtered,rejected)) return false; + } + return filtered && rejected; + } + public: + TSPTWTransitionTest(void) : Base("Search::TSPTW::Transition", TestTags::check()) {} + bool run(void) override { + for (auto representation : {Transition::LAZY,Transition::EAGER}) { + if (!replay(representation) || !replay(representation,Transition::CHEAP) || + !replay(representation,Transition::UNARY)) + return false; + for (int kind : {1,2,3}) + if (!continuation(representation,kind)) return false; + } + return bounds_and_inference(); + } + } tsptwTransitionTest; + + /// A tiny closed tree with a generated goal and deeper alternatives. + class AStarSpace : public Space { + public: + static std::atomic live; + IntVar x, y; + bool maximize, incomparable; + AStarSpace(bool max=false, int fixed=-1, bool failed=false) + : x(*this,0,3), y(*this,0,1), maximize(max), incomparable(false) { + live++; + TupleSet tuples(2); + for (int i=0;i<4;i++) { + tuples.add(IntArgs({i,0})); + if (i) tuples.add(IntArgs({i,1})); + } + tuples.finalize(); + extensional(*this,IntVarArgs({x,y}),tuples); + if (fixed >= 0) { + rel(*this,x,IRT_EQ,fixed); + rel(*this,y,IRT_EQ,0); + } + if (failed) fail(); + Gecode::branch(*this,x,INT_VALUES_MIN()); + Gecode::branch(*this,y,INT_VALUES_MIN()); + } + AStarSpace(AStarSpace& s) + : Space(s), maximize(s.maximize), incomparable(s.incomparable) { + live++; + x.update(*this,s.x); y.update(*this,s.y); + } + ~AStarSpace(void) override { live--; } + Space* copy(void) override { return new AStarSpace(*this); } + std::int64_t score(int value) const { + const std::int64_t minimum[] = { + 9,-2,std::numeric_limits::min(),5}; + const std::int64_t maximum[] = { + std::numeric_limits::min(),2, + std::numeric_limits::max(),-5}; + return maximize ? maximum[value] : minimum[value]; + } + std::int64_t objective(void) const { return score(x.val()); } + bool improves(std::int64_t a, std::int64_t b) const { + return maximize ? a>b : a(s0); + if (incomparable || maximize!=s.maximize) return SC_INCOMPARABLE; + if (objective()==s.objective()) return SC_EQUIVALENT; + return improves(objective(),s.objective()) ? SC_BETTER : SC_WORSE; + } + void constrain(const Space& s0) override { + const auto& s=static_cast(s0); + for (int i=0;i<4;i++) + if (!improves(score(i),s.objective())) rel(*this,x,IRT_NQ,i); + } + static Gecode::Search::HeuristicGuidance guidance(bool max, + bool loose=false) { + using Guidance=Gecode::Search::HeuristicGuidance; + return Guidance(max ? Guidance::MAXIMIZE : Guidance::MINIMIZE, + [loose](const Space& s0) -> std::int64_t { + const auto& s=static_cast(s0); + if (loose && (!s.x.assigned() || !s.y.assigned())) + return s.maximize ? std::numeric_limits::max() : + std::numeric_limits::min(); + std::int64_t bound=s.score(s.x.min()); + for (IntVarValues values(s.x); values(); ++values) + if (s.improves(s.score(values.val()),bound)) + bound=s.score(values.val()); + return bound; + }, + [](const Space& s0) -> std::int64_t { + const auto& s=static_cast(s0); + // An arbitrary preference opposes the optimum at alternative 2. + return s.x.assigned() && s.x.val()==2 ? 1000 : 0; + }); + } + }; + + std::atomic AStarSpace::live(0); + // The parallel test runner shares this fixture's ownership counter. + static std::mutex heuristic_ownership_mutex; + + /// Explicit finite paths with model-authored residual keys and labels. + class PruningSpace : public Space { + public: + struct Node { + int key, time, cost, bound, rank; + std::vector children; + bool history=false; + }; + using Graph=std::vector; + std::shared_ptr graph; + bool maximize; + int vertex=0, ceiling=std::numeric_limits::max(); + class Decisions : public Brancher { + class Decision : public Choice { + public: + int vertex; + Decision(const Brancher& b, int v, unsigned int n) + : Choice(b,n), vertex(v) {} + void archive(Archive& e) const override { + Choice::archive(e); e << vertex << alternatives(); + } + }; + public: + Decisions(Home home) : Brancher(home) {} + Decisions(Space& home, Decisions& b) : Brancher(home,b) {} + bool status(const Space& s) const override { + return !static_cast(s).value().children.empty(); + } + const Choice* choice(Space& s) override { + const auto& state=static_cast(s); + return new Decision(*this,state.vertex, + static_cast(state.value().children.size())); + } + const Choice* choice(const Space&, Archive& e) override { + int vertex; unsigned int n; e >> vertex >> n; + return new Decision(*this,vertex,n); + } + ExecStatus commit(Space& s0, const Choice& c, unsigned int a) override { + auto& s=static_cast(s0); + const auto& decision=static_cast(c); + s.vertex=(*s.graph)[decision.vertex].children[a]; + return s.value().cost>=s.ceiling ? ES_FAILED : ES_OK; + } + Actor* copy(Space& home) override { return new (home) Decisions(home,*this); } + size_t dispose(Space& home) override { + (void) Brancher::dispose(home); return sizeof(*this); + } + }; + explicit PruningSpace(Graph nodes, bool max=false) + : graph(std::make_shared(std::move(nodes))), maximize(max) { + (void) new (*this) Decisions(*this); + } + PruningSpace(PruningSpace& s) + : Space(s), graph(s.graph), maximize(s.maximize), vertex(s.vertex), + ceiling(s.ceiling) {} + Space* copy(void) override { return new PruningSpace(*this); } + const Node& value(void) const { return (*graph)[vertex]; } + SpaceComparison compare(const Space& s0) const override { + const int other=static_cast(s0).value().cost; + return value().cost==other ? SC_EQUIVALENT : + value().cost(s0).value().cost; + if (value().cost>=ceiling) fail(); + } + static Gecode::Search::HeuristicGuidance guidance(bool max=false) { + using Guidance=Gecode::Search::HeuristicGuidance; + // Maximizing the negated cost mirrors the same finite paths and cuts. + return {max ? Guidance::MAXIMIZE : Guidance::MINIMIZE, + [](const Space& s)->std::int64_t { + const auto& state=static_cast(s); + const std::int64_t bound=state.value().bound; + return state.maximize ? -bound : bound; + },[](const Space& s)->std::int64_t { + return static_cast(s).value().rank; + }}; + } + static Gecode::Search::StatePruning pruning(unsigned long int limit=0) { + using Pruning=Gecode::Search::StatePruning; + return {[](const Space& s)->std::optional { + const auto& node=static_cast(s).value(); + // The proposed key omits a path restriction in these nodes. Decline + // instead of asserting that equal resource labels imply equal futures. + if (node.history) return std::nullopt; + return Pruning::Descriptor{0,{node.key},{node.time,node.cost}}; + },[](const Pruning::Descriptor& a, const Pruning::Descriptor& b) { + if (a.key!=b.key) throw std::runtime_error("hash collision compared"); + if (a.label==b.label) return Pruning::EQUIVALENT; + if (a.label[0]<=b.label[0] && a.label[1]<=b.label[1]) + return Pruning::DOMINATES; + if (a.label[0]>=b.label[0] && a.label[1]>=b.label[1]) + return Pruning::DOMINATED; + return Pruning::INCOMPARABLE; + },limit}; + } + }; + + class StatePruningChecks { + using Graph=PruningSpace::Graph; + using Pruning=Gecode::Search::StatePruning; + using Statistics=Gecode::Search::Statistics; + public: + static int reference(const Graph& graph, int vertex=0) { + const auto& node=graph[vertex]; + if (node.children.empty()) return node.cost; + int best=std::numeric_limits::max(); + for (int child : node.children) best=std::min(best,reference(graph,child)); + return best; + } + private: + static bool solve(const Graph& graph, int kind, Pruning pruning, + int optimum, bool stopped, Statistics& statistics, + unsigned long int width=0, std::vector* costs=nullptr) { + Gecode::Search::Options options; options.clone=false; + std::vector> retained; + { + // Builders copy the policy, including its forgetting ceiling. + std::unique_ptr builder(kind==2 ? + Gecode::column(PruningSpace::guidance(),options,{},pruning) : kind==1 ? + Gecode::cabs(PruningSpace::guidance(),options,{width,0},pruning) : + Gecode::astar(PruningSpace::guidance(),options,pruning)); + std::unique_ptr engine( + (*builder)(new PruningSpace(graph))); + while (std::unique_ptr s{ + static_cast(engine->next())}) { + if (costs) costs->push_back(s->value().cost); + retained.push_back(std::move(s)); + } + statistics=engine->statistics(); + if (retained.empty() || retained.back()->value().cost!=optimum || + engine->stopped()!=stopped) return false; + engine->reset(nullptr); + const auto reset=engine->statistics(); + if (reset.state_pruned || reset.state_descriptors || reset.state_bytes || + engine->next() || engine->stopped()) return false; + } + // Each publication still owns its graph and Space after engine cleanup. + for (const auto& solution : retained) + if (solution->status()!=SS_SOLVED) return false; + return true; + } + public: + static bool semantics(int kind) { + const Graph collision={ + {0,0,0,0,0,{1,2}}, {1,5,5,5,0,{3}}, {2,1,1,1,0,{4}}, + {3,15,15,15,0,{}}, {4,3,3,3,0,{}}}; + const Graph pareto={ + {0,0,0,0,0,{1,2,3,4}}, {1,5,5,5,0,{5}}, {1,5,5,5,0,{5}}, + {1,10,1,1,0,{6}}, {1,1,10,10,0,{7}}, + {2,15,15,15,0,{}}, {2,20,11,11,0,{}}, {2,11,20,20,0,{}}}; + const Graph history={ + {0,0,0,0,0,{1,2}}, {1,1,1,1,0,{3},true}, + {1,1,1,1,0,{4},true}, {2,50,50,50,0,{}}, {2,10,10,10,0,{}}}; + for (const auto& graph : {collision,pareto,history}) { + Statistics off, on, forgotten; + if (!solve(graph,kind,{},reference(graph),false,off) || + off.state_pruned || off.state_descriptors || off.state_bytes || + !solve(graph,kind,PruningSpace::pruning(),reference(graph),false,on) || + !solve(graph,kind,PruningSpace::pruning(1),reference(graph),false,forgotten) || + forgotten.state_descriptors>1) return false; + if (graph.size()==pareto.size() && !on.state_pruned) return false; + if (graph[1].history && on.state_descriptors) return false; + if (kind==1) { + const auto bytes=Pruning::Descriptor{0,{1},{0,0}}.storage(); + const unsigned long int peak=graph[1].history ? 0 : + graph.size()==pareto.size() ? 3 : 2; + if (on.state_descriptors!=peak || on.state_bytes!=peak*bytes || + forgotten.state_descriptors!=(peak ? 1UL : 0UL) || + forgotten.state_bytes!=(peak ? bytes : 0)) return false; + } + } + // Misconfigured declarations fail before clone=false transfers a root. + PruningSpace root(collision); + Gecode::Search::Options options; options.clone=false; + auto missing=PruningSpace::pruning(); missing.compare={}; + try { + std::unique_ptr invalid(kind==2 ? + Gecode::Search::columnengine(&root,PruningSpace::guidance(),options,{},missing) : kind==1 ? + Gecode::Search::cabsengine(&root,PruningSpace::guidance(),options,{},missing) : + Gecode::Search::astarengine(&root,PruningSpace::guidance(),options,missing)); + return false; + } catch (const Gecode::Search::UninitializedStatePruning&) {} + return root.status()==SS_BRANCH; + } + static bool reopen(int kind=0) { + // f falls from 5 to 2 along the detour: admissible but inconsistent. + // The first label has already expanded and published 20 before 12. + const Graph graph={ + {0,0,0,0,0,{1,2}}, {7,10,10,0,0,{3}}, {8,0,0,5,0,{4}}, + {9,20,20,20,0,{}}, {7,2,2,2,0,{5}}, {9,12,12,12,0,{}}}; + for (unsigned long int limit : {0UL,1UL}) { + Statistics statistics; std::vector costs; + if (!solve(graph,kind,PruningSpace::pruning(limit),reference(graph), + false,statistics,0,&costs) || costs!=std::vector({20,12})) + return false; + } + return true; + } + static bool candidate_lifetime(void) { + // Arrival 1 is evicted by 2; arrival 3 is beam-discarded. Neither may + // suppress later arrival 4 with the same exact key and a worse label. + const Graph discarded={ + {0,0,0,0,0,{1,2,3,4}}, {1,1,1,1,0,{5}}, {2,0,0,0,-1,{6}}, + {1,0,0,0,10,{7}}, {1,2,2,2,-2,{8}}, + {3,11,11,11,0,{}}, {4,100,100,100,0,{}}, + {3,10,10,10,0,{}}, {3,12,12,12,0,{}}}; + Statistics capped, complete; + if (!solve(discarded,true,PruningSpace::pruning(),12,true,capped,1) || + !solve(discarded,true,PruningSpace::pruning(),reference(discarded), + false,complete) || !complete.restart) return false; + // A worse-ranked dominator must take the dominated live candidate's + // slot. Sound removal causes no beam loss, so width one proves 10. + const Graph replacement={ + {0,0,0,0,0,{1,2}}, {1,1,1,1,0,{3}}, {1,0,0,0,10,{4}}, + {2,11,11,11,0,{}}, {2,10,10,10,0,{}}}; + const auto bytes=Pruning::Descriptor{0,{1},{0,0}}.storage(); + if (!solve(replacement,true,PruningSpace::pruning(),reference(replacement), + false,complete,1) || complete.state_pruned!=1 || complete.restart || + complete.state_descriptors!=1 || complete.state_bytes!=bytes) return false; + // Widening retains two, then three incomparable labels. One dominator + // removes them together; accounting must release every victim's bytes. + const Graph multiple={ + {0,0,0,0,0,{1,2,3,4}}, {1,5,5,0,0,{5}}, {1,10,1,0,0,{6}}, + {1,1,9,0,0,{7}}, {1,0,0,0,10,{8}}, + {2,15,15,15,0,{}}, {2,20,11,11,0,{}}, + {2,11,20,20,0,{}}, {2,10,10,10,0,{}}}; + return solve(multiple,true,PruningSpace::pruning(),reference(multiple), + false,complete) && complete.state_pruned==6 && complete.restart==2 && + complete.state_descriptors==3 && complete.state_bytes==3*bytes; + } + }; + + class AStarTest : public Base { + static bool ordering(void) { + using namespace Gecode::Search; + // Every root alternative needs another commit. The first arrival has + // the preferred rank but the worse bound; arrivals 2 and 3 have the + // same residual problem, label, and remaining transition count. + const PruningSpace::Graph graph={ + {0,0,0,0,0,{1,2,3}}, {1,0,0,8,-100,{4}}, + {2,0,0,4,100,{5}}, {2,0,0,4,100,{5}}, + {3,0,10,10,0,{}}, {3,0,5,5,0,{}}}; + auto solve=[&](bool max, BestFirstOrder order, bool ranked, bool prune) { + Gecode::Search::Options options; options.clone=false; + auto guidance=PruningSpace::guidance(max); + if (!ranked) guidance.rank={}; + std::unique_ptr builder(Gecode::bestfirst( + guidance,order,options,prune ? PruningSpace::pruning() : StatePruning())); + if (!builder->best()) return false; + std::unique_ptr engine((*builder)(new PruningSpace(graph,max))); + std::vector costs; + while (std::unique_ptr solution{ + static_cast(engine->next())}) + costs.push_back(solution->value().cost); + const std::vector expected=order==RANK_FIRST && ranked ? + std::vector{10,5} : std::vector{5}; + return costs==expected && !engine->stopped() && + costs.back()==StatePruningChecks::reference(graph) && + engine->statistics().state_pruned==(prune ? 1UL : 0UL); + }; + for (bool max : {false,true}) + if (!solve(max,BOUND_FIRST,true,false) || + !solve(max,RANK_FIRST,true,false) || + !solve(max,RANK_FIRST,true,true)) return false; + return solve(false,RANK_FIRST,false,false); + } + static bool solutions(bool max, bool loose, bool interrupt) { + Gecode::Search::NodeStop stop(1); + Gecode::Search::Options o; o.clone=false; + if (interrupt) o.stop=&stop; + Gecode::AStar engine(new AStarSpace(max), + AStarSpace::guidance(max,loose),o); + std::unique_ptr first(engine.next()); + if (!first || first->x.val()!=0) return false; + auto previous=first->objective(); + if (interrupt) { + const auto before=engine.statistics(); + if (engine.next() || !engine.stopped() || + engine.statistics().node!=before.node) return false; + stop.limit(10000); + } + unsigned int found=1; + while (std::unique_ptr solution{engine.next()}) { + if (!solution->improves(solution->objective(),previous)) return false; + previous=solution->objective(); found++; + } + // Enumerating the four scores independently identifies alternative 2. + return previous==first->score(2) && found==(loose ? 3U : 2U) && + !engine.stopped() && engine.statistics().depth==2; + } + static bool adoption(bool max, bool queued) { + Gecode::Search::Options o; o.clone=false; + std::unique_ptr builder( + Gecode::astar(AStarSpace::guidance(max,true),o)); + if (!builder->best()) return false; + std::unique_ptr engine( + (*builder)(new AStarSpace(max))); + std::unique_ptr first(engine->next()); + if (!first) return false; + if (queued) { + std::unique_ptr second( + static_cast(engine->next())); + if (!second || second->x.val()!=1) return false; + } + AStarSpace incumbent(max,queued ? 2 : 1), worse(max,0); + incumbent.status(); worse.status(); + engine->constrain(incumbent); + engine->constrain(incumbent); + engine->constrain(worse); + unsigned int found=0; + while (std::unique_ptr solution{ + static_cast(engine->next())}) { + if (!solution->improves(solution->objective(),incumbent.objective()) || + solution->x.val()!=2) return false; + found++; + } + if (engine->stopped() || found!=(queued ? 0U : 1U)) return false; + worse.incomparable=true; + try { + engine->constrain(worse); + return false; + } catch (const Gecode::Search::Incomparable&) {} + return true; + } + static bool ownership(void) { + Gecode::Search::Options o; + for (bool clone : {false,true}) { + o.clone=clone; + for (int state : {-2,-1,0}) { + std::unique_ptr root(new AStarSpace(false,state,state==-2)); + { + Gecode::AStar engine(root.get(), + AStarSpace::guidance(false),o); + if (!clone) root.release(); + std::unique_ptr result(engine.next()); + if ((state!=-2)!=static_cast(result)) return false; + if (state==0 && (engine.next() || engine.stopped())) return false; + // Destroy after publication with a pending parent and queued work. + if (state==-1) delete engine.next(); + } + root.reset(); + if (AStarSpace::live.load()!=0) return false; + } + } + AStarSpace root; + o.clone=false; o.threads=2; + try { + Gecode::AStar invalid(&root,AStarSpace::guidance(false),o); + return false; + } catch (const Gecode::Search::UnsupportedAStarThreads&) {} + o.threads=1; + using Guidance=Gecode::Search::HeuristicGuidance; + try { + Gecode::AStar invalid(&root,Guidance(Guidance::MINIMIZE,{}),o); + return false; + } catch (const Gecode::Search::UninitializedAStarBound&) {} + o.clone=true; + { + Guidance throws(Guidance::MINIMIZE,[](const Space&) -> std::int64_t { + throw std::runtime_error("guidance failure"); + }); + Gecode::AStar engine(&root,throws,o); + try { delete engine.next(); return false; } + catch (const std::runtime_error&) {} + } + return AStarSpace::live.load()==1; + } + public: + AStarTest(void) : Base("Search::AStar", TestTags::check()) {} + bool run(void) override { + std::lock_guard lock(heuristic_ownership_mutex); + for (bool max : {false,true}) + if (!solutions(max,false,false) || !solutions(max,true,true) || + !adoption(max,false) || !adoption(max,true)) return false; + if (!ordering() || !ownership() || AStarSpace::live.load()!=0 || + !StatePruningChecks::semantics(false) || !StatePruningChecks::reopen()) + return false; + Gecode::Search::Options o; o.clone=false; + std::unique_ptr engine( + Gecode::Search::astarengine(nullptr,AStarSpace::guidance(false),o)); + if (engine->next() || engine->stopped()) return false; + engine->reset(new AStarSpace(false,2)); + std::unique_ptr solution(engine->next()); + if (!solution || engine->statistics().node!=1) return false; + engine->reset(nullptr); + return !engine->next() && !engine->stopped() && + engine->nogoods().ng()==0; + } + } astarTest; + + /// A narrow beam can keep a stable but ultimately infeasible branch. + class CABSFeasibility : public Space { + public: + IntVar x; + IntVarArray y; + CABSFeasibility(bool infeasible=false) : x(*this,0,1), y(*this,3,0,1) { + TupleSet tuples(3); + for (int branch=0;branch<2;branch++) + for (int a=0;a<2;a++) + for (int b=0;b<2;b++) + if (branch || a!=b) tuples.add(IntArgs({branch,a,b})); + tuples.finalize(); + for (int a=0;a<3;a++) + extensional(*this,IntVarArgs({x,y[a],y[(a+1)%3]}),tuples); + if (infeasible) rel(*this,x,IRT_EQ,0); + Gecode::branch(*this,x,INT_VALUES_MIN()); + Gecode::branch(*this,y,INT_VAR_NONE(),INT_VALUES_MIN()); + } + CABSFeasibility(CABSFeasibility& s) : Space(s) { + x.update(*this,s.x); y.update(*this,s.y); + } + Space* copy(void) override { return new CABSFeasibility(*this); } + SpaceComparison compare(const Space& s0) const override { + const auto& s=dynamic_cast(s0); + if (x.val()==s.x.val()) return SC_EQUIVALENT; + return x.val()(s0).x.val()); + } + static Gecode::Search::HeuristicGuidance guidance(void) { + return {Gecode::Search::HeuristicGuidance::MINIMIZE, + [](const Space& s) -> std::int64_t { + return static_cast(s).x.min(); + }}; + } + }; + + class CABSTest : public Base { + static bool discarded_bounds(bool max) { + Gecode::Search::Options options; options.clone=false; + auto solve=[&](const PruningSpace::Graph& graph, unsigned long int width, + int cost, bool stopped, unsigned long int restarts) { + Gecode::CABS engine(new PruningSpace(graph,max), + PruningSpace::guidance(max),options,{width,0}); + int previous=std::numeric_limits::max(); + bool found=false; + while (std::unique_ptr solution{engine.next()}) { + if (solution->value().cost>=previous) return false; + previous=solution->value().cost; + found=true; + } + return found && previous==cost && engine.stopped()==stopped && + engine.statistics().restart==restarts; + }; + for (bool evict : {false,true}) { + // Reject a worse-ranked candidate, or evict one arriving first. + // Equality also excludes strict improvement of the incumbent. + const int bound=evict ? 10 : 20; + const PruningSpace::Graph bounded={ + {0,0,0,0,0,evict ? std::vector{2,1} : std::vector{1,2}}, + {1,0,0,0,0,{3}}, {2,0,0,bound,1,{4}}, + {3,10,10,10,0,{}}, {4,bound,bound,bound,0,{}}}; + if (!solve(bounded,1,StatePruningChecks::reference(bounded),false,0)) + return false; + // Ranking keeps bound 5 over bound 0; the retained bound then falls + // to 1. A later discard at bound 20 must not erase the earlier 0. + const PruningSpace::Graph improving={ + {0,0,0,0,0,evict ? std::vector{2,1} : std::vector{1,2}}, + {1,0,0,5,0,{3,4}}, {2,0,0,0,1,{5}}, + {3,0,0,1,0,{6}}, {4,0,0,20,1,{7}}, + {5,4,4,4,0,{}}, {6,10,10,10,0,{}}, {7,20,20,20,0,{}}}; + if (!solve(improving,1,10,true,0) || + !solve(improving,0,StatePruningChecks::reference(improving),false,1)) + return false; + } + return true; + } + static bool interrupted_discard(bool external) { + // Width loss precedes a shallow goal. The original fourth alternative + // and the retained parent both improve it and survive publication. + const PruningSpace::Graph graph={ + {0,0,0,0,0,{1,2,3,4}}, {1,0,0,0,0,{5}}, {2,0,0,7,1,{6}}, + {3,10,10,10,0,{}}, {4,8,8,8,0,{}}, + {5,6,6,6,0,{}}, {6,7,7,7,0,{}}}; + Gecode::Search::NodeStop stop(10000); + Gecode::Search::Options options; options.clone=false; options.stop=&stop; + Gecode::CABS engine(new PruningSpace(graph), + PruningSpace::guidance(),options,{1,0}); + std::unique_ptr first(engine.next()); + if (!first || first->value().cost!=10) return false; + const auto before=engine.statistics().node; + stop.limit(before-1); + for (int i=0;i<2;i++) + if (engine.next() || !engine.stopped() || + engine.statistics().node!=before) return false; + if (external) { + PruningSpace incumbent(graph); + incumbent.vertex=5; + if (incumbent.status()!=SS_SOLVED) return false; + engine.constrain(incumbent); + } + stop.limit(10000); + std::vector costs; + while (std::unique_ptr solution{engine.next()}) + costs.push_back(solution->value().cost); + return costs==(external ? std::vector{} : std::vector{8,6}) && + !engine.stopped() && !engine.statistics().restart && + engine.statistics().node==before+2; + } + static bool reset_discard(void) { + const PruningSpace::Graph bounded={ + {0,0,0,0,0,{1,2}}, {1,0,0,0,0,{3}}, {2,0,0,20,1,{4}}, + {3,10,10,10,0,{}}, {4,20,20,20,0,{}}}; + auto improving=bounded; + improving[2].bound=0; + improving[4].cost=improving[4].bound=4; + Gecode::Search::Options options; options.clone=false; + for (unsigned long int width : {1UL,2UL}) { + std::unique_ptr engine( + Gecode::Search::cabsengine(new PruningSpace(bounded), + PruningSpace::guidance(),options,{width,0})); + for (int run=0;run<3;run++) { + // Repeat widening after reset to check that width starts at one. + const bool unresolved=run==1 || (run==2 && width==2); + if (run) engine->reset(new PruningSpace(unresolved ? improving : bounded)); + if (engine->stopped() || engine->statistics().node || + engine->statistics().restart) return false; + std::vector costs; + while (std::unique_ptr solution{ + static_cast(engine->next())}) + costs.push_back(solution->value().cost); + const bool widen=unresolved && width==2; + if (costs!=(widen ? std::vector{10,4} : std::vector{10}) || + engine->stopped()!=(unresolved && width==1) || + engine->statistics().restart!=(widen ? 1UL : 0UL)) return false; + } + } + return true; + } + static bool solutions(bool max, unsigned long int width) { + Gecode::Search::Options o; o.clone=false; + Gecode::CABS engine(new AStarSpace(max), + AStarSpace::guidance(max,true),o,{width,0}); + std::int64_t previous=0; + unsigned int found=0; + while (std::unique_ptr s{engine.next()}) { + if (found && !s->improves(s->objective(),previous)) return false; + previous=s->objective(); found++; + } + AStarSpace reference(max,2); + if (width==1) { + // A shallow goal and a deeper improvement survive publication, but + // the misleading rank drops the optimum from the width-one beam. + const auto before=engine.statistics().node; + return found==2 && previous==reference.score(1) && engine.stopped() && + !engine.next() && engine.stopped() && engine.statistics().node==before; + } + return found==3 && previous==reference.objective() && !engine.stopped() && + engine.statistics().restart>0; + } + static bool feasibility(void) { + Gecode::Search::Options o; o.clone=false; + for (unsigned long int width : {1UL,0UL}) { + Gecode::CABS engine(new CABSFeasibility, + CABSFeasibility::guidance(),o,{width,0}); + std::unique_ptr s(engine.next()); + if (width==1) { + if (s || !engine.stopped() || engine.next() || !engine.stopped()) + return false; + } else { + if (!s || s->x.val()!=1 || engine.next() || engine.stopped() || + !engine.statistics().restart) return false; + } + } + Gecode::CABS failed(new CABSFeasibility(true), + CABSFeasibility::guidance(),o); + Gecode::BAB reference(new CABSFeasibility(true),o); + if (failed.next() || failed.stopped() || reference.next() || + reference.stopped()) return false; + Gecode::BAB bab(new AStarSpace,o); + int optimum=-1; + while (std::unique_ptr s{bab.next()}) optimum=s->x.val(); + return optimum==2 && !bab.stopped(); + } + static bool adoption(bool queued) { + Gecode::Search::Options o; o.clone=false; + std::unique_ptr builder( + Gecode::cabs(AStarSpace::guidance(false,true),o)); + if (!builder->best()) return false; + std::unique_ptr engine((*builder)(new AStarSpace)); + std::unique_ptr first(engine->next()); + if (!first) return false; + if (queued) { + std::unique_ptr second( + static_cast(engine->next())); + if (!second || second->x.val()!=1) return false; + } + { + AStarSpace incumbent(false,queued ? 2 : 1), worse(false,0); + incumbent.status(); worse.status(); + engine->constrain(incumbent); + engine->constrain(incumbent); + engine->constrain(worse); + worse.incomparable=true; + try { engine->constrain(worse); return false; } + catch (const Gecode::Search::Incomparable&) {} + } + // The accepted external copy survives the caller's destruction. Its + // exact INT64_MIN objective also bounds out every discarded candidate; + // a weaker adoption still lets the original alternatives recover x=2. + std::unique_ptr optimum( + static_cast(engine->next())); + return (queued ? !optimum : optimum && optimum->x.val()==2) && + !engine->next() && !engine->stopped() && + (!queued || engine->statistics().restart==0); + } + static bool ownership_and_limits(void) { + Gecode::Search::Options o; + for (bool clone : {false,true}) { + o.clone=clone; + for (int state : {-2,-1,0}) { + std::unique_ptr root(new AStarSpace(false,state,state==-2)); + std::unique_ptr result; + { + Gecode::CABS engine(root.get(), + AStarSpace::guidance(false),o); + if (!clone) root.release(); + result.reset(engine.next()); + if ((state!=-2)!=static_cast(result)) return false; + if (state==0 && (engine.next() || engine.stopped())) return false; + } + // A returned solution remains independent of all engine storage. + if (result && result->status()!=SS_SOLVED) return false; + result.reset(); root.reset(); + if (AStarSpace::live.load()!=0) return false; + } + } + o.clone=false; + for (const auto limits : {Gecode::Search::CABSOptions(1,0), + Gecode::Search::CABSOptions(0,1)}) { + std::unique_ptr engine( + Gecode::Search::cabsengine(new AStarSpace, + AStarSpace::guidance(false,true),o,limits)); + while (std::unique_ptr s{engine->next()}) {} + const auto before=engine->statistics().node; + if (!engine->stopped() || engine->next() || !engine->stopped() || + engine->statistics().node!=before) return false; + engine->reset(new AStarSpace(false,2)); + std::unique_ptr s(engine->next()); + if (!s || engine->stopped() || engine->statistics().node!=1 || + engine->statistics().restart || engine->next()) return false; + engine->reset(nullptr); + if (engine->next() || engine->stopped() || engine->nogoods().ng()) + return false; + } + AStarSpace root; + o.threads=2; + try { + Gecode::CABS invalid(&root,AStarSpace::guidance(false),o); + return false; + } catch (const Gecode::Search::UnsupportedCABSThreads&) {} + o.threads=1; + o.tracer=&Gecode::StdSearchTracer::def; + try { + Gecode::CABS invalid(&root,AStarSpace::guidance(false),o); + return false; + } catch (const Gecode::Search::UnsupportedCABSTracer&) {} + o.tracer=nullptr; + using Guidance=Gecode::Search::HeuristicGuidance; + try { + Gecode::CABS invalid(&root,Guidance(Guidance::MINIMIZE,{}),o); + return false; + } catch (const Gecode::Search::UninitializedCABSBound&) {} + o.clone=true; + { + Guidance throws(Guidance::MINIMIZE,[](const Space&) -> std::int64_t { + throw std::runtime_error("guidance failure"); + }); + Gecode::CABS engine(&root,throws,o); + try { delete engine.next(); return false; } + catch (const std::runtime_error&) {} + } + return AStarSpace::live.load()==1; + } + public: + CABSTest(void) : Base("Search::CABS", TestTags::check()) {} + bool run(void) override { + std::lock_guard lock(heuristic_ownership_mutex); + for (bool max : {false,true}) + if (!solutions(max,1) || !solutions(max,0) || !discarded_bounds(max)) + return false; + return interrupted_discard(false) && interrupted_discard(true) && + reset_discard() && feasibility() && adoption(false) && adoption(true) && + ownership_and_limits() && AStarSpace::live.load()==0 && + StatePruningChecks::semantics(true) && StatePruningChecks::candidate_lifetime(); + } + } cabsTest; + + class ColumnTest : public Base { + static bool solutions(bool max, bool ranked, bool interrupt) { + Gecode::Search::NodeStop stop(10000); + Gecode::Search::Options options; options.clone=false; options.stop=&stop; + auto guidance=AStarSpace::guidance(max,false); + if (!ranked) guidance.rank={}; + Gecode::Column engine(new AStarSpace(max),guidance,options); + std::vector values; + std::int64_t previous=0; + while (std::unique_ptr s{engine.next()}) { + if (!values.empty() && !s->improves(s->objective(),previous)) return false; + previous=s->objective(); values.push_back(s->x.val()); + if (interrupt) { + const auto before=engine.statistics().node; + stop.limit(before-1); + if (engine.next() || !engine.stopped() || + engine.statistics().node!=before) return false; + stop.limit(10000); + } + } + const std::vector expected=ranked ? std::vector{0,1,2} : + std::vector{0,2}; + return values==expected && !engine.stopped() && + engine.statistics().depth==2; + } + static bool cycles(unsigned long int growth, bool interrupt) { + // Each depth-one parent publishes then creates a depth-two parent. + // A full cycle must visit that new depth before revisiting depth one. + const PruningSpace::Graph graph={ + {0,0,0,0,0,{1,2,3,4}}, + {1,0,0,0,0,{5,9}}, {2,0,0,0,0,{6,10}}, + {3,0,0,0,0,{7,11}}, {4,0,0,0,0,{8,12}}, + {5,100,100,100,0,{}}, {6,80,80,80,0,{}}, + {7,70,70,70,0,{}}, {8,40,40,40,0,{}}, + {9,0,0,0,0,{13}}, {10,0,0,0,0,{14}}, + {11,0,0,0,0,{15}}, {12,0,0,0,0,{16}}, + {13,90,90,90,0,{}}, {14,60,60,60,0,{}}, + {15,50,50,50,0,{}}, {16,30,30,30,0,{}}}; + Gecode::Search::NodeStop stop(10000); + Gecode::Search::Options options; options.clone=false; options.stop=&stop; + Gecode::Column engine(new PruningSpace(graph), + PruningSpace::guidance(),options,{1,growth,0}); + std::vector costs; + while (std::unique_ptr s{engine.next()}) { + costs.push_back(s->value().cost); + if (interrupt) { + const auto before=engine.statistics().node; + stop.limit(before-1); + if (engine.next() || !engine.stopped() || + engine.statistics().node!=before) return false; + stop.limit(10000); + } + } + const std::vector expected=growth==0 ? + std::vector{100,90,80,60,50,40,30} : growth==1 ? + std::vector{100,90,80,70,60,50,40,30} : + std::vector{100,90,80,70,40,30}; + return costs==expected && !engine.stopped() && !engine.statistics().restart; + } + static bool stale_frontier(void) { + // The root queues four obsolete parents and one improving parent + // before publishing. Ranking puts the obsolete entries first. + const PruningSpace::Graph graph={ + {0,0,0,0,0,{1,2,3,4,5,6}}, + {1,10,10,10,0,{8}}, {2,10,10,10,0,{8}}, + {3,10,10,10,0,{8}}, {4,10,10,10,0,{8}}, + {5,0,0,0,1,{9}}, {6,20,20,20,0,{}}, + {7,7,7,7,0,{}}, {8,10,10,10,0,{}}, {9,5,5,5,0,{}}}; + Gecode::Search::Statistics uninterrupted; + for (bool interrupt : {false,true}) { + Gecode::Search::FailStop stop(10000); + Gecode::Search::Options options; options.clone=false; options.stop=&stop; + Gecode::Column engine(new PruningSpace(graph), + PruningSpace::guidance(),options); + std::unique_ptr first(engine.next()); + if (!first || first->value().cost!=20) return false; + PruningSpace incumbent(graph); + incumbent.vertex=7; + if (incumbent.status()!=SS_SOLVED) return false; + engine.constrain(incumbent); + if (interrupt) { + const auto before=engine.statistics(); + stop.limit(before.fail+1); + if (engine.next() || !engine.stopped() || + engine.statistics().fail!=before.fail+2 || + engine.statistics().node!=before.node) return false; + stop.limit(10000); + } + std::unique_ptr optimum(engine.next()); + if (!optimum || optimum->value().cost!=5 || engine.next() || + engine.stopped()) return false; + const auto after=engine.statistics(); + if (interrupt) { + if (after.fail!=uninterrupted.fail || + after.node!=uninterrupted.node || + after.depth!=uninterrupted.depth) return false; + } else { + uninterrupted=after; + } + } + return true; + } + static bool adoption(bool max) { + Gecode::Search::Options options; options.clone=false; + std::unique_ptr builder( + Gecode::column(AStarSpace::guidance(max,true),options)); + if (!builder->best()) return false; + std::unique_ptr engine((*builder)(new AStarSpace(max))); + std::unique_ptr first(engine->next()); + if (!first) return false; + { + AStarSpace incumbent(max,1), worse(max,0); + incumbent.status(); worse.status(); + engine->constrain(incumbent); engine->constrain(incumbent); + engine->constrain(worse); + worse.incomparable=true; + try { engine->constrain(worse); return false; } + catch (const Gecode::Search::Incomparable&) {} + } + std::unique_ptr optimum(static_cast(engine->next())); + return optimum && optimum->x.val()==2 && !engine->next() && !engine->stopped(); + } + static bool ownership_and_limits(void) { + using namespace Gecode::Search; + Gecode::Search::Options options; + for (bool clone : {false,true}) { + options.clone=clone; + for (int state : {-2,-1,0}) { + std::unique_ptr root(new AStarSpace(false,state,state==-2)); + std::unique_ptr result; + { + Gecode::Column engine(root.get(), + AStarSpace::guidance(false,true),options); + if (!clone) root.release(); + result.reset(engine.next()); + if ((state!=-2)!=static_cast(result)) return false; + if (state==0 && (engine.next() || engine.stopped())) return false; + } + if (result && result->status()!=SS_SOLVED) return false; + result.reset(); root.reset(); + if (AStarSpace::live.load()) return false; + } + } + options.clone=false; + std::unique_ptr capped(columnengine(new AStarSpace, + AStarSpace::guidance(false,true),options,{1,1,1})); + std::unique_ptr first(capped->next()); + if (!first || capped->next() || !capped->stopped()) return false; + const auto before=capped->statistics().node; + if (capped->next() || !capped->stopped() || + capped->statistics().node!=before) return false; + capped->reset(new AStarSpace(false,2)); + std::unique_ptr optimum(capped->next()); + if (!optimum || capped->stopped() || capped->statistics().node!=1 || + capped->next()) return false; + capped->reset(nullptr); + if (capped->next() || capped->stopped() || capped->nogoods().ng()) return false; + // The last child replaces its fully expanded parent in the cap count. + const PruningSpace::Graph chain={ + {0,0,0,0,0,{1}}, {1,0,0,0,0,{2}}, {2,0,0,0,0,{3}}, + {3,3,3,3,0,{}}}; + Gecode::Column unary(new PruningSpace(chain), + PruningSpace::guidance(),options,{1,1,1}); + std::unique_ptr solution(unary.next()); + if (!solution || solution->value().cost!=3 || unary.next() || unary.stopped()) + return false; + Gecode::Column failed(new CABSFeasibility(true), + CABSFeasibility::guidance(),options); + if (failed.next() || failed.stopped()) return false; + Gecode::Column feasible(new CABSFeasibility, + CABSFeasibility::guidance(),options); + std::unique_ptr witness(feasible.next()); + return witness && witness->x.val()==1 && !feasible.next() && !feasible.stopped(); + } + static bool diagnostics(void) { + using namespace Gecode::Search; + AStarSpace root; + Gecode::Search::Options options; options.clone=false; options.threads=2; + auto guidance=AStarSpace::guidance(false); + try { Gecode::Column invalid(&root,guidance,options); return false; } + catch (const UnsupportedColumnThreads&) {} + options.threads=1; options.tracer=&Gecode::StdSearchTracer::def; + try { Gecode::Column invalid(&root,guidance,options); return false; } + catch (const UnsupportedColumnTracer&) {} + options.tracer=nullptr; + try { Gecode::Column invalid(&root,guidance,options,{0,1,0}); return false; } + catch (const InvalidColumnQuota&) {} + guidance.bound={}; + try { Gecode::Column invalid(&root,guidance,options); return false; } + catch (const UninitializedColumnBound&) {} + options.clone=true; + guidance.bound=[](const Space&)->std::int64_t { + throw std::runtime_error("guidance failure"); + }; + { + Gecode::Column engine(&root,guidance,options); + try { delete engine.next(); return false; } + catch (const std::runtime_error&) {} + } + return AStarSpace::live.load()==1 && root.status()==SS_BRANCH; + } + public: + ColumnTest(void) : Base("Search::Column", TestTags::check()) {} + bool run(void) override { + std::lock_guard lock(heuristic_ownership_mutex); + for (bool max : {false,true}) + if (!solutions(max,true,true) || !solutions(max,false,false) || + !adoption(max)) return false; + for (bool interrupt : {false,true}) + for (unsigned long int growth : {0UL,1UL, + std::numeric_limits::max()}) + if (!cycles(growth,interrupt)) return false; + return stale_frontier() && ownership_and_limits() && + AStarSpace::live.load()==0 && + diagnostics() && AStarSpace::live.load()==0 && + StatePruningChecks::semantics(2) && StatePruningChecks::reopen(2); + } + } columnTest; + + /// Scalar integer objective used for comparison tests + class MinObjective : public IntMinimizeSpace { + public: + IntVar x; + MinObjective(int l, int u) : x(*this,l,u) {} + MinObjective(MinObjective& s) : IntMinimizeSpace(s) { + x.update(*this,s.x); + } + virtual Space* copy(void) { return new MinObjective(*this); } + virtual IntVar cost(void) const { return x; } + }; + + /// Scalar maximization objective used to check objective families + class MaxObjective : public IntMaximizeSpace { + public: + IntVar x; + MaxObjective(int v) : x(*this,v,v) {} + MaxObjective(MaxObjective& s) : IntMaximizeSpace(s) { + x.update(*this,s.x); + } + virtual Space* copy(void) { return new MaxObjective(*this); } + virtual IntVar cost(void) const { return x; } + }; + + /// Space without objective comparison support + class PlainSpace : public Space { + public: + PlainSpace(void) {} + PlainSpace(PlainSpace& s) : Space(s) {} + virtual Space* copy(void) { return new PlainSpace(*this); } + }; + + /// Test objective comparison independently of search arbitration + class Comparison : public Base { + public: + Comparison(void) : Base("Search::Comparison", TestTags::check()) {} + virtual bool run(void) { + MinObjective one(1,1), two(2,2), one_again(1,1), open(0,2); + if ((one.compare(two) != SC_BETTER) || + (two.compare(one) != SC_WORSE) || + (one.compare(one_again) != SC_EQUIVALENT)) + return false; + MaxObjective high(2), low(1); + if ((high.compare(low) != SC_BETTER) || + (low.compare(high) != SC_WORSE)) + return false; + try { + (void) open.compare(one); + return false; + } catch (const Int::ValOfUnassignedVar&) {} + try { + (void) one.compare(high); + return false; + } catch (const DynamicCastFailed&) {} + PlainSpace plain; + try { + (void) plain.compare(plain); + return false; + } catch (const SpaceNoComparison&) {} + return true; + } + }; + + Comparison comparison; + + /// Brancher used to exercise internal post-propagation feedback + class FeedbackBrancher : public Brancher { + private: + class FeedbackChoice : public Gecode::Search::BranchFeedbackChoice { + public: + int pos; + FeedbackChoice(const Brancher& brancher, + const Gecode::Search::BranchFeedback& feedback, + int pos0) + : BranchFeedbackChoice(brancher,2U,feedback, + static_cast(pos0)), + pos(pos0) {} + void archive(Archive& archive) const override { + Choice::archive(archive); + archive << pos; + } + }; + ViewArray x; + Gecode::Search::BranchFeedback feedback; + mutable int start; + int throwing_alternative; + + FeedbackBrancher(Home home, ViewArray& x0, + const Gecode::Search::BranchFeedback& feedback0, + int throwing_alternative0) + : Brancher(home), x(x0), feedback(feedback0), start(0), + throwing_alternative(throwing_alternative0) { + home.notice(*this,AP_DISPOSE,true); + } + FeedbackBrancher(Space& home, FeedbackBrancher& brancher) + : Brancher(home,brancher), feedback(brancher.feedback), + start(brancher.start), + throwing_alternative(brancher.throwing_alternative) { + x.update(home,brancher.x); + } + public: + static void post(Home home, const IntVarArgs& x, + const Gecode::Search::BranchFeedback& feedback, + int throwing_alternative) { + ViewArray views(home,x); + (void) new (home) + FeedbackBrancher(home,views,feedback,throwing_alternative); + } + bool status(const Space&) const override { + while ((start < x.size()) && x[start].assigned()) + start++; + return start < x.size(); + } + const Choice* choice(Space&) override { + return new FeedbackChoice(*this,feedback,start); + } + const Choice* choice(const Space&, Archive& archive) override { + int pos; + archive >> pos; + return new FeedbackChoice(*this,feedback,pos); + } + ExecStatus commit(Space& home, const Choice& choice0, + unsigned int alternative) override { + const FeedbackChoice& choice = + static_cast(choice0); + if (throwing_alternative == static_cast(alternative)) + throw std::runtime_error("branch feedback fixture commit"); + if ((choice.pos == x.size()-1) && (alternative == 1U)) + return ES_FAILED; + return me_failed(x[choice.pos].eq(home,static_cast(alternative))) + ? ES_FAILED : ES_OK; + } + Actor* copy(Space& home) override { + return new (home) FeedbackBrancher(home,*this); + } + size_t dispose(Space& home) override { + home.ignore(*this,AP_DISPOSE,true); + feedback.~BranchFeedback(); + (void) Brancher::dispose(home); + return sizeof(*this); + } + }; + + /// Small complete tree for branch-feedback fixtures + class FeedbackSpace : public Space { + public: + IntVarArray x; + FeedbackSpace(unsigned int size, + const Gecode::Search::BranchFeedback& feedback, + int throwing_alternative=-1) + : x(*this,static_cast(size),0,1) { + FeedbackBrancher::post(*this,x,feedback,throwing_alternative); + } + FeedbackSpace(FeedbackSpace& space) : Space(space) { + x.update(*this,space.x); + } + Space* copy(void) override { + return new FeedbackSpace(*this); + } + void constrain(const Space&) override {} + // This fixture enumerates a complete tree without objective pruning. + SpaceComparison compare(const Space&) const override { + return SC_BETTER; + } + bool master(const MetaInfo& info) override { + if ((info.type() == MetaInfo::RESTART) && (info.last() != nullptr)) { + const FeedbackSpace& last = + static_cast(*info.last()); + BoolVarArgs same; + for (int i=0; i(&space); + std::lock_guard lock(mutex); + const unsigned int expected_assigned = + (outcome == Gecode::Search::BranchOutcome::FAILED) ? id : id+1U; + if ((feedback_space == nullptr) || (id >= 8U) || + (alternative >= 2U) || + (feedback_space->assigned() < expected_assigned)) { + invalid = true; + return; + } + unsigned int outcome_index; + switch (outcome) { + case Gecode::Search::BranchOutcome::FAILED: + outcome_index = 0U; break; + case Gecode::Search::BranchOutcome::SOLVED: + outcome_index = 1U; break; + case Gecode::Search::BranchOutcome::BRANCH: + outcome_index = 2U; break; + default: + invalid = true; return; + } + count[id][alternative][outcome_index]++; + } + bool expected(unsigned int size, unsigned int repetitions=1U) { + std::lock_guard lock(mutex); + if (invalid) + return false; + for (unsigned int id=0U; id lock(mutex); + unsigned int total = 0U; + for (unsigned int i=0U; i<8U; i++) + for (unsigned int j=0U; j<2U; j++) + for (unsigned int k=0U; k<3U; k++) + total += count[i][j][k]; + return total; + } + bool valid(void) { + std::lock_guard lock(mutex); + return !invalid; + } + }; + + // Exercise the final alternative after publication, including a commit + // exception after the selected parent has transferred to that child. + static bool native(unsigned int kind, bool throwing) { + bool destroyed = false; + Recorder* recorder = new Recorder(&destroyed); + Gecode::Search::BranchFeedback feedback(recorder); + bool threw = false; + unsigned int solutions = 0U; + { + FeedbackSpace root(1U,feedback,throwing ? 1 : -1); + Gecode::Search::Options options; + options.threads = 1.0; + Gecode::Search::HeuristicGuidance guidance( + Gecode::Search::HeuristicGuidance::MINIMIZE, + [](const Space& space) -> std::int64_t { + return static_cast(space).assigned() ? 0 : -1; + }); + std::unique_ptr search; + switch (kind) { + case 0U: + search.reset(Gecode::Search::astarengine(&root,guidance,options)); + break; + case 1U: + search.reset(Gecode::Search::cabsengine(&root,guidance,options)); + break; + case 2U: + search.reset(Gecode::Search::columnengine(&root,guidance,options)); + break; + default: + GECODE_NEVER; + } + try { + while (std::unique_ptr solution{search->next()}) + solutions++; + } catch (const std::runtime_error&) { + threw = true; + } + if ((solutions != 1U) || (threw != throwing) || + (throwing ? recorder->observations() != 1U : + !recorder->expected(1U)) || search->stopped()) + return false; + } + feedback = Gecode::Search::BranchFeedback(); + return destroyed; + } + + static bool archive_compatible(void) { + Recorder* recorder = new Recorder; + Gecode::Search::BranchFeedback feedback(recorder); + FeedbackSpace root(1U,feedback); + if (root.status() != SS_BRANCH) + return false; + FeedbackSpace* clone = static_cast(root.clone()); + const Choice* choice = root.choice(); + Archive archive; + choice->archive(archive); + const Choice* restored = clone->choice(archive); + clone->commit(*restored,0U); + delete restored; + delete choice; + const bool compatible = (clone->status() == SS_SOLVED) && + (recorder->observations() == 0U); + delete clone; + return compatible; + } + public: + BranchFeedback(void) : Base("Search::BranchFeedback", TestTags::check()) {} + bool run(void) override { + return archive_compatible() && + native(0U,false) && native(0U,true) && + native(1U,false) && native(1U,true) && + native(2U,false) && native(2U,true); + } + }; + + BranchFeedback branch_feedback; /// Values for selecting branchers enum HowToBranch { HTB_NONE, ///< Do not branch @@ -545,6 +2361,7 @@ namespace Test { bool paused; public: std::promise reached; + std::atomic terminate{false}; Pause(const Gecode::Search::WorkerControl& c, unsigned int n) : control(c), node(n), paused(false) {} bool stop(const Gecode::Search::Statistics& s, @@ -554,7 +2371,7 @@ namespace Test { control.request(0U); reached.set_value(); } - return false; + return terminate.load(std::memory_order_acquire); } }; template class E> @@ -584,11 +2401,56 @@ namespace Test { } return ok && !engine.stopped() && (solutions == root.solutions()); } + bool native(unsigned int kind, unsigned int node, bool stopping) { + Gecode::Search::WorkerControl control(node == 0U ? 0U : 1U); + Pause pause(control,node); + Gecode::Search::Options options; + options.threads=1; options.worker_control=control; options.stop=&pause; + AStarSpace root; + const auto guidance=AStarSpace::guidance(false); + std::unique_ptr engine(kind==0U ? + Gecode::Search::astarengine(&root,guidance,options) : kind==1U ? + Gecode::Search::cabsengine(&root,guidance,options) : + Gecode::Search::columnengine(&root,guidance,options)); + std::int64_t optimum=0; + bool found=false; + auto consume=[&] { + while (std::unique_ptr solution{ + static_cast(engine->next())}) { + optimum=solution->objective(); found=true; + } + }; + // A shallow generated goal may return before the pause threshold. + // Keep consuming publications until the engine reaches that boundary. + auto result=std::async(std::launch::async,consume); + pause.reached.get_future().wait(); + bool paused=result.wait_for(std::chrono::milliseconds(20))== + std::future_status::timeout; + const auto before=engine->statistics().node; + if (stopping) { + pause.terminate.store(true,std::memory_order_release); + Gecode::Search::WorkerControlAccess::signal_all(control); + result.get(); + if (!engine->stopped() || engine->statistics().node!=before) + return false; + pause.terminate.store(false,std::memory_order_release); + control.request(1U); + consume(); + } else { + control.request(1U); + result.get(); + } + return paused && found && optimum==root.score(2) && !engine->stopped(); + } public: WorkerControlSequentialPause(void) : Base("Search::WorkerControl::SequentialPause", TestTags::check()) {} bool run(void) override { #ifdef GECODE_HAS_THREADS + std::lock_guard lock(heuristic_ownership_mutex); + for (unsigned int kind : {0U,1U,2U}) + if (!native(kind,0U,false) || !native(kind,1U,false) || + !native(kind,0U,true)) return false; return check(0U) && check(1U) && check(0U) && check(1U); #else From 0cdcb9b69f0133e614cbb4f01f22db484326bb33 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 13 Oct 2026 10:24:45 +0200 Subject: [PATCH 04/24] Add tasks to open-search Zdev-Change-Id: Z7b809ac2be0294a753b5f04da7261e3f03135f5864c0715cc88211329634cfea --- .zdev/open-search/TASKS.md | 5 ++- .zdev/open-search/brief.md | 25 ++++++++++++ ...-as-an-experimental-preview-and-separat.md | 38 +++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 .zdev/open-search/tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index 4892a70b69..d3fed552af 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -2,8 +2,8 @@ # Tasks: open-search -- Total: 31 -- Ready: 0 +- Total: 32 +- Ready: 1 - Blocked: 0 - Done: 31 @@ -40,3 +40,4 @@ | [open-search-029](tasks/029-add-configurable-native-best-first-ordering.md) | Add configurable native best-first ordering | done | open-search-024, open-search-025 | | [open-search-030](tasks/030-evaluate-sequential-cadds-retained-fragment-search.md) | Evaluate sequential CADDS retained-fragment search | done | open-search-018, open-search-025 | | [open-search-031](tasks/031-reconstruct-standalone-native-search-on-open-cp.md) | Reconstruct standalone native search on open-cp | done | — | +| [open-search-032](tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md) | Gate heuristic search as an experimental preview and separate experimental changelog entries | ready | — | diff --git a/.zdev/open-search/brief.md b/.zdev/open-search/brief.md index 24aa424b54..ff609adbf8 100644 --- a/.zdev/open-search/brief.md +++ b/.zdev/open-search/brief.md @@ -1,3 +1,28 @@ +## Experimental preview for 6.5.0 (2026-10-13) + +The user approved a default-off experimental build switch and experimental +changelog metadata. On this native branch, AStar, configurable best-first, +CABS and Column (including fixed-quota CBFS mode) are experimental APIs. +Use GECODE_ENABLE_EXPERIMENTAL / --enable-experimental and the generated +GECODE_HAS_EXPERIMENTAL availability macro. Keep intended namespaces and +native independence from Model/portfolio. Gate these engines and their +engine-specific declarations, examples and tests; preserve shared kernel, +OpenCP and ordinary search contracts independently of this flag. This task +does not modify the separate Model integration branch. + +Changelog entries accept Experimental: true or false; omission means false. +Each output format separates experimental entries under Experimental changes +within each release, retaining module/type ordering. Releases without marked +entries retain their existing presentation. Document preview API stability +without weakening search correctness contracts. + +Testing for this increment uses the existing tidy/generator fixture checks, +with focused coverage of true, false, omission, invalid values and separation +in all three generators. Validate CMake OFF/ON builds and existing search +checks; validate Autoconf/Make flag propagation and build registration, and +compile ordinary search with the default-off configuration. Regenerate tracked +outputs. No new test harness or benchmark campaign is required. + ## Approved branch split (2026-10-13) This section supersedes the earlier combined-branch topology and validation scope. diff --git a/.zdev/open-search/tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md b/.zdev/open-search/tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md new file mode 100644 index 0000000000..08042a8235 --- /dev/null +++ b/.zdev/open-search/tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md @@ -0,0 +1,38 @@ ++++ +schema_version = 1 +id = "open-search-032" +key = "experimental-preview" +area = "open-search" +status = "open" +complexity = "standard" +afk = true +blocked_by = [] ++++ +# Gate heuristic search as an experimental preview and separate experimental changelog entries + +## Outcome + +Gecode users explicitly opt into the open-search engine preview, and generated release notes distinguish experimental changes. + +## Context + +Read the Experimental preview section of brief.md. CBS supplies the existing default-off configure/CMake/config-header pattern. Native engine declarations are in gecode/search.hh with AStar/best-first, CABS and Column source registrations in CMake and Make. Changelog metadata is documented in changelog.in and consumed by misc/genchangelog.py, gentxtchangelog.py, gencurrentchangelog.py and tidy.py. Preserve shared native prerequisites and existing stable search interfaces. + +## Boundaries + +- Work only on feature/open-search; preserve parent, backup and Model integration branches. +- Keep normal namespaces and independent native search; no new engines, compatibility framework, kernel layout variation or benchmark campaign. +- Use existing generator fixture and search test infrastructure; gate only feature-specific checks. + +## Done when + +- [ ] CMake GECODE_ENABLE_EXPERIMENTAL and Autoconf --enable-experimental default off and generate matching GECODE_HAS_EXPERIMENTAL availability. All AStar, best-first, CABS and Column APIs/implementations are available only when enabled; default ordinary search still builds and runs. +- [ ] Public documentation, build instructions and engine release entries clearly identify the experimental 6.5.0 preview and its API stability policy. +- [ ] Experimental metadata accepts exactly true/false and defaults false; all three generated changelog formats separate true entries per release while preserving module/type ordering and unchanged historical presentation when none are marked. Invalid values fail validation. +- [ ] Applicable tracked generated configuration/changelog outputs are regenerated and existing builds/tests remain usable in both configurations. + +## Validation + +- Extend existing tidy/generator fixtures for true, false, omitted, invalid and separate rendering across all three generators; run tidy checks and inspect generated current-release sections. +- Run CMake Release default-OFF and explicit-ON builds with existing check targets; focused native engine tests/examples enabled, ordinary DFS/BAB disabled-mode smoke and compile-failure check for preview API when disabled. +- Verify Autoconf/Make OFF/ON configuration, generated macro, source/example registration and relevant build smoke; no broad TSPTW performance rerun. From a6544e315d44683d5f281b874f2833a118163ad7 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 13 Oct 2026 10:47:13 +0200 Subject: [PATCH 05/24] Gate heuristic search as experimental and separate preview changelog entries Zdev-Change-Id: Z6c14976b91b4983c41b4d8aba39c65183b90baac364c11cd459868c3d515f7ac --- .github/workflows/build.yml | 1 + .zdev/open-search/TASKS.md | 6 +-- ...-as-an-experimental-preview-and-separat.md | 18 +++++-- CMakeLists.txt | 5 ++ Makefile.in | 12 ++++- README.md | 20 ++++++++ changelog.in | 15 ++++++ cmake/GecodeSources.cmake | 11 +++-- configure | 31 ++++++++++++ configure.ac | 1 + docs/astar.md | 6 +++ docs/cabs.md | 6 +++ docs/column.md | 6 +++ examples/CMakeLists.txt | 22 +++++---- examples/tsptw-transition.hh | 3 ++ gecode.m4 | 16 +++++++ gecode/search.hh | 23 +++++++++ gecode/search/astar.hpp | 3 ++ gecode/search/cabs.hpp | 3 ++ gecode/search/column.hpp | 3 ++ gecode/search/exception.cpp | 3 ++ gecode/search/exception.hpp | 3 ++ gecode/search/seq/astar.hh | 3 ++ gecode/search/seq/cabs.hh | 3 ++ gecode/search/seq/choice-expansion.hh | 3 ++ gecode/search/seq/column.hh | 3 ++ gecode/search/seq/state-registry.hh | 3 ++ gecode/support/config.hpp.in | 3 ++ misc/genchangelog.py | 33 ++++++++----- misc/gencurrentchangelog.py | 48 ++++++++++++------- misc/gentxtchangelog.py | 47 +++++++++++------- misc/tidy.py | 43 +++++++++++++++++ test/search.cpp | 31 ++++++++++-- 33 files changed, 362 insertions(+), 75 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2d70f7172f..f7f8b1cd32 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -220,6 +220,7 @@ jobs: shell: bash run: > cmake -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build-ninja" + -DGECODE_ENABLE_EXPERIMENTAL=ON -G Ninja -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DGECODE_ENABLE_QT=OFF diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index d3fed552af..72aad3b826 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -3,9 +3,9 @@ # Tasks: open-search - Total: 32 -- Ready: 1 +- Ready: 0 - Blocked: 0 -- Done: 31 +- Done: 32 | ID | Task | State | Blocked by | | --- | --- | --- | --- | @@ -40,4 +40,4 @@ | [open-search-029](tasks/029-add-configurable-native-best-first-ordering.md) | Add configurable native best-first ordering | done | open-search-024, open-search-025 | | [open-search-030](tasks/030-evaluate-sequential-cadds-retained-fragment-search.md) | Evaluate sequential CADDS retained-fragment search | done | open-search-018, open-search-025 | | [open-search-031](tasks/031-reconstruct-standalone-native-search-on-open-cp.md) | Reconstruct standalone native search on open-cp | done | — | -| [open-search-032](tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md) | Gate heuristic search as an experimental preview and separate experimental changelog entries | ready | — | +| [open-search-032](tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md) | Gate heuristic search as an experimental preview and separate experimental changelog entries | done | — | diff --git a/.zdev/open-search/tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md b/.zdev/open-search/tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md index 08042a8235..cee8141ec7 100644 --- a/.zdev/open-search/tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md +++ b/.zdev/open-search/tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md @@ -3,7 +3,7 @@ schema_version = 1 id = "open-search-032" key = "experimental-preview" area = "open-search" -status = "open" +status = "done" complexity = "standard" afk = true blocked_by = [] @@ -26,13 +26,21 @@ Read the Experimental preview section of brief.md. CBS supplies the existing def ## Done when -- [ ] CMake GECODE_ENABLE_EXPERIMENTAL and Autoconf --enable-experimental default off and generate matching GECODE_HAS_EXPERIMENTAL availability. All AStar, best-first, CABS and Column APIs/implementations are available only when enabled; default ordinary search still builds and runs. -- [ ] Public documentation, build instructions and engine release entries clearly identify the experimental 6.5.0 preview and its API stability policy. -- [ ] Experimental metadata accepts exactly true/false and defaults false; all three generated changelog formats separate true entries per release while preserving module/type ordering and unchanged historical presentation when none are marked. Invalid values fail validation. -- [ ] Applicable tracked generated configuration/changelog outputs are regenerated and existing builds/tests remain usable in both configurations. +- [x] CMake GECODE_ENABLE_EXPERIMENTAL and Autoconf --enable-experimental default off and generate matching GECODE_HAS_EXPERIMENTAL availability. All AStar, best-first, CABS and Column APIs/implementations are available only when enabled; default ordinary search still builds and runs. +- [x] Public documentation, build instructions and engine release entries clearly identify the experimental 6.5.0 preview and its API stability policy. +- [x] Experimental metadata accepts exactly true/false and defaults false; all three generated changelog formats separate true entries per release while preserving module/type ordering and unchanged historical presentation when none are marked. Invalid values fail validation. +- [x] Applicable tracked generated configuration/changelog outputs are regenerated and existing builds/tests remain usable in both configurations. ## Validation - Extend existing tidy/generator fixtures for true, false, omitted, invalid and separate rendering across all three generators; run tidy checks and inspect generated current-release sections. - Run CMake Release default-OFF and explicit-ON builds with existing check targets; focused native engine tests/examples enabled, ordinary DFS/BAB disabled-mode smoke and compile-failure check for preview API when disabled. - Verify Autoconf/Make OFF/ON configuration, generated macro, source/example registration and relevant build smoke; no broad TSPTW performance rerun. + +## Result + +Added a default-off experimental search preview in CMake and Autoconf, with generated availability, gated native APIs/implementations/examples/tests, public documentation and an enabled CI configuration. Added strict optional Experimental changelog metadata and separated experimental sections in all three generators. Shared native contracts remain independent of the flag. Independently verified. + +Validation: + +- CMake OFF check 470/470; ON check 474/474; focused preview tests 4/4. Autoconf OFF/ON search-library builds, ordinary OFF consumers, enabled native consumers and disabled-API negative compiles passed. Native examples and tiny TSPTW checker passed. Tidy true/false/omitted/invalid and rendering fixtures passed; all historical unmarked generator output is byte-identical. Tracked configure regenerated. Independent verification passed at unchanged snapshot W25adf51e7f0f1991. Remote CI was not run. diff --git a/CMakeLists.txt b/CMakeLists.txt index 01b980c7be..463b55aeb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,7 @@ endif() set(GECODE_ENABLE_GIST "${GECODE_ENABLE_GIST_DEFAULT}" CACHE STRING "Enable Gist (AUTO, ON, or OFF)") set_property(CACHE GECODE_ENABLE_GIST PROPERTY STRINGS AUTO ON OFF) option(GECODE_ENABLE_CPPROFILER "Enable CPProfiler support" ON) +option(GECODE_ENABLE_EXPERIMENTAL "Enable experimental preview APIs" OFF) option(GECODE_ENABLE_CBS "Enable counting-based search support" OFF) set(GECODE_ENABLE_EXAMPLES_DEFAULT ${PROJECT_IS_TOP_LEVEL}) option(GECODE_ENABLE_EXAMPLES "Build examples" ${GECODE_ENABLE_EXAMPLES_DEFAULT}) @@ -691,6 +692,9 @@ endif() if(GECODE_ENABLE_CPPROFILER) set(GECODE_HAS_CPPROFILER "/**/") endif() +if(GECODE_ENABLE_EXPERIMENTAL) + set(GECODE_HAS_EXPERIMENTAL "/**/") +endif() if(GECODE_ENABLE_CBS) set(GECODE_HAS_CBS "/**/") endif() @@ -1784,6 +1788,7 @@ include(FeatureSummary) foreach(gecode_option IN ITEMS GECODE_BUILD_SHARED GECODE_BUILD_STATIC GECODE_ENABLE_THREAD GECODE_ENABLE_CPPROFILER GECODE_ENABLE_CBS + GECODE_ENABLE_EXPERIMENTAL GECODE_ENABLE_EXAMPLES GECODE_INSTALL GECODE_ENABLE_SEARCH GECODE_ENABLE_INT_VARS GECODE_ENABLE_SET_VARS GECODE_ENABLE_FLOAT_VARS GECODE_ENABLE_MINIMODEL GECODE_ENABLE_DRIVER diff --git a/Makefile.in b/Makefile.in index 51eeefb971..a8b65da073 100755 --- a/Makefile.in +++ b/Makefile.in @@ -263,7 +263,7 @@ endif SEARCHSRC0 = \ stop options cutoff engine \ - dfs bab astar cabs column lds \ + dfs bab lds \ seq/rbs seq/dead seq/pbs par/pbs \ rbs pbs nogoods exception tracer \ cpprofiler/tracer worker-control @@ -282,6 +282,10 @@ SEARCHHDR0 = \ relax.hh tracer.hpp trace-recorder.hpp \ cpprofiler/message.hpp cpprofiler/connector.hpp +ifeq "@enable_experimental@" "yes" +SEARCHSRC0 += astar cabs column +endif + SEARCHSRC = $(SEARCHSRC0:%=gecode/search/%.cpp) SEARCHHDR = gecode/search.hh $(SEARCHHDR0:%=gecode/search/%) SEARCHOBJ = $(SEARCHSRC:%.cpp=%$(OBJSUFFIX)) @@ -900,7 +904,7 @@ endif INTEXAMPLEHDR0 = \ scowl job-shop-instances INTEXAMPLESRC0 = \ - alpha astar best-first tsptw bacp bibd donald efpa eq20 golomb-ruler \ + alpha bacp bibd donald efpa eq20 golomb-ruler \ graph-color grocery ind-set magic-sequence magic-square \ money ortho-latin partition photo queens sudoku sudoku-advanced kakuro \ nonogram pentominoes crowded-chess black-hole \ @@ -912,6 +916,10 @@ INTEXAMPLESRC0 = \ qcp job-shop open-route common-pattern fifteen-puzzle +ifeq "@enable_experimental@" "yes" +INTEXAMPLESRC0 += astar best-first tsptw +endif + INTEXAMPLEHDR = $(INTEXAMPLEHDR0:%=examples/%.hpp) \ examples/tsptw-data.hh examples/tsptw-transition.hh INTEXAMPLESRC = $(INTEXAMPLESRC0:%=examples/%.cpp) diff --git a/README.md b/README.md index 6bd1d280b4..fe643b8d65 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ state. | `--enable-qt` | `GECODE_ENABLE_QT` | Supported directly | `AUTO` by default; Qt 6 preferred; Qt 5.15+ supported | | `--enable-gist` | `GECODE_ENABLE_GIST` | Supported directly | `AUTO` by default except macOS; disabled automatically if Qt GUI support is unavailable | | `--enable-cpprofiler` | `GECODE_ENABLE_CPPROFILER` | Supported directly | Default `ON` | +| `--enable-experimental` | `GECODE_ENABLE_EXPERIMENTAL` | Supported directly | Default `OFF`; experimental 6.5.0 preview APIs | | `--enable-cbs` | `GECODE_ENABLE_CBS` | Supported directly | Default `OFF` | | `--enable-examples` | `GECODE_ENABLE_EXAMPLES` | Supported directly | Default `ON` for top-level builds, `OFF` for subprojects | | `--enable-search` | `GECODE_ENABLE_SEARCH` | Supported directly | Default `ON` | @@ -117,6 +118,25 @@ build that supports the active macOS SDK; recent Qt 6 releases are the safest choice. Gist is opt-in for CMake builds on macOS. Use `-DGECODE_ENABLE_GIST=ON` when Qt is installed and its framework link interface is usable. +## Experimental search preview + +Gecode 6.5.0 includes an opt-in preview of `AStar`, configurable best-first, +`CABS`, and `Column` (including fixed-quota CBFS), with their guidance, +state-pruning and builder APIs in the normal Gecode namespaces. +Enable it when building the library with `-DGECODE_ENABLE_EXPERIMENTAL=ON` +for CMake or `--enable-experimental` for Autoconf. Both builds disable the +preview by default. The installed configuration defines +`GECODE_HAS_EXPERIMENTAL` only when the library includes these APIs. +Applications can use this macro to check availability; defining it in an +application does not enable the library feature. + +Experimental APIs may change or be removed in future feature releases. +Their documented search correctness contracts still apply. Native preview +engines work directly with ordinary `Space` subclasses and do not require +Model or portfolio setup. Enabled builds include the `astar`, `best-first` +and `tsptw` examples. Ordinary DFS/BAB and shared kernel/OpenCP APIs remain +available independently of the preview option. + ## CMake Package Consumption For CMake build/install workflows and downstream `find_package(Gecode)` usage, diff --git a/changelog.in b/changelog.in index 718b32e457..5a20577c90 100755 --- a/changelog.in +++ b/changelog.in @@ -56,6 +56,8 @@ # driver|iter|support|example|test|gist|flatzinc|other # What: bug|documentation|performance|new|removed|change # Rank: minor|major +# Experimental: true|false +# True entries appear under Experimental changes within each release. # Bug: # Issue: # Thanks: @@ -73,6 +75,19 @@ Date: unreleased [DESCRIPTION] This is the development changelog for the next Gecode release. +[ENTRY] +Module: search +What: new +Rank: major +Experimental: true +[DESCRIPTION] +Add the experimental 6.5.0 native search preview: AStar, configurable +best-first, CABS and Column, including fixed-quota CBFS mode, with bound/rank +guidance, optional state pruning and ordinary engine builders. Enable it with +--enable-experimental or GECODE_ENABLE_EXPERIMENTAL=ON; it is disabled by +default. Preview APIs may change or be removed in future feature releases; +their documented search correctness contracts still apply. + [ENTRY] Module: int What: new diff --git a/cmake/GecodeSources.cmake b/cmake/GecodeSources.cmake index cd319fe88e..a8b1702cbf 100644 --- a/cmake/GecodeSources.cmake +++ b/cmake/GecodeSources.cmake @@ -31,10 +31,7 @@ set(GECODE_KERNEL_SOURCES ) set(GECODE_SEARCH_SOURCES - gecode/search/astar.cpp gecode/search/bab.cpp - gecode/search/cabs.cpp - gecode/search/column.cpp gecode/search/cpprofiler/tracer.cpp gecode/search/cutoff.cpp gecode/search/dfs.cpp @@ -54,6 +51,14 @@ set(GECODE_SEARCH_SOURCES gecode/search/worker-control.cpp ) +if(GECODE_ENABLE_EXPERIMENTAL) + list(APPEND GECODE_SEARCH_SOURCES + gecode/search/astar.cpp + gecode/search/cabs.cpp + gecode/search/column.cpp + ) +endif() + set(GECODE_INT_SOURCES gecode/int/arithmetic.cpp gecode/int/arithmetic/mult.cpp diff --git a/configure b/configure index 0a59cb0548..6b9db2a20b 100755 --- a/configure +++ b/configure @@ -680,6 +680,7 @@ HAVEFLEX enable_cpprofiler LINKCPPROFILER enable_cbs +enable_experimental enable_gist enable_qt QTLIBS @@ -859,6 +860,7 @@ with_mpfr_include with_mpfr_lib enable_qt enable_gist +enable_experimental enable_cbs enable_cpprofiler enable_driver @@ -1531,6 +1533,7 @@ Optional Features: --enable-qt build with Qt support, requires Qt 5.15 or newer [default=yes] --enable-gist build Gecode Interactive Search Tool [default=yes] + --enable-experimental build experimental preview APIs [default=no] --enable-cbs build with support for counting-based search [default=no] --enable-cpprofiler build with support for CPProfiler [default=yes] @@ -3407,6 +3410,8 @@ GECODE_SOVERSION=${ac_gecode_soversion} + + @@ -13888,6 +13893,32 @@ printf "%s\n" "no" >&6; } fi + # Check whether --enable-experimental was given. +if test ${enable_experimental+y} +then : + enableval=$enable_experimental; +else case e in #( + e) enable_experimental=no ;; +esac +fi + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to build experimental preview APIs" >&5 +printf %s "checking whether to build experimental preview APIs... " >&6; } + case $enable_experimental in #( + yes) : + +printf "%s\n" "#define GECODE_HAS_EXPERIMENTAL /**/" >>confdefs.h + ;; #( + no) : + ;; #( + *) : + as_fn_error $? "--enable-experimental accepts only yes or no" "$LINENO" 5 ;; +esac + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enable_experimental" >&5 +printf "%s\n" "$enable_experimental" >&6; } + + + # Check whether --enable-cbs was given. if test ${enable_cbs+y} then : diff --git a/configure.ac b/configure.ac index b2c3ef0d58..85254e03f2 100644 --- a/configure.ac +++ b/configure.ac @@ -275,6 +275,7 @@ dnl ------------------------------------------------------------------ AC_GECODE_MPFR AC_GECODE_QT AC_GECODE_GIST +AC_GECODE_EXPERIMENTAL AC_GECODE_CBS AC_GECODE_CPPROFILER AC_GECODE_FLEXBISON diff --git a/docs/astar.md b/docs/astar.md index 18e23e6108..94223e458f 100644 --- a/docs/astar.md +++ b/docs/astar.md @@ -1,5 +1,11 @@ # Native A* and best-first search +This API is part of the experimental 6.5.0 preview, disabled by default. +Build the library with `-DGECODE_ENABLE_EXPERIMENTAL=ON` (CMake) or +`--enable-experimental` (Autoconf). `GECODE_HAS_EXPERIMENTAL` in the generated +configuration header reports availability. Preview APIs may change or be +removed in future feature releases; the correctness contracts below still apply. + `AStar` searches an ordinary `Space` subclass with its existing branching, `compare`, and `constrain` methods. It returns caller-owned, strictly improving solutions. The native API needs only the search and integer libraries. diff --git a/docs/cabs.md b/docs/cabs.md index 9d467cbc55..c6f7cd1811 100644 --- a/docs/cabs.md +++ b/docs/cabs.md @@ -1,5 +1,11 @@ # Native complete anytime beam search +This API is part of the experimental 6.5.0 preview, disabled by default. +Build the library with `-DGECODE_ENABLE_EXPERIMENTAL=ON` (CMake) or +`--enable-experimental` (Autoconf). `GECODE_HAS_EXPERIMENTAL` in the generated +configuration header reports availability. Preview APIs may change or be +removed in future feature releases; the correctness contracts below still apply. + `CABS` searches an ordinary `Space` subclass with its existing branching, `compare`, and strict-improvement `constrain` methods. It needs no Model, portfolio, or open list. Like [A*](astar.md), it returns caller-owned, strictly diff --git a/docs/column.md b/docs/column.md index 2faa86d998..f5523369e9 100644 --- a/docs/column.md +++ b/docs/column.md @@ -1,5 +1,11 @@ # Native progressive column search +This API is part of the experimental 6.5.0 preview, disabled by default. +Build the library with `-DGECODE_ENABLE_EXPERIMENTAL=ON` (CMake) or +`--enable-experimental` (Autoconf). `GECODE_HAS_EXPERIMENTAL` in the generated +configuration header reports availability. Preview APIs may change or be +removed in future feature releases; the correctness contracts below still apply. + `Column` searches ordinary Spaces with sequential depth queues. It requires no Model declaration or compilation. The Space supplies `compare` and strict `constrain`, and `Search::HeuristicGuidance` supplies an optimistic total bound: diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 55f5038180..dec6abb407 100755 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -116,15 +116,17 @@ foreach(example_source IN LISTS GECODE_EXAMPLE_SOURCES) gecode_add_example("${example_source}") endforeach() -# Standalone native search: no Model, driver, or portfolio setup. -add_executable(astar astar.cpp) -target_link_libraries(astar PRIVATE gecodesearch gecodeint) -set_property(TARGET astar PROPERTY FOLDER "Examples") +if(GECODE_ENABLE_EXPERIMENTAL) + # Standalone native search: no Model, driver, or portfolio setup. + add_executable(astar astar.cpp) + target_link_libraries(astar PRIVATE gecodesearch gecodeint) + set_property(TARGET astar PROPERTY FOLDER "Examples") -add_executable(best-first best-first.cpp) -target_link_libraries(best-first PRIVATE gecodesearch gecodeint) -set_property(TARGET best-first PROPERTY FOLDER "Examples") + add_executable(best-first best-first.cpp) + target_link_libraries(best-first PRIVATE gecodesearch gecodeint) + set_property(TARGET best-first PROPERTY FOLDER "Examples") -add_executable(tsptw tsptw.cpp) -target_link_libraries(tsptw PRIVATE gecodesearch gecodeint) -set_property(TARGET tsptw PROPERTY FOLDER "Examples") + add_executable(tsptw tsptw.cpp) + target_link_libraries(tsptw PRIVATE gecodesearch gecodeint) + set_property(TARGET tsptw PROPERTY FOLDER "Examples") +endif() diff --git a/examples/tsptw-transition.hh b/examples/tsptw-transition.hh index 9584b44675..a2f22d6dd1 100644 --- a/examples/tsptw-transition.hh +++ b/examples/tsptw-transition.hh @@ -55,6 +55,7 @@ namespace TSPTW { return result; } std::int64_t bound(BoundMode mode) const; +#ifdef GECODE_HAS_EXPERIMENTAL static Gecode::Search::HeuristicGuidance guidance(BoundMode mode=PREFIX, std::shared_ptr measurements=nullptr) { @@ -94,6 +95,8 @@ namespace TSPTW { },descriptor_limit}; } +#endif + private: class VisitBrancher; Representation representation; diff --git a/gecode.m4 b/gecode.m4 index b236a906a5..12db3d586d 100755 --- a/gecode.m4 +++ b/gecode.m4 @@ -1382,6 +1382,22 @@ AC_DEFUN([AC_GECODE_GIST], ]) dnl +dnl Experimental preview APIs are opt-in. +AC_DEFUN([AC_GECODE_EXPERIMENTAL], + [ + AC_ARG_ENABLE([experimental], + AS_HELP_STRING([--enable-experimental], + [build experimental preview APIs @<:@default=no@:>@]), + [], [enable_experimental=no]) + AC_MSG_CHECKING(whether to build experimental preview APIs) + AS_CASE([$enable_experimental], + [yes], [AC_DEFINE([GECODE_HAS_EXPERIMENTAL],[],[Whether experimental preview APIs are available])], + [no], [], + [AC_MSG_ERROR([--enable-experimental accepts only yes or no])]) + AC_MSG_RESULT([$enable_experimental]) + AC_SUBST([enable_experimental]) +]) + dnl Macro: dnl AC_GECODE_CBS dnl diff --git a/gecode/search.hh b/gecode/search.hh index c92b4d9dc6..cbb4230cb2 100755 --- a/gecode/search.hh +++ b/gecode/search.hh @@ -1163,6 +1163,7 @@ namespace Gecode { #include +#ifdef GECODE_HAS_EXPERIMENTAL namespace Gecode { namespace Search { /** @@ -1255,6 +1256,11 @@ namespace Gecode { /** * \brief Sequential bound-ordered A* with improving solution publication * + * Experimental 6.5.0 preview: build with GECODE_ENABLE_EXPERIMENTAL=ON + * or --enable-experimental; GECODE_HAS_EXPERIMENTAL reports availability. + * APIs may change or be removed in future feature releases. The documented + * correctness contracts still apply. + * * Spaces must implement compatible scalar integer comparison and strict * improvement constraints. A generated solution can precede a better one; * continue next() until nullptr with stopped()==false to prove optimality of @@ -1291,6 +1297,11 @@ namespace Gecode { /** * \brief Return a native best-first builder with copied guidance and fixed order * + * Experimental 6.5.0 preview: build with GECODE_ENABLE_EXPERIMENTAL=ON + * or --enable-experimental; GECODE_HAS_EXPERIMENTAL reports availability. + * APIs may change or be removed in future feature releases. The documented + * correctness contracts still apply. + * * Uses the same engine and contracts as AStar. BOUND_FIRST preserves AStar * ordering; RANK_FIRST permits arbitrary, inadmissible lower-first ranking. * Missing rank falls back to bound order. Ties use insertion order. Values @@ -1332,6 +1343,11 @@ namespace Gecode { /** * \brief Sequential complete anytime beam search with widening restarts * + * Experimental 6.5.0 preview: build with GECODE_ENABLE_EXPERIMENTAL=ON + * or --enable-experimental; GECODE_HAS_EXPERIMENTAL reports availability. + * APIs may change or be removed in future feature releases. The documented + * correctness contracts still apply. + * * Each native Choice commit advances one layer. Width starts at one and * doubles after a completed pass if discarded bounds still permit improvement * or no incumbent exists. A pass can finish without widening when every @@ -1406,6 +1422,11 @@ namespace Gecode { /** * \brief Sequential progressive column search with fixed-quota CBFS mode * + * Experimental 6.5.0 preview: build with GECODE_ENABLE_EXPERIMENTAL=ON + * or --enable-experimental; GECODE_HAS_EXPERIMENTAL reports availability. + * APIs may change or be removed in future feature releases. The documented + * correctness contracts still apply. + * * Each cycle expands up to the quota from successive depth queues, including * deeper successors generated in that cycle. Quota growth saturates safely. * Deferred work remains queued. Lower-first rank orders each queue when @@ -1450,6 +1471,8 @@ namespace Gecode { #include +#endif + namespace Gecode { /** diff --git a/gecode/search/astar.hpp b/gecode/search/astar.hpp index 84a0a690c3..a067584261 100644 --- a/gecode/search/astar.hpp +++ b/gecode/search/astar.hpp @@ -31,6 +31,7 @@ * */ +#ifdef GECODE_HAS_EXPERIMENTAL namespace Gecode { namespace Search { /// Create a native sequential A* engine @@ -100,4 +101,6 @@ namespace Gecode { } +#endif + // STATISTICS: search-other diff --git a/gecode/search/cabs.hpp b/gecode/search/cabs.hpp index 4a20c48a5d..1d9a293620 100644 --- a/gecode/search/cabs.hpp +++ b/gecode/search/cabs.hpp @@ -31,6 +31,7 @@ * */ +#ifdef GECODE_HAS_EXPERIMENTAL namespace Gecode { namespace Search { /// Create a native sequential CABS engine @@ -85,4 +86,6 @@ namespace Gecode { } +#endif + // STATISTICS: search-other diff --git a/gecode/search/column.hpp b/gecode/search/column.hpp index 243aff0b04..5c79fad1c7 100644 --- a/gecode/search/column.hpp +++ b/gecode/search/column.hpp @@ -31,6 +31,7 @@ * */ +#ifdef GECODE_HAS_EXPERIMENTAL namespace Gecode { namespace Search { /// Create a native sequential column search engine @@ -87,4 +88,6 @@ namespace Gecode { } +#endif + // STATISTICS: search-other diff --git a/gecode/search/exception.cpp b/gecode/search/exception.cpp index 736ac1d06c..5810ebf776 100644 --- a/gecode/search/exception.cpp +++ b/gecode/search/exception.cpp @@ -37,6 +37,7 @@ namespace Gecode { namespace Search { Incomparable::Incomparable(const char* l) : Exception(l,"Incumbent objectives are incomparable") {} +#ifdef GECODE_HAS_EXPERIMENTAL UninitializedStatePruning::UninitializedStatePruning(const char* l) : Exception(l,"State pruning requires extraction and label comparison") {} @@ -72,6 +73,8 @@ namespace Gecode { namespace Search { +#endif + UninitializedCutoff::UninitializedCutoff(const char* l) : Exception(l,"Cutoff for restart-based search is missing") {} diff --git a/gecode/search/exception.hpp b/gecode/search/exception.hpp index 90a280653d..0bbedc190b 100644 --- a/gecode/search/exception.hpp +++ b/gecode/search/exception.hpp @@ -80,6 +80,7 @@ namespace Gecode { namespace Search { /// Initialize with location \a l WorkerControlInUse(const char* l); }; +#ifdef GECODE_HAS_EXPERIMENTAL /// %Exception: Column requires exactly one expanded thread class GECODE_SEARCH_EXPORT UnsupportedColumnThreads : public Exception { public: @@ -135,6 +136,8 @@ namespace Gecode { namespace Search { public: UninitializedStatePruning(const char* l); }; +#endif + /// %Exception: Incumbent objectives are incomparable class GECODE_SEARCH_EXPORT Incomparable : public Exception { public: diff --git a/gecode/search/seq/astar.hh b/gecode/search/seq/astar.hh index cd38be9df6..b6b6e5a43d 100644 --- a/gecode/search/seq/astar.hh +++ b/gecode/search/seq/astar.hh @@ -40,6 +40,7 @@ #include #include +#ifdef GECODE_HAS_EXPERIMENTAL namespace Gecode { namespace Search { namespace Seq { /// Sequential tree search with owned stable frontier clones @@ -101,4 +102,6 @@ namespace Gecode { namespace Search { namespace Seq { #endif +#endif + // STATISTICS: search-seq diff --git a/gecode/search/seq/cabs.hh b/gecode/search/seq/cabs.hh index 8761bdd686..3f567b742c 100644 --- a/gecode/search/seq/cabs.hh +++ b/gecode/search/seq/cabs.hh @@ -39,6 +39,7 @@ #include #include +#ifdef GECODE_HAS_EXPERIMENTAL namespace Gecode { namespace Search { namespace Seq { /// Sequential layered beams, widening while discarded work can improve @@ -111,4 +112,6 @@ namespace Gecode { namespace Search { namespace Seq { #endif +#endif + // STATISTICS: search-seq diff --git a/gecode/search/seq/choice-expansion.hh b/gecode/search/seq/choice-expansion.hh index d1bd9e3d5b..da86e01502 100644 --- a/gecode/search/seq/choice-expansion.hh +++ b/gecode/search/seq/choice-expansion.hh @@ -39,6 +39,7 @@ #include #include +#ifdef GECODE_HAS_EXPERIMENTAL namespace Gecode { namespace Search { namespace Seq { /// One propagated child, interrupted preparation, or exhausted choice @@ -130,4 +131,6 @@ namespace Gecode { namespace Search { namespace Seq { #endif +#endif + // STATISTICS: search-seq diff --git a/gecode/search/seq/column.hh b/gecode/search/seq/column.hh index 1f8d58dc0e..a4fecf8da3 100644 --- a/gecode/search/seq/column.hh +++ b/gecode/search/seq/column.hh @@ -40,6 +40,7 @@ #include #include +#ifdef GECODE_HAS_EXPERIMENTAL namespace Gecode { namespace Search { namespace Seq { /// Sequential depth queues with retained alternatives and progressive quotas @@ -107,4 +108,6 @@ namespace Gecode { namespace Search { namespace Seq { #endif +#endif + // STATISTICS: search-seq diff --git a/gecode/search/seq/state-registry.hh b/gecode/search/seq/state-registry.hh index fb90e704be..52c09e1249 100644 --- a/gecode/search/seq/state-registry.hh +++ b/gecode/search/seq/state-registry.hh @@ -40,6 +40,7 @@ #include #include +#ifdef GECODE_HAS_EXPERIMENTAL namespace Gecode { namespace Search { namespace Seq { /// Persistent descriptor metadata, independent of pending search work @@ -102,4 +103,6 @@ namespace Gecode { namespace Search { namespace Seq { #endif +#endif + // STATISTICS: search-seq diff --git a/gecode/support/config.hpp.in b/gecode/support/config.hpp.in index 9c9588878f..f434087688 100644 --- a/gecode/support/config.hpp.in +++ b/gecode/support/config.hpp.in @@ -67,6 +67,9 @@ /* Whether CPProfiler support available */ #undef GECODE_HAS_CPPROFILER +/* Whether experimental preview APIs are available */ +#undef GECODE_HAS_EXPERIMENTAL + /* Whether to build FLOAT variables */ #undef GECODE_HAS_FLOAT_VARS diff --git a/misc/genchangelog.py b/misc/genchangelog.py index 9bf2db440f..f8cba0b8ba 100644 --- a/misc/genchangelog.py +++ b/misc/genchangelog.py @@ -74,7 +74,6 @@ def main() -> int: whatorder = ["new", "removed", "change", "bug", "performance", "documentation"] - hastext = defaultdict(int) text = defaultdict(str) versions = "" @@ -89,16 +88,17 @@ def flush_current_release() -> None: body = body + f"\\section {sid} Changes in Version {version} ({date})\n\n" body = body + f"{info}\n\n" - for mod in modorder: - if hastext[mod]: - body = body + " - " + modclear[mod] + "\n" - hastext[mod] = 0 - for what in whatorder: - k = f"{mod}-{what}" - if text[k] != "": - body = body + " - " + whatclear[what] + "\n" - body = body + text[k] - text[k] = "" + for experimental in (False, True): + if experimental and any(text[(True, mod, what)] for mod in modorder for what in whatorder): + body += f"\\subsection {sid}_experimental Experimental changes\n\n" + for mod in modorder: + if any(text[(experimental, mod, what)] for what in whatorder): + body += " - " + modclear[mod] + "\n" + for what in whatorder: + k = (experimental, mod, what) + if text[k]: + body += " - " + whatclear[what] + "\n" + text[k] + text[k] = "" body = body + "\n\n" lines = sys.stdin.readlines() @@ -156,12 +156,20 @@ def flush_current_release() -> None: issue = "" thanks = "" author = "" + experimental = False desc = "" more = "" while idx < len(lines): l = lines[idx] idx += 1 + m = re.fullmatch(r"Experimental:[\t ]*(.*)\n?", l) + if m: + value = m.group(1).rstrip("\n") + if value not in {"true", "false"}: + print("genchangelog: invalid Experimental value " + repr(value), file=sys.stderr) + return 1 + experimental = value == "true" m = re.search(r"Module:[\t ]*(.*)$", l) if m: mod = m.group(1) @@ -214,7 +222,6 @@ def flush_current_release() -> None: more = more.replace("&", "\\&") more = re.sub(r"\n([ \t]*)\n", r"\n\1
\n", more) - hastext[mod] = 1 rb = rankclear.get(rank, "") if bug != "": rb = rb + f", bugzilla entry {bug}" @@ -238,7 +245,7 @@ def flush_current_release() -> None: ) moreinfocnt += 1 - k = f"{mod}-{what}" + k = (experimental, mod, what) text[k] = text[k] + f" - {desc} ({rb})\n" + more current = l continue diff --git a/misc/gencurrentchangelog.py b/misc/gencurrentchangelog.py index 4d5ff7424c..6e00f024f6 100644 --- a/misc/gencurrentchangelog.py +++ b/misc/gencurrentchangelog.py @@ -68,9 +68,25 @@ def main() -> int: ] whatorder = ["new", "removed", "change", "bug", "performance", "documentation"] - hastext = defaultdict(int) text = defaultdict(str) + def flush_current_release() -> None: + for experimental in (False, True): + if experimental and any(text[(True, mod, what)] for mod in modorder for what in whatorder): + sys.stdout.write("

Experimental changes

\n") + sys.stdout.write("
    \n") + for mod in modorder: + if any(text[(experimental, mod, what)] for what in whatorder): + sys.stdout.write(f"
  • {modclear[mod]}\n
      \n") + for what in whatorder: + k = (experimental, mod, what) + if text[k]: + sys.stdout.write(f"
    • {whatclear[what]}\n
        " + text[k] + "
    • \n") + sys.stdout.write("
  • \n") + sys.stdout.write("
\n") + if not any(text[(True, mod, what)] for mod in modorder for what in whatorder): + break + lines = sys.stdin.readlines() idx = 0 current = None @@ -91,20 +107,7 @@ def main() -> int: if re.match(r"^\[RELEASE\]", l): if not first: - sys.stdout.write("
    \n") - for mod in modorder: - if hastext[mod]: - sys.stdout.write(f"
  • {modclear[mod]}\n
      \n") - hastext[mod] = 0 - for what in whatorder: - k = f"{mod}-{what}" - if text[k] != "": - sys.stdout.write(f"
    • {whatclear[what]}\n
        ") - sys.stdout.write(text[k]) - sys.stdout.write("
    • \n") - text[k] = "" - sys.stdout.write("
  • \n") - sys.stdout.write("
\n") + flush_current_release() return 0 first = False @@ -135,6 +138,7 @@ def main() -> int: if re.match(r"^\[ENTRY\]", l): entry_number += 1 + experimental = False desc = "" bug = "" rank = "" @@ -149,6 +153,13 @@ def main() -> int: l = l.replace("%Gecode", "Gecode") if re.search(r"\[DESCRIPTION\]", l): break + m = re.fullmatch(r"Experimental:[\t ]*(.*)\n?", l) + if m: + value = m.group(1).rstrip("\n") + if value not in {"true", "false"}: + print("gencurrentchangelog: invalid Experimental value " + repr(value), file=sys.stderr) + return 1 + experimental = value == "true" m = re.search(r"Module:[\t ]*(.*)$", l) if m: mod = m.group(1) @@ -204,16 +215,19 @@ def main() -> int: ) return 1 - hastext[mod] = 1 rb = rankclear[rank] if bug != "": rb = rb + f", bugzilla entry {bug}" if thanks != "": rb = rb + f", thanks to {thanks}" - k = f"{mod}-{what}" + k = (experimental, mod, what) text[k] = text[k] + f"
  • {desc}({rb})
  • \n" continue + if not first: + flush_current_release() + return 0 + sys.stdout.write( """Initial release (2005-12-06) diff --git a/misc/gentxtchangelog.py b/misc/gentxtchangelog.py index 8cffd2b604..d1d4d085de 100644 --- a/misc/gentxtchangelog.py +++ b/misc/gentxtchangelog.py @@ -76,9 +76,24 @@ def main() -> int: whatorder = ["new", "removed", "change", "bug", "performance", "documentation"] - hastext = defaultdict(int) text = defaultdict(str) + def flush_current_release() -> None: + sys.stdout.write(f"Changes in Version {version} ({date})\n\n") + sys.stdout.write(f"Scope:{info}") + for experimental in (False, True): + if experimental and any(text[(True, mod, what)] for mod in modorder for what in whatorder): + sys.stdout.write("Experimental changes\n") + for mod in modorder: + if any(text[(experimental, mod, what)] for what in whatorder): + sys.stdout.write(f"- {modclear[mod]}\n") + for what in whatorder: + k = (experimental, mod, what) + if text[k]: + sys.stdout.write(f" - {whatclear[what]}\n" + text[k]) + text[k] = "" + sys.stdout.write("\n------------------------------------------------------------------------------\n\n") + lines = sys.stdin.readlines() idx = 0 current = None @@ -99,21 +114,7 @@ def main() -> int: if re.match(r"^\[RELEASE\]", l): if not first: - sys.stdout.write(f"Changes in Version {version} ({date})\n\n") - sys.stdout.write(f"Scope:{info}") - - for mod in modorder: - if hastext[mod]: - sys.stdout.write(f"- {modclear[mod]}\n") - hastext[mod] = 0 - for what in whatorder: - k = f"{mod}-{what}" - if text[k] != "": - sys.stdout.write(f" - {whatclear[what]}\n") - sys.stdout.write(text[k]) - text[k] = "" - - sys.stdout.write("\n------------------------------------------------------------------------------\n\n") + flush_current_release() first = False version = "" @@ -144,6 +145,7 @@ def main() -> int: continue if re.match(r"^\[ENTRY\]", l): + experimental = False desc = "" bug = "" issue = "" @@ -158,6 +160,13 @@ def main() -> int: l = l.replace("%Gecode", "Gecode") if re.search(r"\[DESCRIPTION\]", l): break + m = re.fullmatch(r"Experimental:[\t ]*(.*)\n?", l) + if m: + value = m.group(1).rstrip("\n") + if value not in {"true", "false"}: + print("gentxtchangelog: invalid Experimental value " + repr(value), file=sys.stderr) + return 1 + experimental = value == "true" m = re.search(r"Module:[\t ]*(.*)$", l) if m: mod = m.group(1) @@ -193,7 +202,6 @@ def main() -> int: if desc.endswith("\n"): desc = desc[:-1] - hastext[mod] = 1 rb = rankclear.get(rank, "") if bug != "": rb = rb + f", bugzilla entry {bug}" @@ -202,10 +210,13 @@ def main() -> int: if thanks != "": rb = rb + f", thanks to {thanks}" - k = f"{mod}-{what}" + k = (experimental, mod, what) text[k] = text[k] + f" - {desc}({rb})\n" continue + if not first and any(text.values()): + flush_current_release() + sys.stdout.write( """Initial release (2005-12-06) diff --git a/misc/tidy.py b/misc/tidy.py index b84a7d593e..1948ccdaf5 100644 --- a/misc/tidy.py +++ b/misc/tidy.py @@ -61,6 +61,7 @@ class Entry: module: str what: str rank: str + experimental: str bug: str issue: str thanks: str @@ -144,6 +145,7 @@ def parse_changelog() -> tuple[list[Release], list[Entry]]: "module": "", "what": "", "rank": "", + "experimental": "false", "bug": "", "issue": "", "thanks": "", @@ -153,6 +155,7 @@ def parse_changelog() -> tuple[list[Release], list[Entry]]: "module": "Module", "what": "What", "rank": "Rank", + "experimental": "Experimental", "bug": "Bug", "issue": "Issue", "thanks": "Thanks", @@ -233,6 +236,8 @@ def validate_changelog_entries(generated: str) -> list[str]: errors.append(f"line {entry.line}: unknown module {entry.module!r}") if entry.what not in WHATS: errors.append(f"line {entry.line}: unknown change type {entry.what!r}") + if entry.experimental not in {"true", "false"}: + errors.append(f"line {entry.line}: invalid Experimental value {entry.experimental!r}") if entry.rank not in RANKS: errors.append(f"line {entry.line}: unknown rank {entry.rank!r}") @@ -470,10 +475,48 @@ def validate_current_changelog() -> list[str]: return errors +def validate_experimental_changelog() -> list[str]: + errors = [] + release = "[RELEASE]\nVersion: 1.0.0\nDate: 2000-01-01\n[DESCRIPTION]\nFixture.\n" + + def entry(description: str, experimental: str = "", module: str = "search", what: str = "new") -> str: + field = f"Experimental: {experimental}\n" if experimental else "" + return (f"[ENTRY]\nModule: {module}\nWhat: {what}\nRank: minor\n" + + field + f"[DESCRIPTION]\n{description}\n") + + # Mixed input order checks separation while preserving module/type order. + source = release + entry("Preview search change.", "true", what="change") + source += entry("Ordinary omitted.") + entry("Preview kernel.", "true", "kernel") + source += entry("Ordinary false.", "false") + entry("Preview search addition.", "true") + older = release.replace("1.0.0", "0.9.0") + entry("Older ordinary.") + for generator in (GEN_CHANGELOG, ROOT / "misc" / "gentxtchangelog.py", GEN_CURRENT_CHANGELOG): + for suffix in ("", older): + result = run(["uv", "run", str(generator)], stdin=source + suffix) + output = result.stdout + markers = ["Ordinary omitted.", "Ordinary false.", "Experimental changes", + "Preview kernel.", "Preview search addition.", "Preview search change."] + positions = [output.find(marker) for marker in markers] + if result.returncode or min(positions) < 0 or positions != sorted(positions): + errors.append(f"{generator.name}: experimental separation/order regression") + if output.count("Experimental changes") != 1: + errors.append(f"{generator.name}: experimental heading leaked across releases") + omitted = run(["uv", "run", str(generator)], stdin=release + entry("Ordinary.")) + explicit = run(["uv", "run", str(generator)], stdin=release + entry("Ordinary.", "false")) + if omitted.returncode or explicit.returncode or omitted.stdout != explicit.stdout or "Experimental changes" in omitted.stdout: + errors.append(f"{generator.name}: false/omitted historical rendering differs") + for value in ("TRUE", "False", "yes", "1", "", "true "): + invalid = entry("Invalid.", "true").replace("Experimental: true", f"Experimental: {value}") + result = run(["uv", "run", str(generator)], stdin=release + invalid) + if result.returncode == 0 or "invalid Experimental value" not in result.stderr: + errors.append(f"{generator.name}: accepted invalid Experimental {value!r}") + return errors + + def check_changelog(*, fix: bool) -> int: generated = generate_changelog() errors = validate_changelog_entries(generated) errors.extend(validate_current_changelog()) + errors.extend(validate_experimental_changelog()) if is_tracked(CHANGELOG_HH): if CHANGELOG_HH.exists() and CHANGELOG_HH.read_text() == generated: diff --git a/test/search.cpp b/test/search.cpp index 711c0c9aac..95a0fa48e0 100644 --- a/test/search.cpp +++ b/test/search.cpp @@ -42,7 +42,9 @@ #include #include "test/test.hh" +#ifdef GECODE_HAS_EXPERIMENTAL #include "examples/tsptw-transition.hh" +#endif #include #include @@ -221,6 +223,7 @@ namespace Test { } } }; +#ifdef GECODE_HAS_EXPERIMENTAL template static bool heuristic(void) { using Guidance=Gecode::Search::HeuristicGuidance; @@ -280,6 +283,8 @@ namespace Test { } return false; } +#endif + static bool replay(void) { CompletionSpace root(true,false); Gecode::Search::Options o; @@ -324,10 +329,15 @@ namespace Test { public: CompletionNative(void) : Base("Search::Completion::Native", TestTags::check()) {} bool run(void) override { - return replay() && heuristic<0>() && heuristic<1>() && heuristic<2>(); + return replay() +#ifdef GECODE_HAS_EXPERIMENTAL + && heuristic<0>() && heuristic<1>() && heuristic<2>() +#endif + ; } } completionNative; +#ifdef GECODE_HAS_EXPERIMENTAL /// The example's real n-ary transitions survive cloning and continuation. class TSPTWTransitionTest : public Base { using Transition=TSPTW::Transition; @@ -1551,6 +1561,8 @@ namespace Test { } } columnTest; +#endif + /// Scalar integer objective used for comparison tests class MinObjective : public IntMinimizeSpace { public: @@ -1823,6 +1835,7 @@ namespace Test { } }; +#ifdef GECODE_HAS_EXPERIMENTAL // Exercise the final alternative after publication, including a commit // exception after the selected parent has transferred to that child. static bool native(unsigned int kind, bool throwing) { @@ -1869,6 +1882,8 @@ namespace Test { return destroyed; } +#endif + static bool archive_compatible(void) { Recorder* recorder = new Recorder; Gecode::Search::BranchFeedback feedback(recorder); @@ -1891,10 +1906,13 @@ namespace Test { public: BranchFeedback(void) : Base("Search::BranchFeedback", TestTags::check()) {} bool run(void) override { - return archive_compatible() && - native(0U,false) && native(0U,true) && + return archive_compatible() +#ifdef GECODE_HAS_EXPERIMENTAL + && native(0U,false) && native(0U,true) && native(1U,false) && native(1U,true) && - native(2U,false) && native(2U,true); + native(2U,false) && native(2U,true) +#endif + ; } }; @@ -2401,6 +2419,7 @@ namespace Test { } return ok && !engine.stopped() && (solutions == root.solutions()); } +#ifdef GECODE_HAS_EXPERIMENTAL bool native(unsigned int kind, unsigned int node, bool stopping) { Gecode::Search::WorkerControl control(node == 0U ? 0U : 1U); Pause pause(control,node); @@ -2442,15 +2461,19 @@ namespace Test { } return paused && found && optimum==root.score(2) && !engine->stopped(); } +#endif + public: WorkerControlSequentialPause(void) : Base("Search::WorkerControl::SequentialPause", TestTags::check()) {} bool run(void) override { #ifdef GECODE_HAS_THREADS +#ifdef GECODE_HAS_EXPERIMENTAL std::lock_guard lock(heuristic_ownership_mutex); for (unsigned int kind : {0U,1U,2U}) if (!native(kind,0U,false) || !native(kind,1U,false) || !native(kind,0U,true)) return false; +#endif return check(0U) && check(1U) && check(0U) && check(1U); #else From 989b10d8f4b3635b87366120455688724ea326bf Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 13 Oct 2026 12:10:13 +0200 Subject: [PATCH 06/24] Record validated open-search rebase onto current open-cp Zdev-Change-Id: Z3b4a698a6fbd8abd10a3b7bdc565ae3f5b3308cd222245962cbcc7c6d8f503cf --- .zdev/open-search/area.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zdev/open-search/area.toml b/.zdev/open-search/area.toml index 747216d3ba..5049c014ab 100644 --- a/.zdev/open-search/area.toml +++ b/.zdev/open-search/area.toml @@ -5,4 +5,4 @@ objective = "Add A* and complete anytime beam search to Gecode for ordinary and branch = "feature/open-search" lifecycle = "open" parent = "open-cp" -base_commit = "55dc729c80ed5d60a742177612c870bdf366c532" +base_commit = "47fc1640d14af7c634cecf3add5b2dce57ccbf09" From 3433c222834ad8e810b71a7f349dd8146f8d843e Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 13 Oct 2026 13:11:34 +0200 Subject: [PATCH 07/24] Add tasks to open-search Zdev-Change-Id: Z3b6d8222c576b70bbe9e85ec7b92b5365cefe2eaa836ed4adf89b501642550be --- .zdev/open-search/TASKS.md | 5 ++- ...ration-and-completion-contracts-and-com.md | 41 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 .zdev/open-search/tasks/033-repair-reviewed-preparation-and-completion-contracts-and-com.md diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index 72aad3b826..1c4f0f166d 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -2,8 +2,8 @@ # Tasks: open-search -- Total: 32 -- Ready: 0 +- Total: 33 +- Ready: 1 - Blocked: 0 - Done: 32 @@ -41,3 +41,4 @@ | [open-search-030](tasks/030-evaluate-sequential-cadds-retained-fragment-search.md) | Evaluate sequential CADDS retained-fragment search | done | open-search-018, open-search-025 | | [open-search-031](tasks/031-reconstruct-standalone-native-search-on-open-cp.md) | Reconstruct standalone native search on open-cp | done | — | | [open-search-032](tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md) | Gate heuristic search as an experimental preview and separate experimental changelog entries | done | — | +| [open-search-033](tasks/033-repair-reviewed-preparation-and-completion-contracts-and-com.md) | Repair reviewed preparation and completion contracts and compact CABS victims | ready | — | diff --git a/.zdev/open-search/tasks/033-repair-reviewed-preparation-and-completion-contracts-and-com.md b/.zdev/open-search/tasks/033-repair-reviewed-preparation-and-completion-contracts-and-com.md new file mode 100644 index 0000000000..c4c3258006 --- /dev/null +++ b/.zdev/open-search/tasks/033-repair-reviewed-preparation-and-completion-contracts-and-com.md @@ -0,0 +1,41 @@ ++++ +schema_version = 1 +id = "open-search-033" +key = "repair-reviewed-search-contracts" +area = "open-search" +status = "open" +complexity = "advanced" +afk = true +priority = "high" +blocked_by = [] ++++ +# Repair reviewed preparation and completion contracts and compact CABS victims + +## Outcome + +Native and ordinary search respect completion validity, nested stop/statistics and preparation outcomes; CABS removes dominated candidates with one compaction. + +## Context + +The approved Astra review found three reproducible defects: a cached existential completion survives later dependent branching/cuts and yields false solutions; nested completion falls through ordinary DFS choice construction with default stop/statistics; successful preparation can leave a failed space whose null choice is dereferenced. Begin with gecode/search/completion.hh, seq/choice-expansion.hh, kernel brancher lifecycle, ordinary DFS/BAB and test/search.cpp. Reuse the existing fixtures. The user approved applying these findings and the local CABS compaction. The review and four small reproducers are available in /private/tmp/open-search-astra-review-20261013.md and /private/tmp/open-search-*-probe.cpp in this session. + +## Boundaries + +- Preserve native independence from Model, portfolio and open lists, and preserve separate ranking, admissible bounds and optional pruning. +- Fix the lifecycle behavior rather than merely documenting known false solutions away; use the smallest coherent Gecode-compatible protocol. Do not introduce a universal search policy framework. +- Keep existing supported search/completion composition sound; inspect ordinary search and replay paths affected by the hooks. Do not modify feature/comparison or sibling worktrees. +- Assess feature/comparison as the common non-mutating objective comparison seam; retain compatibility and record a concise recommendation without importing unrelated branch changes. + +## Done when + +- [ ] Later dependent decisions and incumbent restrictions cannot reuse an invalid completion witness or publish infeasible solutions. +- [ ] Nested completion obeys enclosing stops, resumes correctly and accounts for its actual search statistics. +- [ ] Successful preparation has an explicit enforced status contract and cannot cause null-choice dereference on failure or completion. +- [ ] CABS replaces repeated dominated-victim erasures with one compaction while preserving descriptor accounting and search coverage. +- [ ] Completion state transitions are readable, focused regressions cover the exhibited defects, and comparison integration recommendation identifies current overlap and remaining dependencies. + +## Validation + +- Add focused behavior regressions in the existing test/search.cpp fixtures for the three reproduced defects, including ordinary/native and nested resumption paths as applicable. +- Build and run existing CMake check in experimental ON and OFF configurations; run the existing native AStar/CABS/Column/TSPTW and completion checks and tiny native examples. +- Run focused affected tests under current ASan+UBSan using the project sanitizer option when the toolchain permits; report any concrete unavailable tooling. Check diff whitespace on touched source. No new test harness or broad benchmark campaign. From 3a1bc84c8d805b92cdcd624ad210e5cc8a14b3f7 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 13 Oct 2026 13:51:06 +0200 Subject: [PATCH 08/24] Repair search completion and keep the preview compatible with Gecode 6.5 Invalidate completion witnesses after dependent decisions and incumbent cuts, propagate nested stop/statistics through ordinary search, and handle prepared terminal spaces. Compact CABS dominated candidates once. Use the existing exact scalar guidance for native incumbent acceptance and remove the extracted Gecode 7 comparison API. Preserve ordinary Gecode 6.x model compatibility and document its existing hidden-completion admission limitation. Independently verified experimental ON 479/479, OFF 475/475, focused and fresh ASan+UBSan 16/16, native examples, independent tiny TSPTW checks and CADDS witness. Zdev-Change-Id: Z7ac9860f3c380e35cd3ad5e9e7581b300387e90413a48deae4c40e1d6614e440 --- .zdev/open-search/TASKS.md | 6 +- .zdev/open-search/brief.md | 12 + ...ration-and-completion-contracts-and-com.md | 24 +- docs/astar.md | 27 +- docs/cabs.md | 15 +- docs/column.md | 9 +- examples/astar.cpp | 5 - examples/best-first.cpp | 5 - examples/tsptw-transition.hh | 7 - examples/tsptw.cpp | 5 - gecode/kernel/core.cpp | 29 +- gecode/kernel/core.hpp | 40 +- gecode/kernel/exception.cpp | 3 - gecode/kernel/exception.hpp | 7 - gecode/minimodel.hh | 6 - gecode/minimodel/optimize.cpp | 26 -- gecode/search.hh | 23 +- gecode/search/astar.cpp | 5 +- gecode/search/cabs.cpp | 17 +- gecode/search/column.cpp | 5 +- gecode/search/completion.hh | 81 ++-- gecode/search/exception.cpp | 3 - gecode/search/exception.hpp | 5 - gecode/search/par/bab.hh | 1 + gecode/search/par/bab.hpp | 43 ++- gecode/search/par/dfs.hh | 1 + gecode/search/par/dfs.hpp | 23 +- gecode/search/par/engine.hh | 2 + gecode/search/par/engine.hpp | 2 +- gecode/search/par/path.hpp | 2 + gecode/search/par/pbs.hh | 1 + gecode/search/par/pbs.hpp | 12 +- gecode/search/seq/bab.hh | 2 + gecode/search/seq/bab.hpp | 34 +- gecode/search/seq/choice-expansion.hh | 5 +- gecode/search/seq/dfs.hh | 2 + gecode/search/seq/dfs.hpp | 21 +- gecode/search/seq/lds.hh | 2 + gecode/search/seq/lds.hpp | 25 +- gecode/search/seq/path.hpp | 2 + gecode/search/seq/rbs.cpp | 10 +- gecode/search/support.hh | 18 +- misc/tsptw-cadds-study.cpp | 13 +- test/search.cpp | 364 ++++++++++++------ 44 files changed, 596 insertions(+), 354 deletions(-) diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index 1c4f0f166d..4f57a40099 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -3,9 +3,9 @@ # Tasks: open-search - Total: 33 -- Ready: 1 +- Ready: 0 - Blocked: 0 -- Done: 32 +- Done: 33 | ID | Task | State | Blocked by | | --- | --- | --- | --- | @@ -41,4 +41,4 @@ | [open-search-030](tasks/030-evaluate-sequential-cadds-retained-fragment-search.md) | Evaluate sequential CADDS retained-fragment search | done | open-search-018, open-search-025 | | [open-search-031](tasks/031-reconstruct-standalone-native-search-on-open-cp.md) | Reconstruct standalone native search on open-cp | done | — | | [open-search-032](tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md) | Gate heuristic search as an experimental preview and separate experimental changelog entries | done | — | -| [open-search-033](tasks/033-repair-reviewed-preparation-and-completion-contracts-and-com.md) | Repair reviewed preparation and completion contracts and compact CABS victims | ready | — | +| [open-search-033](tasks/033-repair-reviewed-preparation-and-completion-contracts-and-com.md) | Repair reviewed preparation and completion contracts and compact CABS victims | done | — | diff --git a/.zdev/open-search/brief.md b/.zdev/open-search/brief.md index ff609adbf8..30c839dc96 100644 --- a/.zdev/open-search/brief.md +++ b/.zdev/open-search/brief.md @@ -1,3 +1,15 @@ +## Gecode 6.5 comparison boundary (2026-10-13) + +The user clarified that `feature/comparison` targets Gecode 7. The 6.5 +experimental preview must avoid that dependency. Native heuristic guidance +already provides an exact scalar objective on solutions, so use that value +and its objective direction for incumbent acceptance if sufficient. Remove +extracted Gecode 7 comparison APIs rather than making ordinary Gecode 6.x +models implement them. Ranking remains independent of sound bounds and +solution acceptance. A full ordinary-engine comparison migration is deferred +to Gecode 7. If a required search capability truly depends on it, identify +that dependency before including it in the 6.5 preview. + ## Experimental preview for 6.5.0 (2026-10-13) The user approved a default-off experimental build switch and experimental diff --git a/.zdev/open-search/tasks/033-repair-reviewed-preparation-and-completion-contracts-and-com.md b/.zdev/open-search/tasks/033-repair-reviewed-preparation-and-completion-contracts-and-com.md index c4c3258006..e14a257e8d 100644 --- a/.zdev/open-search/tasks/033-repair-reviewed-preparation-and-completion-contracts-and-com.md +++ b/.zdev/open-search/tasks/033-repair-reviewed-preparation-and-completion-contracts-and-com.md @@ -3,7 +3,7 @@ schema_version = 1 id = "open-search-033" key = "repair-reviewed-search-contracts" area = "open-search" -status = "open" +status = "done" complexity = "advanced" afk = true priority = "high" @@ -24,18 +24,28 @@ The approved Astra review found three reproducible defects: a cached existential - Preserve native independence from Model, portfolio and open lists, and preserve separate ranking, admissible bounds and optional pruning. - Fix the lifecycle behavior rather than merely documenting known false solutions away; use the smallest coherent Gecode-compatible protocol. Do not introduce a universal search policy framework. - Keep existing supported search/completion composition sound; inspect ordinary search and replay paths affected by the hooks. Do not modify feature/comparison or sibling worktrees. -- Assess feature/comparison as the common non-mutating objective comparison seam; retain compatibility and record a concise recommendation without importing unrelated branch changes. +- Target the experimental Gecode 6.5 preview. Do not depend on feature/comparison, which targets Gecode 7. Remove the extracted Gecode 7 comparison API from this branch if native scalar guidance suffices; retain existing Gecode 6.x ordinary-model compatibility and record the comparison assessment. ## Done when -- [ ] Later dependent decisions and incumbent restrictions cannot reuse an invalid completion witness or publish infeasible solutions. -- [ ] Nested completion obeys enclosing stops, resumes correctly and accounts for its actual search statistics. -- [ ] Successful preparation has an explicit enforced status contract and cannot cause null-choice dereference on failure or completion. -- [ ] CABS replaces repeated dominated-victim erasures with one compaction while preserving descriptor accounting and search coverage. -- [ ] Completion state transitions are readable, focused regressions cover the exhibited defects, and comparison integration recommendation identifies current overlap and remaining dependencies. +- [x] Later dependent decisions and incumbent restrictions cannot reuse an invalid completion witness or publish infeasible solutions. +- [x] Nested completion obeys enclosing stops, resumes correctly and accounts for its actual search statistics. +- [x] Successful preparation has an explicit enforced status contract and cannot cause null-choice dereference on failure or completion. +- [x] CABS replaces repeated dominated-victim erasures with one compaction while preserving descriptor accounting and search coverage. +- [x] Completion state transitions are readable, focused regressions cover the exhibited defects, and native incumbent acceptance no longer requires the Gecode 7 Space comparison API. Record any separate legacy ordinary-search limitation honestly. ## Validation - Add focused behavior regressions in the existing test/search.cpp fixtures for the three reproduced defects, including ordinary/native and nested resumption paths as applicable. - Build and run existing CMake check in experimental ON and OFF configurations; run the existing native AStar/CABS/Column/TSPTW and completion checks and tiny native examples. - Run focused affected tests under current ASan+UBSan using the project sanitizer option when the toolchain permits; report any concrete unavailable tooling. Check diff whitespace on touched source. No new test harness or broad benchmark campaign. + +## Result + +Repaired completion invalidation, nested stop/statistics and prepared terminal handling across ordinary/native engines; compacted CABS victims once. Removed extracted Gecode 7 comparison API: native exact scalar guidance supports the Gecode 6.5 experimental preview. Documented legacy ordinary hidden-completion admission limitation. + +Validation: + +- Independent review passed against unchanged snapshot W229fed27ab5740d6; experimental ON 479/479 and OFF 475/475. +- Focused main-thread checks 16/16 and fresh Debug ASan+UBSan 16/16 passed without diagnostics; all three new regressions failed before fixes. +- Tiny native examples, independently checked TSPTW formulations and engines, rebuilt CADDS witness, and diff whitespace passed. clang-format and clang-tidy unavailable. diff --git a/docs/astar.md b/docs/astar.md index 94223e458f..a1af084546 100644 --- a/docs/astar.md +++ b/docs/astar.md @@ -6,8 +6,8 @@ Build the library with `-DGECODE_ENABLE_EXPERIMENTAL=ON` (CMake) or configuration header reports availability. Preview APIs may change or be removed in future feature releases; the correctness contracts below still apply. -`AStar` searches an ordinary `Space` subclass with its existing branching, -`compare`, and `constrain` methods. It returns caller-owned, strictly improving +`AStar` searches an ordinary `Space` subclass with its existing branching +and `constrain` methods. It returns caller-owned, strictly improving solutions. The native API needs only the search and integer libraries. [The closed example](../examples/astar.cpp) compares A*, CABS, and BAB on the same bounded model with objectives 4, -2, -7, and 1. Continuing A* proves -7 optimal. @@ -33,9 +33,16 @@ The bound must be optimistic **total** objective value on a propagated stable space: a lower bound for `MINIMIZE`, an upper bound for `MAXIMIZE`, and the exact objective on every solution. A transition model supplies its own prefix plus remaining-cost bound. The engine adds no cost and compares signed 64-bit values -directly. The bound, `compare`, and strict-improvement `constrain` must describe +directly. The bound and strict-improvement `constrain` must describe the same compatible scalar integer objective. The caller must justify the bound -and keep callback arithmetic in range. +and keep callback arithmetic in range. Solution acceptance uses this exact +scalar value, independently of rank; no Space comparison API is required. + +Ordinary Gecode 6.x BAB, restart and portfolio engines retain their existing +incumbent admission test: they apply `constrain` to a temporary clone and check +propagation-visible failure. An external incumbent whose improvement can only +be established by exhausting hidden completion search is not guaranteed +admission by that test. Native scalar adoption uses the exact objective value. A* selects the best total bound first. An optional third guidance argument supplies a signed 64-bit rank; lower ranks break equal-bound ties, followed by @@ -72,8 +79,8 @@ Space, while sound total bounds and the compatible incumbent restriction prune. Generated solutions are still published immediately. Neither ordering guarantees that the first goal is optimal or has the best rank. Natural finite-tree exhaustion with `stopped()==false` proves the best accepted incumbent optimal, -or infeasibility when none exists, provided the bound, comparison and optional -state-pruning contracts preserve coverage. Best-first uses the callback lifetime, +or infeasibility when none exists, provided the bound, strict improvement and +optional state-pruning contracts preserve coverage. Best-first uses the callback lifetime, numeric, ownership, stop, exception and descriptor contracts below. The native factory is `Search::bestfirstengine(root, guidance, ordering, options, pruning)`; `Search::astarengine` retains its signature and selects bound-first. @@ -96,7 +103,7 @@ and weighted-search repair are outside this API. Guidance is copied into engines and builders. Callbacks read the space passed to them without posting constraints. Captured dependencies must outlive every engine and builder using them; do not capture another space's local variables. -If guidance, comparison, or a model operation throws during search, destroy the +If guidance or a model operation throws during search, destroy the engine before using it again. Constructor failure leaves the supplied root with the caller. After successful construction, `Options::clone=false` transfers the root to the engine; the default clones it and leaves the supplied root with the @@ -113,9 +120,9 @@ while (std::unique_ptr solution{engine->next()}) { } ``` -Equivalent or worse external solutions do not replace the incumbent; -incomparable ones raise `Search::Incomparable`. Each accepted incumbent has an -independent owned copy. Queued parents +The exact scalar guidance value decides incumbent acceptance in the declared +objective direction. Equal or worse external solutions do not replace the +incumbent. Each accepted incumbent has an independent owned copy. Queued parents and saved choices stay unchanged while alternatives remain. The engine commits each original alternative on an owned child, cloning the selected parent for earlier alternatives and transferring it for the final one. It then applies a diff --git a/docs/cabs.md b/docs/cabs.md index c6f7cd1811..56cf250374 100644 --- a/docs/cabs.md +++ b/docs/cabs.md @@ -6,8 +6,8 @@ Build the library with `-DGECODE_ENABLE_EXPERIMENTAL=ON` (CMake) or configuration header reports availability. Preview APIs may change or be removed in future feature releases; the correctness contracts below still apply. -`CABS` searches an ordinary `Space` subclass with its existing branching, -`compare`, and strict-improvement `constrain` methods. It needs no Model, +`CABS` searches an ordinary `Space` subclass with its existing branching +and strict-improvement `constrain` methods. It needs no Model, portfolio, or open list. Like [A*](astar.md), it returns caller-owned, strictly improving scalar integer solutions through `next()`. @@ -29,8 +29,8 @@ if (search.stopped()) { The bound is an optimistic **total** objective value on a stable propagated space: lower for minimization, upper for maximization, and exact on solutions. -The engine adds no prefix cost. Bounds, comparison, and constraints must agree -on the same scalar integer objective. Values are signed 64-bit integers and +The engine adds no prefix cost. Bounds and strict-improvement constraints must +agree on the same scalar integer objective. Values are signed 64-bit integers and compared directly; callback arithmetic must stay in range. Callbacks read only the supplied space, must not post constraints, and must not capture another space's local variables. Captured dependencies must outlive engines and builders. @@ -96,11 +96,12 @@ counts other than one and rejects tracers before taking root ownership. The default `Options::clone=true` keeps the supplied root with the caller; `clone=false` transfers it after successful construction. Returned solutions and -the retained incumbent have independent ownership. Equivalent or worse external -incumbents are ignored, and incomparable ones raise `Search::Incomparable`. +the retained incumbent have independent ownership. The exact scalar guidance +value decides acceptance in the declared objective direction; equal or worse +external incumbents are ignored. Cuts affect committed children, leaving saved source choices and the restart root unchanged. Already inherited cuts are not repeated, so retained completion -branchers can finish. If a callback, comparison, or model operation throws, +branchers can finish. If a callback or model operation throws, destroy the engine before using it again. The engine stores cloned states and reuses each selected parent for its final diff --git a/docs/column.md b/docs/column.md index f5523369e9..f6a53125eb 100644 --- a/docs/column.md +++ b/docs/column.md @@ -7,7 +7,7 @@ configuration header reports availability. Preview APIs may change or be removed in future feature releases; the correctness contracts below still apply. `Column` searches ordinary Spaces with sequential depth queues. It requires -no Model declaration or compilation. The Space supplies `compare` and strict +no Model declaration or compilation. The Space supplies strict-improvement `constrain`, and `Search::HeuristicGuidance` supplies an optimistic total bound: a lower bound for minimization or upper bound for maximization, exact on a solution. The engine adds no prefix cost. Guidance follows the [A* callback @@ -33,7 +33,8 @@ configuration. Builders copy callbacks and quotas. Root ownership follows `options.clone`: the default preserves the caller's root; false transfers it after configuration validation. Returned solutions and external incumbents are independently owned clones. `constrain(incumbent)` adopts a compatible, -strictly improving incumbent and ignores equivalent or worse values. +strictly improving incumbent using the exact scalar guidance value; it ignores +equal or worse values. A cycle visits successive committed depths from zero. At each depth it selects up to the positive quota of parents, finishes every original n-ary Choice, and @@ -78,8 +79,8 @@ node owns a stable Space; the engine reuses each selected parent for its final alternative. Descriptor vectors and queue/container capacity add storage. `c_d` and `a_d` do not enable recomputation. No-good extraction is empty. Only one expanded thread and no tracer are supported, with explicit exceptions -for unsupported settings and zero initial quota. If a callback, comparison or -model operation throws, destroy the engine before using it again. +for unsupported settings and zero initial quota. If a callback or model +operation throws, destroy the engine before using it again. Build `astar` to run progressive and fixed quotas against the same misleading rank model used by A*/BAB/CABS. The [TSPTW example](../examples/data/tsptw/README.md) diff --git a/examples/astar.cpp b/examples/astar.cpp index 455db17d14..ad03da2a53 100644 --- a/examples/astar.cpp +++ b/examples/astar.cpp @@ -64,11 +64,6 @@ class Alternative : public Space { objective.update(*this,s.objective); } Space* copy(void) override { return new Alternative(*this); } - SpaceComparison compare(const Space& s0) const override { - const auto& s = dynamic_cast(s0); - if (objective.val() == s.objective.val()) return SC_EQUIVALENT; - return objective.val() < s.objective.val() ? SC_BETTER : SC_WORSE; - } void constrain(const Space& s0) override { const auto& s = static_cast(s0); rel(*this,objective,IRT_LE,s.objective.val()); diff --git a/examples/best-first.cpp b/examples/best-first.cpp index d8af29d163..73b5fbaa89 100644 --- a/examples/best-first.cpp +++ b/examples/best-first.cpp @@ -80,11 +80,6 @@ class Additive : public Space { objective.update(*this,s.objective); } Space* copy(void) override { return new Additive(*this); } - SpaceComparison compare(const Space& s0) const override { - const auto& s=static_cast(s0); - if (objective.val()==s.objective.val()) return SC_EQUIVALENT; - return objective.val()(s0).objective.val()); } diff --git a/examples/tsptw-transition.hh b/examples/tsptw-transition.hh index a2f22d6dd1..d1b55c1f66 100644 --- a/examples/tsptw-transition.hh +++ b/examples/tsptw-transition.hh @@ -33,7 +33,6 @@ namespace TSPTW { std::shared_ptr measurements=nullptr); Transition(Transition& s); Gecode::Space* copy(void) override { return new Transition(*this); } - Gecode::SpaceComparison compare(const Gecode::Space& s) const override; void constrain(const Gecode::Space& s) override; std::vector tour(void) const; int cost(void) const { return prefix_cost; } @@ -334,12 +333,6 @@ namespace TSPTW { return feasible; } - inline Gecode::SpaceComparison - Transition::compare(const Gecode::Space& s0) const { - const auto& s=static_cast(s0); - if (cost()==s.cost()) return Gecode::SC_EQUIVALENT; - return cost()(s0).cost()); diff --git a/examples/tsptw.cpp b/examples/tsptw.cpp index 9acf519a12..26acc12618 100644 --- a/examples/tsptw.cpp +++ b/examples/tsptw.cpp @@ -114,11 +114,6 @@ class TSPTWClosed : public Space { return static_cast(s).total.min(); }}; } - SpaceComparison compare(const Space& s0) const override { - const auto& s=static_cast(s0); - if (total.val()==s.total.val()) return SC_EQUIVALENT; - return total.val()(s0); rel(*this,total,IRT_LE,s.total.val()); diff --git a/gecode/kernel/core.cpp b/gecode/kernel/core.cpp index 05f3b57ae5..fa05fccd20 100644 --- a/gecode/kernel/core.cpp +++ b/gecode/kernel/core.cpp @@ -42,6 +42,7 @@ */ #include +#include namespace Gecode { @@ -571,6 +572,9 @@ namespace Gecode { return true; } + bool + Brancher::commit_restricts(void) const { return true; } + bool Brancher::retained(void) const { return false; } @@ -579,8 +583,14 @@ namespace Gecode { bool Space::prepare(Search::Statistics& stat, const Search::Options& options) { - return b_status == Brancher::cast(&bl) || - b_status->prepare(*this,stat,options); + while (!failed() && b_status != Brancher::cast(&bl)) { + Brancher* active = b_status; + if (!active->prepare(*this,stat,options)) + return false; + if (status(stat) != SS_BRANCH || b_status == active) + return true; + } + return true; } void @@ -650,6 +660,9 @@ namespace Gecode { if (failed()) return; if (Brancher* b = brancher(c.bid)) { + // Resolve the archived choice before resetting the brancher cursors. + if (retained_branchers && b->commit_restricts()) + invalidate_branchers(); // There is a matching brancher if (pc.p.bid_sc & sc_trace) { TraceRecorder* tr = findtracerecorder(); @@ -681,6 +694,9 @@ namespace Gecode { if (failed()) return; if (Brancher* b = brancher(c.bid)) { + // Resolve the archived choice before resetting the brancher cursors. + if (retained_branchers && b->commit_restricts()) + invalidate_branchers(); // There is a matching brancher if (pc.p.bid_sc & sc_trace) { TraceRecorder* tr = findtracerecorder(); @@ -931,17 +947,14 @@ namespace Gecode { Space::constrain(const Space&) { } - SpaceComparison - Space::compare(const Space&) const { - throw SpaceNoComparison("Space::compare"); - } - bool Space::master(const MetaInfo& mi) { switch (mi.type()) { case MetaInfo::RESTART: - if (mi.last() != nullptr) + if (mi.last() != nullptr) { + invalidate_branchers(); constrain(*mi.last()); + } mi.nogoods().post(*this); // Perform a restart even if a solution has been found return true; diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index 4b8da0c627..77efb6647a 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -53,19 +53,6 @@ namespace Gecode { - /** - * \brief Result of comparing two spaces according to their objectives - * - * Adding Space::compare changes the ordinary C++ ABI of Space by extending - * its virtual function table. - */ - enum SpaceComparison { - SC_BETTER, ///< The receiver has a better objective - SC_EQUIVALENT, ///< Both objectives are equivalent - SC_WORSE, ///< The receiver has a worse objective - SC_INCOMPARABLE ///< Neither objective dominates the other - }; - class Space; /** @@ -1501,10 +1488,17 @@ namespace Gecode { * */ virtual bool status(const Space& home) const = 0; - /// Prepare a choice; false suspends search without changing its path. + /** + * Prepare a choice; false suspends search without changing its path. + * True completes preparation and may leave the space failed, solved, or + * branching. Space::prepare propagates and prepares a newly active brancher. + */ GECODE_KERNEL_EXPORT virtual bool prepare(Space& home, Search::Statistics& stat, const Search::Options& options); + /// Whether committing a choice can change feasibility (default: true). + GECODE_KERNEL_EXPORT + virtual bool commit_restricts(void) const; /// Whether an exhausted brancher must survive subsequent restrictions. GECODE_KERNEL_EXPORT virtual bool retained(void) const; @@ -2185,24 +2179,6 @@ namespace Gecode { * \ingroup TaskModelScript */ GECODE_KERNEL_EXPORT virtual void constrain(const Space& best); - /** - * \brief Compare this space's objective with \a other - * - * Comparison is read-only and receiver-relative. The model determines - * what objective information suffices for a sound result. The default - * implementation throws SpaceNoComparison; SC_INCOMPARABLE is an ordering - * result rather than missing information. - * - * Optimization models overriding constrain with a different objective - * must also override compare. SC_BETTER must be irreflexive and transitive, - * SC_WORSE its reverse, and SC_EQUIVALENT transitive and interchangeable - * when comparing a third solution. On objective-complete solutions, - * SC_BETTER must agree with the strict improvement restriction posted by - * constrain. A better incumbent's restriction must admit a subset of the - * solutions admitted by the old restriction. - */ - GECODE_KERNEL_EXPORT virtual SpaceComparison - compare(const Space& other) const; /** * \brief Master configuration function for meta search engines * diff --git a/gecode/kernel/exception.cpp b/gecode/kernel/exception.cpp index 994d76f9c4..e16c6cd61a 100644 --- a/gecode/kernel/exception.cpp +++ b/gecode/kernel/exception.cpp @@ -47,9 +47,6 @@ namespace Gecode { SpaceNoBrancher::SpaceNoBrancher(const char* l) : Exception(l,"Attempt to commit with no brancher") {} - SpaceNoComparison::SpaceNoComparison(const char* l) - : Exception(l,"Space does not support comparison") {} - SpaceIllegalAlternative::SpaceIllegalAlternative(const char* l) : Exception(l,"Attempt to commit with illegal alternative") {} diff --git a/gecode/kernel/exception.hpp b/gecode/kernel/exception.hpp index d118b10a9f..792f4975e6 100644 --- a/gecode/kernel/exception.hpp +++ b/gecode/kernel/exception.hpp @@ -68,13 +68,6 @@ namespace Gecode { SpaceNoBrancher(const char* l); }; - /// %Exception: Comparison not implemented by space - class GECODE_KERNEL_EXPORT SpaceNoComparison : public Exception { - public: - /// Initialize with location \a l - SpaceNoComparison(const char* l); - }; - /// %Exception: Commit with illegal alternative class GECODE_KERNEL_EXPORT SpaceIllegalAlternative : public Exception { public: diff --git a/gecode/minimodel.hh b/gecode/minimodel.hh index 79c778833b..5b7bc145d0 100755 --- a/gecode/minimodel.hh +++ b/gecode/minimodel.hh @@ -2424,9 +2424,6 @@ namespace Gecode { /// Member function constraining according to decreasing cost GECODE_MINIMODEL_EXPORT virtual void constrain(const Space& best); - /// Compare integer costs (both costs must be assigned) - GECODE_MINIMODEL_EXPORT - virtual SpaceComparison compare(const Space& other) const; /// Return variable with current cost virtual IntVar cost(void) const = 0; }; @@ -2444,9 +2441,6 @@ namespace Gecode { /// Member function constraining according to increasing cost GECODE_MINIMODEL_EXPORT virtual void constrain(const Space& best); - /// Compare integer costs (both costs must be assigned) - GECODE_MINIMODEL_EXPORT - virtual SpaceComparison compare(const Space& other) const; /// Return variable with current cost virtual IntVar cost(void) const = 0; }; diff --git a/gecode/minimodel/optimize.cpp b/gecode/minimodel/optimize.cpp index 9e159b364f..6442ada179 100755 --- a/gecode/minimodel/optimize.cpp +++ b/gecode/minimodel/optimize.cpp @@ -35,14 +35,6 @@ namespace Gecode { - static SpaceComparison - compare_int(int x, int y, bool minimize) { - if (x == y) - return SC_EQUIVALENT; - return ((x < y) == minimize) ? SC_BETTER : SC_WORSE; - } - - void IntMinimizeSpace::constrain(const Space& _best) { const IntMinimizeSpace* best = @@ -53,15 +45,6 @@ namespace Gecode { } - SpaceComparison - IntMinimizeSpace::compare(const Space& other) const { - const IntMinimizeSpace* s = dynamic_cast(&other); - if (s == nullptr) - throw DynamicCastFailed("IntMinimizeSpace::compare"); - return compare_int(cost().val(),s->cost().val(),true); - } - - void IntMaximizeSpace::constrain(const Space& _best) { const IntMaximizeSpace* best = @@ -72,15 +55,6 @@ namespace Gecode { } - SpaceComparison - IntMaximizeSpace::compare(const Space& other) const { - const IntMaximizeSpace* s = dynamic_cast(&other); - if (s == nullptr) - throw DynamicCastFailed("IntMaximizeSpace::compare"); - return compare_int(cost().val(),s->cost().val(),false); - } - - void IntLexMinimizeSpace::constrain(const Space& _best) { const IntLexMinimizeSpace* best = diff --git a/gecode/search.hh b/gecode/search.hh index cbb4230cb2..7203e61909 100755 --- a/gecode/search.hh +++ b/gecode/search.hh @@ -1171,9 +1171,9 @@ namespace Gecode { namespace Search { * * The bound is an optimistic total objective value on a stable Space: * a lower bound for minimization, an upper bound for maximization, and - * the exact objective on a solution. It must agree with Space::compare - * and Space::constrain. The engine adds no prefix cost. Engines define how - * they use the optional lower-first ranking; rank never justifies pruning. + * the exact objective on a solution. It must agree with the strict + * improvement restriction in Space::constrain. The engine adds no prefix cost. + * Engines define how they use the optional lower-first ranking; rank never justifies pruning. * Callbacks and their captured * dependencies must remain valid for all engine clones and must not post * constraints or refer to another Space's local state. @@ -1261,8 +1261,9 @@ namespace Gecode { * APIs may change or be removed in future feature releases. The documented * correctness contracts still apply. * - * Spaces must implement compatible scalar integer comparison and strict - * improvement constraints. A generated solution can precede a better one; + * Spaces must implement strict improvement constraints compatible with the + * guidance's exact scalar integer objective on solutions. A generated solution + * can precede a better one; * continue next() until nullptr with stopped()==false to prove optimality of * the best accepted incumbent, including external adoption, or infeasibility * if no incumbent was accepted. Returned solutions belong to the caller. @@ -1272,7 +1273,8 @@ namespace Gecode { * There is no hard memory guarantee and no-good extraction is empty. * Only one expanded thread and no tracer are supported. Capacity-one * worker control and ordinary resumable stops are supported. - * If guidance, state pruning, or comparison throws, destroy the engine before further use. + * If guidance, state pruning, or a model operation throws, destroy the engine + * before further use. * \ingroup TaskModelSearch */ template @@ -1309,7 +1311,7 @@ namespace Gecode { * Rank selects pending branching Spaces only and never prunes. Generated * solutions are published immediately, so the first need not be optimal. * Natural finite-tree exhaustion with stopped()==false proves closure under - * the bound, comparison, and optional sound StatePruning contracts. + * the bound, strict improvement, and optional sound StatePruning contracts. * \ingroup TaskModelSearch */ template @@ -1370,9 +1372,10 @@ namespace Gecode { * solutions, temporary successor, and brancher trial Spaces. It is a node * quota, without a byte or process-memory guarantee. Clones store the search; * c_d/a_d do not enable replay and no-good extraction is empty. - * One expanded thread and no tracer are supported. Guidance, comparison, - * and constraints obey the same scalar integer contract as AStar. - * If guidance, state pruning, or comparison throws, destroy the engine before further use. + * One expanded thread and no tracer are supported. Guidance and strict + * improvement constraints obey the same scalar integer contract as AStar. + * If guidance, state pruning, or a model operation throws, destroy the engine + * before further use. * \ingroup TaskModelSearch */ template diff --git a/gecode/search/astar.cpp b/gecode/search/astar.cpp index b7013a6f6e..009a622cee 100644 --- a/gecode/search/astar.cpp +++ b/gecode/search/astar.cpp @@ -140,9 +140,8 @@ namespace Gecode { namespace Search { namespace Seq { AStar::publish(std::unique_ptr s) { // Clean exhausted branchers, retaining completion branchers as usual. (void) s->choice(); - if (best && !Search::better(*s,*best,"AStar::next")) - return nullptr; const std::int64_t bound = guidance.bound(*s); + if (!improves(bound)) return nullptr; std::unique_ptr result(s->clone()); best = std::move(s); best_bound = bound; @@ -212,8 +211,8 @@ namespace Gecode { namespace Search { namespace Seq { void AStar::constrain(const Space& b) { - if (best && !Search::better(b,*best,"AStar::constrain")) return; const std::int64_t bound = guidance.bound(b); + if (!improves(bound)) return; std::unique_ptr incumbent(b.clone()); best = std::move(incumbent); best_bound = bound; diff --git a/gecode/search/cabs.cpp b/gecode/search/cabs.cpp index 311a2b1b47..da156017ef 100644 --- a/gecode/search/cabs.cpp +++ b/gecode/search/cabs.cpp @@ -141,8 +141,17 @@ namespace Gecode { namespace Search { namespace Seq { remove_descriptor(candidates[victim]); candidates[victims.front()] = std::move(entry); add_descriptor(candidates[victims.front()]); - for (std::size_t j=victims.size(); j>1; j--) - candidates.erase(candidates.begin()+victims[j-1]); + std::size_t write = victims.front()+1; + std::size_t victim = 1; + for (std::size_t read=write; read s) { (void) s->choice(); - if (best && !Search::better(*s,*best,"CABS::next")) return nullptr; const std::int64_t bound = guidance.bound(*s); + if (!improves(bound)) return nullptr; std::unique_ptr result(s->clone()); best = std::move(s); best_bound = bound; @@ -319,8 +328,8 @@ namespace Gecode { namespace Search { namespace Seq { void CABS::constrain(const Space& b) { - if (best && !Search::better(b,*best,"CABS::constrain")) return; const std::int64_t bound = guidance.bound(b); + if (!improves(bound)) return; std::unique_ptr incumbent(b.clone()); best = std::move(incumbent); best_bound = bound; diff --git a/gecode/search/column.cpp b/gecode/search/column.cpp index f3ddb7108f..d8a627c58c 100644 --- a/gecode/search/column.cpp +++ b/gecode/search/column.cpp @@ -148,9 +148,8 @@ namespace Gecode { namespace Search { namespace Seq { Column::publish(std::unique_ptr s) { // Clean exhausted branchers, retaining completion branchers as usual. (void) s->choice(); - if (best && !Search::better(*s,*best,"Column::next")) - return nullptr; const std::int64_t bound = guidance.bound(*s); + if (!improves(bound)) return nullptr; std::unique_ptr result(s->clone()); best = std::move(s); best_bound = bound; @@ -248,8 +247,8 @@ namespace Gecode { namespace Search { namespace Seq { void Column::constrain(const Space& b) { - if (best && !Search::better(b,*best,"Column::constrain")) return; const std::int64_t bound = guidance.bound(b); + if (!improves(bound)) return; std::unique_ptr incumbent(b.clone()); best = std::move(incumbent); best_bound = bound; diff --git a/gecode/search/completion.hh b/gecode/search/completion.hh index 797d1a47ae..f478020ba2 100644 --- a/gecode/search/completion.hh +++ b/gecode/search/completion.hh @@ -7,7 +7,7 @@ namespace Gecode { namespace Search { - /// Brancher-owned existential DFS, advanced at the native choice boundary. + /// Brancher-owned existential DFS, advanced before engine choice construction. class Completion : public Brancher { struct Pending : Stop { std::unique_ptr> engine; @@ -19,14 +19,17 @@ namespace Gecode { namespace Search { Statistics delta(const Statistics& s) const { Statistics d=s; - d.node-=published.node; d.fail-=published.fail; + d.node-=published.node; + d.fail-=published.fail; d.propagate-=published.propagate; - d.restart-=published.restart; d.nogood-=published.nogood; + d.restart-=published.restart; + d.nogood-=published.nogood; // Depth is the maximum constituent depth, as in operator+=. return d; } bool stop(const Statistics& s, const Options&) override { - if (!options || !options->stop) return false; + if (!options || !options->stop) + return false; Statistics total=*caller; total+=delta(s); return options->stop->stop(total,*options); @@ -66,51 +69,76 @@ namespace Gecode { namespace Search { : Choice(b,1), fail(f), revision(r) {} size_t size(void) const { return sizeof(*this); } void archive(Archive& e) const override { - Choice::archive(e); e.put(fail); e.put(revision); + Choice::archive(e); + e.put(fail); + e.put(revision); } }; public: + // A unary completion commit records a witness without restricting the + // source. Any later feasibility-changing commit, posting, or incumbent cut + // invalidates that witness, including changes to non-variable model state. + bool commit_restricts(void) const override { return false; } bool retained(void) const override { return !suppressed; } void invalidate(Space&) override { - delete pending; pending=nullptr; - result=UNSET; done=false; revision++; + delete pending; + pending=nullptr; + result=UNSET; + done=false; + revision++; } bool prepare(Space& home, Statistics& stat, const Options& options) override { - if (result != UNSET) return true; - if (options.stop && options.stop->stop(stat,options)) return false; - if (!pending) pending=new Pending; + if (result != UNSET) + return true; + if (options.stop && options.stop->stop(stat,options)) + return false; + if (!pending) + pending=new Pending; pending->caller=&stat; pending->options=&options; try { if (!pending->engine) { Options o=options; - o.clone=false; o.threads=1; o.stop=pending; o.tracer=nullptr; + o.clone=false; + o.threads=1; + o.stop=pending; + o.tracer=nullptr; // The trial runs inside the owning worker's engine. Its control is // already bound there and cannot be attached to a second engine. o.worker_control=WorkerControl(); // Suppression belongs only to this trial clone, never to its source. suppressed=true; std::unique_ptr root; - try { root.reset(trial(home)); } - catch (...) { suppressed=false; throw; } + try { + root.reset(trial(home)); + } catch (...) { + suppressed=false; + throw; + } suppressed=false; pending->engine.reset(new DFS(root.get(),o)); root.release(); } std::unique_ptr solution(pending->engine->next()); pending->publish(); - if (solution) result=FOUND; - else if (!pending->engine->stopped()) result=EXHAUSTED; - pending->caller=nullptr; pending->options=nullptr; + if (solution) + result=FOUND; + else if (!pending->engine->stopped()) + result=EXHAUSTED; + pending->caller=nullptr; + pending->options=nullptr; } catch (...) { pending->publish(); - pending->caller=nullptr; pending->options=nullptr; + pending->caller=nullptr; + pending->options=nullptr; throw; } - if (result == UNSET) return false; - delete pending; pending=nullptr; + if (result == UNSET) + return false; + delete pending; + pending=nullptr; return true; } const Choice* choice(Space& home) override { @@ -120,19 +148,26 @@ namespace Gecode { namespace Search { return new CompletionChoice(*this,result==EXHAUSTED,revision); } const Choice* choice(const Space&, Archive& e) override { - bool fail; unsigned int r; + bool fail; + unsigned int r; e >> fail >> r; return new CompletionChoice(*this,fail,r); } ExecStatus commit(Space&, const Choice& c, unsigned int) override { const CompletionChoice& ch=static_cast(c); - if (ch.fail) return ES_FAILED; + // Exhaustion remains valid under strengthening, unlike success. + if (ch.fail) + return ES_FAILED; // A strengthened replay must return to preparation before being solved. - if (revision == ch.revision) { done=true; result=FOUND; } + if (revision == ch.revision) { + done=true; + result=FOUND; + } return ES_OK; } size_t dispose(Space& home) override { - delete pending; pending=nullptr; + delete pending; + pending=nullptr; (void) Brancher::dispose(home); return sizeof(*this); } diff --git a/gecode/search/exception.cpp b/gecode/search/exception.cpp index 5810ebf776..2bfb2afe46 100644 --- a/gecode/search/exception.cpp +++ b/gecode/search/exception.cpp @@ -34,9 +34,6 @@ #include namespace Gecode { namespace Search { - Incomparable::Incomparable(const char* l) - : Exception(l,"Incumbent objectives are incomparable") {} - #ifdef GECODE_HAS_EXPERIMENTAL UninitializedStatePruning::UninitializedStatePruning(const char* l) : Exception(l,"State pruning requires extraction and label comparison") {} diff --git a/gecode/search/exception.hpp b/gecode/search/exception.hpp index 0bbedc190b..09a55fafbe 100644 --- a/gecode/search/exception.hpp +++ b/gecode/search/exception.hpp @@ -138,11 +138,6 @@ namespace Gecode { namespace Search { }; #endif - /// %Exception: Incumbent objectives are incomparable - class GECODE_SEARCH_EXPORT Incomparable : public Exception { - public: - Incomparable(const char* l); - }; //@} }} diff --git a/gecode/search/par/bab.hh b/gecode/search/par/bab.hh index 4a1911b208..184d583269 100644 --- a/gecode/search/par/bab.hh +++ b/gecode/search/par/bab.hh @@ -69,6 +69,7 @@ namespace Gecode { namespace Search { namespace Par { using Engine::Worker::m; using Engine::Worker::path; using Engine::Worker::cur; + using Engine::Worker::counted; using Engine::Worker::d; using Engine::Worker::idle; using Engine::Worker::node; diff --git a/gecode/search/par/bab.hpp b/gecode/search/par/bab.hpp index 3afbe8ca9f..737ed666b6 100755 --- a/gecode/search/par/bab.hpp +++ b/gecode/search/par/bab.hpp @@ -60,13 +60,16 @@ namespace Gecode { namespace Search { namespace Par { best = nullptr; path.reset((s == nullptr) ? 0 : ngdl); d = 0; + counted = false; mark = 0; idle = false; if ((s == nullptr) || (s->status(*this) == SS_FAILED)) { delete s; cur = nullptr; + counted = false; } else { cur = s; + counted = false; } Search::Worker::reset(); } @@ -119,8 +122,10 @@ namespace Gecode { namespace Search { namespace Par { delete best; best = b->clone(); mark = path.entries(); - if (cur != nullptr) + if (cur != nullptr) { + cur->invalidate_branchers(); cur->constrain(*best); + } m.release(); } template @@ -128,8 +133,10 @@ namespace Gecode { namespace Search { namespace Par { BAB::solution(Space* s) { m_search.acquire(); if (best != nullptr) { - s->constrain(*best); - if (s->status() == SS_FAILED) { + std::unique_ptr probe(s->clone()); + probe->invalidate_branchers(); + probe->constrain(*best); + if (probe->status() == SS_FAILED) { delete s; m_search.release(); return; @@ -169,9 +176,12 @@ namespace Gecode { namespace Search { namespace Par { path.ngdl(0); d = 0; cur = s; + counted = false; mark = 0; - if (best != nullptr) + if (best != nullptr) { + cur->invalidate_branchers(); cur->constrain(*best); + } Statistics t = *this; Search::Worker::reset(r_d); (*this) += t; @@ -200,8 +210,10 @@ namespace Gecode { namespace Search { namespace Par { BAB::constrain(const Space& b) { m_search.acquire(); if (best != nullptr) { - best->constrain(b); - if (best->status() != SS_FAILED) { + std::unique_ptr probe(best->clone()); + probe->invalidate_branchers(); + probe->constrain(b); + if (probe->status() != SS_FAILED) { m_search.release(); return; } @@ -287,7 +299,19 @@ namespace Gecode { namespace Search { namespace Par { m.release(); engine().stop(); } else { - node++; + if (!counted) { + node++; + counted = true; + } + SpaceStatus status = cur->status(*this); + if (status == SS_BRANCH) { + if (!this->prepare(*cur,engine().opt())) { + m.release(); + engine().stop(); + break; + } + status = cur->status(*this); + } SearchTracer::EdgeInfo ei; if (tracer) { if (path.entries() > 0) { @@ -300,7 +324,7 @@ namespace Gecode { namespace Search { namespace Par { } } unsigned int nid = tracer.nid(); - switch (cur->status(*this)) { + switch (status) { case SS_FAILED: if (tracer) { SearchTracer::NodeInfo ni(SearchTracer::NodeType::FAILED, @@ -310,6 +334,7 @@ namespace Gecode { namespace Search { namespace Par { fail++; delete cur; cur = nullptr; + counted = false; path.next(); m.release(); break; @@ -325,6 +350,7 @@ namespace Gecode { namespace Search { namespace Par { Space* s = cur->clone(); delete cur; cur = nullptr; + counted = false; path.next(); m.release(); engine().solution(s); @@ -347,6 +373,7 @@ namespace Gecode { namespace Search { namespace Par { tracer.node(ei,ni); } cur->commit(*ch,0); + counted = false; m.release(); } break; diff --git a/gecode/search/par/dfs.hh b/gecode/search/par/dfs.hh index 326db42664..332ecfe2b9 100644 --- a/gecode/search/par/dfs.hh +++ b/gecode/search/par/dfs.hh @@ -69,6 +69,7 @@ namespace Gecode { namespace Search { namespace Par { using Engine::Worker::m; using Engine::Worker::path; using Engine::Worker::cur; + using Engine::Worker::counted; using Engine::Worker::d; using Engine::Worker::idle; using Engine::Worker::node; diff --git a/gecode/search/par/dfs.hpp b/gecode/search/par/dfs.hpp index 205d7c31be..6394709733 100755 --- a/gecode/search/par/dfs.hpp +++ b/gecode/search/par/dfs.hpp @@ -88,12 +88,15 @@ namespace Gecode { namespace Search { namespace Par { tracer.round(); path.reset((s != nullptr) ? ngdl : 0); d = 0; + counted = false; idle = false; if ((s == nullptr) || (s->status(*this) == SS_FAILED)) { delete s; cur = nullptr; + counted = false; } else { cur = s; + counted = false; } Search::Worker::reset(); } @@ -139,6 +142,7 @@ namespace Gecode { namespace Search { namespace Par { path.ngdl(0); d = 0; cur = s; + counted = false; Statistics t = *this; Search::Worker::reset(r_d); (*this) += t; @@ -231,7 +235,19 @@ namespace Gecode { namespace Search { namespace Par { m.release(); engine().stop(); } else { - node++; + if (!counted) { + node++; + counted = true; + } + SpaceStatus status = cur->status(*this); + if (status == SS_BRANCH) { + if (!this->prepare(*cur,engine().opt())) { + m.release(); + engine().stop(); + break; + } + status = cur->status(*this); + } SearchTracer::EdgeInfo ei; if (tracer) { if (path.entries() > 0) { @@ -244,7 +260,7 @@ namespace Gecode { namespace Search { namespace Par { } } unsigned int nid = tracer.nid(); - switch (cur->status(*this)) { + switch (status) { case SS_FAILED: if (tracer) { SearchTracer::NodeInfo ni(SearchTracer::NodeType::FAILED, @@ -254,6 +270,7 @@ namespace Gecode { namespace Search { namespace Par { fail++; delete cur; cur = nullptr; + counted = false; path.next(); m.release(); break; @@ -269,6 +286,7 @@ namespace Gecode { namespace Search { namespace Par { Space* s = cur->clone(); delete cur; cur = nullptr; + counted = false; path.next(); m.release(); engine().solution(s); @@ -291,6 +309,7 @@ namespace Gecode { namespace Search { namespace Par { tracer.node(ei,ni); } cur->commit(*ch,0); + counted = false; m.release(); } break; diff --git a/gecode/search/par/engine.hh b/gecode/search/par/engine.hh index 9acdcc80b3..c4f01adf47 100644 --- a/gecode/search/par/engine.hh +++ b/gecode/search/par/engine.hh @@ -65,6 +65,8 @@ namespace Gecode { namespace Search { namespace Par { Path path; /// Current space being explored Space* cur; + /// Whether the current node has already been counted before suspension + bool counted; /// Distance until next clone unsigned int d; /// Whether the worker is idle diff --git a/gecode/search/par/engine.hpp b/gecode/search/par/engine.hpp index ae39c77073..598e5ffe22 100644 --- a/gecode/search/par/engine.hpp +++ b/gecode/search/par/engine.hpp @@ -101,7 +101,7 @@ namespace Gecode { namespace Search { namespace Par { forceinline Engine::Worker::Worker(Space* s, Engine& e) : tracer(e.opt().tracer), _engine(e), - path(s == nullptr ? 0 : e.opt().nogoods_limit), d(0), + path(s == nullptr ? 0 : e.opt().nogoods_limit), counted(false), d(0), idle(false) { tracer.worker(); if (s != nullptr) { diff --git a/gecode/search/par/path.hpp b/gecode/search/par/path.hpp index d027e2feb4..5e57168359 100644 --- a/gecode/search/par/path.hpp +++ b/gecode/search/par/path.hpp @@ -370,6 +370,7 @@ namespace Gecode { namespace Search { namespace Par { assert(ds.entries()-1 == lc()); if ((best != nullptr) && (mark > ds.entries()-1)) { mark = ds.entries()-1; + s->invalidate_branchers(); s->constrain(*best); } ds.top().space(nullptr); @@ -389,6 +390,7 @@ namespace Gecode { namespace Search { namespace Par { if ((best != nullptr) && (l < mark)) { mark = l; + s->invalidate_branchers(); s->constrain(*best); // The space on the stack could be failed now as an additional // constraint might have been added. diff --git a/gecode/search/par/pbs.hh b/gecode/search/par/pbs.hh index 8db69e5507..2bbf7014da 100644 --- a/gecode/search/par/pbs.hh +++ b/gecode/search/par/pbs.hh @@ -38,6 +38,7 @@ #ifndef GECODE_SEARCH_PAR_PBS_HH #define GECODE_SEARCH_PAR_PBS_HH +#include #include #include diff --git a/gecode/search/par/pbs.hpp b/gecode/search/par/pbs.hpp index e4c8f33135..352797cb80 100755 --- a/gecode/search/par/pbs.hpp +++ b/gecode/search/par/pbs.hpp @@ -95,8 +95,10 @@ namespace Gecode { namespace Search { namespace Par { forceinline bool CollectBest::add(Space* s, Slave* r) { if (b != nullptr) { - b->constrain(*s); - if (b->status() == SS_FAILED) { + std::unique_ptr probe(b->clone()); + probe->invalidate_branchers(); + probe->constrain(*s); + if (probe->status() == SS_FAILED) { delete b; } else { delete s; @@ -110,8 +112,10 @@ namespace Gecode { namespace Search { namespace Par { forceinline bool CollectBest::constrain(const Space& s) { if (b != nullptr) { - b->constrain(s); - if (b->status() == SS_FAILED) { + std::unique_ptr probe(b->clone()); + probe->invalidate_branchers(); + probe->constrain(s); + if (probe->status() == SS_FAILED) { delete b; } else { return false; diff --git a/gecode/search/seq/bab.hh b/gecode/search/seq/bab.hh index 9bd4ff1fe3..26e5e587f2 100644 --- a/gecode/search/seq/bab.hh +++ b/gecode/search/seq/bab.hh @@ -57,6 +57,8 @@ namespace Gecode { namespace Search { namespace Seq { Path path; /// Current space being explored Space* cur; + /// Whether the current node has already been counted before suspension + bool counted; /// Distance until next clone unsigned int d; /// Number of entries not yet constrained to be better diff --git a/gecode/search/seq/bab.hpp b/gecode/search/seq/bab.hpp index dade2cb075..d8a5fba02b 100644 --- a/gecode/search/seq/bab.hpp +++ b/gecode/search/seq/bab.hpp @@ -42,7 +42,7 @@ namespace Gecode { namespace Search { namespace Seq { template forceinline BAB::BAB(Space* s, const Options& o) - : tracer(o.tracer), opt(o), path(opt.nogoods_limit), d(0), mark(0), + : tracer(o.tracer), opt(o), path(opt.nogoods_limit), counted(false), d(0), mark(0), best(nullptr) { if (tracer) { tracer.engine(SearchTracer::EngineType::BAB, 1U); @@ -51,6 +51,7 @@ namespace Gecode { namespace Search { namespace Seq { if ((s == nullptr) || (s->status(*this) == SS_FAILED)) { fail++; cur = nullptr; + counted = false; if (!o.clone) delete s; } else { @@ -90,14 +91,23 @@ namespace Gecode { namespace Search { namespace Seq { break; path.next(); } - node++; + if (!counted) { + node++; + counted = true; + } + SpaceStatus status = cur->status(*this); + if (status == SS_BRANCH) { + if (!prepare(*cur,opt)) + return nullptr; + status = cur->status(*this); + } SearchTracer::EdgeInfo ei; if (tracer && (path.entries() > 0)) { typename Path::Edge& top = path.top(); ei.init(tracer.wid(), top.nid(), top.truealt(), *cur, *top.choice()); } unsigned int nid = tracer.nid(); - switch (cur->status(*this)) { + switch (status) { case SS_FAILED: if (tracer) { SearchTracer::NodeInfo ni(SearchTracer::NodeType::FAILED, @@ -107,6 +117,7 @@ namespace Gecode { namespace Search { namespace Seq { fail++; delete cur; cur = nullptr; + counted = false; path.next(); break; case SS_SOLVED: @@ -121,6 +132,7 @@ namespace Gecode { namespace Search { namespace Seq { delete best; best = cur; cur = nullptr; + counted = false; path.next(); mark = path.entries(); } @@ -142,6 +154,7 @@ namespace Gecode { namespace Search { namespace Seq { tracer.node(ei,ni); } cur->commit(*ch,0); + counted = false; break; } default: @@ -163,15 +176,18 @@ namespace Gecode { namespace Search { namespace Seq { BAB::constrain(const Space& b) { if (best != nullptr) { // Check whether b is in fact better than best - best->constrain(b); - if (best->status(*this) != SS_FAILED) + std::unique_ptr probe(best->clone()); + probe->invalidate_branchers(); + probe->constrain(b); + if (probe->status(*this) != SS_FAILED) return; - else - delete best; + delete best; } best = b.clone(); - if (cur != nullptr) + if (cur != nullptr) { + cur->invalidate_branchers(); cur->constrain(b); + } mark = path.entries(); } @@ -183,11 +199,13 @@ namespace Gecode { namespace Search { namespace Seq { best = nullptr; path.reset(); d = 0; + counted = false; mark = 0; delete cur; if ((s == nullptr) || (s->status(*this) == SS_FAILED)) { delete s; cur = nullptr; + counted = false; } else { cur = s; } diff --git a/gecode/search/seq/choice-expansion.hh b/gecode/search/seq/choice-expansion.hh index da86e01502..59f8e14d4e 100644 --- a/gecode/search/seq/choice-expansion.hh +++ b/gecode/search/seq/choice-expansion.hh @@ -44,7 +44,7 @@ namespace Gecode { namespace Search { namespace Seq { /// One propagated child, interrupted preparation, or exhausted choice struct ExpansionStep { - enum State { CHILD, SUSPENDED, EXHAUSTED }; + enum State { CHILD, PREPARED_TERMINAL, SUSPENDED, EXHAUSTED }; State state; std::unique_ptr space; SpaceStatus status; @@ -93,6 +93,9 @@ namespace Gecode { namespace Search { namespace Seq { if (!choice) { if (!worker.prepare(*parent,opt)) return {ExpansionStep::SUSPENDED,nullptr,SS_FAILED}; + const SpaceStatus status = parent->status(worker); + if (status != SS_BRANCH) + return {ExpansionStep::PREPARED_TERMINAL,std::move(parent),status}; choice.reset(parent->choice()); alternative = 0; } diff --git a/gecode/search/seq/dfs.hh b/gecode/search/seq/dfs.hh index f08215073e..fe1458856e 100644 --- a/gecode/search/seq/dfs.hh +++ b/gecode/search/seq/dfs.hh @@ -53,6 +53,8 @@ namespace Gecode { namespace Search { namespace Seq { Path path; /// Current space being explored Space* cur; + /// Whether the current node has already been counted before suspension + bool counted; /// Distance until next clone unsigned int d; public: diff --git a/gecode/search/seq/dfs.hpp b/gecode/search/seq/dfs.hpp index 0a90f8a53a..ab8e90f2bf 100644 --- a/gecode/search/seq/dfs.hpp +++ b/gecode/search/seq/dfs.hpp @@ -36,7 +36,7 @@ namespace Gecode { namespace Search { namespace Seq { template forceinline DFS::DFS(Space* s, const Options& o) - : tracer(o.tracer), opt(o), path(opt.nogoods_limit), d(0) { + : tracer(o.tracer), opt(o), path(opt.nogoods_limit), counted(false), d(0) { if (tracer) { tracer.engine(SearchTracer::EngineType::DFS, 1U); tracer.worker(); @@ -44,6 +44,7 @@ namespace Gecode { namespace Search { namespace Seq { if ((s == nullptr) || (s->status(*this) == SS_FAILED)) { fail++; cur = nullptr; + counted = false; if (!opt.clone) delete s; } else { @@ -58,9 +59,11 @@ namespace Gecode { namespace Search { namespace Seq { delete cur; path.reset(); d = 0; + counted = false; if ((s == nullptr) || (s->status(*this) == SS_FAILED)) { delete s; cur = nullptr; + counted = false; } else { cur = s; } @@ -99,14 +102,23 @@ namespace Gecode { namespace Search { namespace Seq { break; path.next(); } - node++; + if (!counted) { + node++; + counted = true; + } + SpaceStatus status = cur->status(*this); + if (status == SS_BRANCH) { + if (!prepare(*cur,opt)) + return nullptr; + status = cur->status(*this); + } SearchTracer::EdgeInfo ei; if (tracer && (path.entries() > 0)) { typename Path::Edge& top = path.top(); ei.init(tracer.wid(), top.nid(), top.truealt(), *cur, *top.choice()); } unsigned int nid = tracer.nid(); - switch (cur->status(*this)) { + switch (status) { case SS_FAILED: if (tracer) { SearchTracer::NodeInfo ni(SearchTracer::NodeType::FAILED, @@ -116,6 +128,7 @@ namespace Gecode { namespace Search { namespace Seq { fail++; delete cur; cur = nullptr; + counted = false; path.next(); break; case SS_SOLVED: @@ -129,6 +142,7 @@ namespace Gecode { namespace Search { namespace Seq { (void) cur->choice(); Space* s = cur; cur = nullptr; + counted = false; path.next(); return s; } @@ -149,6 +163,7 @@ namespace Gecode { namespace Search { namespace Seq { tracer.node(ei,ni); } cur->commit(*ch,0); + counted = false; break; } default: diff --git a/gecode/search/seq/lds.hh b/gecode/search/seq/lds.hh index d95869b55a..4fff00b300 100644 --- a/gecode/search/seq/lds.hh +++ b/gecode/search/seq/lds.hh @@ -79,6 +79,8 @@ namespace Gecode { namespace Search { namespace Seq { Support::DynamicStack ds; /// Current space Space* cur; + /// Whether the discrepancy node was counted before preparation suspended + bool counted; /// Current discrepancy unsigned int d; /// Whether entire search space has been exhausted diff --git a/gecode/search/seq/lds.hpp b/gecode/search/seq/lds.hpp index 01f5c628f5..f323e918e6 100644 --- a/gecode/search/seq/lds.hpp +++ b/gecode/search/seq/lds.hpp @@ -98,7 +98,7 @@ namespace Gecode { namespace Search { namespace Seq { template forceinline void Probe::init(Space* s) { - cur = s; d = 0U; exhausted = true; + cur = s; d = 0U; exhausted = true; counted = false; if (tracer) tracer.ei()->invalidate(); } @@ -110,7 +110,7 @@ namespace Gecode { namespace Search { namespace Seq { delete cur; while (!ds.empty()) delete ds.pop().space(); - cur = s; d = d0; exhausted = true; + cur = s; d = d0; exhausted = true; counted = false; if (tracer) tracer.ei()->invalidate(); Worker::reset(0); @@ -165,6 +165,7 @@ namespace Gecode { namespace Search { namespace Seq { cur->commit(*ch,a); } node++; + counted = false; d++; } check_discrepancy: @@ -175,6 +176,12 @@ namespace Gecode { namespace Search { namespace Seq { cur = s; return nullptr; } + if (!prepare(*s,opt)) { + cur = s; + return nullptr; + } + if (s->status(*this) != SS_BRANCH) + break; const Choice* ch = s->choice(); if (ch->alternatives() > 1) exhausted = false; @@ -211,8 +218,18 @@ namespace Gecode { namespace Search { namespace Seq { return s; } } else { - node++; - switch (cur->status(*this)) { + if (!counted) { + node++; + counted = true; + } + SpaceStatus status = cur->status(*this); + if (status == SS_BRANCH) { + if (!prepare(*cur,opt)) + return nullptr; + status = cur->status(*this); + } + counted = false; + switch (status) { case SS_FAILED: if (tracer) { SearchTracer::NodeInfo ni(SearchTracer::NodeType::FAILED, diff --git a/gecode/search/seq/path.hpp b/gecode/search/seq/path.hpp index 4a4cddd157..6f3b9ca7ef 100644 --- a/gecode/search/seq/path.hpp +++ b/gecode/search/seq/path.hpp @@ -306,6 +306,7 @@ namespace Gecode { namespace Search { namespace Seq { assert(ds.entries()-1 == lc()); if ((best != nullptr) && (mark > ds.entries()-1)) { mark = ds.entries()-1; + s->invalidate_branchers(); s->constrain(*best); } ds.top().space(nullptr); @@ -325,6 +326,7 @@ namespace Gecode { namespace Search { namespace Seq { if ((best != nullptr) && (l < mark)) { mark = l; + s->invalidate_branchers(); s->constrain(*best); // The space on the stack could be failed now as an additional // constraint might have been added. diff --git a/gecode/search/seq/rbs.cpp b/gecode/search/seq/rbs.cpp index 033c525b1d..c747c9943d 100755 --- a/gecode/search/seq/rbs.cpp +++ b/gecode/search/seq/rbs.cpp @@ -36,6 +36,7 @@ */ +#include #include namespace Gecode { namespace Search { namespace Seq { @@ -67,6 +68,7 @@ namespace Gecode { namespace Search { namespace Seq { // Reset number of no-goods found ng.ng(0); MetaInfo mi(stop->restarts(),MetaInfo::RR_SOL,sslr,e->statistics().fail,last,ng); + master->invalidate_branchers(); bool r = master->master(mi); stop->nogood(ng.ng()); if (stop->status(master) == SS_FAILED) { @@ -103,6 +105,7 @@ namespace Gecode { namespace Search { namespace Seq { NoGoods& ng = e->nogoods(); ng.ng(0); MetaInfo mi(stop->restarts(),e->stopped() ? MetaInfo::RR_LIM : MetaInfo::RR_CMPL,sslr,e->statistics().fail,last,ng); + master->invalidate_branchers(); (void) master->master(mi); stop->nogood(ng.ng()); unsigned long long int nl = ++(*co); @@ -131,14 +134,17 @@ namespace Gecode { namespace Search { namespace Seq { if (!best) throw NoBest("RBS::constrain"); if (last != nullptr) { - last->constrain(b); - if (last->status() == SS_FAILED) { + std::unique_ptr probe(last->clone()); + probe->invalidate_branchers(); + probe->constrain(b); + if (probe->status() == SS_FAILED) { delete last; } else { return; } } last = b.clone(); + master->invalidate_branchers(); master->constrain(b); e->constrain(b); } diff --git a/gecode/search/support.hh b/gecode/search/support.hh index 4cd83a3e3c..da4cd9e0cb 100644 --- a/gecode/search/support.hh +++ b/gecode/search/support.hh @@ -34,27 +34,11 @@ #ifndef GECODE_SEARCH_SUPPORT_HH #define GECODE_SEARCH_SUPPORT_HH +#include #include namespace Gecode { namespace Search { - /// Whether incoming solution \a s should replace incumbent \a b - forceinline bool - better(const Space& s, const Space& b, const char* l) { - switch (s.compare(b)) { - case SC_BETTER: - return true; - case SC_EQUIVALENT: - case SC_WORSE: - return false; - case SC_INCOMPARABLE: - throw Incomparable(l); - default: - GECODE_NEVER; - } - return false; - } - /// Clone space \a s depending on options \a o forceinline Space* snapshot(Space* s, const Options& o); diff --git a/misc/tsptw-cadds-study.cpp b/misc/tsptw-cadds-study.cpp index 8874f11c34..48456546f7 100644 --- a/misc/tsptw-cadds-study.cpp +++ b/misc/tsptw-cadds-study.cpp @@ -73,9 +73,10 @@ class Study { } Owned publish(Owned s) { if (optimize) { - if (incumbent && s->compare(*incumbent)!=SC_BETTER) return {}; + const auto value=guidance.bound(*s); + if (incumbent && value>=incumbent_bound) return {}; incumbent.reset(s->clone()); - incumbent_bound=guidance.bound(*s); + incumbent_bound=value; revision++; } return s; @@ -181,7 +182,9 @@ class Study { suspensions++; return {Step::SUSPENDED,{}}; } if (child.state==Search::Seq::ExpansionStep::EXHAUSTED) continue; - commits++; work++; + if (child.state==Search::Seq::ExpansionStep::CHILD) { + commits++; work++; + } if (child.status==SS_FAILED) { worker.fail++; continue; } const auto bound=guidance.bound(*child.space); if (optimize && incumbent && bound>=incumbent_bound) { @@ -266,10 +269,6 @@ class Witness : public Space { explicit Witness(int state=0) : node(state) { (void)new(*this)Branch(*this); } Witness(Witness& s) : Space(s),node(s.node),ceiling(s.ceiling),cost(s.cost),path(s.path) {} Space* copy() override { return new Witness(*this); } - SpaceComparison compare(const Space& s) const override { - const int other=static_cast(s).cost; - return cost(s).cost; if (node>=10 && cost>=ceiling) fail(); diff --git a/test/search.cpp b/test/search.cpp index 95a0fa48e0..042b3d7b19 100644 --- a/test/search.cpp +++ b/test/search.cpp @@ -120,10 +120,13 @@ namespace Test { std::shared_ptr> constraints; public: IntVarArray x; + std::shared_ptr> decisions; CompletionSpace(bool sat, bool nested, bool outer=false, std::shared_ptr> constraints0={}, - bool completion=true) - : constraints(std::move(constraints0)), x(*this,sat ? 4 : 5,1,4) { + bool completion=true, + std::shared_ptr> decisions0={}) + : constraints(std::move(constraints0)), x(*this,sat ? 4 : 5,1,4), + decisions(std::move(decisions0)) { for (int i=0;i(home.clone())); IntVarArgs args(count); for (int i=0;ix[first+i]; - branch(*s,args,INT_VAR_NONE(),INT_VAL_MIN()); + branch(*s,args,INT_VAR_NONE(),INT_VAL( + [decisions=s->decisions](const Space&, IntVar v, int) { + if (decisions) decisions->fetch_add(1,std::memory_order_relaxed); + return v.min(); + })); return s.release(); } @@ -199,14 +208,6 @@ namespace Test { cost.update(*this,s.cost); } Space* copy(void) override { return new ObjectiveSpace(*this); } - SpaceComparison compare(const Space& s0) const override { - const ObjectiveSpace& s=dynamic_cast(s0); - if (cost.val() < s.cost.val()) - return SC_BETTER; - if (cost.val() > s.cost.val()) - return SC_WORSE; - return SC_EQUIVALENT; - } void constrain(const Space& s0) override { if (constraints) constraints->fetch_add(1,std::memory_order_relaxed); @@ -337,6 +338,243 @@ namespace Test { } } completionNative; + /// Later decisions must revalidate an existential completion witness. + class CompletionDependent : public Base { + class DependentSpace : public CompletionSpace { + public: + IntVar cost; + DependentSpace(bool maximum) + : CompletionSpace(true,false,true), cost(*this,0,3) { + for (int i=0; i(s).cost.val(); + if (me_failed(IntView(cost).lq(*this,value-1))) fail(); + } + }; + static bool search(unsigned int kind, bool maximum) { + Gecode::Search::Options options; + options.clone=false; options.c_d=3; options.a_d=1; + std::unique_ptr engine; + switch (kind) { + case 0: engine.reset(Gecode::Search::dfsengine(new DependentSpace(maximum),options)); break; + case 1: engine.reset(Gecode::Search::babengine(new DependentSpace(maximum),options)); break; + case 2: + options.threads=2; + engine.reset(Gecode::Search::babengine(new DependentSpace(maximum),options)); break; +#ifdef GECODE_HAS_EXPERIMENTAL + default: + { + using Guidance=Gecode::Search::HeuristicGuidance; + Guidance guidance(Guidance::MINIMIZE,[](const Space& s) -> std::int64_t { + return static_cast(s).cost.min(); + }); + if (kind==3) + engine.reset(Gecode::Search::astarengine(new DependentSpace(maximum),guidance,options)); + else if (kind==4) + engine.reset(Gecode::Search::cabsengine(new DependentSpace(maximum),guidance,options)); + else + engine.reset(Gecode::Search::columnengine(new DependentSpace(maximum),guidance,options)); + } + break; +#else + default: GECODE_NEVER; +#endif + } + unsigned int solutions=0; + while (std::unique_ptr s{ + static_cast(engine->next())}) { + if (s->cost.val()!=3) return false; + // Verify the reported objective with ordinary hidden-variable search. + std::unique_ptr check(static_cast(s->clone())); + check->invalidate_branchers(); + branch(*check,check->x,INT_VAR_NONE(),INT_VAL_MIN()); + Gecode::Search::Options witness_options; witness_options.clone=false; + Gecode::DFS witness(check.release(),witness_options); + std::unique_ptr valid(witness.next()); + if (!valid) return false; + solutions++; + } + return solutions && !engine->stopped(); + } + public: + CompletionDependent(void) : Base("Search::Completion::Dependent",TestTags::check()) {} + bool run(void) override { + for (bool maximum : {false,true}) + for (unsigned int kind=0; kind< +#ifdef GECODE_HAS_EXPERIMENTAL + 6 +#else + 3 +#endif + ; kind++) + if (!search(kind,maximum)) return false; + return true; + } + } completionDependent; + + /// Nested trials retain their work and publish their counters across stops. + class CompletionNested : public Base { + static bool same(const Gecode::Search::Statistics& a, + const Gecode::Search::Statistics& b) { + return a.node==b.node && a.fail==b.fail && a.propagate==b.propagate && + a.depth==b.depth && a.restart==b.restart && a.nogood==b.nogood; + } + static Gecode::Search::Engine* engine(unsigned int kind, CompletionSpace* root, + const Gecode::Search::Options& options) { + switch (kind) { + case 0: return Gecode::Search::dfsengine(root,options); + case 1: return Gecode::Search::ldsengine(root,options); + case 2: + { + Gecode::Search::Options parallel=options; parallel.threads=2; + return Gecode::Search::dfsengine(root,parallel); + } +#ifdef GECODE_HAS_EXPERIMENTAL + default: + { + using Guidance=Gecode::Search::HeuristicGuidance; + Guidance guidance(Guidance::MINIMIZE,[](const Space&) -> std::int64_t { return 0; }); + if (kind==3) return Gecode::Search::astarengine(root,guidance,options); + if (kind==4) return Gecode::Search::cabsengine(root,guidance,options); + return Gecode::Search::columnengine(root,guidance,options); + } +#else + default: GECODE_NEVER; +#endif + } + return nullptr; + } + static bool search(unsigned int kind, bool sat) { + Gecode::Search::Options options; + options.clone=false; options.c_d=5; options.a_d=2; + std::unique_ptr full(engine(kind,new CompletionSpace(sat,true),options)); + std::unique_ptr solution(full->next()); + if (static_cast(solution)!=sat || full->stopped()) return false; + auto decisions=std::make_shared>(0); + Gecode::Search::NodeStop stop(3); options.stop=&stop; + std::unique_ptr sliced( + engine(kind,new CompletionSpace(sat,true,false,{},true,decisions),options)); + if (sliced->next() || !sliced->stopped()) return false; + const auto before=sliced->statistics(); + const auto observed=decisions->load(); + if (observed>=23) return false; + if (sliced->next() || !sliced->stopped() || + !same(before,sliced->statistics()) || decisions->load()!=observed) return false; + stop.limit(100000); + std::unique_ptr resumed(sliced->next()); + return static_cast(resumed)==sat && !sliced->stopped() && + same(full->statistics(),sliced->statistics()) && + sliced->statistics().node>before.node; + } + public: + CompletionNested(void) : Base("Search::Completion::Nested",TestTags::check()) {} + bool run(void) override { + for (bool sat : {false,true}) + for (unsigned int kind=0; kind< +#ifdef GECODE_HAS_EXPERIMENTAL + 6 +#else + 3 +#endif + ; kind++) + if (!search(kind,sat)) return false; + return true; + } + } completionNested; + + /// Successful preparation can end in failure or skip to a solved brancher. + class PreparedTerminal : public Base { + class PreparedSpace : public Space { + class PreparedBrancher : public Brancher { + bool prepared, failure; + std::shared_ptr> choices; + class PreparedChoice : public Choice { + public: + PreparedChoice(const Brancher& b) : Choice(b,1) {} + }; + public: + PreparedBrancher(Home home, bool f, std::shared_ptr> c) + : Brancher(home), prepared(false), failure(f), choices(std::move(c)) { + home.notice(*this,AP_DISPOSE,false); + } + PreparedBrancher(Space& home, PreparedBrancher& b) + : Brancher(home,b), prepared(b.prepared), failure(b.failure), choices(b.choices) {} + bool status(const Space&) const override { return !prepared; } + bool prepare(Space& home, Gecode::Search::Statistics&, + const Gecode::Search::Options&) override { + prepared=true; + if (failure) home.fail(); + return true; + } + const Choice* choice(Space&) override { + (*choices)++; + return new PreparedChoice(*this); + } + const Choice* choice(const Space&, Archive&) override { return new PreparedChoice(*this); } + ExecStatus commit(Space&, const Choice&, unsigned int) override { return ES_OK; } + Actor* copy(Space& home) override { return new (home) PreparedBrancher(home,*this); } + size_t dispose(Space& home) override { + home.ignore(*this,AP_DISPOSE,false); + choices.~shared_ptr(); + (void) Brancher::dispose(home); + return sizeof(*this); + } + }; + public: + PreparedSpace(bool failure, std::shared_ptr> choices) { + (void) new (*this) PreparedBrancher(*this,false,choices); + (void) new (*this) PreparedBrancher(*this,failure,choices); + } + PreparedSpace(PreparedSpace& s) : Space(s) {} + Space* copy(void) override { return new PreparedSpace(*this); } + }; + public: + PreparedTerminal(void) : Base("Search::Completion::PreparedTerminal",TestTags::check()) {} + bool run(void) override { + for (bool failure : {false,true}) + for (unsigned int kind=0; kind< +#ifdef GECODE_HAS_EXPERIMENTAL + 6 +#else + 3 +#endif + ; kind++) { + auto choices=std::make_shared>(0); + Gecode::Search::NodeStop stop(100); + Gecode::Search::Options options; options.clone=false; options.stop=&stop; + std::unique_ptr engine; + if (kind==0) engine.reset(Gecode::Search::dfsengine(new PreparedSpace(failure,choices),options)); + else if (kind==1) engine.reset(Gecode::Search::ldsengine(new PreparedSpace(failure,choices),options)); + else if (kind==2) { + options.threads=2; + engine.reset(Gecode::Search::babengine(new PreparedSpace(failure,choices),options)); + } +#ifdef GECODE_HAS_EXPERIMENTAL + else { + using Guidance=Gecode::Search::HeuristicGuidance; + Guidance guidance(Guidance::MINIMIZE,[](const Space&) -> std::int64_t { return 0; }); + if (kind==3) engine.reset(Gecode::Search::astarengine(new PreparedSpace(failure,choices),guidance,options)); + else if (kind==4) engine.reset(Gecode::Search::cabsengine(new PreparedSpace(failure,choices),guidance,options)); + else engine.reset(Gecode::Search::columnengine(new PreparedSpace(failure,choices),guidance,options)); + } +#endif + std::unique_ptr result(engine->next()); + auto stat=engine->statistics(); + if (static_cast(result)==failure || choices->load() || engine->stopped() || + stat.node!=1 || stat.fail!=(failure ? 1U : 0U) || stat.depth) return false; + } + return true; + } + } preparedTerminal; + + #ifdef GECODE_HAS_EXPERIMENTAL /// The example's real n-ary transitions survive cloning and continuation. class TSPTWTransitionTest : public Base { @@ -576,9 +814,9 @@ namespace Test { public: static std::atomic live; IntVar x, y; - bool maximize, incomparable; + bool maximize; AStarSpace(bool max=false, int fixed=-1, bool failed=false) - : x(*this,0,3), y(*this,0,1), maximize(max), incomparable(false) { + : x(*this,0,3), y(*this,0,1), maximize(max) { live++; TupleSet tuples(2); for (int i=0;i<4;i++) { @@ -596,7 +834,7 @@ namespace Test { Gecode::branch(*this,y,INT_VALUES_MIN()); } AStarSpace(AStarSpace& s) - : Space(s), maximize(s.maximize), incomparable(s.incomparable) { + : Space(s), maximize(s.maximize) { live++; x.update(*this,s.x); y.update(*this,s.y); } @@ -614,12 +852,6 @@ namespace Test { bool improves(std::int64_t a, std::int64_t b) const { return maximize ? a>b : a(s0); - if (incomparable || maximize!=s.maximize) return SC_INCOMPARABLE; - if (objective()==s.objective()) return SC_EQUIVALENT; - return improves(objective(),s.objective()) ? SC_BETTER : SC_WORSE; - } void constrain(const Space& s0) override { const auto& s=static_cast(s0); for (int i=0;i<4;i++) @@ -709,11 +941,6 @@ namespace Test { ceiling(s.ceiling) {} Space* copy(void) override { return new PruningSpace(*this); } const Node& value(void) const { return (*graph)[vertex]; } - SpaceComparison compare(const Space& s0) const override { - const int other=static_cast(s0).value().cost; - return value().cost==other ? SC_EQUIVALENT : - value().cost(s0).value().cost; if (value().cost>=ceiling) fail(); @@ -970,11 +1197,6 @@ namespace Test { found++; } if (engine->stopped() || found!=(queued ? 0U : 1U)) return false; - worse.incomparable=true; - try { - engine->constrain(worse); - return false; - } catch (const Gecode::Search::Incomparable&) {} return true; } static bool ownership(void) { @@ -1065,11 +1287,6 @@ namespace Test { x.update(*this,s.x); y.update(*this,s.y); } Space* copy(void) override { return new CABSFeasibility(*this); } - SpaceComparison compare(const Space& s0) const override { - const auto& s=dynamic_cast(s0); - if (x.val()==s.x.val()) return SC_EQUIVALENT; - return x.val()(s0).x.val()); } @@ -1247,9 +1464,6 @@ namespace Test { engine->constrain(incumbent); engine->constrain(incumbent); engine->constrain(worse); - worse.incomparable=true; - try { engine->constrain(worse); return false; } - catch (const Gecode::Search::Incomparable&) {} } // The accepted external copy survives the caller's destruction. Its // exact INT64_MIN objective also bounds out every discarded candidate; @@ -1457,9 +1671,6 @@ namespace Test { incumbent.status(); worse.status(); engine->constrain(incumbent); engine->constrain(incumbent); engine->constrain(worse); - worse.incomparable=true; - try { engine->constrain(worse); return false; } - catch (const Gecode::Search::Incomparable&) {} } std::unique_ptr optimum(static_cast(engine->next())); return optimum && optimum->x.val()==2 && !engine->next() && !engine->stopped(); @@ -1563,71 +1774,6 @@ namespace Test { #endif - /// Scalar integer objective used for comparison tests - class MinObjective : public IntMinimizeSpace { - public: - IntVar x; - MinObjective(int l, int u) : x(*this,l,u) {} - MinObjective(MinObjective& s) : IntMinimizeSpace(s) { - x.update(*this,s.x); - } - virtual Space* copy(void) { return new MinObjective(*this); } - virtual IntVar cost(void) const { return x; } - }; - - /// Scalar maximization objective used to check objective families - class MaxObjective : public IntMaximizeSpace { - public: - IntVar x; - MaxObjective(int v) : x(*this,v,v) {} - MaxObjective(MaxObjective& s) : IntMaximizeSpace(s) { - x.update(*this,s.x); - } - virtual Space* copy(void) { return new MaxObjective(*this); } - virtual IntVar cost(void) const { return x; } - }; - - /// Space without objective comparison support - class PlainSpace : public Space { - public: - PlainSpace(void) {} - PlainSpace(PlainSpace& s) : Space(s) {} - virtual Space* copy(void) { return new PlainSpace(*this); } - }; - - /// Test objective comparison independently of search arbitration - class Comparison : public Base { - public: - Comparison(void) : Base("Search::Comparison", TestTags::check()) {} - virtual bool run(void) { - MinObjective one(1,1), two(2,2), one_again(1,1), open(0,2); - if ((one.compare(two) != SC_BETTER) || - (two.compare(one) != SC_WORSE) || - (one.compare(one_again) != SC_EQUIVALENT)) - return false; - MaxObjective high(2), low(1); - if ((high.compare(low) != SC_BETTER) || - (low.compare(high) != SC_WORSE)) - return false; - try { - (void) open.compare(one); - return false; - } catch (const Int::ValOfUnassignedVar&) {} - try { - (void) one.compare(high); - return false; - } catch (const DynamicCastFailed&) {} - PlainSpace plain; - try { - (void) plain.compare(plain); - return false; - } catch (const SpaceNoComparison&) {} - return true; - } - }; - - Comparison comparison; - /// Brancher used to exercise internal post-propagation feedback class FeedbackBrancher : public Brancher { private: @@ -1723,10 +1869,6 @@ namespace Test { return new FeedbackSpace(*this); } void constrain(const Space&) override {} - // This fixture enumerates a complete tree without objective pruning. - SpaceComparison compare(const Space&) const override { - return SC_BETTER; - } bool master(const MetaInfo& info) override { if ((info.type() == MetaInfo::RESTART) && (info.last() != nullptr)) { const FeedbackSpace& last = From 9a71d16176f253720d36f435a0fb861ab994df54 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 13 Oct 2026 15:22:39 +0200 Subject: [PATCH 09/24] Use concrete wording in source comments and search documentation --- .github/ci/generated-source-freshness.sh | 2 +- changelog.in | 8 ++-- docs/astar.md | 19 ++++---- docs/cabs.md | 26 +++++------ docs/column.md | 2 +- docs/tsptw-cheap-deadline-filter.md | 12 ++--- docs/worker-control.md | 4 +- examples/astar.cpp | 2 +- examples/common-pattern.hh | 2 +- examples/data/tsptw/README.md | 44 +++++++++---------- examples/data/tsptw/bounds-comparison.json | 2 +- .../data/tsptw/cheap-deadline-comparison.json | 2 +- examples/data/tsptw/cp-baseline-tuning.json | 2 +- gecode/flatzinc/restart-random.hpp | 2 +- gecode/int.hh | 6 +-- gecode/int/arithmetic/gcd.hpp | 4 +- gecode/int/min-distance/prop.hpp | 2 +- gecode/kernel/core.hpp | 4 +- gecode/search.hh | 18 ++++---- gecode/search/astar.cpp | 6 +-- gecode/search/branch-feedback.hh | 4 +- gecode/search/cabs.cpp | 20 ++++----- gecode/search/column.cpp | 12 ++--- gecode/search/pbs.hpp | 6 +-- gecode/search/rbs.hpp | 2 +- gecode/search/seq/astar.hh | 2 +- gecode/search/seq/cabs.hh | 4 +- gecode/search/seq/choice-expansion.hh | 2 +- gecode/search/seq/column.hh | 8 ++-- gecode/search/seq/state-registry.hh | 20 ++++----- gecode/search/worker-control.cpp | 4 +- misc/common-pattern/README.md | 8 ++-- misc/common-pattern/compare.cpp | 2 +- misc/test-tsptw-benchmark.py | 2 +- misc/tsptw-benchmark.py | 2 +- misc/tsptw-validate.py | 4 +- test/int/arithmetic.cpp | 2 +- test/int/extensional.cpp | 4 +- test/int/open-var-list.cpp | 10 ++--- test/search.cpp | 18 ++++---- 40 files changed, 153 insertions(+), 152 deletions(-) diff --git a/.github/ci/generated-source-freshness.sh b/.github/ci/generated-source-freshness.sh index 5be8629767..a2f2d108d6 100755 --- a/.github/ci/generated-source-freshness.sh +++ b/.github/ci/generated-source-freshness.sh @@ -225,7 +225,7 @@ normalize_generated_whitespace() { # Ignore any autom4te cache so output matches a clean source release. autoconf --force # Autoconf 2.72 appends blank lines after the generated script body; source - # releases retain exactly one final newline. + # releases have exactly one final newline. normalize_generated_whitespace configure uv run --script ./misc/genvarimp.py -typehpp "${vis_files[@]}" \ > gecode/kernel/var-type.hpp diff --git a/changelog.in b/changelog.in index 5a20577c90..35148d2394 100755 --- a/changelog.in +++ b/changelog.in @@ -170,7 +170,7 @@ What: performance Rank: major [DESCRIPTION] Reduce construction memory for regular constraints by building the layered -graph from sparse forward frontiers and retaining only backward co-reachable +graph from sparse forward frontiers and including only backward co-reachable states. This avoids allocating one temporary state for every automaton state at every sequence position while preserving the persistent graph. @@ -180,7 +180,7 @@ What: new Rank: major [DESCRIPTION] Add DFA::nfa factories for finite automata with nondeterministic transitions. -The factories retain nondeterminism without determinization or minimization; +The factories support nondeterminism without determinization or minimization; epsilon transitions are not supported. Regular propagation now preserves branching assigned prefixes when cloning such automata. @@ -191,7 +191,7 @@ Rank: major [DESCRIPTION] Add self-declared check, standard, and sweep tags to the public test harness. The standard check targets use a focused integrity selection, while standard -testing retains broad coverage without the exhaustive multi-hour sweeps. +testing provides broad coverage without the exhaustive multi-hour sweeps. [ENTRY] Module: test @@ -233,7 +233,7 @@ Issue: 166 Thanks: Mats Carlsson [DESCRIPTION] Fix domain-level global cardinality propagation losing solutions by counting -assigned variables twice and retaining stale conflict marks when cardinality +assigned variables twice and leaving stale conflict marks when cardinality bounds shrink. [ENTRY] diff --git a/docs/astar.md b/docs/astar.md index a1af084546..2bb8661858 100644 --- a/docs/astar.md +++ b/docs/astar.md @@ -20,7 +20,7 @@ Search::HeuristicGuidance guidance(Search::HeuristicGuidance::MINIMIZE, }); AStar search(root, guidance); while (std::unique_ptr solution{search.next()}) { - // Read or retain the improvement; the caller owns it. + // Inspect or save the improvement; the caller owns it. } if (search.stopped()) { // Raise or reset the stop limit and call next() again to resume. @@ -38,9 +38,9 @@ the same compatible scalar integer objective. The caller must justify the bound and keep callback arithmetic in range. Solution acceptance uses this exact scalar value, independently of rank; no Space comparison API is required. -Ordinary Gecode 6.x BAB, restart and portfolio engines retain their existing -incumbent admission test: they apply `constrain` to a temporary clone and check -propagation-visible failure. An external incumbent whose improvement can only +Ordinary Gecode 6.x BAB, restart and portfolio engines test incumbent admission +by applying `constrain` to a temporary clone and checking propagation-visible +failure. An external incumbent whose improvement can only be established by exhausting hidden completion search is not guaranteed admission by that test. Native scalar adoption uses the exact objective value. @@ -83,7 +83,7 @@ or infeasibility when none exists, provided the bound, strict improvement and optional state-pruning contracts preserve coverage. Best-first uses the callback lifetime, numeric, ownership, stop, exception and descriptor contracts below. The native factory is `Search::bestfirstengine(root, guidance, ordering, options, pruning)`; -`Search::astarengine` retains its signature and selects bound-first. +`Search::astarengine` selects bound-first. The [additive example](../examples/best-first.cpp) minimizes two explicit stages. The first-stage costs are 0 and 9, with final-arc costs `[5,6]` and `[1,2]`. @@ -163,7 +163,7 @@ the guidance lifetime rules. Providing just one callback raises For example, consider the finite chain `root -> A -> B -> goal`, where `A` and `B` have identical variable domains and prefix costs but different brancher stages. A domain-only key with equal labels is unsafe: A* and column search -retain `A`'s descriptor after expanding it, reject `B` as equivalent, and lose +store `A`'s descriptor after expanding it, reject `B` as equivalent, and lose the only route to the goal. `A` needs two remaining commits; `B` needs one. For this chain, adding the exact brancher stage to the key distinguishes the residual problems and preserves progress. More generally, include enough stage @@ -184,7 +184,7 @@ std::unique_ptr builder( // The native factory also accepts pruning as its final argument. ``` -A* retains nondominated descriptor values after expansion, without extra Space +A* stores nondominated descriptor values after expansion, without extra Space clones. A better label enters the frontier before replacing dominated metadata, even when an older representative already expanded. Admissible inconsistent bounds therefore remain supported. Older pending Spaces and saved Choice @@ -214,6 +214,7 @@ Build and run the additive example with `build/open-search/bin/best-first`; it links the same native libraries. [Native column search](column.md) uses the same ownership, guidance and state -pruning declarations with retained depth queues. Its optional rank orders each -queue independently of bound, and full cycles increase the expansion quota. +pruning declarations with queues of deferred work at each depth. Its optional +rank orders each queue independently of bound, and full cycles increase the +expansion quota. Quota growth zero selects fixed-quota CBFS. diff --git a/docs/cabs.md b/docs/cabs.md index 56cf250374..15b0ff9117 100644 --- a/docs/cabs.md +++ b/docs/cabs.md @@ -18,7 +18,7 @@ Search::HeuristicGuidance guidance(Search::HeuristicGuidance::MINIMIZE, }); CABS search(root, guidance); while (std::unique_ptr solution{search.next()}) { - // Read or retain this improvement; the caller owns it. + // Inspect or save this improvement; the caller owns it. } if (search.stopped()) { // Search has no proof. Ordinary stop limits can be raised to resume. @@ -41,16 +41,16 @@ bound in the objective direction. Equal priorities use generation order. Rank never justifies sound pruning. Each native `Choice` commit advances one layer. The engine starts at width one, -considers every generated terminal node, and retains at most that many unresolved +considers every generated terminal node, and queues at most that many unresolved next-layer candidates. A shallow goal can be returned before deeper improvements; publication preserves the remaining parents, alternatives, and preparation. -After all retained nodes and original alternatives in a pass finish, CABS can +After all admitted nodes and original alternatives in a pass finish, CABS can prove optimality when every width-discarded bound excludes strict improvement of the accepted incumbent. It keeps the minimum discarded bound for minimization or maximum for maximization across every layer, independently of rank. This also applies when a discarded bound equals the incumbent objective. If a bound still permits improvement, or no incumbent exists after width loss, CABS doubles -the width and restarts from an unchanged stable root, retaining the incumbent. +the width and restarts from an unchanged stable root, keeping the incumbent. A pass with no width loss can prove optimality or infeasibility. Finite search trees are required for the widening completeness claim. Natural exhaustion proves optimality of the best accepted incumbent, including @@ -81,7 +81,7 @@ also reports a stop. These intrinsic stops remain stopped on repeated `next()` calls. `Search::Engine::reset(new_root)` starts fresh, clearing the stop, incumbent, pending work, and statistics; it takes ownership of the new root. -The frontier quota counts retained pending search spaces: unexpanded entries +The frontier quota counts pending search spaces: unexpanded entries in the current layer, next-layer candidates, and a selected parent with remaining alternatives. Moving a parent between these owners does not consume an extra slot, and replacing a candidate uses its existing slot. The quota excludes the @@ -89,19 +89,19 @@ immutable restart root, owned incumbent, caller-owned returned solutions, a temporary successor, and hidden brancher trial spaces. It is a **node quota**, without a byte or process-memory guarantee. -Ordinary stop objects retain continuation. Raise or reset their limits and call +Search can resume after an ordinary stop. Raise or reset the stop limits and call `next()` again to continue the same pass. Capacity-one `WorkerControl` supports parking and resuming without reporting exhaustion. CABS rejects expanded thread counts other than one and rejects tracers before taking root ownership. The default `Options::clone=true` keeps the supplied root with the caller; `clone=false` transfers it after successful construction. Returned solutions and -the retained incumbent have independent ownership. The exact scalar guidance +the engine's incumbent have independent ownership. The exact scalar guidance value decides acceptance in the declared objective direction; equal or worse external incumbents are ignored. Cuts affect committed children, leaving saved source choices and the restart root -unchanged. Already inherited cuts are not repeated, so retained completion -branchers can finish. If a callback or model operation throws, +unchanged. Already inherited cuts are not repeated, so completion +branchers can finish their pending checks. If a callback or model operation throws, destroy the engine before using it again. The engine stores cloned states and reuses each selected parent for its final @@ -121,24 +121,24 @@ std::unique_ptr builder( cabs(TSPTW::Transition::guidance(), options, limits, pruning)); ``` -Descriptors belong only to retained **next-layer candidates**. CABS compares +Descriptors belong only to admitted **next-layer candidates**. CABS compares neither selected/current-layer nodes nor previously expanded nodes. A live equivalent or dominating candidate can reject an arrival. A new dominator takes -one victim's retained slot before removing any other victims, even if its beam +one victim's existing slot before removing any other victims, even if its beam rank is worse. Sound replacement does not lose coverage. An arrival discarded by ranking registers nothing; an evicted candidate loses its descriptor. Descriptors are cleared as candidates become the current layer, between passes and on reset. A beam-discarded representative therefore cannot suppress a later surviving route. Earlier width-discarded bounds remain in the pass aggregate; sound label pruning does not erase them. Completion still requires processing -all retained work and bounding out every width-discarded alternative. +all admitted work and bounding out every width-discarded alternative. `descriptor_limit` forgets all candidate descriptors if their count exceeds the ceiling, preserving the candidates themselves. It is independent of width and frontier limits and never reports a stop. Empty policy performs no extraction, descriptor allocation or registry work. `state_pruned` counts rejected arrivals and removed dominated live candidates, separately from failures and beam loss. -`state_descriptors`/`state_bytes` report retained descriptor peaks using the +`state_descriptors`/`state_bytes` report peak descriptor storage using the [A* storage estimate](astar.md); all three are zero when pruning is disabled. The existing stop, proof and independently owned solution contracts still apply. diff --git a/docs/column.md b/docs/column.md index f6a53125eb..b45a0644c2 100644 --- a/docs/column.md +++ b/docs/column.md @@ -74,7 +74,7 @@ and brancher trials are excluded. If an otherwise admissible child cannot fit, search stops incompletely and stays stopped until `Search::Engine::reset`. An ordinary stop can resume. -This is a Space count, without a byte or process-RSS guarantee. Each retained +This is a Space count, without a byte or process-RSS guarantee. Each queued node owns a stable Space; the engine reuses each selected parent for its final alternative. Descriptor vectors and queue/container capacity add storage. `c_d` and `a_d` do not enable recomputation. No-good extraction is empty. diff --git a/docs/tsptw-cheap-deadline-filter.md b/docs/tsptw-cheap-deadline-filter.md index fb14b9bc19..6556c31945 100644 --- a/docs/tsptw-cheap-deadline-filter.md +++ b/docs/tsptw-cheap-deadline-filter.md @@ -3,7 +3,7 @@ The native transition example accepts `--residual-propagation off|cheap|unary`. `off` remains the default. `cheap` checks necessary customer and depot deadlines after each committed visit, without constructing a residual unary Space. -`unary` retains its existing scheduling relaxation and Gecode fixpoint policy. +`unary` uses its scheduling relaxation and Gecode fixpoint policy. For current location `i`, time `t`, and each unvisited customer `u`, let `d[i,u]` be the shortest path length in the supplied matrix. The cheap check uses @@ -66,7 +66,7 @@ The runner does not measure frontier or clone buffer bytes. ## Observed results and decision All 72 rows completed. The checker accepted 476 terminal-row publications and -three more publications in retained interrupted streams. There were 23 outer +three more publications in saved interrupted streams. There were 23 outer timeouts, no RSS stops and no terminal execution, measurement or invalid-output failures. Forty-nine rows have summaries; the other 23 have unavailable phase times and node counts. Each configuration has 12 observations, including four @@ -102,7 +102,7 @@ Both repetitions agree on these useful differences: without proof. BAB OFF finds no tour. CHEAP BAB inference costs 34.910–35.054 ms; UNARY's stopped summaries record 1,545.946–1,548.630 ms. -Retain CHEAP as an explicit option. It removes enough deadline dead ends to +Keep CHEAP as an explicit option. It removes enough deadline dead ends to improve this small comparison while avoiding full unary inference. These four development cases and two easy controls do not establish a universal speedup or settle defaults. The variant and Gendreau cases remain unproved. @@ -113,13 +113,13 @@ whether reusable residual state can preserve its stronger pruning at lower cost. This comparison adds no RSS-stop evidence for a fixed-width continuation study: its largest sampled CABS peaks are about 283 MiB OFF, 97 MiB CHEAP and 77 MiB UNARY, and frontier bytes are unmeasured. Any continuation study should -retain its separate justification and budget. No such study is implemented here. +have its own justification and budget. No such study is implemented here. The 72 terminal rows use 77.537 s of launch-through-reap wall time. Two capture cleanup calls failed with a permission error; inspection found both children absent. The ledger charges 39.635 s and 20.234 s conservatively through absence confirmation, including their observed 2.008 s and 2.006 s attempts. Their partial -streams remain available. Validation includes two retained short-cap timeouts +streams remain available. Validation includes two recorded short-cap timeouts before successful retries of the required integration checks. At implementation handoff, shared usage is **170.957548 s**: correctness @@ -184,4 +184,4 @@ The frozen report records checks from the earlier combined branch. Its tiny CLI checks with BAB and CABS proved waiting cost 5 and both late fixtures infeasible with `residual_propagation=cheap` and `propagation_ms=0`. The report's build paths and timings describe that run, rather than current native branch -validation. The standalone branch retains the same suffix tests and CLI checker. +validation. The standalone branch uses the same suffix tests and CLI checker. diff --git a/docs/worker-control.md b/docs/worker-control.md index a1a0f26623..9419306cd7 100644 --- a/docs/worker-control.md +++ b/docs/worker-control.md @@ -58,7 +58,7 @@ request becomes visible remain nondeterministic. Shrinking parks resident operating-system threads. It does not destroy their thread objects or discard their engine-local search state. Growing wakes those threads again. Set the capacity to the largest allocation the engine may -receive. Parked workers retain their stacks and other per-worker state. +receive. Parked workers keep their stacks and other per-worker state. ## Handle lifetime and ownership @@ -92,7 +92,7 @@ requests are harmless and cannot access destroyed scheduler state. An internal engine owner that constructs successive leaf engines can call `WorkerControlAccess::prepare_reuse` after destroying the previous engine and joining its workers. The replacement must have the same resolved capacity. -The control retains its shared identity, latest request (including pause), +The control keeps its shared identity, latest request (including pause), request generation, and event storage. Preparing an attached control raises `Search::WorkerControlInUse`; attaching with a different capacity raises `Search::InvalidWorkerRequest`. No public reuse operation is provided. diff --git a/examples/astar.cpp b/examples/astar.cpp index ad03da2a53..ea81917d81 100644 --- a/examples/astar.cpp +++ b/examples/astar.cpp @@ -41,7 +41,7 @@ using namespace Gecode; // A bounded closed model: option zero is a shallow goal, while the other -// options retain a second decision. Rank deliberately disfavors the optimum. +// options leave a second decision. Rank deliberately disfavors the optimum. class Alternative : public Space { public: IntVar option, detail, objective; diff --git a/examples/common-pattern.hh b/examples/common-pattern.hh index 236d59c8a1..6af8de190f 100644 --- a/examples/common-pattern.hh +++ b/examples/common-pattern.hh @@ -73,7 +73,7 @@ namespace PatternMining { return sequence; } - /// Read one sequence per line, retaining duplicate rows as distinct records + /// Read one sequence per line, counting duplicate rows as distinct records inline Sequences read_database(std::istream& stream, Format format) { Sequences input; diff --git a/examples/data/tsptw/README.md b/examples/data/tsptw/README.md index a04c36cae2..0e97246609 100644 --- a/examples/data/tsptw/README.md +++ b/examples/data/tsptw/README.md @@ -48,7 +48,7 @@ The five generated `tiny/` instances are separate from canonical benchmarks: The transition example also offers `--residual-propagation cheap` between OFF and the existing unary relaxation. [The deadline-filter note](../../../docs/tsptw-cheap-deadline-filter.md) explains its shortest-path checks and bounded development comparison; -[the saved report](cheap-deadline-comparison.json) retains each case and repetition. +[the saved report](cheap-deadline-comparison.json) records each case and repetition. The default remains OFF. `published/` contains one unchanged canonical input and one published @@ -130,7 +130,7 @@ nonnegative integers; omitting an ordinary limit disables it. Time is measured from the start of instance reading and preprocessing. Stops are checked at engine boundaries and do not interrupt propagation. CABS also accepts `--max-width N` and `--frontier-limit N`; zero disables either cap. The frontier -cap counts retained nodes and does not limit process memory. Runs use one +cap counts pending nodes and does not limit process memory. Runs use one search thread. The executable emits JSON lines. Each `solution` record contains a customer @@ -171,7 +171,7 @@ unsound for nonmetric matrices. The visit list has at most n positions: n-1 customer visits and one explicit depot return. Brancher preparation materializes the next position without changing the concrete state. One n-ary choice commit assigns one action and -updates that state once. Archived choices retain both position and customer +updates that state once. Archived choices store both position and customer IDs; historical BAB replay can materialize an absent position before assigning it. An unfinished state with no applicable action fails. The final return checks the depot due time, adds the return arc exactly once and closes the list. @@ -186,7 +186,7 @@ prepare/choice/commit journey before native incumbent adoption. `--formulation eager` reserves all n path variables upfront. It uses the same Space, brancher, applicability, ordering, incumbent cuts and search engines. Only path allocation changes. The two transition representations emit identical -improving tours under the same engine; the closed model retains its own +improving tours under the same engine; the closed model uses its own cost-regret and successor branching. ```sh @@ -249,7 +249,7 @@ return `std::nullopt` from extraction where sufficient state is unavailable. The declaration does not justify pruning ordinary partial successor CP stores. Returned tours remain independently owned and use the same checker. -A* and column retain descriptor metadata after expansion and admit improved labels +A* and column store descriptor metadata after expansion and admit improved labels again, including with inconsistent admissible bounds. CABS compares only live next-layer candidates; beam-discarded states never become representatives. See [A*](../../../docs/astar.md) and [CABS](../../../docs/cabs.md) for callback @@ -257,7 +257,7 @@ contracts, optional metadata forgetting and candidate lifetimes. Forgetting removes metadata alone and never removes search work or causes a stop. Summary records identify `state_pruning`. `state_pruned` counts sound state -removals; `state_descriptors` and `state_bytes` record retained peaks. The byte +removals; `state_descriptors` and `state_bytes` record peak descriptor storage. The byte estimate includes descriptor objects and owned key/label vector capacities, excluding container capacity and allocator overhead. It does not include Space/frontier storage or measure process RSS. These counters stay zero with @@ -327,7 +327,7 @@ configurations on the frozen development cases `Dumas/n20w20.001.txt`, `variants/Dumas/customer-due-plus-100/n20w100.003.txt`. Each ran twice under transition A*, Pareto off, one thread, no seed, a five-second native limit and a seven-second external guard. Runs were sequential, interleaved by case, with -configuration order reversed for repetition two. The record retains exact +configuration order reversed for repetition two. The record includes exact commands, input and binary hashes, every independently checked incumbent, individual timings, node counts, stops and sampled storage. Compilation was completed before capture and was not timed. No held-out outcomes were used. @@ -370,7 +370,7 @@ the stronger options explicit and the prefix/off defaults unchanged. Both ordinary Space formulations support `--engine column`. Its default quota starts at one and grows by one after every complete depth cycle. Set `--quota-growth 0` for fixed-quota CBFS, or `--initial-quota N` for another -positive initial quota. Quotas schedule expansion and retain deferred work. +positive initial quota. Quotas schedule expansion; deferred work stays queued. `--frontier-limit N` caps pending Space count, including an unfinished parent; zero is unlimited. Cap termination reports `stopped` and supplies no proof. `--max-width` remains specific to CABS. Column summaries record `initial_quota`, @@ -438,7 +438,7 @@ launch a campaign or enforce a budget shared across named runs. Both solver routes receive one thread, the same launch-to-reap wall deadline and a 1 GiB sampled RSS watchdog. `ps` reports the summed RSS of the owned process group, including uv and its Python child. Sampling happens every nominal 50 ms -plus probe overhead; records retain sample count, maximum gap and total probe +plus probe overhead; records include sample count, maximum gap and total probe time. Termination between samples can miss the peak; no observed live RSS is `null`. The watchdog can overshoot and is not a hard memory limit. Probe failure terminates the sample as a measurement failure. Stops kill and reap the process @@ -460,7 +460,7 @@ Build before capture. `--compilation-seconds S` records a separately measured build duration; omission explicitly leaves it unavailable. Compilation is never silently attributed to search. -Each row retains checked incumbent times and costs, the summary, final objective, +Each row records checked incumbent times and costs, the summary, final objective, proof or stop reason, expansions and sampled peak RSS. Native descriptor peaks, bound/inference timings and basic allocation sizes remain available in the summary. Frontier bytes and actual clone footprint are unavailable. The native @@ -469,7 +469,7 @@ storage. DIDPPy does not expose equivalent descriptor/clone counters here. `analyze` reads only saved config and rows, writes `analysis.json`, and reports expected/completed/unfinished/failed counts and valid-run wall-time medians by configuration. With `closed-bab` present, it also pairs every other configuration -with that baseline on the same case and repetition. Pairs retain status, checked +with that baseline on the same case and repetition. Pairs record status, checked final objective, first-incumbent time, quality at 100/1,000/5,000/15,000 reported milliseconds when within the allowance, proof/time, termination, outer wall time and sampled peak RSS. Counts distinguish wins, ties, losses, missing/failed pairs @@ -545,7 +545,7 @@ canonical Gendreau n20, where regret branching finds no tour. On Gendreau n60 it finds cost 527 without proof; regret branching finds no tour. On the marked Gendreau window variant it proves 202, while regret candidates end at 208 without proof. The counterexample is canonical `AFG/rbg038a.tw`: DOM/regret BAB and regret -RBS retain checked cost 2493 in both repetitions, while size candidates and +RBS find checked cost 2493 in both repetitions, while size candidates and VAL/regret find no tour. No candidate finds a tour on `Dumas/n60w100.001.txt`. Canonical cases and four deterministic window variants remain separately marked in the report; scaled costs are compared only within each case. @@ -589,7 +589,7 @@ remains separate; no held-out outcomes were inspected. All 806 emitted campaign incumbents passed the independent Fraction checker. There were three outer timeouts, zero RSS terminations and no invalid output, -execution or measurement failures. The timeout rows retain checked feasible +execution or measurement failures. The timeout rows include checked feasible quality where available and never proof. Eighteen short samples finished without a live RSS observation. The largest observed sampling gap was 465 ms and the longest sample took 15.101 wall seconds: the nominal 50 ms watchdog can miss @@ -618,7 +618,7 @@ canonical instances and one variant per family. Source instances remain disjoint from development. Each case/configuration has one observation, a 30-second outer cap, one search thread and the same sampled 1 GiB process-group RSS watchdog. The watchdog requests samples every 50 ms; process-list probes and termination -can extend that interval and overshoot either limit. Captures retain the observed +can extend that interval and overshoot either limit. Captures record the observed gaps, sampled RSS overshoot and termination/reap time. Short processes can finish during a probe, so a sampled peak can miss brief allocations and is not a kernel peak-RSS measurement. @@ -650,7 +650,7 @@ The actual shared ledger is `/tmp/open-search-016-budget.jsonl`. It includes builds, validation, development, failed attempts and held-out launch-through-reap time, enforces a 7,200-second total, and reserves 300 seconds for verification and 600 seconds for termination overhead and contingency. Pre-freeze work stays -below 400 seconds. Resume retains this ledger and skips validated terminal rows, +below 400 seconds. Resume reuses this ledger and skips validated terminal rows, including failures; partial attempts remain beside their replacements. The RSS probe requires process-list and owned-process termination permission on sandboxed hosts. Two held-out termination attempts failed after normal stopped summaries; @@ -675,17 +675,17 @@ uv run --script misc/tsptw-benchmark.py analyze --name heldout-comparison-016 \ --output examples/data/tsptw/heldout-comparison.json ``` -Analysis launches no solver. It reads the expected frozen rows, retains all +Analysis launches no solver. It reads the expected frozen rows, includes all terminal outcomes, and groups families and canonical/variant cases. Publication checkpoints at 100 ms, 1 s, 5 s, 15 s and 30 s use solver-reported clocks; outer wall time includes launch and reap. Launch-inclusive publication timestamps are unavailable. A final gap is reported only against a matching campaign optimality proof; an observed best cost is not an established optimum. Killed processes -can retain checked incumbents but supply no proof or post-kill phase summaries. +can include checked incumbents but supply no proof or post-kill phase summaries. Frontier/clone buffer bytes remain unavailable, and `sizeof` allocation fields exclude dynamic storage. Supported counts, inference/setup times, descriptor storage and sampled RSS remain in the individual records and compact comparison. -Runtime pairs retain terminal status and proof-reported time separately. A lower +Runtime pairs record terminal status and proof-reported time separately. A lower launch-wall time from an RSS-stopped run records earlier termination; it does not establish a speedup or completed search. Aggregate wall-time wins are not algorithm runtime gains when observations have different completion and stop @@ -693,7 +693,7 @@ conditions. The [saved comparison](heldout-comparison.json) summarizes all 192 terminal rows and per-case costs/checkpoints from 569 independently checked publications. Raw -captures retain the full traces. Offline replay also checked three publications +captures include the full traces. Offline replay also checked three publications in the two archived interrupted attempts; those attempts do not contribute a better cost to a replacement row. The final matrix has 81 normal exits (44 proofs and 37 ordinary stops), 61 outer time stops @@ -718,7 +718,7 @@ quality than BAB on 11 cases, worse quality on two and equal cost on five; six cases have no tour from either. Column has 10 better, three worse and five equal outcomes. At one second, CABS has nine better and three worse outcomes than BAB, and column has ten better and three worse. All four engines prove the same four -optimal and two infeasible cases. A* retains no additional tour over BAB in the +optimal and two infeasible cases. A* finds no additional tour over BAB in the final observations. The closed engines each prove six optimal cases and one infeasible case; CABS adds one feasible case. DIDPPy CABS adds four feasible cases to A*'s three, with the same three optimality proofs. Runtime tallies remain @@ -762,7 +762,7 @@ work comprises 3,548.328856 measured group seconds and separate conservative cleanup allowances of 128.747037 and 57.084679 s. The measured total across phases is 3,703.902846 s. Development includes its first process-list failure (0.005793 s); held-out includes both interrupted attempts (30.007945 and -30.008852 s). No process remains pending. Verification has used 0 s and retains +30.008852 s). No process remains pending. Verification has used 0 s and has its 300 s reserve; 3,310.265438 s of the total remains. The ledger and raw captures are external artifacts, and these figures describe this handoff. @@ -771,7 +771,7 @@ bounded fixed-width continuation experiment for deterministic TSPTW transitions. The fixed-width study found that an original-Choice cursor matched border regeneration's Space reduction with fewer keys and commitments on its worked trees. Compare that restricted cursor -with CABS, recording inference work as well as retained Spaces: current normal +with CABS, recording inference work as well as stored Spaces: current normal summaries spend much of search inside unary inference. Generic Space border replay still lacks its required contract. The suffix-repair study found no improvement on its two development neighborhoods and supports no LNBS diff --git a/examples/data/tsptw/bounds-comparison.json b/examples/data/tsptw/bounds-comparison.json index 5c389284b3..c49d7ebfe9 100644 --- a/examples/data/tsptw/bounds-comparison.json +++ b/examples/data/tsptw/bounds-comparison.json @@ -75,7 +75,7 @@ "node": "Native search statistics.node records expanded/visited search nodes, not arc evaluations.", "rss": "ps -o rss= -p PID reports process RSS in KiB, sampled serially at nominal 50ms plus ps/loop overhead; peaks between samples can be missed. Includes native process only. Kill above 1GiB is a sampled watchdog, not a hard allocation limit.", "storage": "Sampled process RSS includes frontier and model storage; state_bytes reports descriptor objects/key/label capacities only, excluding containers/allocator overhead. No native frontier-byte counter exists; field is null.", - "guard": "7 seconds from launch, polled with the RSS loop. On guard/watchdog, kill the owned process group, reap it and retain partial output; no proof is assigned without a native complete summary.", + "guard": "7 seconds from launch, polled with the RSS loop. On guard/watchdog, kill the owned process group, reap it and save partial output; no proof is assigned without a native complete summary.", "timing_precision": "Example times have 0.001ms precision; sampled RSS has 1024-byte units. Probe overhead is common across configurations.", "reported_native_elapsed_sum_ms": "Sum of eight available native summary times; excludes ten RSS-killed processes whose elapsed/counter summaries are unavailable." }, diff --git a/examples/data/tsptw/cheap-deadline-comparison.json b/examples/data/tsptw/cheap-deadline-comparison.json index 49a4598d56..6171afd55b 100644 --- a/examples/data/tsptw/cheap-deadline-comparison.json +++ b/examples/data/tsptw/cheap-deadline-comparison.json @@ -4335,7 +4335,7 @@ "raw_results": "build/tsptw-results/cheap-deadline-028", "recommendation": { "change_defaults": false, - "fixed_width_continuation_study": "This comparison adds no memory-stop evidence. Retain separate justification and budget for any continuation study.", + "fixed_width_continuation_study": "This comparison adds no memory-stop evidence. Require separate justification and budget for any continuation study.", "incremental_unary_study": "A separately scoped bounded study is justified by available unary inference time; distinguish rebuilding and propagation before choosing an implementation.", "keep_cheap_option": true }, diff --git a/examples/data/tsptw/cp-baseline-tuning.json b/examples/data/tsptw/cp-baseline-tuning.json index 846e4cea90..9ac0dad0fb 100644 --- a/examples/data/tsptw/cp-baseline-tuning.json +++ b/examples/data/tsptw/cp-baseline-tuning.json @@ -1243,7 +1243,7 @@ "measurements": "Pair costs only within a case and its integer scale. Native reported milliseconds start at main entry and include preprocessing; end-to-end wall seconds cover launch through reap. Checked timeout/RSS incumbents count as feasible quality, never proof. Lower outer wall time can reflect earlier resource termination. Timing counts and medians across distinct cases do not establish a universal speedup.", "observed_tradeoffs": [ "VAL/regret BAB loses the AFG/rbg038a tour in both repetitions and adds no quality or proof wins against DOM/regret.", - "DOM/size BAB finds no tour on AFG/rbg038a; DOM/regret BAB and regret RBS retain checked cost 2493 without proof.", + "DOM/size BAB finds no tour on AFG/rbg038a; DOM/regret BAB and regret RBS find checked cost 2493 without proof.", "No candidate finds a tour on Dumas/n60w100 within the allowance.", "Size BAB and size RBS prove 249 on the Dumas window variant and 267 on canonical Gendreau n20, find cost 527 on Gendreau n60 without proof, and prove 202 rather than the regret candidates final 208 on the Gendreau window variant." ], diff --git a/gecode/flatzinc/restart-random.hpp b/gecode/flatzinc/restart-random.hpp index c272d2aaac..0e46564d9f 100644 --- a/gecode/flatzinc/restart-random.hpp +++ b/gecode/flatzinc/restart-random.hpp @@ -42,7 +42,7 @@ namespace Gecode { namespace FlatZinc { namespace Internal { uniform_int_offset(Random& random, unsigned long long int width) { const unsigned long long int chunk_width = 1ULL << 31; - // Retain the established seeded sequence for ordinary integer ranges. + // Use the established seeded sequence for ordinary integer ranges. if ((width <= chunk_width) || (width > (1ULL << 32))) return random(width); diff --git a/gecode/int.hh b/gecode/int.hh index 3edc4ecb10..22cc33436d 100755 --- a/gecode/int.hh +++ b/gecode/int.hh @@ -2195,7 +2195,7 @@ namespace Gecode { * \brief Finite automaton with deterministic construction by default * * The constructors and init() require deterministic transitions. - * Use nfa() to retain nondeterministic transitions without determinization + * Use nfa() to support nondeterministic transitions without determinization * or minimization. Every transition consumes one symbol; epsilon * transitions are not supported. * @@ -2323,7 +2323,7 @@ namespace Gecode { * * Start state is \a s, transitions are \a t, and final states are * \a f. No sentinel elements are needed. As for the array overload, - * nondeterminism is retained and epsilon transitions are not supported. + * nondeterministic transitions are supported and epsilon transitions are not supported. */ GECODE_INT_EXPORT static DFA nfa(int s, std::initializer_list t, @@ -3321,7 +3321,7 @@ namespace Gecode { * The default organization is MDP_SINGLE. * Uses forward pruning from assigned endpoints and pairwise upper bounds * on \a z. Both organizations implement the same filtering. MDP_DECOMPOSED - * retains separate actors and failure counts; MDP_SINGLE retains one + * uses separate actors and failure counts; MDP_SINGLE uses one * actor, linear advisor state, and a quadratic array of pair witnesses. * This is incomplete propagation, not full bounds or domain consistency. * diff --git a/gecode/int/arithmetic/gcd.hpp b/gecode/int/arithmetic/gcd.hpp index 754d1aa638..3eeb24ad56 100644 --- a/gecode/int/arithmetic/gcd.hpp +++ b/gecode/int/arithmetic/gcd.hpp @@ -94,7 +94,7 @@ namespace Gecode { namespace Int { namespace Arithmetic { static_cast(g))*g; // Use the interval hull: interior holes must not affect a BND actor's // fixpoint. The candidates are isolated arithmetic supports, not a - // domain enumeration. On exhaustion retain the original endpoint. + // domain enumeration. On exhaustion use the original endpoint. for (int budget=64; (budget > 0) && (v <= x.max()); --budget, v+=g) if (gcd_value(a,static_cast(v)) == g) { bound=static_cast(v); @@ -113,7 +113,7 @@ namespace Gecode { namespace Int { namespace Arithmetic { if (!gcd_support_min(x,a,g,l) || !gcd_support_min(MinusView(x),a,g,negative_u)) return ES_FAILED; - // On budget exhaustion retain the original endpoint: never advance a + // On budget exhaustion use the original endpoint: never advance a // partially checked bound, which could cause an unbounded closure walk. GECODE_ME_CHECK(x.gq(home,l)); GECODE_ME_CHECK(x.lq(home,-negative_u)); diff --git a/gecode/int/min-distance/prop.hpp b/gecode/int/min-distance/prop.hpp index fa3048bf22..193980b69b 100644 --- a/gecode/int/min-distance/prop.hpp +++ b/gecode/int/min-distance/prop.hpp @@ -374,7 +374,7 @@ namespace Gecode { namespace Int { namespace MinDistance { for (int j=i+1; jretain(std::move(*descriptor)); + // Pending Spaces keep their original Choice cursors and coverage. + registry->store(std::move(*descriptor)); state_descriptors = std::max(state_descriptors,registry->size()); state_bytes = std::max(state_bytes,registry->storage()); } Space* AStar::publish(std::unique_ptr s) { - // Clean exhausted branchers, retaining completion branchers as usual. + // Delete exhausted branchers; completion branchers must survive restrictions. (void) s->choice(); const std::int64_t bound = guidance.bound(*s); if (!improves(bound)) return nullptr; diff --git a/gecode/search/branch-feedback.hh b/gecode/search/branch-feedback.hh index 6d8fcf76a3..c9859624eb 100644 --- a/gecode/search/branch-feedback.hh +++ b/gecode/search/branch-feedback.hh @@ -108,7 +108,7 @@ namespace Gecode { namespace Search { } }; - /// Retained feedback for a stolen alternative whose choice can be deleted + /// Saved feedback for a stolen alternative whose choice can be deleted class PendingBranchFeedback { private: BranchFeedback _feedback; @@ -121,7 +121,7 @@ namespace Gecode { namespace Search { explicit operator bool(void) const { return static_cast(_feedback); } - /// Retain feedback from \a choice for \a alternative, if opted in + /// Copy feedback from \a choice for \a alternative, if opted in void set(const Choice& choice, unsigned int alternative) { if (const BranchFeedbackChoice* feedback_choice = dynamic_cast(&choice)) { diff --git a/gecode/search/cabs.cpp b/gecode/search/cabs.cpp index da156017ef..7de5c85e3c 100644 --- a/gecode/search/cabs.cpp +++ b/gecode/search/cabs.cpp @@ -105,8 +105,8 @@ namespace Gecode { namespace Search { namespace Seq { } bool - CABS::retain(std::unique_ptr s, unsigned long int depth, - unsigned long long int revision) { + CABS::admit(std::unique_ptr s, unsigned long int depth, + unsigned long long int revision) { const std::int64_t bound = guidance.bound(*s); if (!improves(bound)) { fail++; @@ -118,10 +118,10 @@ namespace Gecode { namespace Search { namespace Seq { descriptor = pruning.extract(*s); if (descriptor) for (std::size_t i=0; ihash != retained->hash || - descriptor->key != retained->key) continue; - const auto relation = pruning.compare(*descriptor,*retained); + const auto& existing = candidates[i].descriptor; + if (!existing || descriptor->hash != existing->hash || + descriptor->key != existing->key) continue; + const auto relation = pruning.compare(*descriptor,*existing); if (relation == StatePruning::EQUIVALENT || relation == StatePruning::DOMINATED) { state_pruned++; @@ -135,7 +135,7 @@ namespace Gecode { namespace Search { namespace Seq { std::move(descriptor)}; Earlier earlier{guidance.sense,static_cast(guidance.rank)}; if (!victims.empty()) { - // A dominator takes a victim's retained slot regardless of rank. Only + // A dominator takes a victim's existing slot regardless of rank. Only // after it survives may the other dominated live candidates disappear. for (std::size_t victim : victims) remove_descriptor(candidates[victim]); @@ -165,7 +165,7 @@ namespace Gecode { namespace Search { namespace Seq { discarded > *discarded_bound)) discarded_bound = discarded; if (!replace) return true; - // Replacement uses the discarded entry's retained slot. + // Replacement uses the discarded entry's existing slot. std::pop_heap(candidates.begin(),candidates.end(),earlier); remove_descriptor(candidates.back()); candidates.back() = std::move(entry); @@ -206,7 +206,7 @@ namespace Gecode { namespace Search { namespace Seq { CABS::record_descriptors(void) { if (pruning.descriptor_limit && candidate_descriptors > pruning.descriptor_limit) { - // Forget metadata only; every retained candidate still gets expanded. + // Forget metadata only; every admitted candidate still gets expanded. for (auto& candidate : candidates) candidate.descriptor.reset(); candidate_descriptors = 0; candidate_bytes = 0; @@ -312,7 +312,7 @@ namespace Gecode { namespace Search { namespace Seq { if (Space* solution = publish(std::move(step.space))) return solution; break; case SS_BRANCH: - if (!retain(std::move(step.space),child_depth,incumbent_revision)) + if (!admit(std::move(step.space),child_depth,incumbent_revision)) return nullptr; break; default: diff --git a/gecode/search/column.cpp b/gecode/search/column.cpp index d8a627c58c..cb0ebd448f 100644 --- a/gecode/search/column.cpp +++ b/gecode/search/column.cpp @@ -108,8 +108,8 @@ namespace Gecode { namespace Search { namespace Seq { } bool - Column::retain(std::unique_ptr s, unsigned long int depth, - unsigned long long int revision) { + Column::admit(std::unique_ptr s, unsigned long int depth, + unsigned long long int revision) { const std::int64_t bound = guidance.bound(*s); if (!improves(bound)) { fail++; @@ -137,8 +137,8 @@ namespace Gecode { namespace Search { namespace Seq { queued++; if (!descriptor) return true; // Admit fresh work before replacing labels, even after earlier expansion. - // Old pending Spaces retain their original Choice cursors and coverage. - registry->retain(std::move(*descriptor)); + // Pending Spaces keep their original Choice cursors and coverage. + registry->store(std::move(*descriptor)); state_descriptors = std::max(state_descriptors,registry->size()); state_bytes = std::max(state_bytes,registry->storage()); return true; @@ -146,7 +146,7 @@ namespace Gecode { namespace Search { namespace Seq { Space* Column::publish(std::unique_ptr s) { - // Clean exhausted branchers, retaining completion branchers as usual. + // Delete exhausted branchers; completion branchers must survive restrictions. (void) s->choice(); const std::int64_t bound = guidance.bound(*s); if (!improves(bound)) return nullptr; @@ -231,7 +231,7 @@ namespace Gecode { namespace Search { namespace Seq { if (Space* solution = publish(std::move(step.space))) return solution; break; case SS_BRANCH: - if (!retain(std::move(step.space),child_depth,incumbent_revision)) + if (!admit(std::move(step.space),child_depth,incumbent_revision)) return nullptr; break; default: diff --git a/gecode/search/pbs.hpp b/gecode/search/pbs.hpp index abf17c7b67..a73565226f 100644 --- a/gecode/search/pbs.hpp +++ b/gecode/search/pbs.hpp @@ -159,7 +159,7 @@ namespace Gecode { namespace Search { Engine* pbsseq(T* master, const Search::Statistics& stat, Options& opt) { Stop* stop = opt.stop; - // Retain each seed: a throwing nested builder may consume its own clone. + // Keep ownership of each seed: a throwing nested builder may consume its own clone. opt.clone = true; Region r; @@ -249,7 +249,7 @@ namespace Gecode { namespace Search { Engine* pbspar(T* master, const Search::Statistics& stat, Options& opt) { Stop* stop = opt.stop; - // Retain each seed: a throwing nested builder may consume its own clone. + // Keep ownership of each seed: a throwing nested builder may consume its own clone. opt.clone = true; Region r; @@ -373,7 +373,7 @@ namespace Gecode { if (o.assets == 1) { std::unique_ptr seed(master); (void) master->slave(0); - // Retain the seed if a nested builder rejects its worker control. + // Keep ownership of the seed if a nested builder rejects its worker control. opt.clone = true; e = Search::build(seed.get(),opt); return; diff --git a/gecode/search/rbs.hpp b/gecode/search/rbs.hpp index d5895124a6..270e88984f 100644 --- a/gecode/search/rbs.hpp +++ b/gecode/search/rbs.hpp @@ -88,7 +88,7 @@ namespace Gecode { Search::Statistics stat; std::unique_ptr stop(Search::Seq::rbsstop(m_opt.stop)); e_opt.stop = stop.get(); - // Retain the seed while a potentially nested builder owns its clone. + // Keep ownership of the seed while a potentially nested builder owns its clone. e_opt.clone = true; Search::WrapTraceRecorder::engine(e_opt.tracer, SearchTracer::EngineType::RBS, 1U); diff --git a/gecode/search/seq/astar.hh b/gecode/search/seq/astar.hh index b6b6e5a43d..63e6a8493e 100644 --- a/gecode/search/seq/astar.hh +++ b/gecode/search/seq/astar.hh @@ -74,7 +74,7 @@ namespace Gecode { namespace Search { namespace Seq { unsigned long long int order; /// Revision of the currently accepted incumbent unsigned long long int incumbent_revision; - /// Selected parent, also retained across interrupted preparation + /// Selected parent, also saved across interrupted preparation ChoiceExpansion expansion; SpaceStatus current_status; unsigned long int current_depth; diff --git a/gecode/search/seq/cabs.hh b/gecode/search/seq/cabs.hh index 3f567b742c..5070ae1507 100644 --- a/gecode/search/seq/cabs.hh +++ b/gecode/search/seq/cabs.hh @@ -91,8 +91,8 @@ namespace Gecode { namespace Search { namespace Seq { bool limited; bool improves(std::int64_t bound) const; - bool retain(std::unique_ptr s, unsigned long int depth, - unsigned long long int revision); + bool admit(std::unique_ptr s, unsigned long int depth, + unsigned long long int revision); void add_descriptor(const Entry& entry); void remove_descriptor(const Entry& entry); void record_descriptors(void); diff --git a/gecode/search/seq/choice-expansion.hh b/gecode/search/seq/choice-expansion.hh index 59f8e14d4e..6a133ff41f 100644 --- a/gecode/search/seq/choice-expansion.hh +++ b/gecode/search/seq/choice-expansion.hh @@ -125,7 +125,7 @@ namespace Gecode { namespace Search { namespace Seq { const SpaceStatus status = child->status(worker); branch_feedback(*decision,alternative,*child,branch_outcome(status)); alternative++; - // The final source is already retired before retained-node admission. + // The final source is already retired before successor admission. return {ExpansionStep::CHILD,std::move(child),status}; } }; diff --git a/gecode/search/seq/column.hh b/gecode/search/seq/column.hh index a4fecf8da3..98e9ecf5cf 100644 --- a/gecode/search/seq/column.hh +++ b/gecode/search/seq/column.hh @@ -43,7 +43,7 @@ #ifdef GECODE_HAS_EXPERIMENTAL namespace Gecode { namespace Search { namespace Seq { - /// Sequential depth queues with retained alternatives and progressive quotas + /// Sequential depth queues with deferred alternatives and progressive quotas class Column : public Worker { public: struct Configuration { @@ -79,7 +79,7 @@ namespace Gecode { namespace Search { namespace Seq { unsigned long long int order; /// Revision of the currently accepted incumbent unsigned long long int incumbent_revision; - /// Selected parent, also retained across interrupted preparation + /// Selected parent, also saved across interrupted preparation ChoiceExpansion expansion; SpaceStatus current_status; unsigned long int current_depth; @@ -90,8 +90,8 @@ namespace Gecode { namespace Search { namespace Seq { std::int64_t best_bound; bool improves(std::int64_t bound) const; - bool retain(std::unique_ptr s, unsigned long int depth, - unsigned long long int revision); + bool admit(std::unique_ptr s, unsigned long int depth, + unsigned long long int revision); bool select(void); Space* publish(std::unique_ptr s); void initialize(Space* s, bool clone); diff --git a/gecode/search/seq/state-registry.hh b/gecode/search/seq/state-registry.hh index 52c09e1249..b10239abe4 100644 --- a/gecode/search/seq/state-registry.hh +++ b/gecode/search/seq/state-registry.hh @@ -56,13 +56,13 @@ namespace Gecode { namespace Search { namespace Seq { StateRegistry(const StatePruning::Compare& c, unsigned long int l) : compare(c), limit(l), descriptors(0), descriptor_bytes(0) {} - /// Whether an equivalent or better retained label subsumes this descriptor + /// Whether an equivalent or better stored label subsumes this descriptor bool subsumes(const StatePruning::Descriptor& descriptor) const { const auto bucket = buckets.find(descriptor.hash); if (bucket != buckets.end()) - for (const auto& retained : bucket->second) { - if (descriptor.key != retained.key) continue; - const auto relation = compare(descriptor,retained); + for (const auto& existing : bucket->second) { + if (descriptor.key != existing.key) continue; + const auto relation = compare(descriptor,existing); if (relation == StatePruning::EQUIVALENT || relation == StatePruning::DOMINATED) return true; @@ -70,16 +70,16 @@ namespace Gecode { namespace Search { namespace Seq { return false; } - /// Retain admitted metadata, replacing dominated labels and forgetting at limit - void retain(StatePruning::Descriptor descriptor) { + /// Store admitted metadata, replacing dominated labels and forgetting at limit + void store(StatePruning::Descriptor descriptor) { auto& bucket = buckets[descriptor.hash]; bucket.erase(std::remove_if(bucket.begin(),bucket.end(), - [&](const StatePruning::Descriptor& retained) { - if (descriptor.key != retained.key || - compare(descriptor,retained) != StatePruning::DOMINATES) + [&](const StatePruning::Descriptor& existing) { + if (descriptor.key != existing.key || + compare(descriptor,existing) != StatePruning::DOMINATES) return false; descriptors--; - descriptor_bytes -= retained.storage(); + descriptor_bytes -= existing.storage(); return true; }),bucket.end()); if (limit && descriptors >= limit) clear(); diff --git a/gecode/search/worker-control.cpp b/gecode/search/worker-control.cpp index 6a4cb53ad3..2626922788 100644 --- a/gecode/search/worker-control.cpp +++ b/gecode/search/worker-control.cpp @@ -140,14 +140,14 @@ namespace Gecode { namespace Search { Support::Lock lock(state->mutex); if (state->lifecycle != WorkerControl::State::Lifecycle::READY_TO_BIND) throw WorkerControlInUse("WorkerControlAccess::attach"); - const unsigned int retained_capacity = + const unsigned int configured_capacity = state->capacity.load(std::memory_order_relaxed); #ifndef GECODE_HAS_THREADS if (state->requested.load(std::memory_order_relaxed) == 0U) throw InvalidWorkerRequest("WorkerControlAccess::attach"); #endif if ((capacity == 0U) || - ((retained_capacity != 0U) && (retained_capacity != capacity)) || + ((configured_capacity != 0U) && (configured_capacity != capacity)) || (state->requested.load(std::memory_order_relaxed) > capacity)) throw InvalidWorkerRequest("WorkerControlAccess::attach"); if (state->events == nullptr) diff --git a/misc/common-pattern/README.md b/misc/common-pattern/README.md index bb12170540..9b42711257 100644 --- a/misc/common-pattern/README.md +++ b/misc/common-pattern/README.md @@ -60,7 +60,7 @@ whose values are still unassigned. `length()` is the variable for the eventual list length. The private frequency propagator uses prefix projection: after assigning an item, -it retains the input suffixes that can extend the current pattern. One earliest +it stores the input suffixes that can extend the current pattern. One earliest matching cursor per supporting record suffices for ordinary subsequences. It filters locally infrequent items from constructed positions and bounds further growth by the remaining supporting suffix lengths. Immutable, rank-compressed @@ -105,7 +105,7 @@ to require both items. Support is a percentage in `(0,100]` with up to six decim places; the threshold is the exact ceiling of that fraction of the record count. `-language` accepts `none`, `motif`, `re10`, `re14`, or `re2`. The last three are -Section 5(d)'s expressions, retained as comparison presets. Explicit `-language` +Section 5(d)'s expressions, provided as comparison presets. Explicit `-language` overrides `-motif`; the default language uses `-motif` (enabled by default). Letters mean item IDs `A=1` through `Z=26`. Named expressions on integer data require that same mapping; the parser does not infer or remap dataset labels. The motif's interior and RE2's middle @@ -131,7 +131,7 @@ For larger databases use `-file` and select a format: beginning with `@`, `#`, or `%` are skipped. Multi-item itemsets are rejected; silently flattening them would change the mining problem. -Empty lines are skipped. Every retained row has its own support identity. Required +Empty lines are skipped. Every nonempty row has its own support identity. Required items use comma-separated letters in letter mode and integer IDs otherwise: ```sh @@ -252,7 +252,7 @@ bounds new timed invocations, while correctness runs still precede measurement. Changed inputs, queries, build, helper, runner, timeout or repetition plan require another name. Missing/malformed records are rerun and their partial logs replaced. Errors, invalid output and timeouts remain terminal evidence and cause a nonzero -collection exit. Analysis invokes no targets, retains older format-1 results, and +collection exit. Analysis invokes no targets, reads older format-1 results, and reports failures/unfinished work beside successful medians and ranges in seconds. The authors' project link diff --git a/misc/common-pattern/compare.cpp b/misc/common-pattern/compare.cpp index b52e8e69d3..c947830fb7 100644 --- a/misc/common-pattern/compare.cpp +++ b/misc/common-pattern/compare.cpp @@ -57,7 +57,7 @@ namespace { * The paper baseline filters after contiguous prefix assignments. * All variants filter initially. The padded-projection variant also caps * remaining nonzero positions, matching the open projection's length bound. - * Both retain the paper's fixed positions and zero termination. + * Both use the paper's fixed positions and zero termination. */ class PaperProjection : public NaryPropagator { protected: diff --git a/misc/test-tsptw-benchmark.py b/misc/test-tsptw-benchmark.py index 629949bc87..3abd042a9d 100644 --- a/misc/test-tsptw-benchmark.py +++ b/misc/test-tsptw-benchmark.py @@ -238,7 +238,7 @@ def test_recovery_requires_absence_and_keeps_measured_accounting(self): self.assertIn('exit time unobserved', recovery['timing']) self.assertFalse(pending) benchmark.require_reaped(samples) - # Historical reaps retain their original accounting interpretation. + # Historical reaps use their original accounting interpretation. ledger.write_text('\n'.join(json.dumps(row) for row in ( dict(event='budget', total_seconds=7200), dict(event='launch', id='old', phase='correctness'), diff --git a/misc/tsptw-benchmark.py b/misc/tsptw-benchmark.py index b156b4a789..cb07f8c270 100755 --- a/misc/tsptw-benchmark.py +++ b/misc/tsptw-benchmark.py @@ -83,7 +83,7 @@ def budget_header(path): header['total_seconds'] <= header['verification_reserve_seconds']+header['cleanup_reserve_seconds']): raise ValueError('invalid task budget header') elif header.get('event') != 'budget' or header.get('total_seconds') != 7200: - raise ValueError('expected the retained 7200-second evaluation ledger') + raise ValueError('expected the saved 7200-second evaluation ledger') return header diff --git a/misc/tsptw-validate.py b/misc/tsptw-validate.py index 1c88ca0294..78e1e3836f 100644 --- a/misc/tsptw-validate.py +++ b/misc/tsptw-validate.py @@ -243,7 +243,7 @@ def validate_solver(solver, manifest): "--formulation", formulation, "--state-pruning", "pareto"], capture_output=True, text=True) assert result.returncode == 1 and not result.stdout, result - # Equal-cost routes still retain decisions after cost branching. + # Equal-cost routes still need decisions after cost branching. branching = directory/"branching.txt" branching.write_text("5\n" + "\n".join( " ".join("0" if i == j else "1" for j in range(5)) for i in range(5)) + @@ -259,7 +259,7 @@ def validate_solver(solver, manifest): for limit in ("--max-width", "--frontier-limit"): summary, _ = run_solver(solver, branching, "cabs", (limit, "1"), formulation=formulation) - # A ceiling stops only when further retained work or widening + # A ceiling stops only when further pending work or widening # is needed. Tight total bounds can prove this equal-cost tree # complete before hitting the ceiling. assert summary["termination"] in ("stopped", "optimal"), summary diff --git a/test/int/arithmetic.cpp b/test/int/arithmetic.cpp index 0105cf41f7..62f47b554b 100644 --- a/test/int/arithmetic.cpp +++ b/test/int/arithmetic.cpp @@ -1012,7 +1012,7 @@ namespace Test { namespace Int { Gecode::IntVarArgs() << x << y); } { - // Direct n-ary evaluation retains zero after an overflowing prefix. + // Direct n-ary evaluation returns zero after an overflowing prefix. TestSpace home; const int hi=Gecode::Int::Limits::max; IntVar a(home,hi,hi); diff --git a/test/int/extensional.cpp b/test/int/extensional.cpp index 5adad62b19..cfeffb294e 100755 --- a/test/int/extensional.cpp +++ b/test/int/extensional.cpp @@ -701,7 +701,7 @@ namespace Test { namespace Int { virtual void post(Gecode::Space& home, Gecode::IntVarArray& x) { using namespace Gecode; // The first assigned layer has one edge and can be discarded on - // cloning. The second has two edges and must be retained. Two + // cloning. The second has two edges and must be copied. Two // paths read the prefix 0,0,0,0, while only one reads 0,0,1,1. DFA d = DFA::nfa(0, { @@ -799,7 +799,7 @@ namespace Test { namespace Int { } }; - /// %Test retaining separate terminal states when merging would overflow + /// %Test keeping separate terminal states when merging would overflow class RegTerminalUnmerged : public Test { public: /// Create and register test diff --git a/test/int/open-var-list.cpp b/test/int/open-var-list.cpp index 269cd046f5..baa4a2594f 100644 --- a/test/int/open-var-list.cpp +++ b/test/int/open-var-list.cpp @@ -272,7 +272,7 @@ namespace Test { namespace Int { DFS restarted(&root); while (SelectorSpace* solution = restarted.next()) record(solution); - // Extraction may omit literals, but must retain every unvisited word. + // Extraction may omit literals, but must include every unvisited word. for (int code=2; code<16; code++) if (!found[code]) return false; @@ -894,11 +894,11 @@ namespace Test { namespace Int { } CallbackSpace* clone = static_cast(source->clone()); delete source; - const bool retained = (domain.use_count() > 1) && - (transition.use_count() > 1); + const bool shared_storage = (domain.use_count() > 1) && + (transition.use_count() > 1); delete clone; - return retained && (domain.use_count() == 1) && - (transition.use_count() == 1); + return shared_storage && (domain.use_count() == 1) && + (transition.use_count() == 1); } }; diff --git a/test/search.cpp b/test/search.cpp index 042b3d7b19..91815e239a 100644 --- a/test/search.cpp +++ b/test/search.cpp @@ -256,7 +256,7 @@ namespace Test { if (!sliced->stopped()) { if (optimum!=0 || !interruptions || !same(total,sliced->statistics())) return false; - // Adoption during retained preparation cuts committed clones, + // Adoption during suspended preparation cuts committed clones, // including hidden completion state that must be revalidated. stop.limit.store(2,std::memory_order_relaxed); std::unique_ptr adopted( @@ -419,7 +419,7 @@ namespace Test { } } completionDependent; - /// Nested trials retain their work and publish their counters across stops. + /// Nested trials resume their work and publish their counters across stops. class CompletionNested : public Base { static bool same(const Gecode::Search::Statistics& a, const Gecode::Search::Statistics& b) { @@ -994,7 +994,7 @@ namespace Test { int optimum, bool stopped, Statistics& statistics, unsigned long int width=0, std::vector* costs=nullptr) { Gecode::Search::Options options; options.clone=false; - std::vector> retained; + std::vector> solutions; { // Builders copy the policy, including its forgetting ceiling. std::unique_ptr builder(kind==2 ? @@ -1006,10 +1006,10 @@ namespace Test { while (std::unique_ptr s{ static_cast(engine->next())}) { if (costs) costs->push_back(s->value().cost); - retained.push_back(std::move(s)); + solutions.push_back(std::move(s)); } statistics=engine->statistics(); - if (retained.empty() || retained.back()->value().cost!=optimum || + if (solutions.empty() || solutions.back()->value().cost!=optimum || engine->stopped()!=stopped) return false; engine->reset(nullptr); const auto reset=engine->statistics(); @@ -1017,7 +1017,7 @@ namespace Test { engine->next() || engine->stopped()) return false; } // Each publication still owns its graph and Space after engine cleanup. - for (const auto& solution : retained) + for (const auto& solution : solutions) if (solution->status()!=SS_SOLVED) return false; return true; } @@ -1099,7 +1099,7 @@ namespace Test { if (!solve(replacement,true,PruningSpace::pruning(),reference(replacement), false,complete,1) || complete.state_pruned!=1 || complete.restart || complete.state_descriptors!=1 || complete.state_bytes!=bytes) return false; - // Widening retains two, then three incomparable labels. One dominator + // Widening admits two, then three incomparable labels. One dominator // removes them together; accounting must release every victim's bytes. const Graph multiple={ {0,0,0,0,0,{1,2,3,4}}, {1,5,5,0,0,{5}}, {1,10,1,0,0,{6}}, @@ -1325,7 +1325,7 @@ namespace Test { {3,10,10,10,0,{}}, {4,bound,bound,bound,0,{}}}; if (!solve(bounded,1,StatePruningChecks::reference(bounded),false,0)) return false; - // Ranking keeps bound 5 over bound 0; the retained bound then falls + // Ranking keeps bound 5 over bound 0; the selected bound then falls // to 1. A later discard at bound 20 must not erase the earlier 0. const PruningSpace::Graph improving={ {0,0,0,0,0,evict ? std::vector{2,1} : std::vector{1,2}}, @@ -1340,7 +1340,7 @@ namespace Test { } static bool interrupted_discard(bool external) { // Width loss precedes a shallow goal. The original fourth alternative - // and the retained parent both improve it and survive publication. + // and the pending parent both improve it and survive publication. const PruningSpace::Graph graph={ {0,0,0,0,0,{1,2,3,4}}, {1,0,0,0,0,{5}}, {2,0,0,7,1,{6}}, {3,10,10,10,0,{}}, {4,8,8,8,0,{}}, From 23cd6ee6f464df3b7391bf9649c12972f999571f Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 13 Oct 2026 15:25:06 +0200 Subject: [PATCH 10/24] Simplify heuristic search examples around the public interfaces --- docs/astar.md | 3 +- docs/cabs.md | 12 ++--- examples/astar.cpp | 107 +++++++++++++++++++-------------------- examples/best-first.cpp | 109 +++++++++++++++++----------------------- 4 files changed, 102 insertions(+), 129 deletions(-) diff --git a/docs/astar.md b/docs/astar.md index 2bb8661858..bf6e1d2ee5 100644 --- a/docs/astar.md +++ b/docs/astar.md @@ -93,8 +93,7 @@ The admissible total bound is `g+h` throughout. Greedy rank `h` prefers the second route and publishes 10, then 5. Fixed weight 2 with rank `g+2*h` prefers the first route and publishes 5. Both runs continue -to prove the independently enumerated optimum 5. The example checks addition -and multiplication before evaluating them and rejects signed 64-bit overflow. +to prove the independently enumerated optimum 5. The example uses small fixed integer costs. These ranks use actual remaining costs; a CP total objective bound is not a remaining-cost estimate. The fixed weight supplies exploration order only, with no first-goal optimality or approximation-ratio guarantee. Dynamic weights diff --git a/docs/cabs.md b/docs/cabs.md index 15b0ff9117..19204d64ce 100644 --- a/docs/cabs.md +++ b/docs/cabs.md @@ -149,11 +149,9 @@ cmake --build build/open-search --target astar build/open-search/bin/astar ``` -The example runs A*, BAB, and CABS on one bounded formulation. A misleading rank -makes capped width one return 4 and -2, then stop without a proof. Width two, width four, and -unrestricted widening recover -7 and prove it optimal. A frontier quota of one -stops after 4. The example prints elapsed milliseconds, nodes, and restarts for -fixed width/frontier settings. Its tiny timings illustrate the reporting and do -not establish performance gains or justify default tuning. It links only the -search and integer libraries; installed consumers can link `Gecode::gecodesearch` +The example runs A*, BAB, CABS, column search, and CBFS on one bounded +formulation. A misleading rank makes capped width one return 4 and -2, then +stop without a proof. Unrestricted CABS widens to recover -7 and prove it +optimal. Each run prints its improvements and whether search completed. It +links only the search and integer libraries; installed consumers can link `Gecode::gecodesearch` and `Gecode::gecodeint`. diff --git a/examples/astar.cpp b/examples/astar.cpp index ea81917d81..aa0106da83 100644 --- a/examples/astar.cpp +++ b/examples/astar.cpp @@ -33,25 +33,24 @@ #include #include -#include -#include #include #include using namespace Gecode; -// A bounded closed model: option zero is a shallow goal, while the other -// options leave a second decision. Rank deliberately disfavors the optimum. +// Option zero is a shallow goal; the other options leave a second decision. class Alternative : public Space { public: IntVar option, detail, objective; + Alternative(void) : option(*this,0,3), detail(*this,0,1), objective(*this,-7,4) { element(*this,IntArgs({4,-2,-7,1}),option,objective); TupleSet tuples(2); - for (int i=0;i<4;i++) { + for (int i=0; i<4; i++) { tuples.add(IntArgs({i,0})); - if (i) tuples.add(IntArgs({i,1})); + if (i != 0) + tuples.add(IntArgs({i,1})); } tuples.finalize(); extensional(*this,IntVarArgs({option,detail}),tuples); @@ -63,63 +62,59 @@ class Alternative : public Space { detail.update(*this,s.detail); objective.update(*this,s.objective); } - Space* copy(void) override { return new Alternative(*this); } - void constrain(const Space& s0) override { - const auto& s = static_cast(s0); - rel(*this,objective,IRT_LE,s.objective.val()); + Space* copy(void) override { + return new Alternative(*this); + } + void constrain(const Space& incumbent) override { + const Alternative& best = static_cast(incumbent); + rel(*this,objective,IRT_LE,best.objective.val()); } }; -// Use one model and one ordinary options value for all engines. The tiny -// elapsed times illustrate measurement output; they do not establish speedups. -static bool run(const char* name, Search::Engine* raw, - unsigned long int width=0, unsigned long int frontier=0) { - std::unique_ptr engine(raw); - const auto start=std::chrono::steady_clock::now(); - int last=0; - bool found=false; +static void +run(const char* name, Search::Base& search) { std::cout << name << " improvements:"; - while (std::unique_ptr s{ - static_cast(engine->next())}) { - last=s->objective.val(); - found=true; - std::cout << ' ' << last; - } - const double milliseconds=std::chrono::duration( - std::chrono::steady_clock::now()-start).count(); - const auto statistics=engine->statistics(); - std::cout << "\n" << name << " width=" << width << " frontier=" << frontier - << " objective="; - if (found) std::cout << last; else std::cout << "none"; - std::cout << " status=" << (engine->stopped() ? "stopped" : "proven") - << " ms=" << std::fixed << std::setprecision(3) << milliseconds - << " nodes=" << statistics.node << " restarts=" << statistics.restart - << '\n'; - if (width==1) return found && last==-2 && engine->stopped(); - if (frontier==1) return found && last==4 && engine->stopped(); - return found && last==-7 && !engine->stopped(); + while (std::unique_ptr solution{search.next()}) + std::cout << ' ' << solution->objective.val(); + std::cout << (search.stopped() ? " (stopped)" : " (complete)") << '\n'; } -int main(void) { +int +main(void) { + Alternative root; Search::HeuristicGuidance guidance(Search::HeuristicGuidance::MINIMIZE, - [](const Space& s) -> std::int64_t { - return static_cast(s).objective.min(); + [](const Space& space) -> std::int64_t { + const Alternative& s = static_cast(space); + return s.objective.min(); }, - [](const Space& s) -> std::int64_t { - const auto& a=static_cast(s); - return a.option.assigned() && a.option.val()==2 ? 1000 : 0; + [](const Space& space) -> std::int64_t { + const Alternative& s = static_cast(space); + // Deliberately rank the optimal option last to show beam widening. + return s.option.assigned() && s.option.val() == 2 ? 1000 : 0; }); - Search::Options options; - options.clone=false; - bool valid=run("A*",Search::astarengine(new Alternative,guidance,options)); - for (const auto limits : {Search::ColumnOptions(), Search::ColumnOptions(1,0)}) - valid=run(limits.quota_growth ? "Column" : "CBFS", - Search::columnengine(new Alternative,guidance,options,limits)) && valid; - valid=run("BAB",Search::babengine(new Alternative,options)) && valid; - for (const auto limits : {Search::CABSOptions(1,0), Search::CABSOptions(2,0), - Search::CABSOptions(4,0), Search::CABSOptions(), - Search::CABSOptions(0,1), Search::CABSOptions(0,4)}) - valid=run("CABS",Search::cabsengine(new Alternative,guidance,options,limits), - limits.max_width,limits.frontier_limit) && valid; - return valid ? 0 : 1; + + AStar astar(&root,guidance); + run("A*",astar); + + BAB bab(&root); + run("BAB",bab); + + CABS cabs(&root,guidance); + run("CABS",cabs); + + // Width one finds improvements but cannot prove the best one optimal. + CABS beam(&root,guidance,Search::Options::def, + Search::CABSOptions(1)); + run("CABS (maximum width 1)",beam); + + Column column(&root,guidance); + run("Column",column); + + // A fixed expansion quota of one selects cyclic best-first search. + Column cbfs(&root,guidance,Search::Options::def, + Search::ColumnOptions(1,0)); + run("CBFS",cbfs); + return 0; } + +// STATISTICS: example-any diff --git a/examples/best-first.cpp b/examples/best-first.cpp index 73b5fbaa89..00e78d628b 100644 --- a/examples/best-first.cpp +++ b/examples/best-first.cpp @@ -35,40 +35,23 @@ #include #include #include -#include #include -#include -#include using namespace Gecode; -// All costs and weights in this additive minimization example are nonnegative. -static std::int64_t add_cost(std::int64_t a, std::int64_t b) { - if (a > std::numeric_limits::max()-b) - throw std::overflow_error("additive cost overflow"); - return a+b; -} - -static std::int64_t weighted_cost(std::int64_t g, std::int64_t h, - std::int64_t weight) { - if (h && weight > std::numeric_limits::max()/h) - throw std::overflow_error("weighted remaining cost overflow"); - return add_cost(g,weight*h); -} - -// Choose a route, then its final arc. Prefix costs are 0 and 9; remaining -// arc costs are [5,6] and [1,2]. All root alternatives remain nonterminal. +// Choose a route, then its final arc. All root alternatives need both decisions. class Additive : public Space { - static constexpr int prefix[2]={0,9}; - static constexpr int tail[2][2]={{5,6},{1,2}}; + static constexpr int prefix[2] = {0,9}; + static constexpr int tail[2][2] = {{5,6},{1,2}}; public: IntVar route, detail, objective; + Additive(void) : route(*this,0,1), detail(*this,0,1), objective(*this,0,11) { TupleSet tuples(3); for (int r=0; r<2; r++) for (int d=0; d<2; d++) - tuples.add(IntArgs({r,d,static_cast(add_cost(prefix[r],tail[r][d]))})); + tuples.add(IntArgs({r,d,prefix[r]+tail[r][d]})); tuples.finalize(); extensional(*this,IntVarArgs({route,detail,objective}),tuples); branch(*this,route,INT_VALUES_MIN()); @@ -79,64 +62,62 @@ class Additive : public Space { detail.update(*this,s.detail); objective.update(*this,s.objective); } - Space* copy(void) override { return new Additive(*this); } - void constrain(const Space& s0) override { - rel(*this,objective,IRT_LE,static_cast(s0).objective.val()); + Space* copy(void) override { + return new Additive(*this); + } + void constrain(const Space& incumbent) override { + const Additive& best = static_cast(incumbent); + rel(*this,objective,IRT_LE,best.objective.val()); } - // g counts determined arcs; after both decisions it is the full cost. + + // Cost of the arcs already chosen. std::int64_t g(void) const { - if (!route.assigned()) return 0; + if (!route.assigned()) + return 0; return detail.assigned() ? objective.val() : prefix[route.val()]; } - // h bounds remaining arcs only. Restrictions may strengthen these bounds, - // but the original cheapest arc remains a valid lower bound. + + // Lower bound on the cost of the arcs still to be chosen. std::int64_t h(void) const { if (!route.assigned()) - return std::min(add_cost(prefix[0],std::min(tail[0][0],tail[0][1])), - add_cost(prefix[1],std::min(tail[1][0],tail[1][1]))); - if (detail.assigned()) return 0; + return std::min(prefix[0]+std::min(tail[0][0],tail[0][1]), + prefix[1]+std::min(tail[1][0],tail[1][1])); + if (detail.assigned()) + return 0; return std::min(tail[route.val()][0],tail[route.val()][1]); } - static std::int64_t optimum(void) { - std::int64_t best=std::numeric_limits::max(); - for (int r=0; r<2; r++) - for (int d=0; d<2; d++) - best=std::min(best,add_cost(prefix[r],tail[r][d])); - return best; - } }; -static bool run(const char* name, Search::HeuristicGuidance::Evaluate rank, - const std::vector& expected) { +static void +run(const char* name, Search::HeuristicGuidance::Evaluate rank) { Search::HeuristicGuidance guidance(Search::HeuristicGuidance::MINIMIZE, - [](const Space& s0)->std::int64_t { - const auto& s=static_cast(s0); - return add_cost(s.g(),s.h()); - },std::move(rank)); - Search::Options options; options.clone=false; + [](const Space& space) -> std::int64_t { + const Additive& s = static_cast(space); + return s.g()+s.h(); + },rank); std::unique_ptr builder( - bestfirst(guidance,Search::RANK_FIRST,options)); - std::unique_ptr engine((*builder)(new Additive)); - std::vector costs; + bestfirst(guidance,Search::RANK_FIRST)); + Additive root; + std::unique_ptr search((*builder)(&root)); + std::cout << name << " improvements:"; - while (std::unique_ptr solution{ - static_cast(engine->next())}) { - costs.push_back(solution->objective.val()); - std::cout << ' ' << costs.back(); + while (std::unique_ptr solution{search->next()}) { + const Additive& s = static_cast(*solution); + std::cout << ' ' << s.objective.val(); } - std::cout << " status=" << (engine->stopped() ? "stopped" : "proven") << '\n'; - return costs==expected && costs.back()==Additive::optimum() && !engine->stopped(); + std::cout << (search->stopped() ? " (stopped)" : " (complete)") << '\n'; } -int main(void) { - const bool greedy=run("Greedy h",[](const Space& s)->std::int64_t { - return static_cast(s).h(); - },{10,5}); - const bool weighted=run("Fixed weight 2: g+2*h",[](const Space& s0)->std::int64_t { - const auto& s=static_cast(s0); - return weighted_cost(s.g(),s.h(),2); - },{5}); - return greedy && weighted ? 0 : 1; +int +main(void) { + run("Greedy h",[](const Space& space) -> std::int64_t { + return static_cast(space).h(); + }); + run("Weight 2: g+2*h",[](const Space& space) -> std::int64_t { + const Additive& s = static_cast(space); + return s.g()+2*s.h(); + }); + return 0; } // STATISTICS: example-any From d5d6e61050471f8d1d2ae0526fe292b17d666633 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 13 Oct 2026 15:45:22 +0200 Subject: [PATCH 11/24] Keep brancher preparation policy in the search layer Remove kernel dependencies on search types and use a preparation driver with kernel propagation statistics. Pack restriction notification into existing Space control state, and use persistent brancher lifecycle hooks. Validated experimental ON/OFF checks, focused completion/search checks, ASan+UBSan, kernel-only linkage, source inventory and examples. Independent Astra high review passed. Zdev-Change-Id: Zd9f8a980386fc4e0ee6d9c5087730288f4ace2dd62e3b52d7e837fe0311b69e8 --- .zdev/open-search/TASKS.md | 5 +- ...-coupling-and-simplify-heuristic-search.md | 51 +++++++++++++++ Makefile.in | 2 +- cmake/GecodeSources.cmake | 1 + examples/tsptw-transition.hh | 5 +- examples/tsptw.cpp | 28 +++++---- gecode/kernel/core.cpp | 33 +++++----- gecode/kernel/core.hpp | 44 ++++++++++--- gecode/search.hh | 9 +++ gecode/search/completion.hh | 10 +-- gecode/search/prepare.cpp | 63 +++++++++++++++++++ gecode/search/worker.hh | 2 +- misc/tsptw-cadds-study.cpp | 4 +- test/search.cpp | 21 +++---- 14 files changed, 213 insertions(+), 65 deletions(-) create mode 100644 .zdev/open-search/tasks/034-remove-kernel-search-coupling-and-simplify-heuristic-search.md create mode 100644 gecode/search/prepare.cpp diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index 4f57a40099..2a9795932f 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -2,10 +2,10 @@ # Tasks: open-search -- Total: 33 +- Total: 34 - Ready: 0 - Blocked: 0 -- Done: 33 +- Done: 34 | ID | Task | State | Blocked by | | --- | --- | --- | --- | @@ -42,3 +42,4 @@ | [open-search-031](tasks/031-reconstruct-standalone-native-search-on-open-cp.md) | Reconstruct standalone native search on open-cp | done | — | | [open-search-032](tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md) | Gate heuristic search as an experimental preview and separate experimental changelog entries | done | — | | [open-search-033](tasks/033-repair-reviewed-preparation-and-completion-contracts-and-com.md) | Repair reviewed preparation and completion contracts and compact CABS victims | done | — | +| [open-search-034](tasks/034-remove-kernel-search-coupling-and-simplify-heuristic-search.md) | Remove kernel search coupling and simplify heuristic search examples | done | open-search-033 | diff --git a/.zdev/open-search/tasks/034-remove-kernel-search-coupling-and-simplify-heuristic-search.md b/.zdev/open-search/tasks/034-remove-kernel-search-coupling-and-simplify-heuristic-search.md new file mode 100644 index 0000000000..ed7b8f5647 --- /dev/null +++ b/.zdev/open-search/tasks/034-remove-kernel-search-coupling-and-simplify-heuristic-search.md @@ -0,0 +1,51 @@ ++++ +schema_version = 1 +id = "open-search-034" +key = "restore-kernel-search-boundaries" +area = "open-search" +status = "done" +complexity = "advanced" +afk = true +priority = "high" +blocked_by = ["open-search-033"] ++++ +# Remove kernel search coupling and simplify heuristic search examples + +## Outcome + +The kernel preparation protocol is independent of search policy; brancher notification uses no extra Space field; examples explain the public search interfaces plainly. + +## Context + +The user reviewed draft PR 256 and requested concrete wording, removal of kernel/core.hpp's Search dependency, a better brancher lifecycle mechanism, and clearer examples. Commits b8bb70a475 and 202474992b cover wording and example simplification. Continue the boundary correction without losing task 033's completion validity, nested stops/statistics or prepared-terminal fixes. + +## Boundaries + +- Keep nested search policy and accounting in the search layer. Use a small kernel callback for preparation rather than opaque context, unchecked downcasts or a general search policy framework. +- Remove the standalone brancher-notification boolean without making ordinary commits scan every brancher. Check propagation dispatch and brancher identity allocation when using existing kernel control bits. +- Preserve ordinary/native search correctness, replay compatibility, separate ranking and bounds, and the Gecode 6.5 experimental scope. +- Use concrete source comments and private helper names. Preserve license text, dictionary data and historical approved task records. No new benchmark campaign or redundant test harness. + +## Done when + +- [x] Kernel headers and core.cpp reference no Search types and kernel-only compilation/linkage succeeds. +- [x] Preparation still propagates newly active branchers and safely classifies terminal outcomes; nested completion observes outer stop limits, resumes and accounts for actual work. +- [x] The standalone notification boolean is removed, lifecycle hooks describe their actual purpose, and ordinary commit notification remains a cheap conditional. +- [x] Small examples use public search interfaces and print improvements and completion status without embedded regression assertions or timing machinery. +- [x] The source scan leaves retain variants only in unchanged dictionary/license text; independent review and required validation pass. + +## Validation + +- Build experimental ON/OFF and run existing focused search, completion and worker-control checks; run current ASan+UBSan checks when practical. +- Build and run the AStar and best-first examples; independently check tiny TSPTW formulations after preparation caller adaptation. +- Check kernel-only include/linkage, all build registrations, report measurements unchanged, and git diff whitespace. + +## Result + +Separated kernel preparation from search control; removed the per-Space notification boolean using packed control state; renamed lifecycle hooks and simplified public examples and wording. + +Validation: + +- Independent Astra high review passed against equal snapshot We013cfe7156fe882; kernel-only and restriction/disable/trace probes, source inventory and whitespace checks passed. +- Experimental ON 479/479 and OFF 475/475; focused ON 24/24 and OFF 20/20; current ASan+UBSan 24/24. Independent release and sanitizer replays passed; macOS leak detection unsupported. +- Both simplified examples, 60 independently checked tiny TSPTW combinations and CADDS witness passed; targeted clang-tidy had no diagnostics. Packed notification reduces theoretical brancher IDs from 30 to 29 bits. diff --git a/Makefile.in b/Makefile.in index a8b65da073..4f7d67ad14 100755 --- a/Makefile.in +++ b/Makefile.in @@ -262,7 +262,7 @@ endif # SEARCHSRC0 = \ - stop options cutoff engine \ + stop options cutoff engine prepare \ dfs bab lds \ seq/rbs seq/dead seq/pbs par/pbs \ rbs pbs nogoods exception tracer \ diff --git a/cmake/GecodeSources.cmake b/cmake/GecodeSources.cmake index a8b1702cbf..509c0affcc 100644 --- a/cmake/GecodeSources.cmake +++ b/cmake/GecodeSources.cmake @@ -42,6 +42,7 @@ set(GECODE_SEARCH_SOURCES gecode/search/options.cpp gecode/search/par/pbs.cpp gecode/search/pbs.cpp + gecode/search/prepare.cpp gecode/search/rbs.cpp gecode/search/seq/dead.cpp gecode/search/seq/pbs.cpp diff --git a/examples/tsptw-transition.hh b/examples/tsptw-transition.hh index d1b55c1f66..ee795929d7 100644 --- a/examples/tsptw-transition.hh +++ b/examples/tsptw-transition.hh @@ -140,9 +140,8 @@ namespace TSPTW { bool status(const Gecode::Space& home) const override { return !static_cast(home).returned; } - bool prepare(Gecode::Space& home, Gecode::Search::Statistics& stat, - const Gecode::Search::Options&) override { - auto& s=static_cast(home); + bool prepare(Gecode::Space& home, Gecode::StatusStatistics& stat) override { + auto& s = static_cast(home); (void) s.materialize(s.depth); (void) s.status(stat); return true; diff --git a/examples/tsptw.cpp b/examples/tsptw.cpp index 26acc12618..88a93dd46e 100644 --- a/examples/tsptw.cpp +++ b/examples/tsptw.cpp @@ -194,7 +194,7 @@ make_seed(TSPTWClosed& root, const std::vector& tour, TourValue value) { static std::unique_ptr make_seed(TSPTW::Transition& root, const std::vector& tour, TourValue value) { - if (root.status()==SS_FAILED) + if (root.status() == SS_FAILED) throw std::runtime_error("checked seed rejected by transition root"); std::unique_ptr seed( static_cast(root.clone())); @@ -203,17 +203,18 @@ make_seed(TSPTW::Transition& root, const std::vector& tour, TourValue value // Replay checked customer IDs and then the explicit return using the same // decisions as search, without directly installing residual state. for (std::size_t p=0; p<=tour.size(); p++) { - const int node=p==tour.size() ? 0 : tour[p]; - if (seed->status()!=SS_BRANCH || !seed->prepare(stat,options)) + const int node = p == tour.size() ? 0 : tour[p]; + if (seed->status() != SS_BRANCH || !Search::prepare(*seed,stat,options) || + seed->status() != SS_BRANCH) throw std::runtime_error("checked seed rejected by transition model"); - const auto actions=seed->actions(); - const auto action=std::find(actions.begin(),actions.end(),node); - if (action==actions.end()) + const auto actions = seed->actions(); + const auto action = std::find(actions.begin(),actions.end(),node); + if (action == actions.end()) throw std::runtime_error("checked seed has no applicable transition"); std::unique_ptr choice(seed->choice()); seed->commit(*choice,static_cast(action-actions.begin())); } - if (seed->status()!=SS_SOLVED || seed->cost()!=value.cost) + if (seed->status() != SS_SOLVED || seed->cost() != value.cost) throw std::runtime_error("checked seed rejected by transition model"); return seed; } @@ -366,16 +367,17 @@ static int run_search(const RunOptions& options, Model& root, } std::unique_ptr seed; if (incumbent) seed=make_seed(root,best_tour,*incumbent); - int root_positions=0, prepared_positions=0, solution_positions=0; + int root_positions = 0, prepared_positions = 0, solution_positions = 0; if constexpr (std::is_same::value) { - if (seed) solution_positions=seed->allocated_positions(); - root_positions=root.allocated_positions(); - if (root.status()==SS_BRANCH) { + if (seed) + solution_positions = seed->allocated_positions(); + root_positions = root.allocated_positions(); + if (root.status() == SS_BRANCH) { Search::Statistics stat; // Record the first prepared prefix without committing a visit. - (void) root.prepare(stat,search); + (void) Search::prepare(root,stat,search); } - prepared_positions=root.allocated_positions(); + prepared_positions = root.allocated_positions(); } const auto guidance=[&]() { if constexpr (std::is_same::value) diff --git a/gecode/kernel/core.cpp b/gecode/kernel/core.cpp index fa05fccd20..c1ae454ce7 100644 --- a/gecode/kernel/core.cpp +++ b/gecode/kernel/core.cpp @@ -42,7 +42,6 @@ */ #include -#include namespace Gecode { @@ -119,8 +118,7 @@ namespace Gecode { VarImpDisposerBase* Space::vd[AllVarConf::idx_d]; #endif - Space::Space(void) : mm(ssd.data().sm), no_gpi(false), - retained_branchers(false) { + Space::Space(void) : mm(ssd.data().sm), no_gpi(false) { #ifdef GECODE_HAS_CBS var_id_counter = 0; #endif @@ -290,7 +288,7 @@ namespace Gecode { // Check whether space is stable but not failed if (pc.p.active >= &pc.p.queue[0]) { ModEventDelta med_o; - if ((pc.p.bid_sc & ((1 << sc_bits) - 1)) == 0) { + if ((pc.p.bid_sc & (sc_disabled | sc_trace)) == 0) { // No support for disabled propagators and tracing // Check whether space is stable but not failed goto f_unstable; @@ -351,7 +349,7 @@ namespace Gecode { GECODE_NEVER; } f_stable: ; - } else if ((pc.p.bid_sc & ((1 << sc_bits) - 1)) == sc_disabled) { + } else if ((pc.p.bid_sc & (sc_disabled | sc_trace)) == sc_disabled) { // Support for disabled propagators goto d_unstable; d_execute: @@ -567,8 +565,10 @@ namespace Gecode { } + BrancherPreparation::~BrancherPreparation(void) {} + bool - Brancher::prepare(Space&, Search::Statistics&, const Search::Options&) { + Brancher::prepare(Space&, StatusStatistics&) { return true; } @@ -576,18 +576,18 @@ namespace Gecode { Brancher::commit_restricts(void) const { return true; } bool - Brancher::retained(void) const { return false; } + Brancher::persistent(void) const { return false; } void Brancher::invalidate(Space&) {} bool - Space::prepare(Search::Statistics& stat, const Search::Options& options) { + Space::prepare(BrancherPreparation& preparation) { while (!failed() && b_status != Brancher::cast(&bl)) { Brancher* active = b_status; - if (!active->prepare(*this,stat,options)) + if (!preparation.prepare(*this,*active)) return false; - if (status(stat) != SS_BRANCH || b_status == active) + if (status(preparation.statistics()) != SS_BRANCH || b_status == active) return true; } return true; @@ -595,11 +595,11 @@ namespace Gecode { void Space::invalidate_branchers(void) { - if (!retained_branchers || failed()) + if (!(pc.p.bid_sc & sc_restrictions) || failed()) return; for (Brancher* b=Brancher::cast(bl.next()); b != Brancher::cast(&bl); b=Brancher::cast(b->next())) - if (b->retained()) + if (b->persistent()) b->invalidate(*this); b_status = b_commit = Brancher::cast(bl.next()); } @@ -615,7 +615,7 @@ namespace Gecode { while (b != Brancher::cast(&bl)) { Brancher* d = b; b = Brancher::cast(b->next()); - if (failed() || !d->retained()) { + if (failed() || !d->persistent()) { d->unlink(); rfree(d,d->dispose(*this)); } @@ -631,7 +631,7 @@ namespace Gecode { while (b != b_status) { Brancher* d = b; b = Brancher::cast(b->next()); - if (!d->retained()) { + if (!d->persistent()) { d->unlink(); rfree(d,d->dispose(*this)); } @@ -661,7 +661,7 @@ namespace Gecode { return; if (Brancher* b = brancher(c.bid)) { // Resolve the archived choice before resetting the brancher cursors. - if (retained_branchers && b->commit_restricts()) + if ((pc.p.bid_sc & sc_restrictions) && b->commit_restricts()) invalidate_branchers(); // There is a matching brancher if (pc.p.bid_sc & sc_trace) { @@ -695,7 +695,7 @@ namespace Gecode { return; if (Brancher* b = brancher(c.bid)) { // Resolve the archived choice before resetting the brancher cursors. - if (retained_branchers && b->commit_restricts()) + if ((pc.p.bid_sc & sc_restrictions) && b->commit_restricts()) invalidate_branchers(); // There is a matching brancher if (pc.p.bid_sc & sc_trace) { @@ -774,7 +774,6 @@ namespace Gecode { Space::Space(Space& s) : ssd(s.ssd), mm(ssd.data().sm,s.mm,s.pc.p.n_sub*sizeof(Propagator**)), - retained_branchers(s.retained_branchers), #ifdef GECODE_HAS_CBS var_id_counter(s.var_id_counter), #endif diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index f7d86ba87e..4676c44c16 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -145,7 +145,8 @@ namespace Gecode { class AFC; class Choice; class Brancher; - namespace Search { class Statistics; class Options; } + class StatusStatistics; + class BrancherPreparation; class Group; class PropagatorGroup; class BrancherGroup; @@ -1494,14 +1495,13 @@ namespace Gecode { * branching. Space::prepare propagates and prepares a newly active brancher. */ GECODE_KERNEL_EXPORT - virtual bool prepare(Space& home, Search::Statistics& stat, - const Search::Options& options); + virtual bool prepare(Space& home, StatusStatistics& stat); /// Whether committing a choice can change feasibility (default: true). GECODE_KERNEL_EXPORT virtual bool commit_restricts(void) const; /// Whether an exhausted brancher must survive subsequent restrictions. GECODE_KERNEL_EXPORT - virtual bool retained(void) const; + virtual bool persistent(void) const; /// Invalidate previously established choices after strengthening. GECODE_KERNEL_EXPORT virtual void invalidate(Space& home); @@ -1760,6 +1760,28 @@ namespace Gecode { StatusStatistics& operator +=(const StatusStatistics& s); }; + /** + * \brief Driver for brancher preparation before choice construction + * + * The driver borrows propagation statistics for the duration of preparation. + * Its callback completes the current brancher's preparation or suspends it + * without advancing the search path. The space propagates after a completed + * callback and repeats when a different brancher becomes active. + */ + class GECODE_VTABLE_EXPORT BrancherPreparation { + private: + StatusStatistics& stat; + public: + /// Borrow the propagation counter used by preparation + explicit BrancherPreparation(StatusStatistics& s) : stat(s) {} + /// Return the borrowed propagation counter + StatusStatistics& statistics(void) { return stat; } + /// Prepare the selected brancher; false suspends choice construction + virtual bool prepare(Space& home, Brancher& brancher) = 0; + /// Destructor + GECODE_KERNEL_EXPORT virtual ~BrancherPreparation(void); + }; + /** * \brief %Statistics for execution of clone * @@ -1838,8 +1860,6 @@ namespace Gecode { * If equal to &bl, no brancher does exist. */ Brancher* b_status; - /// Whether any brancher requires notification on restriction posting. - bool retained_branchers; /** * \brief Points to the first brancher to be used for commit * @@ -1868,13 +1888,15 @@ namespace Gecode { static const unsigned reserved_bid = 0U; /// Number of bits for status control - static const unsigned int sc_bits = 2; + static const unsigned int sc_bits = 3; /// No special features activated static const unsigned int sc_fast = 0; /// Disabled propagators are supported static const unsigned int sc_disabled = 1; /// Tracing is supported static const unsigned int sc_trace = 2; + /// Persistent branchers need notification before restrictions + static const unsigned int sc_restrictions = 4; union { /// Data only available during propagation or branching @@ -2252,9 +2274,11 @@ namespace Gecode { SpaceStatus status(StatusStatistics& stat); /// Prepare the current brancher before a search engine changes its path. GECODE_KERNEL_EXPORT - bool prepare(Search::Statistics& stat, const Search::Options& options); - /// Enable restriction notifications for branchers that survive exhaustion. - void retain_branchers(void) { retained_branchers = true; } + bool prepare(BrancherPreparation& preparation); + /// Enable notification before restrictions for persistent branchers. + void notice_brancher_restrictions(void) { + pc.p.bid_sc |= sc_restrictions; + } /// Notify branchers that survive exhaustion before strengthening the space. GECODE_KERNEL_EXPORT void invalidate_branchers(void); diff --git a/gecode/search.hh b/gecode/search.hh index a4a9a8eaef..79afd4b455 100755 --- a/gecode/search.hh +++ b/gecode/search.hh @@ -838,6 +838,15 @@ namespace Gecode { namespace Search { expand(void) const; }; + /** + * Prepare the active brancher using the caller's search statistics/options. + * Completion trials observe the enclosing stop and publish their work here. + * False suspends choice construction; true may leave a failed, solved, or + * branching space. The caller must classify status before making a choice. + */ + GECODE_SEARCH_EXPORT bool + prepare(Space& home, Statistics& stat, const Options& options); + }} #include diff --git a/gecode/search/completion.hh b/gecode/search/completion.hh index f478020ba2..77c7d6ca5c 100644 --- a/gecode/search/completion.hh +++ b/gecode/search/completion.hh @@ -8,7 +8,7 @@ namespace Gecode { namespace Search { /// Brancher-owned existential DFS, advanced before engine choice construction. - class Completion : public Brancher { + class GECODE_VTABLE_EXPORT Completion : public Brancher { struct Pending : Stop { std::unique_ptr> engine; Statistics published; @@ -52,7 +52,7 @@ namespace Gecode { namespace Search { Completion(Home home) : Brancher(home), pending(nullptr), result(UNSET), revision(0), done(false), suppressed(false) { - static_cast(home).retain_branchers(); + static_cast(home).notice_brancher_restrictions(); } Completion(Space& home, Completion& b) : Brancher(home,b), pending(nullptr), result(b.result), @@ -80,7 +80,7 @@ namespace Gecode { namespace Search { // source. Any later feasibility-changing commit, posting, or incumbent cut // invalidates that witness, including changes to non-variable model state. bool commit_restricts(void) const override { return false; } - bool retained(void) const override { return !suppressed; } + bool persistent(void) const override { return !suppressed; } void invalidate(Space&) override { delete pending; pending=nullptr; @@ -88,8 +88,8 @@ namespace Gecode { namespace Search { done=false; revision++; } - bool prepare(Space& home, Statistics& stat, - const Options& options) override { + virtual bool prepare(Space& home, Statistics& stat, + const Options& options) { if (result != UNSET) return true; if (options.stop && options.stop->stop(stat,options)) diff --git a/gecode/search/prepare.cpp b/gecode/search/prepare.cpp new file mode 100644 index 0000000000..ea56a0419f --- /dev/null +++ b/gecode/search/prepare.cpp @@ -0,0 +1,63 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include + +namespace Gecode { namespace Search { + + namespace { + /// Supply search control only to branchers that perform nested search. + class Preparation : public BrancherPreparation { + Statistics& caller; + const Options& options; + public: + Preparation(Statistics& stat, const Options& opt) + : BrancherPreparation(stat), caller(stat), options(opt) {} + bool prepare(Space& home, Brancher& brancher) override { + if (Completion* completion = dynamic_cast(&brancher)) + return completion->prepare(home,caller,options); + return brancher.prepare(home,caller); + } + }; + } + + bool + prepare(Space& home, Statistics& stat, const Options& options) { + Preparation preparation(stat,options); + return home.prepare(preparation); + } + +}} + +// STATISTICS: search-other diff --git a/gecode/search/worker.hh b/gecode/search/worker.hh index dc277b3ad3..e9d3517fde 100644 --- a/gecode/search/worker.hh +++ b/gecode/search/worker.hh @@ -101,7 +101,7 @@ namespace Gecode { namespace Search { forceinline bool Worker::prepare(Space& s, const Options& o) { - bool prepared = s.prepare(*this,o); + bool prepared = Search::prepare(s,*this,o); _stopped |= !prepared; return prepared; } diff --git a/misc/tsptw-cadds-study.cpp b/misc/tsptw-cadds-study.cpp index 48456546f7..f9fb23ab8f 100644 --- a/misc/tsptw-cadds-study.cpp +++ b/misc/tsptw-cadds-study.cpp @@ -235,7 +235,7 @@ class Witness : public Space { bool status(const Space& h) const override { return static_cast(h).node<10; } - bool prepare(Space&,Search::Statistics&,const Search::Options&) override { + bool prepare(Space&,StatusStatistics&) override { if (!prepared) { prepared=true; return false; } return true; } @@ -343,7 +343,7 @@ static void witnesses() { // Directly establish the fresh-choice index shift, using clone-related Spaces. Search::Statistics stat; Search::Options opt; auto source=std::make_unique(); (void)source->status(); - require(!source->prepare(stat,opt) && source->prepare(stat,opt),"prepare"); + require(!Gecode::Search::prepare(*source,stat,opt) && Gecode::Search::prepare(*source,stat,opt),"prepare"); std::unique_ptr original(source->choice()); auto cut=std::unique_ptr(static_cast(source->clone())); cut->ceiling=6; (void)cut->status(); diff --git a/test/search.cpp b/test/search.cpp index 91815e239a..262563d827 100644 --- a/test/search.cpp +++ b/test/search.cpp @@ -292,7 +292,7 @@ namespace Test { o.c_d=5; o.a_d=2; Gecode::Search::Statistics stat; Slice stop; o.stop=&stop; - if (root.status()!=SS_BRANCH || root.prepare(stat,o)) return false; + if (root.status()!=SS_BRANCH || Gecode::Search::prepare(root,stat,o)) return false; std::unique_ptr clone( static_cast(root.clone())); // Restricting a pending source must discard its obsolete trial. @@ -300,7 +300,7 @@ namespace Test { root.constrain(root); if (root.status()!=SS_BRANCH) return false; stop.limit.store(10000,std::memory_order_relaxed); - if (!clone->prepare(stat,o)) return false; + if (!Gecode::Search::prepare(*clone,stat,o)) return false; std::unique_ptr ch(clone->choice()); Archive archive; ch->archive(archive); std::unique_ptr archived(clone->choice(archive)); @@ -309,7 +309,7 @@ namespace Test { replayed->commit(*archived,0); if (replayed->status()!=SS_SOLVED) return false; (void) replayed->choice(); - // Retention through solved cleanup and strengthening without failure. + // Completion branchers survive solved cleanup and later restrictions. replayed->invalidate_branchers(); replayed->constrain(root); if (replayed->status()!=SS_BRANCH) return false; @@ -317,9 +317,9 @@ namespace Test { if (replayed->status()!=SS_BRANCH) return false; Slice small; o.stop=&small; Gecode::Search::Statistics revalidation; - if (replayed->prepare(revalidation,o)) return false; + if (Gecode::Search::prepare(*replayed,revalidation,o)) return false; small.limit.store(10000,std::memory_order_relaxed); - if (!replayed->prepare(revalidation,o)) return false; + if (!Gecode::Search::prepare(*replayed,revalidation,o)) return false; std::unique_ptr failed(replayed->choice()); replayed->commit(*failed,0); if (replayed->status()!=SS_FAILED) return false; @@ -507,8 +507,7 @@ namespace Test { PreparedBrancher(Space& home, PreparedBrancher& b) : Brancher(home,b), prepared(b.prepared), failure(b.failure), choices(b.choices) {} bool status(const Space&) const override { return !prepared; } - bool prepare(Space& home, Gecode::Search::Statistics&, - const Gecode::Search::Options&) override { + bool prepare(Space& home, StatusStatistics&) override { prepared=true; if (failure) home.fail(); return true; @@ -588,7 +587,7 @@ namespace Test { static bool visit(Transition& s, int node) { Gecode::Search::Options options; Gecode::Search::Statistics stat; - if (s.status()!=SS_BRANCH || !s.prepare(stat,options)) return false; + if (s.status()!=SS_BRANCH || !Gecode::Search::prepare(s,stat,options)) return false; const auto actions=s.actions(); const auto action=std::find(actions.begin(),actions.end(),node); if (action==actions.end()) return false; @@ -606,7 +605,7 @@ namespace Test { if (root.status()!=SS_BRANCH || root.allocated_positions()!=(lazy ? 0 : 4)) return false; std::unique_ptr absent(static_cast(root.clone())); - if (!root.prepare(stat,options) || root.allocated_positions()!=(lazy ? 1 : 4) || + if (!Gecode::Search::prepare(root,stat,options) || root.allocated_positions()!=(lazy ? 1 : 4) || root.position(0).assigned() || root.depth || root.location || root.time || root.cost() || root.unvisited!=std::vector({1,2,3})) return false; @@ -621,7 +620,7 @@ namespace Test { absent->position(0).val()!=1 || root.position(0).assigned()) return false; std::unique_ptr next(static_cast(absent->clone())); - if (!absent->prepare(stat,options) || absent->depth!=1 || absent->cost()!=1 || + if (!Gecode::Search::prepare(*absent,stat,options) || absent->depth!=1 || absent->cost()!=1 || absent->allocated_positions()!=(lazy ? 2 : 4) || absent->position(1).assigned()) return false; original.reset(absent->choice()); @@ -758,7 +757,7 @@ namespace Test { if (state.status()!=SS_BRANCH) return true; Gecode::Search::Statistics stat; Gecode::Search::Options options; - if (!state.prepare(stat,options)) return false; + if (!Gecode::Search::prepare(state,stat,options)) return false; const auto actions=state.actions(); std::unique_ptr choice(state.choice()); for (std::size_t a=0; a Date: Tue, 13 Oct 2026 16:44:37 +0200 Subject: [PATCH 12/24] Invalidate completion witnesses at constraint posting Notify branchers through PostInfo when a cached Home posts a new restriction. Exercise the resulting false-solution case in the existing completion fixture. Zdev-Change-Id: Z2b63a02c2beaea98e961b403daf9d21faa5b48bd9f2a48f055d02e2bf02d937e --- .zdev/open-search/TASKS.md | 8 ++-- ...-witnesses-when-posting-through-an-exis.md | 48 +++++++++++++++++++ ...enchmark-tours-against-optimality-claim.md | 37 ++++++++++++++ gecode/kernel/core.hpp | 1 + test/search.cpp | 20 +++++++- 5 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 .zdev/open-search/tasks/035-invalidate-completion-witnesses-when-posting-through-an-exis.md create mode 100644 .zdev/open-search/tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index 2a9795932f..b625d17b59 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -2,10 +2,10 @@ # Tasks: open-search -- Total: 34 -- Ready: 0 +- Total: 36 +- Ready: 1 - Blocked: 0 -- Done: 34 +- Done: 35 | ID | Task | State | Blocked by | | --- | --- | --- | --- | @@ -43,3 +43,5 @@ | [open-search-032](tasks/032-gate-heuristic-search-as-an-experimental-preview-and-separat.md) | Gate heuristic search as an experimental preview and separate experimental changelog entries | done | — | | [open-search-033](tasks/033-repair-reviewed-preparation-and-completion-contracts-and-com.md) | Repair reviewed preparation and completion contracts and compact CABS victims | done | — | | [open-search-034](tasks/034-remove-kernel-search-coupling-and-simplify-heuristic-search.md) | Remove kernel search coupling and simplify heuristic search examples | done | open-search-033 | +| [open-search-035](tasks/035-invalidate-completion-witnesses-when-posting-through-an-exis.md) | Invalidate completion witnesses when posting through an existing Home | done | open-search-034 | +| [open-search-036](tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md) | Check all validated benchmark tours against optimality claims | ready | open-search-034 | diff --git a/.zdev/open-search/tasks/035-invalidate-completion-witnesses-when-posting-through-an-exis.md b/.zdev/open-search/tasks/035-invalidate-completion-witnesses-when-posting-through-an-exis.md new file mode 100644 index 0000000000..5c12e93c31 --- /dev/null +++ b/.zdev/open-search/tasks/035-invalidate-completion-witnesses-when-posting-through-an-exis.md @@ -0,0 +1,48 @@ ++++ +schema_version = 1 +id = "open-search-035" +key = "invalidate-completion-on-reused-home-posting" +area = "open-search" +status = "done" +complexity = "standard" +afk = true +priority = "high" +blocked_by = ["open-search-034"] ++++ +# Invalidate completion witnesses when posting through an existing Home + +## Outcome + +Constraints posted through fresh, copied or reused Home objects cannot leave an invalid completion witness that reports a false solution. + +## Context + +The independent whole-branch review and coordinator reproduced a false solution on four pairwise-distinct variables initially in 1..4: cache Home before completion; prepare and commit completion; post rel(cached_home,x,IRT_LQ,3). Status still reports SS_SOLVED although fresh completion proves infeasibility. Notification currently occurs only in Space-to-Home construction. Inspect Home, PostInfo/GECODE_POST, Search::Completion and the existing CompletionNative fixture. Reproducer: /private/tmp/open-search-audit-cached-home.cpp. + +## Boundaries + +- Fix the actual posting boundary without reintroducing a kernel dependency on search policy or types. +- Preserve completion preparation, nested stop/statistics, choice replay, cloning, cuts and the cheap ordinary-space notification path. Do not indiscriminately cancel completion on incidental Home copies. +- Extend the existing search fixture with the exhibited behavior; no new harness, API framework or benchmark campaign. + +## Done when + +- [x] A regression through the public posting API fails before the fix and passes after it: reused/copied Home restrictions trigger completion revalidation and reject the infeasible model. +- [x] Fresh Home posting, completion suspension/resume, nested accounting, replay and prepared-terminal behavior continue to pass existing checks. +- [x] Posting notification stays within the kernel's existing lifecycle abstraction, with no additional per-Space field or higher-layer coupling. + +## Validation + +- Run existing focused search/completion/worker-control checks and experimental ON/OFF check targets. +- Run focused current ASan+UBSan checks; run the small search examples and existing tiny TSPTW checker after the shared kernel change. +- Verify the regression fails on the original code, review preparation-internal posting for cancellation hazards, and check diff whitespace. + +## Result + +Invalidate completion witnesses when constraints are posted through cached Home objects. + +Validation: + +- One existing CompletionNative case failed before the fix and passed afterward; independent Gecode style and lifecycle review passed. +- Full check targets: experimental ON 479/479, OFF 475/475. Focused ON 14/14 and OFF 10/10, five iterations. +- Current ASan+UBSan focused 14/14, five iterations; macOS leak detection unavailable. Small examples and 60 independently checked tiny TSPTW combinations passed. diff --git a/.zdev/open-search/tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md b/.zdev/open-search/tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md new file mode 100644 index 0000000000..91bf884b6c --- /dev/null +++ b/.zdev/open-search/tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md @@ -0,0 +1,37 @@ ++++ +schema_version = 1 +id = "open-search-036" +key = "check-all-witnesses-against-optimum" +area = "open-search" +status = "open" +complexity = "standard" +afk = true +priority = "high" +blocked_by = ["open-search-034"] ++++ +# Check all validated benchmark tours against optimality claims + +## Outcome + +Benchmark proof validation uses every independently checked incumbent, even when its run is excluded from performance comparisons. + +## Context + +The independent review and coordinator checked real feasible tours of costs 5 and 10. comparison_report accepts a claimed optimum of 10 when the cost-5 row is invalid-output, because paired_sample removes that row's trace before contradiction checking; changing only its status to timeout correctly rejects the proof. The adjacent infeasibility guard already considers every checked witness. Work in misc/tsptw-benchmark.py and misc/test-tsptw-benchmark.py. + +## Boundaries + +- Separate proof-consistency checking from performance-quality filtering. Failed rows still supply no proof and stay excluded from quality comparisons. +- Use existing record/parser tests and helpers; no new test harness, benchmark campaign, report-schema change or rewriting of historical measurements. + +## Done when + +- [ ] A focused regression fails before the fix and passes after it when an independently checked cheaper tour comes from an invalid-output or other failed run. +- [ ] Every independently checked incumbent cost is considered before status-based performance filtering; valid costs equal to or above the proved optimum are accepted. +- [ ] Failed-run quality filtering and existing infeasibility contradiction checks keep their behavior. + +## Validation + +- Run the existing benchmark record test suite and exercise the checked 5-versus-10 counterexample. +- Confirm failed-run performance samples remain excluded and report structures and historical measurement files are unchanged. +- Check Python syntax and diff whitespace; no solver runs are needed for this report-analysis fix. diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index 4676c44c16..ad82031d50 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -3593,6 +3593,7 @@ namespace Gecode { : h(home), pg(home.propagatorgroup()), pid(h.ssd.data().gpi.pid()), nested(h.pc.p.vti.what() != ViewTraceInfo::OTHER) { + h.invalidate_branchers(); h.pc.p.vti.post(pg); } diff --git a/test/search.cpp b/test/search.cpp index 262563d827..8f88f49882 100644 --- a/test/search.cpp +++ b/test/search.cpp @@ -286,6 +286,24 @@ namespace Test { } #endif + static bool posting(void) { + CompletionSpace root(true,false); + Home home(root); + Gecode::Search::Statistics stat; + Gecode::Search::Options o; + if (root.status()!=SS_BRANCH || + !Gecode::Search::prepare(root,stat,o)) return false; + std::unique_ptr ch(root.choice()); + root.commit(*ch,0); + if (root.status()!=SS_SOLVED) return false; + // Posting copies a Home cached before the witness was established. + rel(home,root.x,IRT_LQ,3); + if (root.status()!=SS_BRANCH) return false; + Gecode::DFS dfs(&root); + std::unique_ptr solution(dfs.next()); + return !solution && !dfs.stopped(); + } + static bool replay(void) { CompletionSpace root(true,false); Gecode::Search::Options o; @@ -330,7 +348,7 @@ namespace Test { public: CompletionNative(void) : Base("Search::Completion::Native", TestTags::check()) {} bool run(void) override { - return replay() + return posting() && replay() #ifdef GECODE_HAS_EXPERIMENTAL && heuristic<0>() && heuristic<1>() && heuristic<2>() #endif From 92561dfdbb2e741de0413e4247ab0cc1b203fc4c Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 13 Oct 2026 16:59:09 +0200 Subject: [PATCH 13/24] Check every validated tour against benchmark optimality claims Reject a contradicted optimum even when the cheaper checked tour comes from a failed run. Keep failed runs excluded from performance comparisons. Zdev-Change-Id: Z177814b6014bcfe8af3cbd9f8be9308b0e620c4ae4485487ab77125935d2a342 --- .zdev/open-search/TASKS.md | 6 ++-- ...enchmark-tours-against-optimality-claim.md | 17 ++++++++--- misc/test-tsptw-benchmark.py | 28 +++++++++++++++++++ misc/tsptw-benchmark.py | 5 ++-- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index b625d17b59..a81db00e10 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -3,9 +3,9 @@ # Tasks: open-search - Total: 36 -- Ready: 1 +- Ready: 0 - Blocked: 0 -- Done: 35 +- Done: 36 | ID | Task | State | Blocked by | | --- | --- | --- | --- | @@ -44,4 +44,4 @@ | [open-search-033](tasks/033-repair-reviewed-preparation-and-completion-contracts-and-com.md) | Repair reviewed preparation and completion contracts and compact CABS victims | done | — | | [open-search-034](tasks/034-remove-kernel-search-coupling-and-simplify-heuristic-search.md) | Remove kernel search coupling and simplify heuristic search examples | done | open-search-033 | | [open-search-035](tasks/035-invalidate-completion-witnesses-when-posting-through-an-exis.md) | Invalidate completion witnesses when posting through an existing Home | done | open-search-034 | -| [open-search-036](tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md) | Check all validated benchmark tours against optimality claims | ready | open-search-034 | +| [open-search-036](tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md) | Check all validated benchmark tours against optimality claims | done | open-search-034 | diff --git a/.zdev/open-search/tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md b/.zdev/open-search/tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md index 91bf884b6c..c2abefbb75 100644 --- a/.zdev/open-search/tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md +++ b/.zdev/open-search/tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md @@ -3,7 +3,7 @@ schema_version = 1 id = "open-search-036" key = "check-all-witnesses-against-optimum" area = "open-search" -status = "open" +status = "done" complexity = "standard" afk = true priority = "high" @@ -26,12 +26,21 @@ The independent review and coordinator checked real feasible tours of costs 5 an ## Done when -- [ ] A focused regression fails before the fix and passes after it when an independently checked cheaper tour comes from an invalid-output or other failed run. -- [ ] Every independently checked incumbent cost is considered before status-based performance filtering; valid costs equal to or above the proved optimum are accepted. -- [ ] Failed-run quality filtering and existing infeasibility contradiction checks keep their behavior. +- [x] A focused regression fails before the fix and passes after it when an independently checked cheaper tour comes from an invalid-output or other failed run. +- [x] Every independently checked incumbent cost is considered before status-based performance filtering; valid costs equal to or above the proved optimum are accepted. +- [x] Failed-run quality filtering and existing infeasibility contradiction checks keep their behavior. ## Validation - Run the existing benchmark record test suite and exercise the checked 5-versus-10 counterexample. - Confirm failed-run performance samples remain excluded and report structures and historical measurement files are unchanged. - Check Python syntax and diff whitespace; no solver runs are needed for this report-analysis fix. + +## Result + +Check all independently validated incumbents against optimality claims before performance filtering. + +Validation: + +- The existing Records regression failed against baseline for invalid-output and execution-error; independently checked cost-5 and cost-10 tours pass after the fix. Equal/higher costs accepted and failed-run quality excluded. +- All 13 record tests, Python syntax checks and diff whitespace passed. Independent Astra review passed; no solver runs, report-schema changes or historical measurement edits. diff --git a/misc/test-tsptw-benchmark.py b/misc/test-tsptw-benchmark.py index 3abd042a9d..0013a0e25b 100644 --- a/misc/test-tsptw-benchmark.py +++ b/misc/test-tsptw-benchmark.py @@ -256,6 +256,34 @@ def test_infeasibility_proof_rejects_any_checked_tour_for_same_case(self): with self.assertRaisesRegex(ValueError, 'infeasibility proof for waiting.txt'): benchmark.comparison_report(config, [infeasible, feasible]) + def test_optimality_proof_checks_failed_run_tours_before_quality_filtering(self): + case = dict(id='checked-tours') + config = dict(cases=[case], seconds=1, configs={}) + data = ([[0, 2, 4], [3, 0, 1], [2, 3, 0]], [(0, 20)]*3, 1) + traces = [dict(benchmark.checker.check_tour(data, tour), + tour=tour, checked=True, elapsed_ms=1) for tour in ([1, 2], [2, 1])] + self.assertEqual([trace['scaled_cost'] for trace in traces], [5, 10]) + record = dict(row=dict(case=case, config='sample', repetition=0), status='ok', + incumbents=[traces[1]], final_objective=10, proof='optimal', + summary=dict(elapsed_ms=2), stop_reason='optimal', wall_seconds=0.003, + peak_rss_bytes=None, metrics={}) + for status in ('invalid-output', 'execution-error'): + failed = dict(record, status=status, incumbents=[traces[0]], final_objective=5, + proof=None, summary=None, stop_reason=status) + with self.subTest(status=status), self.assertRaisesRegex(ValueError, 'optimality proof'): + benchmark.comparison_report(config, [record, failed]) + optimum = dict(record, incumbents=[traces[0]], final_objective=5) + for trace in traces: + with self.subTest(status=status, cost=trace['scaled_cost']): + failed = dict(failed, incumbents=[trace], final_objective=trace['scaled_cost']) + report = benchmark.comparison_report(config, [optimum, failed]) + sample = report['cases'][0]['measurements'][1] + self.assertFalse(sample['quality_usable']) + self.assertIsNone(sample['final_objective']) + self.assertIsNone(sample['gap_to_proved_optimum']) + self.assertTrue(all(cost is None for cost in sample['quality_at_reported_ms'].values())) + self.assertIsNone(sample['proof']) + def test_checker_keeps_valid_trace_before_rejecting_false_proof(self): data = benchmark.checker.read_instance(benchmark.DATA/'tiny/waiting.txt') solution = dict(type='solution', elapsed_ms=1, tour=[1, 2], **{k: v for k, v in diff --git a/misc/tsptw-benchmark.py b/misc/tsptw-benchmark.py index cb07f8c270..837e6f0375 100755 --- a/misc/tsptw-benchmark.py +++ b/misc/tsptw-benchmark.py @@ -743,12 +743,13 @@ def comparison_report(config, records): if len(proven) > 1: raise ValueError('conflicting optimality proofs for '+case['id']) reference = next(iter(proven), None) + if reference is not None and any(incumbent['scaled_cost'] < reference + for record in selected for incumbent in record['incumbents']): + raise ValueError('checked candidate contradicts an optimality proof') measured = [] for record in selected: sample = paired_sample(record, checkpoints) objective = sample['final_objective'] - if reference is not None and objective is not None and objective < reference: - raise ValueError('checked candidate contradicts an optimality proof') sample.update(config=record['row']['config'], repetition=record['row']['repetition'], gap_to_proved_optimum=float(Fraction(objective-reference, reference)) if objective is not None and reference else 0 if objective == reference == 0 else None, From a04a20171c68b9cc78017955b351aa6d5dc90a4a Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Wed, 14 Oct 2026 06:31:04 +0200 Subject: [PATCH 14/24] Plan fifteen-puzzle heuristic search integration --- .zdev/_notes/fifteen-puzzle.md | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .zdev/_notes/fifteen-puzzle.md diff --git a/.zdev/_notes/fifteen-puzzle.md b/.zdev/_notes/fifteen-puzzle.md new file mode 100644 index 0000000000..dc08895eb8 --- /dev/null +++ b/.zdev/_notes/fifteen-puzzle.md @@ -0,0 +1,58 @@ +# 15-puzzle search integration + +Await the BAB-only example commit from `feature/open-cp` before changing source. +Keep CPU-heavy builds and benchmarks deferred until the MPG measurement matrix +finishes. Do not change MPG's source, results, or frozen measurement library. + +## Prepared extension + +Extend upstream `examples/fifteen-puzzle.cpp`, preserving its documentation, +size validation, shared slide/goal rules, and separate open/fixed models. +Upstream owns the example's CMake and Autoconf registration. + +- Add `bab`, `astar`, and `cabs` search selections. +- Add open-model depth and both models' total Manhattan bounds: + `max(depth + Manhattan(current board), minimum length - 1)`. +- Supply the same minimizing bound through `Search::HeuristicGuidance` to + A* and CABS. Use small engine adapters for `Script::run` and the default + unrestricted CABS widening policy. +- Guard experimental registrations, guidance, and adapters with + `GECODE_HAS_EXPERIMENTAL`; keep BAB available in ordinary builds. +- Use upstream `known_length()` in the open-model depth query. Update the + downstream TSPTW caller in `examples/tsptw-transition.hh` when integrating + the parent rename. + +Preserve standard `INT_VAL_MIN` branching. Length includes the start and moves +are length minus one. The constructor posts the start unconditionally; the +materialization transition returns for index zero. Keep this an educational +example without an embedded oracle, optimum assertions, or benchmark machinery. + +Reference implementation: +`/Users/zayenz/.codex/worktrees/bba6/MPG/rst/examples/src/fifteen-puzzle.cpp`. + +Once the measurement restriction lifts, build the example with experimental +search enabled and disabled, and run small representative open/fixed cases +through BAB, A*, and CABS. Use the existing example validation conventions; +the extension does not need a new permanent test harness. + +## Repeated posting and memory + +The MPG/CP handoff reports roughly 698.5 MiB peak RSS for open BAB versus +5.9 MiB for fixed BAB at scramble 16, limit 16, seed 3, with 780737 nodes in +each run. These measurements are preliminary and have not been independently +checked here. The growing result file is +`/Users/zayenz/.codex/worktrees/bba6/MPG/rst/examples/results/fifteen-puzzle-engines-60s.csv`. + +Source inspection confirms that freshly posted propagators allocate +`Kernel::GPI::Info` from shared space data (`gecode/kernel/core.hpp`, +`Propagator::Propagator(Home)`). Clones reuse the existing pointer. GPI allocates +entries monotonically in blocks and frees the blocks in its destructor +(`gecode/kernel/gpi.hpp`). Open transitions repeatedly post new constraints, +so this metadata can accumulate across explored states even after those spaces +are deleted. The fixed formulation posts its constraints initially. + +Follow up by measuring allocation counts and memory attributable to GPI, and +profiling runtime to distinguish posting/propagation costs from search costs. +The inspected lifetime fits the reported RSS difference; its contribution +still needs measurement. Any change to allocation or AFC sharing needs separate +design work and should leave the ongoing comparison untouched. From bc348f4395c2cce14723aa07bed2a290dfc5b4c9 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Wed, 14 Oct 2026 06:45:40 +0200 Subject: [PATCH 15/24] Add tasks to open-search Zdev-Change-Id: Z4d366a03b245f7a3abeb3df97fa01dd5a57aaab19774146e8029115ac9bacefc --- .zdev/open-search/TASKS.md | 5 ++- ...ch-to-the-shared-fifteen-puzzle-example.md | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 .zdev/open-search/tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index a81db00e10..3d1561f5c6 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -2,8 +2,8 @@ # Tasks: open-search -- Total: 36 -- Ready: 0 +- Total: 37 +- Ready: 1 - Blocked: 0 - Done: 36 @@ -45,3 +45,4 @@ | [open-search-034](tasks/034-remove-kernel-search-coupling-and-simplify-heuristic-search.md) | Remove kernel search coupling and simplify heuristic search examples | done | open-search-033 | | [open-search-035](tasks/035-invalidate-completion-witnesses-when-posting-through-an-exis.md) | Invalidate completion witnesses when posting through an existing Home | done | open-search-034 | | [open-search-036](tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md) | Check all validated benchmark tours against optimality claims | done | open-search-034 | +| [open-search-037](tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md) | Add heuristic search to the shared fifteen-puzzle example | ready | — | diff --git a/.zdev/open-search/tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md b/.zdev/open-search/tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md new file mode 100644 index 0000000000..6f2806641e --- /dev/null +++ b/.zdev/open-search/tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md @@ -0,0 +1,37 @@ ++++ +schema_version = 1 +id = "open-search-037" +key = "fifteen-puzzle-search" +area = "open-search" +status = "open" +complexity = "routine" +afk = true +blocked_by = [] ++++ +# Add heuristic search to the shared fifteen-puzzle example + +## Outcome + +The upstream fifteen-puzzle example supports BAB, A*, and CABS for its open and fixed representations, using the same Manhattan total bound. + +## Context + +The MPG thread supplied a working reference implementation and coordinated a BAB-only base in feature/open-cp. Wait for its relayed commit, refresh this branch onto that parent, then extend examples/fifteen-puzzle.cpp. Read ../../_notes/fifteen-puzzle.md for the exact source path, model invariants, and separate memory follow-up. The serial measurement campaign has finished. Preserve upstream documentation, size checks, registrations, and limit=12 default. Use known_length() after the upstream rename; the existing downstream TSPTW caller also needs adaptation. + +## Boundaries + +- Keep the open/fixed model design and standard INT_VAL_MIN branching. Add no oracle, embedded optimum assertions, benchmark framework, or new permanent tests. +- Use the same minimizing total Manhattan bound for both heuristic engines and default unrestricted CABS widening. Guard experimental registrations and adapters; BAB must work with experimental search disabled. +- Do not modify MPG source/results or change the kernel's shared propagator metadata. Keep the GPI concern as a separate follow-up. + +## Done when + +- [ ] Both model representations support bab, astar, and cabs through Script::run with experimental search enabled. +- [ ] The example builds and runs BAB with experimental search disabled, using a promptly completing default. +- [ ] The extension preserves upstream model behavior and uses known_length() consistently, including the existing TSPTW caller. + +## Validation + +- Build the example with experimental search enabled and disabled using the existing project configuration. +- Run small open/fixed cases with BAB, A*, and CABS, including an already solved start and a bounded infeasible case; inspect results and stop/exhaustion reporting. No fresh performance campaign is required. +- Build the existing TSPTW example after updating its renamed query; check the diff against the CP base and canonical MPG source for accidental model changes. From 7a848175ebe9f46a25004f747d45859467675720 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Wed, 14 Oct 2026 07:08:17 +0200 Subject: [PATCH 16/24] Add A* and CABS to the fifteen-puzzle example Zdev-Change-Id: Z5e0354b9ca7c3acb8bafb8a35ba21177068604db5820513e8a0723839ae04a64 --- .zdev/_notes/fifteen-puzzle.md | 11 +-- .zdev/open-search/TASKS.md | 6 +- .zdev/open-search/area.toml | 2 +- ...ch-to-the-shared-fifteen-puzzle-example.md | 20 ++++-- examples/fifteen-puzzle.cpp | 70 ++++++++++++++++++- examples/tsptw-transition.hh | 2 +- 6 files changed, 95 insertions(+), 16 deletions(-) diff --git a/.zdev/_notes/fifteen-puzzle.md b/.zdev/_notes/fifteen-puzzle.md index dc08895eb8..21819a627a 100644 --- a/.zdev/_notes/fifteen-puzzle.md +++ b/.zdev/_notes/fifteen-puzzle.md @@ -1,8 +1,9 @@ # 15-puzzle search integration -Await the BAB-only example commit from `feature/open-cp` before changing source. -Keep CPU-heavy builds and benchmarks deferred until the MPG measurement matrix -finishes. Do not change MPG's source, results, or frozen measurement library. +The BAB-only example from `feature/open-cp` was integrated at parent commit +`60325e4801d092ffad42b014bbca7708039b5826`. The MPG measurement matrix is +finished. Task `open-search-037` owns the extension and focused validation. +Do not change MPG's source, results, or frozen measurement library. ## Prepared extension @@ -30,8 +31,8 @@ example without an embedded oracle, optimum assertions, or benchmark machinery. Reference implementation: `/Users/zayenz/.codex/worktrees/bba6/MPG/rst/examples/src/fifteen-puzzle.cpp`. -Once the measurement restriction lifts, build the example with experimental -search enabled and disabled, and run small representative open/fixed cases +Build the example with experimental search enabled and disabled, and run +small representative open/fixed cases through BAB, A*, and CABS. Use the existing example validation conventions; the extension does not need a new permanent test harness. diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index 3d1561f5c6..db6e0e372a 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -3,9 +3,9 @@ # Tasks: open-search - Total: 37 -- Ready: 1 +- Ready: 0 - Blocked: 0 -- Done: 36 +- Done: 37 | ID | Task | State | Blocked by | | --- | --- | --- | --- | @@ -45,4 +45,4 @@ | [open-search-034](tasks/034-remove-kernel-search-coupling-and-simplify-heuristic-search.md) | Remove kernel search coupling and simplify heuristic search examples | done | open-search-033 | | [open-search-035](tasks/035-invalidate-completion-witnesses-when-posting-through-an-exis.md) | Invalidate completion witnesses when posting through an existing Home | done | open-search-034 | | [open-search-036](tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md) | Check all validated benchmark tours against optimality claims | done | open-search-034 | -| [open-search-037](tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md) | Add heuristic search to the shared fifteen-puzzle example | ready | — | +| [open-search-037](tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md) | Add heuristic search to the shared fifteen-puzzle example | done | — | diff --git a/.zdev/open-search/area.toml b/.zdev/open-search/area.toml index 5049c014ab..b510535213 100644 --- a/.zdev/open-search/area.toml +++ b/.zdev/open-search/area.toml @@ -5,4 +5,4 @@ objective = "Add A* and complete anytime beam search to Gecode for ordinary and branch = "feature/open-search" lifecycle = "open" parent = "open-cp" -base_commit = "47fc1640d14af7c634cecf3add5b2dce57ccbf09" +base_commit = "60325e4801d092ffad42b014bbca7708039b5826" diff --git a/.zdev/open-search/tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md b/.zdev/open-search/tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md index 6f2806641e..0320261e75 100644 --- a/.zdev/open-search/tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md +++ b/.zdev/open-search/tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md @@ -3,7 +3,7 @@ schema_version = 1 id = "open-search-037" key = "fifteen-puzzle-search" area = "open-search" -status = "open" +status = "done" complexity = "routine" afk = true blocked_by = [] @@ -26,12 +26,24 @@ The MPG thread supplied a working reference implementation and coordinated a BAB ## Done when -- [ ] Both model representations support bab, astar, and cabs through Script::run with experimental search enabled. -- [ ] The example builds and runs BAB with experimental search disabled, using a promptly completing default. -- [ ] The extension preserves upstream model behavior and uses known_length() consistently, including the existing TSPTW caller. +- [x] Both model representations support bab, astar, and cabs through Script::run with experimental search enabled. +- [x] The example builds and runs BAB with experimental search disabled, using a promptly completing default. +- [x] The extension preserves upstream model behavior and uses known_length() consistently, including the existing TSPTW caller. ## Validation - Build the example with experimental search enabled and disabled using the existing project configuration. - Run small open/fixed cases with BAB, A*, and CABS, including an already solved start and a bounded infeasible case; inspect results and stop/exhaustion reporting. No fresh performance campaign is required. - Build the existing TSPTW example after updating its renamed query; check the diff against the CP base and canonical MPG source for accidental model changes. + +## Result + +Added A* and CABS to the shared open/fixed fifteen-puzzle example with the same Manhattan total bound; kept BAB as default. Refreshed onto CP commit 60325e4801 and adapted the TSPTW query to known_length(). Independent verification passed without findings. + +Validation: + +- Release experimental ON: cmake --build build/open-search-split --target fifteen-puzzle tsptw -j2 passed. +- Release experimental OFF: cmake --build build/open-search-experimental-off --target fifteen-puzzle -j2 passed; help exposes only BAB and the default completes. +- ASan/UBSan: build/open-search-033-asan-ubsan fifteen-puzzle build and representative runs passed without diagnostics. +- Independent verifier ran 36 small normal/sanitized open/fixed BAB/A*/CABS cases through exhaustion, including solved and infeasible starts, and replayed every printed solution for legal slides and the goal. Defaults completed at 12 moves in all three configurations. +- Compared with CP base and canonical MPG source: model behavior, documentation, size checks, registrations and move-limit default preserved. git diff --check passed; verifier snapshot W4e57d393d7d88659 comparison remained equal. diff --git a/examples/fifteen-puzzle.cpp b/examples/fifteen-puzzle.cpp index 46853f7325..61d0ea405a 100644 --- a/examples/fifteen-puzzle.cpp +++ b/examples/fifteen-puzzle.cpp @@ -34,9 +34,11 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -51,6 +53,7 @@ class PuzzleOptions : public Options { Driver::UnsignedIntOption _limit; public: enum { MODEL_OPEN, MODEL_FIXED }; + enum { SEARCH_BAB, SEARCH_ASTAR, SEARCH_CABS }; PuzzleOptions(void) : Options("FifteenPuzzle"), _scramble("scramble","number of random slides from the goal",12), @@ -60,6 +63,12 @@ class PuzzleOptions : public Options { model(MODEL_OPEN); model(MODEL_OPEN,"open","construct states as the path grows"); model(MODEL_FIXED,"fixed","construct all bounded states initially"); + search(SEARCH_BAB); + search(SEARCH_BAB,"bab","depth-first branch and bound"); +#ifdef GECODE_HAS_EXPERIMENTAL + search(SEARCH_ASTAR,"astar","A* with Manhattan distance"); + search(SEARCH_CABS,"cabs","complete anytime beam search"); +#endif solutions(0); seed(1); } @@ -294,6 +303,12 @@ class OpenPuzzle : public Script { i++; return i; } + int + bound(void) const { + const int g = depth(); + const int h = manhattan(values(state(g))); + return std::max(g+h,path.length().min()-1); + } void constrain(const Space& solution) override { const OpenPuzzle& best = static_cast(solution); @@ -371,6 +386,12 @@ class FixedPuzzle : public Script { return result; } int + bound(void) const { + const int g = depth(); + const int h = manhattan(values(state(g))); + return std::max(g+h,length.min()-1); + } + int depth(void) const { int i = 0; while (i+1 < path.size() && path[i+1].assigned()) @@ -391,6 +412,51 @@ class FixedPuzzle : public Script { } }; +#ifdef GECODE_HAS_EXPERIMENTAL +// Both heuristic engines use the same total objective bound. +template +static Search::HeuristicGuidance +guidance(void) { + return Search::HeuristicGuidance(Search::HeuristicGuidance::MINIMIZE, + [](const Space& home) -> std::int64_t { + return static_cast(home).bound(); + }); +} + +template +// Adapt the guidance argument to the constructor expected by Script::run. +class PuzzleAStar : public AStar { +public: + PuzzleAStar(Model* root, const Search::Options& opt) + : AStar(root,guidance(),opt) {} +}; + +template +class PuzzleCABS : public CABS { +public: + PuzzleCABS(Model* root, const Search::Options& opt) + : CABS(root,guidance(),opt) {} +}; +#endif + +template +static void +run_model(const PuzzleOptions& opt) { + switch (opt.search()) { + case PuzzleOptions::SEARCH_BAB: + Script::run(opt); + break; +#ifdef GECODE_HAS_EXPERIMENTAL + case PuzzleOptions::SEARCH_ASTAR: + Script::run(opt); + break; + case PuzzleOptions::SEARCH_CABS: + Script::run(opt); + break; +#endif + } +} + /** \brief Main-function * \relates OpenPuzzle */ @@ -403,9 +469,9 @@ main(int argc, char* argv[]) { print_board(std::cout,initial_board(opt)); } if (opt.model() == PuzzleOptions::MODEL_OPEN) - Script::run(opt); + run_model(opt); else - Script::run(opt); + run_model(opt); return 0; } diff --git a/examples/tsptw-transition.hh b/examples/tsptw-transition.hh index ee795929d7..d01c3a8795 100644 --- a/examples/tsptw-transition.hh +++ b/examples/tsptw-transition.hh @@ -37,7 +37,7 @@ namespace TSPTW { std::vector tour(void) const; int cost(void) const { return prefix_cost; } int allocated_positions(void) const { - return representation==LAZY ? lazy.prefix_size() : eager.size(); + return representation==LAZY ? lazy.known_length() : eager.size(); } Gecode::IntVar position(int p) const { return representation==LAZY ? lazy[p] : eager[p]; From 9c3f78fbaae797c9cad084473a8bd6f33bd0c50d Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Wed, 14 Oct 2026 07:58:24 +0200 Subject: [PATCH 17/24] Add tasks to open-search Zdev-Change-Id: Z1f66b0a7fe00cc498450492b8845cf17c3aef7550ecdf9d3d1e39337de916690 --- .zdev/open-search/TASKS.md | 5 ++- ...hecks-into-the-heuristic-puzzle-example.md | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 .zdev/open-search/tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index db6e0e372a..f106c05f54 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -2,8 +2,8 @@ # Tasks: open-search -- Total: 37 -- Ready: 0 +- Total: 38 +- Ready: 1 - Blocked: 0 - Done: 37 @@ -46,3 +46,4 @@ | [open-search-035](tasks/035-invalidate-completion-witnesses-when-posting-through-an-exis.md) | Invalidate completion witnesses when posting through an existing Home | done | open-search-034 | | [open-search-036](tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md) | Check all validated benchmark tours against optimality claims | done | open-search-034 | | [open-search-037](tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md) | Add heuristic search to the shared fifteen-puzzle example | done | — | +| [open-search-038](tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md) | Carry deferred goal checks into the heuristic puzzle example | ready | — | diff --git a/.zdev/open-search/tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md b/.zdev/open-search/tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md new file mode 100644 index 0000000000..cca25987a5 --- /dev/null +++ b/.zdev/open-search/tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md @@ -0,0 +1,37 @@ ++++ +schema_version = 1 +id = "open-search-038" +key = "fifteen-puzzle-deferred-goal" +area = "open-search" +status = "open" +complexity = "routine" +afk = true +blocked_by = [] ++++ +# Carry deferred goal checks into the heuristic puzzle example + +## Outcome + +The open fifteen-puzzle model constructs boards only as needed and detects goals after assignment, while BAB, A*, and CABS keep their existing search configuration. + +## Context + +The MPG comparison found that imposing the initial Manhattan distance on open path length materializes unassigned boards early. The canonical source at /Users/zayenz/.codex/worktrees/bba6/MPG/rst/examples/src/fifteen-puzzle.cpp now uses OpenPuzzle::wait_goal(i) in the constructor and transition. The feature/open-cp thread is applying this correction to the BAB-only base; wait for its committed update, then refresh this branch onto it and resolve any overlap while preserving this branch's heuristic extension. Read the current example and Gecode wait(IntVarArgs, callback) implementation for callback and cloning behavior. + +## Boundaries + +- Use the upstream deferred goal correction: wait for board assignment, capture only its index, use the callback's supplied home, then fix length at a goal or require one successor. Keep the fixed model unchanged. +- Preserve BAB as default, both heuristic options/adapters and experimental guards, and the Manhattan total bounds. Remove only the open model's initial Manhattan length constraint. +- Do not adopt the diagnostic NoGPI patch, change kernel metadata allocation/tracing/group control, edit MPG source/results, or run another performance campaign. Add no permanent tests or example assertions. + +## Done when + +- [ ] The constructor and each materialized transition use clone-safe deferred goal checks, and the open model has no initial Manhattan length constraint. +- [ ] The example still supports both representations with BAB, A*, and CABS, and builds/runs BAB with experimental search disabled. +- [ ] Small solution, already solved, and insufficient-limit cases behave correctly; printed solutions contain legal slides ending at the goal. + +## Validation + +- Build fifteen-puzzle in the existing experimental ON, OFF, and ASan/UBSan configurations. Check source against the CP correction and unchanged heuristic extension. +- Run small representative open/fixed BAB/A*/CABS cases through exhaustion, including solved starts and insufficient move limits; replay printed paths externally. Check default completion and sanitizer diagnostics. +- Confirm the deferred open model only requires a successor after an assigned non-goal board; use a temporary root inspection if needed, without changing the example's output or adding a permanent fixture. From a0192bdb9c6a1488de47d11ef07b46dc6a021429 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Wed, 14 Oct 2026 08:22:59 +0200 Subject: [PATCH 18/24] Carry deferred puzzle goal checks into the heuristic branch Zdev-Change-Id: Za2ccac84325ef9977c45b27488d8e27a764851fe8ad4ea800abcbe71e448fcb1 --- .zdev/_notes/fifteen-puzzle.md | 9 ++++++++- .zdev/open-search/TASKS.md | 6 +++--- .zdev/open-search/area.toml | 2 +- ...hecks-into-the-heuristic-puzzle-example.md | 20 +++++++++++++++---- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/.zdev/_notes/fifteen-puzzle.md b/.zdev/_notes/fifteen-puzzle.md index 21819a627a..7d4dc12baa 100644 --- a/.zdev/_notes/fifteen-puzzle.md +++ b/.zdev/_notes/fifteen-puzzle.md @@ -2,9 +2,16 @@ The BAB-only example from `feature/open-cp` was integrated at parent commit `60325e4801d092ffad42b014bbca7708039b5826`. The MPG measurement matrix is -finished. Task `open-search-037` owns the extension and focused validation. +finished. Task `open-search-037` delivered the extension and focused validation. Do not change MPG's source, results, or frozen measurement library. +Task `open-search-038` carries forward the deferred goal correction from CP +commit `81fff38e8e843e29c0d26a65e004fca1e926ee87`. The open model waits for board +assignment before closing the path or requiring a successor; Manhattan distance +stays in heuristic guidance. Upstream validation found that open BAB's default +exceeds a 30-second Debug cutoff after losing early backward goal propagation. +Default usability needs follow-up; this correction adds no extra pruning rule. + ## Prepared extension Extend upstream `examples/fifteen-puzzle.cpp`, preserving its documentation, diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index f106c05f54..700b2ae7b5 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -3,9 +3,9 @@ # Tasks: open-search - Total: 38 -- Ready: 1 +- Ready: 0 - Blocked: 0 -- Done: 37 +- Done: 38 | ID | Task | State | Blocked by | | --- | --- | --- | --- | @@ -46,4 +46,4 @@ | [open-search-035](tasks/035-invalidate-completion-witnesses-when-posting-through-an-exis.md) | Invalidate completion witnesses when posting through an existing Home | done | open-search-034 | | [open-search-036](tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md) | Check all validated benchmark tours against optimality claims | done | open-search-034 | | [open-search-037](tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md) | Add heuristic search to the shared fifteen-puzzle example | done | — | -| [open-search-038](tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md) | Carry deferred goal checks into the heuristic puzzle example | ready | — | +| [open-search-038](tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md) | Carry deferred goal checks into the heuristic puzzle example | done | — | diff --git a/.zdev/open-search/area.toml b/.zdev/open-search/area.toml index b510535213..5a1c45ecea 100644 --- a/.zdev/open-search/area.toml +++ b/.zdev/open-search/area.toml @@ -5,4 +5,4 @@ objective = "Add A* and complete anytime beam search to Gecode for ordinary and branch = "feature/open-search" lifecycle = "open" parent = "open-cp" -base_commit = "60325e4801d092ffad42b014bbca7708039b5826" +base_commit = "81fff38e8e843e29c0d26a65e004fca1e926ee87" diff --git a/.zdev/open-search/tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md b/.zdev/open-search/tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md index cca25987a5..ce2e82b1c4 100644 --- a/.zdev/open-search/tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md +++ b/.zdev/open-search/tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md @@ -3,7 +3,7 @@ schema_version = 1 id = "open-search-038" key = "fifteen-puzzle-deferred-goal" area = "open-search" -status = "open" +status = "done" complexity = "routine" afk = true blocked_by = [] @@ -26,12 +26,24 @@ The MPG comparison found that imposing the initial Manhattan distance on open pa ## Done when -- [ ] The constructor and each materialized transition use clone-safe deferred goal checks, and the open model has no initial Manhattan length constraint. -- [ ] The example still supports both representations with BAB, A*, and CABS, and builds/runs BAB with experimental search disabled. -- [ ] Small solution, already solved, and insufficient-limit cases behave correctly; printed solutions contain legal slides ending at the goal. +- [x] The constructor and each materialized transition use clone-safe deferred goal checks, and the open model has no initial Manhattan length constraint. +- [x] The example still supports both representations with BAB, A*, and CABS, and builds/runs BAB with experimental search disabled. +- [x] Small solution, already solved, and insufficient-limit cases behave correctly; printed solutions contain legal slides ending at the goal. ## Validation - Build fifteen-puzzle in the existing experimental ON, OFF, and ASan/UBSan configurations. Check source against the CP correction and unchanged heuristic extension. - Run small representative open/fixed BAB/A*/CABS cases through exhaustion, including solved starts and insufficient move limits; replay printed paths externally. Check default completion and sanitizer diagnostics. - Confirm the deferred open model only requires a successor after an assigned non-goal board; use a temporary root inspection if needed, without changing the example's output or adding a permanent fixture. + +## Result + +Carried CP deferred-goal correction 81fff38e8e into the heuristic branch by managed rebase. Constructor and transition wait for assigned boards with index-only callbacks using the supplied space. Fixed constraints and heuristic configuration are preserved. Independent verification passed without required findings. + +Validation: + +- Built fifteen-puzzle in Release experimental ON and OFF and Debug ASan/UBSan; all passed. Selected clang-tidy core/C++ analyzers and git diff --check passed. +- Independent verifier ran 42 small cases through exhaustion across available engines and both representations, including solved and insufficient-limit inputs. Replayed every printed path; no sanitizer diagnostics. +- Temporary Release inspector at scramble=20, limit=32, seed=3 confirmed 32 tile variables, two path positions, depth 0 and bound 16; the stable clone matched. The solved start with limit 0 had one board and bound 0; a non-goal start with limit 0 failed. +- Known usability regression: open BAB default reached a 10-second Release cutoff without a solution; fixed default finished at 12 moves in 38 ms. Upstream observed the open default exceeding 30 seconds in Debug. Default usability remains a separate follow-up. +- Compared against the saved branch tip and CP correction: only 18 lines in fifteen-puzzle source changed; fixed model, bounds, experimental guards and adapters preserved. Kernel unchanged; NoGPI excluded. Snapshot W5b38b585ce5fa57b remained equal. From 409589b559d9d282fd46a28d9024b85daf94ea8f Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Wed, 14 Oct 2026 08:59:27 +0200 Subject: [PATCH 19/24] Add tasks to open-search Zdev-Change-Id: Z8250ae07d6ba9ac0ea51ee13ba4b2a941119b65f72344f3fb2277034a51cd169 --- .zdev/open-search/TASKS.md | 5 ++- ...uning-in-the-deferred-open-puzzle-model.md | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 .zdev/open-search/tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index 700b2ae7b5..c8efb10c59 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -2,8 +2,8 @@ # Tasks: open-search -- Total: 38 -- Ready: 0 +- Total: 39 +- Ready: 1 - Blocked: 0 - Done: 38 @@ -47,3 +47,4 @@ | [open-search-036](tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md) | Check all validated benchmark tours against optimality claims | done | open-search-034 | | [open-search-037](tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md) | Add heuristic search to the shared fifteen-puzzle example | done | — | | [open-search-038](tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md) | Carry deferred goal checks into the heuristic puzzle example | done | — | +| [open-search-039](tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md) | Restore pruning in the deferred open puzzle model | ready | — | diff --git a/.zdev/open-search/tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md b/.zdev/open-search/tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md new file mode 100644 index 0000000000..092f4a43eb --- /dev/null +++ b/.zdev/open-search/tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md @@ -0,0 +1,37 @@ ++++ +schema_version = 1 +id = "open-search-039" +key = "fifteen-puzzle-assigned-pruning" +area = "open-search" +status = "open" +complexity = "routine" +afk = true +blocked_by = [] ++++ +# Restore pruning in the deferred open puzzle model + +## Outcome + +The open puzzle rejects assigned boards that cannot reach the goal within the move cap and constrains an already materialized final board to the goal, restoring practical BAB defaults without eager future boards. + +## Context + +Task038 carried deferred goal checks forward, but the open BAB default exceeded a 10-second Release cutoff. The current canonical source /Users/zayenz/.codex/worktrees/bba6/MPG/rst/examples/src/fifteen-puzzle.cpp adds check_bound, final_goal and hooks in wait_goal/constrain. Its SHA256 is 1f0905c45f97addcfef060806b959063cac831ea45bd79410d76c0e3d0e12a92. Independent source review found no required corrections, including clone safety, immediate upper-bound updates and BAB replay paths. The CP thread is adapting and validating this ordinary correction first; wait for its commit, refresh this branch onto it, then verify the heuristic extension. The serial measurement run has completed, so focused builds may resume. + +## Boundaries + +- Prune only assigned boards with the admissible depth-plus-Manhattan bound against the current maximum moves. Preserve deferred goal checks and avoid imposing a Manhattan minimum on open-list length. +- Force the cap board to goal when first materialized or when an incumbent cut makes an already materialized board the cap. Preserve index-only callbacks using their supplied space, fixed-model constraints, BAB default, A*/CABS adapters and experimental guards. +- Keep kernel metadata, NoGPI and compact/rolling-board variants outside this task. Do not modify MPG source/results or run another performance campaign. Add no permanent tests or embedded example assertions. + +## Done when + +- [ ] The current ordinary CP correction is incorporated, including assigned-board pruning and both cap-goal hooks, with the heuristic extension preserved. +- [ ] The root for scramble20/limit32/seed3 still constructs two boards; goal and insufficient-limit cases remain correct across available engines and representations. +- [ ] The open BAB default completes within the prior 10-second Release check, and the example builds/runs with experimental search enabled and disabled. + +## Validation + +- Build fifteen-puzzle in existing Release experimental ON/OFF and Debug ASan/UBSan configurations; inspect the actual CP delta and unchanged native/kernel/fixed-model code. +- Run small open/fixed BAB/A*/CABS cases through exhaustion, including improving incumbents, solved starts and insufficient limits; replay printed paths externally and check sanitizer diagnostics. Check both Release defaults with a 10-second cutoff. +- Use temporary root/clone inspection to confirm two boards at scramble=20, limit=32, seed=3 and cap-goal behavior after an incumbent cut on an already materialized successor. Keep inspection outside the example and do not create a permanent fixture. From 84cd7e8dddb388b53673808c76e6f2344a90660c Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Wed, 14 Oct 2026 09:20:31 +0200 Subject: [PATCH 20/24] Restore puzzle pruning without eager board construction Zdev-Change-Id: Z17833b1b93100a39353a843fda7c32be690aaef1321f2a08d9832c1d2a32accb --- .zdev/_notes/fifteen-puzzle.md | 10 +++++++++- .zdev/open-search/TASKS.md | 6 +++--- .zdev/open-search/area.toml | 2 +- ...uning-in-the-deferred-open-puzzle-model.md | 20 +++++++++++++++---- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/.zdev/_notes/fifteen-puzzle.md b/.zdev/_notes/fifteen-puzzle.md index 7d4dc12baa..089a3fac5c 100644 --- a/.zdev/_notes/fifteen-puzzle.md +++ b/.zdev/_notes/fifteen-puzzle.md @@ -10,7 +10,15 @@ commit `81fff38e8e843e29c0d26a65e004fca1e926ee87`. The open model waits for boar assignment before closing the path or requiring a successor; Manhattan distance stays in heuristic guidance. Upstream validation found that open BAB's default exceeds a 30-second Debug cutoff after losing early backward goal propagation. -Default usability needs follow-up; this correction adds no extra pruning rule. +The default slowdown was addressed in task `open-search-039`. + +Task `open-search-039` carried that follow-up from CP commit +`fc26a2a8fcb78d5b1c617e341a1ef13224f40019`. Assigned boards fail when their +depth plus Manhattan distance exceeds the current move cap. A materialized +board at the final cap is constrained to the goal, including after incumbent +cuts. These rules preserve deferred construction and the heuristic extension. +Focused Release validation completed the open defaults in 9–10 ms, with the +two-board root, clone independence and pending-cap behavior checked separately. ## Prepared extension diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index c8efb10c59..5b76205294 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -3,9 +3,9 @@ # Tasks: open-search - Total: 39 -- Ready: 1 +- Ready: 0 - Blocked: 0 -- Done: 38 +- Done: 39 | ID | Task | State | Blocked by | | --- | --- | --- | --- | @@ -47,4 +47,4 @@ | [open-search-036](tasks/036-check-all-validated-benchmark-tours-against-optimality-claim.md) | Check all validated benchmark tours against optimality claims | done | open-search-034 | | [open-search-037](tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md) | Add heuristic search to the shared fifteen-puzzle example | done | — | | [open-search-038](tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md) | Carry deferred goal checks into the heuristic puzzle example | done | — | -| [open-search-039](tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md) | Restore pruning in the deferred open puzzle model | ready | — | +| [open-search-039](tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md) | Restore pruning in the deferred open puzzle model | done | — | diff --git a/.zdev/open-search/area.toml b/.zdev/open-search/area.toml index 5a1c45ecea..97e519af7a 100644 --- a/.zdev/open-search/area.toml +++ b/.zdev/open-search/area.toml @@ -5,4 +5,4 @@ objective = "Add A* and complete anytime beam search to Gecode for ordinary and branch = "feature/open-search" lifecycle = "open" parent = "open-cp" -base_commit = "81fff38e8e843e29c0d26a65e004fca1e926ee87" +base_commit = "fc26a2a8fcb78d5b1c617e341a1ef13224f40019" diff --git a/.zdev/open-search/tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md b/.zdev/open-search/tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md index 092f4a43eb..f0e0e011d6 100644 --- a/.zdev/open-search/tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md +++ b/.zdev/open-search/tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md @@ -3,7 +3,7 @@ schema_version = 1 id = "open-search-039" key = "fifteen-puzzle-assigned-pruning" area = "open-search" -status = "open" +status = "done" complexity = "routine" afk = true blocked_by = [] @@ -26,12 +26,24 @@ Task038 carried deferred goal checks forward, but the open BAB default exceeded ## Done when -- [ ] The current ordinary CP correction is incorporated, including assigned-board pruning and both cap-goal hooks, with the heuristic extension preserved. -- [ ] The root for scramble20/limit32/seed3 still constructs two boards; goal and insufficient-limit cases remain correct across available engines and representations. -- [ ] The open BAB default completes within the prior 10-second Release check, and the example builds/runs with experimental search enabled and disabled. +- [x] The current ordinary CP correction is incorporated, including assigned-board pruning and both cap-goal hooks, with the heuristic extension preserved. +- [x] The root for scramble20/limit32/seed3 still constructs two boards; goal and insufficient-limit cases remain correct across available engines and representations. +- [x] The open BAB default completes within the prior 10-second Release check, and the example builds/runs with experimental search enabled and disabled. ## Validation - Build fifteen-puzzle in existing Release experimental ON/OFF and Debug ASan/UBSan configurations; inspect the actual CP delta and unchanged native/kernel/fixed-model code. - Run small open/fixed BAB/A*/CABS cases through exhaustion, including improving incumbents, solved starts and insufficient limits; replay printed paths externally and check sanitizer diagnostics. Check both Release defaults with a 10-second cutoff. - Use temporary root/clone inspection to confirm two boards at scramble=20, limit=32, seed=3 and cap-goal behavior after an incumbent cut on an already materialized successor. Keep inspection outside the example and do not create a permanent fixture. + +## Result + +Carried CP pruning correction fc26a2a8fc into the heuristic branch. Assigned-board budget checks and both cap-goal hooks restore the open BAB default while preserving deferred construction, the fixed model and A*/CABS configuration. Resolved the rebase overlap with one shared depth query and the existing heuristic bound. Independent verification passed without findings. + +Validation: + +- Release experimental ON/OFF and Debug ASan/UBSan fifteen-puzzle builds passed. Selected clang-analyzer checks passed with actual Release flags; established Gecode formatting preserved because no project formatter configuration exists. +- Independent verifier ran 44 Release and 30 sanitized cases through exhaustion across both representations and available engines. Printed paths replayed legally; incumbents strictly improved; solved and insufficient-limit cases passed. Sanitizer stderr was empty. +- ON/OFF open defaults returned 12 moves in 9–10 ms under a 10-second cutoff; fixed defaults completed in 46–47 ms. This resolves the previous default slowdown on the ordinary kernel. +- Sanitized root/clone probe at scramble=20, limit=32, seed=3 confirmed two boards and bound 16; clone growth left the root unchanged. First-cap materialization solved; a real three-move incumbent forced an already materialized cap before propagation. Pending-board and assigned-blank-prefix cases correctly failed the impossible two-move restriction. +- The source delta is confined to 39 lines in the example; fixed/shared rules, heuristic adapters, bounds and guards, and kernel are unchanged. NoGPI and compact experiments are excluded. Snapshot Wfb14909dd8e16dad remained equal; git diff whitespace checks passed. From b4121332b555c03d01e86f38308d5546a5d3c7b4 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Wed, 14 Oct 2026 13:06:40 +0200 Subject: [PATCH 21/24] Add tasks to open-search Zdev-Change-Id: Z2c89afed43b151d906b16e91f0f13f8baa69bb77d0ed9b81d51b1193fd69066c --- .zdev/open-search/TASKS.md | 5 ++- ...open-list-construction-across-the-stack.md | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 .zdev/open-search/tasks/040-use-scoped-nogpi-for-open-list-construction-across-the-stack.md diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index 5b76205294..ab65b9a514 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -2,8 +2,8 @@ # Tasks: open-search -- Total: 39 -- Ready: 0 +- Total: 40 +- Ready: 1 - Blocked: 0 - Done: 39 @@ -48,3 +48,4 @@ | [open-search-037](tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md) | Add heuristic search to the shared fifteen-puzzle example | done | — | | [open-search-038](tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md) | Carry deferred goal checks into the heuristic puzzle example | done | — | | [open-search-039](tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md) | Restore pruning in the deferred open puzzle model | done | — | +| [open-search-040](tasks/040-use-scoped-nogpi-for-open-list-construction-across-the-stack.md) | Use scoped NoGPI for open-list construction across the stack | ready | — | diff --git a/.zdev/open-search/tasks/040-use-scoped-nogpi-for-open-list-construction-across-the-stack.md b/.zdev/open-search/tasks/040-use-scoped-nogpi-for-open-list-construction-across-the-stack.md new file mode 100644 index 0000000000..bc41e3fede --- /dev/null +++ b/.zdev/open-search/tasks/040-use-scoped-nogpi-for-open-list-construction-across-the-stack.md @@ -0,0 +1,37 @@ ++++ +schema_version = 1 +id = "open-search-040" +key = "no-gpi-stack" +area = "open-search" +status = "open" +complexity = "standard" +afk = true +priority = "high" +blocked_by = [] ++++ +# Use scoped NoGPI for open-list construction across the stack + +## Outcome + +Integrate the reviewed NoGPI kernel feature into feature/open-cp, suppress GPI by default for open-list factories and transitions, then update and validate feature/open-search on that parent. + +## Context + +The user explicitly authorized updating both branches. Import only the three reviewed feature/no-gpi commits, without the newer main baseline. Preserve backup refs and the unrelated open-route edits in the parent checkout. Use a temporary parent checkout for implementation; coordination owns commits, parent fast-forward, descendant rebase and publication. + +## Boundaries + +- Scope suppression in materialize and the transition invocation in append, including direct append. Callback-local Space::UseGPI permits selected postings; document AFC and callback scopes. No new configuration toggle. +- Preserve known_length, experimental search guards and ordinary posting outside open-list calls. Avoid benchmark campaigns and unrelated cleanup. Existing kernel tests travel with the feature; add only a focused Gecode-style open-list contract check if useful. + +## Done when + +- [ ] The reviewed kernel commits and default open-list NoGPI scopes are on feature/open-cp, with public callback documentation and selected UseGPI supported. +- [ ] feature/open-search is rebased onto the updated parent with its heuristic changes preserved and backups available. +- [ ] Both branches pass focused existing open-list/AFC tests and example checks; the search branch also passes heuristic checks with experimental ON/OFF and fully instrumented ASan/UBSan where applicable. Independent review passes. + +## Validation + +- Build affected parent and child code; run existing AFC/open-list checks and representative open/fixed fifteen-puzzle runs. Verify factory, direct append transition, nested growth, opt-in and restoration through a proportionate Gecode-style check. +- Run focused native heuristic tests and experimental-disabled BAB on the child. Run a fully instrumented ASan/UBSan build and selected checks; inspect diagnostics. +- Review branch ancestry, ranges and diffs, source style, generated-source freshness if changed, and unchanged user edits. From 97a84c0c4bc7f91a98cead1f5436ed7bf80cdb0a Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Wed, 14 Oct 2026 13:21:26 +0200 Subject: [PATCH 22/24] Record NoGPI stack integration and verification Zdev-Change-Id: Zcc1487603e0b94ece3dae6c00772e0eddaaec4de507909a74213977161725ea1 --- .zdev/open-search/TASKS.md | 6 +++--- .zdev/open-search/area.toml | 2 +- ...r-open-list-construction-across-the-stack.md | 17 +++++++++++++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index ab65b9a514..4b79e42bdf 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -3,9 +3,9 @@ # Tasks: open-search - Total: 40 -- Ready: 1 +- Ready: 0 - Blocked: 0 -- Done: 39 +- Done: 40 | ID | Task | State | Blocked by | | --- | --- | --- | --- | @@ -48,4 +48,4 @@ | [open-search-037](tasks/037-add-heuristic-search-to-the-shared-fifteen-puzzle-example.md) | Add heuristic search to the shared fifteen-puzzle example | done | — | | [open-search-038](tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md) | Carry deferred goal checks into the heuristic puzzle example | done | — | | [open-search-039](tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md) | Restore pruning in the deferred open puzzle model | done | — | -| [open-search-040](tasks/040-use-scoped-nogpi-for-open-list-construction-across-the-stack.md) | Use scoped NoGPI for open-list construction across the stack | ready | — | +| [open-search-040](tasks/040-use-scoped-nogpi-for-open-list-construction-across-the-stack.md) | Use scoped NoGPI for open-list construction across the stack | done | — | diff --git a/.zdev/open-search/area.toml b/.zdev/open-search/area.toml index 97e519af7a..b1b9642780 100644 --- a/.zdev/open-search/area.toml +++ b/.zdev/open-search/area.toml @@ -5,4 +5,4 @@ objective = "Add A* and complete anytime beam search to Gecode for ordinary and branch = "feature/open-search" lifecycle = "open" parent = "open-cp" -base_commit = "fc26a2a8fcb78d5b1c617e341a1ef13224f40019" +base_commit = "2be74e7df376592c4a8125e10743105c5df0ab96" diff --git a/.zdev/open-search/tasks/040-use-scoped-nogpi-for-open-list-construction-across-the-stack.md b/.zdev/open-search/tasks/040-use-scoped-nogpi-for-open-list-construction-across-the-stack.md index bc41e3fede..5c6b136079 100644 --- a/.zdev/open-search/tasks/040-use-scoped-nogpi-for-open-list-construction-across-the-stack.md +++ b/.zdev/open-search/tasks/040-use-scoped-nogpi-for-open-list-construction-across-the-stack.md @@ -3,7 +3,7 @@ schema_version = 1 id = "open-search-040" key = "no-gpi-stack" area = "open-search" -status = "open" +status = "done" complexity = "standard" afk = true priority = "high" @@ -26,12 +26,21 @@ The user explicitly authorized updating both branches. Import only the three rev ## Done when -- [ ] The reviewed kernel commits and default open-list NoGPI scopes are on feature/open-cp, with public callback documentation and selected UseGPI supported. -- [ ] feature/open-search is rebased onto the updated parent with its heuristic changes preserved and backups available. -- [ ] Both branches pass focused existing open-list/AFC tests and example checks; the search branch also passes heuristic checks with experimental ON/OFF and fully instrumented ASan/UBSan where applicable. Independent review passes. +- [x] The reviewed kernel commits and default open-list NoGPI scopes are on feature/open-cp, with public callback documentation and selected UseGPI supported. +- [x] feature/open-search is rebased onto the updated parent with its heuristic changes preserved and backups available. +- [x] Both branches pass focused existing open-list/AFC tests and example checks; the search branch also passes heuristic checks with experimental ON/OFF and fully instrumented ASan/UBSan where applicable. Independent review passes. ## Validation - Build affected parent and child code; run existing AFC/open-list checks and representative open/fixed fifteen-puzzle runs. Verify factory, direct append transition, nested growth, opt-in and restoration through a proportionate Gecode-style check. - Run focused native heuristic tests and experimental-disabled BAB on the child. Run a fully instrumented ASan/UBSan build and selected checks; inspect diagnostics. - Review branch ancestry, ranges and diffs, source style, generated-source freshness if changed, and unchanged user edits. + +## Result + +Imported reviewed NoGPI kernel support into feature/open-cp and made open-list factories and transitions use NoGPI by default, including direct append. Documented callback-local UseGPI and added one focused Gecode-style contract fixture. Rebased feature/open-search through zdev, preserving search changes and backup branches. Independent verification passed without findings; unrelated parent edit preserved. + +Validation: + +- Parent Release: 45 AFC/open-list/open-regular checks and 10 puzzle cases passed. Child Release ON: 53 checks and 30 puzzle cases; OFF: 49 checks and 10 BAB cases. Independent optimum calculations and path replay passed. +- Fully instrumented ASan/UBSan with CBS: 54 checks and 12 puzzle cases passed with empty stderr. Selected clang-tidy analyzer checks passed. Kernel commit/range comparison, ancestry, scope restoration, cloning, guards, whitespace and parent edit preservation checked. From 29f51f0a176df2dc8376e7df2776aeadf948d1ae Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Wed, 14 Oct 2026 14:06:12 +0200 Subject: [PATCH 23/24] Add tasks to open-search Zdev-Change-Id: Z88c7680506b777813639342d75a33f1506023620484baf105835b54619f0ea89 --- .zdev/open-search/TASKS.md | 5 +-- ...pi-on-open-list-materialization-methods.md | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 .zdev/open-search/tasks/041-document-nogpi-on-open-list-materialization-methods.md diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index 4b79e42bdf..4ac8f8cdbb 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -2,8 +2,8 @@ # Tasks: open-search -- Total: 40 -- Ready: 0 +- Total: 41 +- Ready: 1 - Blocked: 0 - Done: 40 @@ -49,3 +49,4 @@ | [open-search-038](tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md) | Carry deferred goal checks into the heuristic puzzle example | done | — | | [open-search-039](tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md) | Restore pruning in the deferred open puzzle model | done | — | | [open-search-040](tasks/040-use-scoped-nogpi-for-open-list-construction-across-the-stack.md) | Use scoped NoGPI for open-list construction across the stack | done | — | +| [open-search-041](tasks/041-document-nogpi-on-open-list-materialization-methods.md) | Document NoGPI on open-list materialization methods | ready | — | diff --git a/.zdev/open-search/tasks/041-document-nogpi-on-open-list-materialization-methods.md b/.zdev/open-search/tasks/041-document-nogpi-on-open-list-materialization-methods.md new file mode 100644 index 0000000000..1b4c6202ec --- /dev/null +++ b/.zdev/open-search/tasks/041-document-nogpi-on-open-list-materialization-methods.md @@ -0,0 +1,33 @@ ++++ +schema_version = 1 +id = "open-search-041" +key = "no-gpi-method-docs" +area = "open-search" +status = "open" +complexity = "routine" +afk = true +blocked_by = [] ++++ +# Document NoGPI on open-list materialization methods + +## Outcome + +Make the NoGPI default visible in the Doxygen comments for materialize and the methods that invoke it, on feature/open-cp and its search descendant. + +## Context + +Factory, Transition and append already document NoGPI. materialize currently omits it; get and close invoke materialize. Edit the parent checkout public header and refresh the child through the managed rebase. Preserve the unrelated open-route edit. + +## Boundaries + +- Documentation only. State the default scope and callback-local UseGPI override at materialize; use short references for get and close. Do not repeat the full kernel metadata contract or change behavior. +- No build or runtime tests for this comment-only change; independent source review and whitespace checks suffice. Coordination owns parent source commit, child rebase and publication. + +## Done when + +- [ ] materialize documents NoGPI and the selected UseGPI override; get and close make their materialization scope discoverable. +- [ ] Both branches contain the documentation and remain correctly stacked; the parent user edit is unchanged and independent review passes. + +## Validation + +- Review comments against materialize/get/close call paths, existing Factory/Transition documentation and Gecode Doxygen conventions. Run git diff --check and compare branch source deltas and the saved parent user edit. From 4b2408817153977dea3a992e32c0e402d4da806d Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Wed, 14 Oct 2026 14:11:14 +0200 Subject: [PATCH 24/24] Record NoGPI method documentation review Zdev-Change-Id: Zf72514dc0d043eb83ea6e7e6ba0a8d9d9758646927c111172976b8d5d22c78b1 --- .zdev/open-search/TASKS.md | 6 +++--- .zdev/open-search/area.toml | 2 +- ...t-nogpi-on-open-list-materialization-methods.md | 14 +++++++++++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.zdev/open-search/TASKS.md b/.zdev/open-search/TASKS.md index 4ac8f8cdbb..ed5cca6807 100644 --- a/.zdev/open-search/TASKS.md +++ b/.zdev/open-search/TASKS.md @@ -3,9 +3,9 @@ # Tasks: open-search - Total: 41 -- Ready: 1 +- Ready: 0 - Blocked: 0 -- Done: 40 +- Done: 41 | ID | Task | State | Blocked by | | --- | --- | --- | --- | @@ -49,4 +49,4 @@ | [open-search-038](tasks/038-carry-deferred-goal-checks-into-the-heuristic-puzzle-example.md) | Carry deferred goal checks into the heuristic puzzle example | done | — | | [open-search-039](tasks/039-restore-pruning-in-the-deferred-open-puzzle-model.md) | Restore pruning in the deferred open puzzle model | done | — | | [open-search-040](tasks/040-use-scoped-nogpi-for-open-list-construction-across-the-stack.md) | Use scoped NoGPI for open-list construction across the stack | done | — | -| [open-search-041](tasks/041-document-nogpi-on-open-list-materialization-methods.md) | Document NoGPI on open-list materialization methods | ready | — | +| [open-search-041](tasks/041-document-nogpi-on-open-list-materialization-methods.md) | Document NoGPI on open-list materialization methods | done | — | diff --git a/.zdev/open-search/area.toml b/.zdev/open-search/area.toml index b1b9642780..9ba6309be3 100644 --- a/.zdev/open-search/area.toml +++ b/.zdev/open-search/area.toml @@ -5,4 +5,4 @@ objective = "Add A* and complete anytime beam search to Gecode for ordinary and branch = "feature/open-search" lifecycle = "open" parent = "open-cp" -base_commit = "2be74e7df376592c4a8125e10743105c5df0ab96" +base_commit = "462123d05d8195fefa00dc9be7990ee2d08b1a82" diff --git a/.zdev/open-search/tasks/041-document-nogpi-on-open-list-materialization-methods.md b/.zdev/open-search/tasks/041-document-nogpi-on-open-list-materialization-methods.md index 1b4c6202ec..3c5cff6b73 100644 --- a/.zdev/open-search/tasks/041-document-nogpi-on-open-list-materialization-methods.md +++ b/.zdev/open-search/tasks/041-document-nogpi-on-open-list-materialization-methods.md @@ -3,7 +3,7 @@ schema_version = 1 id = "open-search-041" key = "no-gpi-method-docs" area = "open-search" -status = "open" +status = "done" complexity = "routine" afk = true blocked_by = [] @@ -25,9 +25,17 @@ Factory, Transition and append already document NoGPI. materialize currently omi ## Done when -- [ ] materialize documents NoGPI and the selected UseGPI override; get and close make their materialization scope discoverable. -- [ ] Both branches contain the documentation and remain correctly stacked; the parent user edit is unchanged and independent review passes. +- [x] materialize documents NoGPI and the selected UseGPI override; get and close make their materialization scope discoverable. +- [x] Both branches contain the documentation and remain correctly stacked; the parent user edit is unchanged and independent review passes. ## Validation - Review comments against materialize/get/close call paths, existing Factory/Transition documentation and Gecode Doxygen conventions. Run git diff --check and compare branch source deltas and the saved parent user edit. + +## Result + +Documented NoGPI and callback-local UseGPI on materialize(), with short references from get() and close(). Parent documentation commit 462123d05d is incorporated through the managed child rebase. Independent source review passed; existing failure documentation and user edit preserved. + +Validation: + +- Checked documentation against materialize/get/close call paths and kernel scopes; parent and child NoGPI excerpts match. Ancestry and whitespace checks passed. Existing parent example edit is byte-identical. No builds or runtime tests required for comment-only change.