Skip to content

Persistent shared DAG for constraint propagation#225

Open
dpsanders wants to merge 8 commits into
masterfrom
dag-persistent
Open

Persistent shared DAG for constraint propagation#225
dpsanders wants to merge 8 commits into
masterfrom
dag-persistent

Conversation

@dpsanders
Copy link
Copy Markdown
Member

Summary

  • Redesigns the DAG engine so the graph is persistent (built once, reused across all pave iterations) and shared across constraints
  • SharedDAG is the central object that accumulates constraints via add_expression!. Variable nodes and common subexpressions are reused across all expressions added to the same DAG.
  • Multi-constraint propagation: propagate!(dag, X) contracts all constraints jointly each iteration, so narrowing from one constraint immediately benefits others through shared variable nodes
  • DAGContractor(dag, expr, vars) and DAGSeparator(dag, expr, vars, constraint) accept an existing SharedDAG, letting multiple separators share the same graph

Key results

Node sharing: Two expressions x²+y²-1 and x²-y on the same SharedDAG use 10 nodes vs 15 separate (33% fewer — the subexpression is shared).

Joint propagation: x²+y²≤1 AND x²-y≤0 on [-10,10]² correctly narrows to [-1,1]×[0,1] in a single propagate! call, because the second constraint forces y≥x²≥0.

Benchmarks (persistent DAG vs code-generation):

Problem ϵ Codegen DAG Ratio
Unit disk 2D single call 1.2 μs 6.8 μs 6x
Unit disk 2D 0.1 294 μs 2.5 ms 8x
Unit disk 2D 0.01 2.5 ms 20 ms 8x
Annulus 2D 0.1 1.7 ms 10 ms 6x
3D torus 1.0 12 ms 97 ms 8x

Both approaches produce identical paving results on all test cases.

Usage

# Standalone (creates its own DAG):
S = DAGSeparator(x^2 + y^2 <= 1, [x, y])
inner, boundary = pave(X, S, 0.1)

# Shared DAG (multiple constraints, joint propagation):
dag = SharedDAG([x, y])
S1 = DAGSeparator(dag, x^2 + y^2 - 1, [x, y], interval(-Inf, 0.0))
S2 = DAGSeparator(dag, x^2 - y, [x, y], interval(-Inf, 0.0))
result = propagate!(dag, IntervalBox(interval(-10, 10), 2))

Test plan

  • 13 test sets in test/test_dag.jl — SharedDAG construction, CSE node reuse, multi-constraint propagation, shared DAG contractors/separators, paving comparison with original, combined separators, 3D
  • All 4 existing test sets in test/runtests.jl still pass
  • Benchmarks in benchmark/bench_dag_vs_codegen.jl

🤖 Generated with Claude Code

David Sanders and others added 8 commits April 2, 2026 03:35
…ompatibility

Update compat bounds: IntervalArithmetic 1, IntervalBoxes 0.3,
IntervalContractors 0.6, ReversePropagation 0.4, Symbolics 7.

IntervalArithmetic v1.0 follows IEEE 1788 and deliberately does not define
Base.isequal/Base.hash for Interval. This broke @register_symbolic x ∈ y::Interval
since SymbolicUtils needs isequal/hash for hash-consing. Instead of type-pirating
those methods, decompose x ∈ interval(a,b) into (x >= a) & (x <= b) at the
symbolic level.

Also fix pre-existing bug in separator() where & and | used Base.intersect/union
instead of ⊓/⊔ (defined for AbstractSeparator in set_operations.jl).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cherry-pick infrastructure changes from PR #220:
- Update GitHub Actions versions (checkout v6, setup-julia v2, cache v3, codecov v6)
- Test on Julia 1.11 instead of 1.10
- Set julia compat to 1.11
- Remove obsolete REQUIRE file (Pkg.jl era)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch from the old Documenter HTML backend to DocumenterVitepress for a
modern VitePress-based documentation site. Add new pages explaining
contractors/separators and the internal architecture, update index.md to
the current API, add GitHub Actions workflow for doc deployment, and
remove stale mkdocs.yml and Manifest.toml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements an explicit DAG (directed acyclic graph) for forward-backward
interval constraint propagation (HC4Revise), inspired by Schichl & Neumaier.
This provides an inspectable, iterable alternative to the existing
ReversePropagation code-generation approach.

New types: DAGContractor, DAGSeparator, ConstraintDAG
New files: src/dag/{nodes,build,propagate,contractor}.jl
Tests: test/test_dag.jl (27 tests)
Benchmarks: benchmark/bench_dag_vs_codegen.jl

Both approaches produce identical paving results. The DAG approach is
currently ~10x slower due to DAG reconstruction per call and dynamic
dispatch, with clear optimization paths documented in CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Redesign the DAG engine so the graph is built once and reused:

- SharedDAG: persistent DAG that accumulates constraints via
  add_expression!. Variable nodes and common subexpressions (CSE)
  are shared across all expressions added to the same DAG.

- Multi-constraint propagation: forward-backward passes contract
  all constraints jointly, so narrowing from one constraint
  immediately benefits others through shared variable nodes.

- DAGContractor/DAGSeparator accept an existing SharedDAG,
  allowing multiple separators to share the same graph.

Benchmarks show ~30% speedup vs the previous rebuild-per-call
approach, and shared DAGs use 33% fewer nodes than separate ones.

Resolves Project.toml merge conflict from cherry-pick.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant