B2BSynthesis

class B2BSynthesis(config=None, *, apply=True, filter=None)

Bases: GenericPass[DAGCircuit, DAGCircuit]

A transpilation pass breaking gates down into smaller ones in bosonic space.

Some gates can be rewritten into a circuit of smaller bosonic gates before they reach the boson-to-qubit stage, which then maps and synthesizes those. This pass does that for the gates named in methods, with the plugin that methods names for each, so the choice is made once for a whole transpilation pipeline instead of gate by gate. It is the bosonic counterpart of B2QSynthesis, one stage earlier: where that pass maps every instruction to qubits, this one rewrites some gates into others and leaves the rest as they are.

pm.optimization = BosonicPassManager(
    [
        B2BSynthesis(
            {
                "Evolution": ("SuzukiTrotter", (), {"order": 2, "reps": 4}),
                "ModeRotation": "Clements",
            }
        )
    ]
)

Usage

There are two ways to configure the plugins used by this transpiler pass:

  1. Providing a config during initialization:

    >>> from qiskit_bosons.transpiler import passes
    >>>
    >>> config = {"Evolution": ("SuzukiTrotter", (), {"order": 2})}
    >>> synth = passes.B2BSynthesis(config)
    
  2. Manually populating the methods attribute:

    >>> from qiskit_bosons.circuit.library import synthesis
    >>> from qiskit_bosons.transpiler import passes
    >>>
    >>> synth = passes.B2BSynthesis()
    >>> synth.methods["Evolution"] = synthesis.BosonicSuzukiTrotter(order=2)
    

    Through this manual approach it is also possible to inject custom B2BSynthesisPlugin implementations without requiring them to be registered through an entry-point.

It belongs in the optimization stage, ahead of the layout and the synthesis. The method named here takes precedence over the synthesis of an individual gate. To use the synthesis of the gates themselves instead, leave this pass out and call decompose().

Each selected gate is replaced by the circuit that its plugin produces. A gate whose type is not a key of methods is left untouched, as is any gate that is atomic (also with apply=False: it is not even tagged with the plugin) and any node rejected by an optional filter. A gate that has no synthesis at all, such as an InitializeModes, passes through no matter what.

This is where the grouping of the operator pays off for the outer Trotter error. A product formula handed to MapperFnEvolutionSynthesis only subdivides the terms within a single group and never interleaves the groups, so raising its repetition count does not reduce the error between groups that do not commute. A formula applied here splits along the groups themselves.

Nothing in this step depends on the truncation or the encoding, which only enter later. The same decomposition therefore serves every LevelSpec. See BosonicClementsDecomposition for what it implies for the cutoff.

Note

Running the pass again is harmless: the factors a plugin emits are atomic, and an atomic gate is a terminal factor which this pass leaves in place rather than splitting further. The same exemption makes the pass a fixed point over the output of BosonicQDrift, whose sampled gates are atomic because the random draw is the Trotterization it performs.

Note

Set apply to False to only select the method and leave the expansion to something else, such as Qiskit’s Decompose or a decompose() call. Be aware that a gate which is never expanded reaches the boson-to-qubit stage whole, where it is mapped without its synthesis ever being read, and every method then produces identical output.

Note

Decomposing is a choice, not a requirement. A ModeRotation that is left whole is lowered by a boson-to-qubit synthesis plugin registered for it, if there is one; see Lower a rotation without decomposing it.

Caution

Not every method suits every operator. An Evolution whose operator groups all mutually commute (a Hamiltonian made only of number operators, say) is synthesized exactly at any order, so a higher order only adds depth. Use filter to restrict the pass to the gates that benefit.

Expanding an operator that carries no groups splits it term by term, and a lone hopping term \(b^\dagger_i b_j\) is not Hermitian, so its exponential is not unitary. Nothing complains until the mapped operator reaches PauliEvolutionGate, which raises ValueError: Operator contains complex coefficients, which are not supported. Assigning conjugate-paired groups is what makes each factor Hermitian; see groups_are_hermitian().

Initializing this transpiler pass can be done with the arguments listed below.

Parameters:
  • config (B2BSynthesisConfig | None) – an optional dictionary to pre-populate the methods with plugins provided by the B2BSynthesisPluginManager. See B2BSynthesisConfig.

  • apply (bool) – whether to expand each selected gate into the factors its plugin produces. When False, the gate is only tagged with the plugin (unless it is atomic, which is never touched) and something else has to expand it later. See apply.

  • filter (Callable[[DAGOpNode], bool] | None) – an optional predicate deciding which nodes to break down. It is called with the DAGOpNode of a gate whose type is in methods, and the node is left untouched unless it returns True. If None (the default), every such node is selected.

Raises:
  • KeyError – if config names a gate or a method for which no plugin is installed.

  • TypeError – if config gives a value that is none of the forms of B2BSynthesisConfig.

Attributes

methods: dict[str, B2BSynthesisPlugin]

A dictionary of the boson-to-boson synthesis methods, by gate.

The keys of this dictionary correspond to the __name__ of a BosonicGate, and the values are instances of the B2BSynthesisPlugin protocol. A gate that is not a key is not touched by this pass, so the dictionary is empty by default, which makes the pass do nothing. Pre-populate it with plugins provided by the B2BSynthesisPluginManager through the config argument.

apply

Whether to expand each selected gate into the factors its plugin produces.

When this is False, the pass only selects the method: it tags each selected gate with its plugin (see BosonicGate.with_synthesis()), except an atomic gate, which is left as it is, and leaves the expansion to whatever builds the gate’s definition later, such as Qiskit’s Decompose or a decompose() call. Note that a gate which is never expanded reaches the boson-to-qubit stage whole, where it is mapped without its synthesis ever being read.

filter

The predicate selecting which nodes to break down.

Methods

run(dag)

Runs this transpilation pass.

Every node whose gate type is in methods and that is accepted by filter is replaced by the gates that its plugin produces, or (when apply is False) by an equivalent gate merely carrying the plugin. All other nodes are left untouched. The input DAG is modified in place.

Parameters:

dag (DAGCircuit) – the input circuit with boson-based instructions. Only DAGOpNode with BosonicGate instances as their op are supported, plus Barrier, which is carried through untouched.

Returns:

The output circuit which is still acting on a bosonic register.

Return type:

DAGCircuit

Inherited Methods

execute(passmanager_ir, state, callback=None)

Execute optimization task for input Qiskit IR.

Parameters:
  • passmanager_ir (IR) – Qiskit IR to optimize.

  • state (PassManagerState) – State associated with workflow execution by the pass manager itself.

  • callback (Callable[[Task, IR_OUT, PropertySet, float, int], None] | None) – A callback function which is called per execution of optimization task.

Returns:

Optimized Qiskit IR and state of the workflow.

Return type:

tuple[IR_OUT, PassManagerState]

name()

Name of the pass.

Return type:

str

update_status(state, run_state)

Update workflow status.

Parameters:
  • state (PassManagerState) – Pass manager state to update.

  • run_state (RunState) – Completion status of current task.

Returns:

Updated pass manager state.

Return type:

PassManagerState