Skip to content

Placement

PhysicalPlacementStrategy dataclass

PhysicalPlacementStrategy(
    arch_spec: ArchSpec = get_physical_arch_spec(),
    traversal: RustPlacementTraversal = (
        lambda: RustPlacementTraversal(strategy="entropy")
    )(),
    target_generator: (
        TargetGeneratorABC | TargetGeneratorCallable | None
    ) = None,
)

Bases: MoveToPlacementStrategyABC


              flowchart TD
              bloqade.lanes.heuristics.physical.placement.PhysicalPlacementStrategy[PhysicalPlacementStrategy]
              bloqade.lanes.analysis.placement.strategy.MoveToPlacementStrategyABC[MoveToPlacementStrategyABC]
              bloqade.lanes.analysis.placement.strategy.PlacementStrategyABC[PlacementStrategyABC]

                              bloqade.lanes.analysis.placement.strategy.MoveToPlacementStrategyABC --> bloqade.lanes.heuristics.physical.placement.PhysicalPlacementStrategy
                                bloqade.lanes.analysis.placement.strategy.PlacementStrategyABC --> bloqade.lanes.analysis.placement.strategy.MoveToPlacementStrategyABC
                



              click bloqade.lanes.heuristics.physical.placement.PhysicalPlacementStrategy href "" "bloqade.lanes.heuristics.physical.placement.PhysicalPlacementStrategy"
              click bloqade.lanes.analysis.placement.strategy.MoveToPlacementStrategyABC href "" "bloqade.lanes.analysis.placement.strategy.MoveToPlacementStrategyABC"
              click bloqade.lanes.analysis.placement.strategy.PlacementStrategyABC href "" "bloqade.lanes.analysis.placement.strategy.PlacementStrategyABC"
            

Physical placement strategy backed by the Rust TargetSolver.

rust_bound_stats_total property

rust_bound_stats_total: dict[str, float]

Branch-and-bound pruning statistics summed over every solve.

Counters and depth sums add; the per-instance root_lower_bound / incumbent_cost / gap do not aggregate meaningfully across solves, so the widest observed gap is kept as max_optimality_gap instead.

Empty — not zeroed — before the first solve, and after any number of solves run with :pyattr:RustPlacementTraversal.completion_bound set to None: an unbounded run measured nothing, and reporting zeros would be indistinguishable from a bounded run that pruned nothing. Key-check rather than expecting the keys to exist.

rust_entropy_fallback_count property

rust_entropy_fallback_count: int

Number of solved Rust entropy stages that used the budget-exhaustion fallback (Push and Rotate, with the greedy sequential router as the out-of-regime tertiary).

rust_nodes_expanded_total property

rust_nodes_expanded_total: int

Total Rust solver node expansions for this strategy instance.

rust_proven_total property

rust_proven_total: int

Solves whose plan the bound proved optimal.

Read against the solve count rather than alone: False on a solve means unproven, not suboptimal.

traced_blocked_locations property

traced_blocked_locations: tuple[LocationAddress, ...]

Spectator atom positions for the traced CZ layer (atoms not in the active placement).

traced_target property

traced_target: dict[int, LocationAddress]

First candidate target for the traced CZ layer (used by visualizers).

compute_moves

compute_moves(
    state_before: ConcreteState, state_after: ConcreteState
) -> tuple[tuple[LaneAddress, ...], ...]

Route atoms between two fixed layouts for user-directed MoveTo.

Delegates to the shared compute_move_layers primitive, passing this strategy's own traversal and cached :class:SearchEngine so MoveTo routing uses the same search configuration and per-arch lane index as cz_placements (the docstring on compute_move_layers notes the two callsites are meant to share a traversal).

The import is deferred to call time: heuristics.move_synthesis imports this module, so a top-level import would be a cycle.

Source code in .venv/lib/python3.12/site-packages/bloqade/lanes/heuristics/physical/movement.py
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
def compute_moves(
    self,
    state_before: ConcreteState,
    state_after: ConcreteState,
) -> tuple[tuple[LaneAddress, ...], ...]:
    """Route atoms between two fixed layouts for user-directed ``MoveTo``.

    Delegates to the shared ``compute_move_layers`` primitive, passing this
    strategy's own ``traversal`` and cached :class:`SearchEngine` so MoveTo
    routing uses the same search configuration and per-arch lane index as
    ``cz_placements`` (the docstring on ``compute_move_layers`` notes the two
    callsites are meant to share a traversal).

    The import is deferred to call time: ``heuristics.move_synthesis``
    imports this module, so a top-level import would be a cycle.
    """
    from bloqade.lanes.heuristics.move_synthesis import compute_move_layers

    return compute_move_layers(
        self.arch_spec,
        state_before,
        state_after,
        engine=self._get_engine(),
        traversal=self.traversal,
    )

RustPlacementTraversal dataclass

RustPlacementTraversal(
    strategy: SearchStrategyName = "entropy",
    max_movesets_per_group: int = 3,
    max_goal_candidates: int = 3,
    max_expansions: int | None = 300,
    restarts: int = 1,
    lookahead: bool = False,
    collect_entropy_trace: bool = False,
    seed: int = 0,
    bound_terminates: bool = True,
    completion_bound: str | None = None,
    block_spectators: bool = True,
)

Config for the Rust search engine (TargetSolver).

restarts and lookahead are now exposed and threaded into SolveOptions; per-strategy entropy knobs (max_movesets_per_group, max_goal_candidates, collect_entropy_trace) feed EntropyOptions via :func:_move_search_from_traversal.

Not yet exposed (Rust defaults used): weight, deadlock_policy, w_t. These will be threaded through once validated via Rust-only benchmarking.

block_spectators class-attribute instance-attribute

block_spectators: bool = True

Scope each CZ solve to the participating qubits.

When True (default), qubits not in the CZ's controls/targets are treated as blocked obstacles for that solve rather than free atoms to route. This keeps the search effort independent of how many unrelated qubits share the same merged StaticPlacement block (under always_merge every CZ would otherwise search over all block qubits, inflating nodes_expanded).

Set False to let each CZ route over all block qubits. Note: blocking spectators removes routing freedom, so for some strategies it can change (sometimes increase) move counts (e.g. DFS may relocate a spectator to shorten a participant's path); the search-effort reduction is not always move-count-free.

bound_terminates class-attribute instance-attribute

bound_terminates: bool = True

Let the completion bound end the search once the plan is proven optimal.

On by default. The proof is the root certificate -- the plan's cost has reached h(root), which lower-bounds every legal plan -- so it holds without a complete generator, and the solve reports proven.

Stopping skips no expansion: a cut root cannot be expanded from, so what it ends is a spin over already-cut nodes. False restores that spin and is useful only for A/B measurement. Requires completion_bound; inert without one.

completion_bound class-attribute instance-attribute

completion_bound: str | None = None

Admissible completion bound for branch-and-bound pruning.

None (default) prunes a branch only once its accumulated cost alone reaches the incumbent's. "weighted_distance" additionally prunes when g + h reaches it, where h is the max objective-weighted distance over unresolved atoms. Affects which subtrees are explored, never candidate generation, and never returns a worse plan.

Only consulted by the entropy strategy, and ignored for loose-goal (CZ pair) solves where the bound is not admissible.