Docs / Getting started

From checkout to first solve.

This walkthrough gets a small graph through the complete DeltaSearch loop. It intentionally uses the public API directly so you can see what the CLI is doing underneath.

1. Install the alpha locally

Use Python 3.10 or newer. The editable install keeps imports pointed at your checkout, which is useful while the API is still changing.

git clone https://github.com/sachncs/delta-search.git
cd delta-search
python -m venv .venv
source .venv/bin/activate       # Windows: .venv\Scripts\activate
python -m pip install -e ".[dev]"
Optional integrations

Install .[all] when you need NetworkX, matplotlib, scikit-learn, or tqdm. The core package remains standard-library only.

2. Build and solve a graph

Save this as examples/first_solve.py or run it in a Python shell. PrizeCollectingVertexCoverProblem is useful for a first example because it demonstrates both reward and penalty.

from delta_search import (
    Graph,
    GreedySolver,
    PrizeCollectingVertexCoverProblem,
)

graph = Graph[int].from_edges([
    (1, 2), (2, 3), (3, 1),
    (3, 4), (4, 5), (5, 2),
])

problem = PrizeCollectingVertexCoverProblem(
    graph,
    default_penalty=2.0,
)
result = GreedySolver(problem).solve(max_iterations=200)

print(f"objective: {result.best_objective:.2f}")
print(f"iterations: {result.iteration}")
print(f"reason: {result.convergence_reason}")

The result is a SolverState, not just a number. Keep the state around when you need the best graph, convergence reason, or iteration count.

3. Try the CLI

The command-line interface is useful for JSON fixtures and repeatable smoke tests. It is intentionally small: validate inputs first, then solve.

# See available commands
delta-search --help

# Validate a graph fixture
delta-search validate --graph input.json

# Solve a maximum planar subgraph instance
delta-search solve \
  --problem mps \
  --graph input.json \
  --output result.json

4. Define a custom problem

Subclass SubgraphExtractionProblem[NodeT] and implement the domain contract. Keep domain rules here; do not fork the greedy loop.

class MaxEdgesProblem(SubgraphExtractionProblem[int]):
    def calculate_delta(self, state, action):
        if action.action_type is ActionType.ADD_EDGE:
            return DeltaResult(1.0, 0.0, True)
        if action.action_type is ActionType.REMOVE_EDGE:
            return DeltaResult(-1.0, 0.0, True)
        return DeltaResult(0.0, 0.0, True)

    def compute_reward(self, state):
        return float(state.graph.num_edges)

    def compute_penalty(self, state):
        return 0.0

    def is_feasible(self, state):
        return True

In production code, annotate the state type, validate constructor inputs, and add tests for both the delta and its inverse. See the contract deep dive.

5. Observe what happened

Heuristics become much easier to trust when the run is inspectable. An observer can log every candidate evaluation and every completed iteration without changing the solver policy.

class TraceObserver:
    def on_action_evaluated(self, action, delta, elapsed_ms):
        print(action, delta, f"{elapsed_ms:.2f} ms")

    def on_iteration_complete(self, iteration, best_action, objective):
        print(iteration, objective, best_action)

    def on_convergence(self, iterations, final_objective):
        print(f"stopped after {iterations}: {final_objective}")

result = GreedySolver(problem).solve(
    max_iterations=100,
    observer=TraceObserver(),
)

Where to go next