# B2BSynthesis

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

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

Bases: [`GenericPass`](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.passmanager.GenericPass)[[`DAGCircuit`](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.dagcircuit.DAGCircuit), [`DAGCircuit`](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.dagcircuit.DAGCircuit)]

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`](#qiskit_bosons.transpiler.passes.B2BSynthesis.methods), with the plugin that [`methods`](#qiskit_bosons.transpiler.passes.B2BSynthesis.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`](qiskit_bosons.transpiler.passes.B2QSynthesis.html.md#qiskit_bosons.transpiler.passes.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.

```python
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:
   ```pycon
   >>> from qiskit_bosons.transpiler import passes
   >>>
   >>> config = {"Evolution": ("SuzukiTrotter", (), {"order": 2})}
   >>> synth = passes.B2BSynthesis(config)
   ```
2. Manually populating the [`methods`](#qiskit_bosons.transpiler.passes.B2BSynthesis.methods) attribute:
   ```pycon
   >>> 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`](qiskit_bosons.circuit.library.synthesis.B2BSynthesisPlugin.html.md#qiskit_bosons.circuit.library.synthesis.B2BSynthesisPlugin) implementations without requiring them to be registered through
   an [entry-point](https://setuptools.pypa.io/en/latest/userguide/entry_point.html).

It belongs in the `optimization` stage, ahead of the layout and the synthesis. The method named
here takes precedence over the [`synthesis`](qiskit_bosons.circuit.BosonicGate.html.md#qiskit_bosons.circuit.BosonicGate.synthesis) of an individual gate. To use the
synthesis of the gates themselves instead, leave this pass out and call
[`decompose()`](qiskit_bosons.circuit.BosonicCircuit.html.md#qiskit_bosons.circuit.BosonicCircuit.decompose).

Each selected gate is replaced by the circuit that its plugin produces. A gate whose type is not a
key of [`methods`](#qiskit_bosons.transpiler.passes.B2BSynthesis.methods) is left untouched, as is any gate that is [`atomic`](qiskit_bosons.circuit.BosonicGate.html.md#qiskit_bosons.circuit.BosonicGate.atomic)
(also with `apply=False`: it is not even tagged with the plugin) and any node rejected by an
optional [`filter`](#qiskit_bosons.transpiler.passes.B2BSynthesis.filter). A gate that has no synthesis at all, such as an
[`InitializeModes`](qiskit_bosons.circuit.library.InitializeModes.html.md#qiskit_bosons.circuit.library.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`](qiskit_bosons.transpiler.passes.synthesis.MapperFnEvolutionSynthesis.html.md#qiskit_bosons.transpiler.passes.synthesis.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`](qiskit_bosons.circuit.library.synthesis.BosonicClementsDecomposition.html.md#qiskit_bosons.circuit.library.synthesis.BosonicClementsDecomposition) for what it
implies for the cutoff.

#### NOTE
Running the pass again is harmless: the factors a plugin emits are
[`atomic`](qiskit_bosons.circuit.BosonicGate.html.md#qiskit_bosons.circuit.BosonicGate.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`](qiskit_bosons.circuit.library.synthesis.BosonicQDrift.html.md#qiskit_bosons.circuit.library.synthesis.BosonicQDrift), whose sampled gates are atomic because the random
draw *is* the Trotterization it performs.

#### NOTE
Set [`apply`](#qiskit_bosons.transpiler.passes.B2BSynthesis.apply) to `False` to only *select* the method and leave the expansion to
something else, such as Qiskit’s [`Decompose`](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.transpiler.passes.Decompose) or a
[`decompose()`](qiskit_bosons.circuit.BosonicCircuit.html.md#qiskit_bosons.circuit.BosonicCircuit.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`](qiskit_bosons.circuit.BosonicGate.html.md#qiskit_bosons.circuit.BosonicGate.synthesis) ever being read, and every method then produces identical
output.

#### NOTE
Decomposing is a choice, not a requirement. A [`ModeRotation`](qiskit_bosons.circuit.library.ModeRotation.html.md#qiskit_bosons.circuit.library.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](../guides/mode_rotation.html.md#mode-rotation-direct-explanation).

#### CAUTION
Not every method suits every operator. An [`Evolution`](qiskit_bosons.circuit.library.Evolution.html.md#qiskit_bosons.circuit.library.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`](#qiskit_bosons.transpiler.passes.B2BSynthesis.filter) to restrict the
pass to the gates that benefit.

Expanding an operator that carries no [`groups`](qiskit_bosons.operators.OperatorTrait.html.md#qiskit_bosons.operators.OperatorTrait.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`](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.circuit.library.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*](qiskit_bosons.transpiler.passes.optimization.b2b_synthesis.B2BSynthesisConfig.html.md#qiskit_bosons.transpiler.passes.optimization.b2b_synthesis.B2BSynthesisConfig) *|* *None*) – an optional dictionary to pre-populate the [`methods`](#qiskit_bosons.transpiler.passes.B2BSynthesis.methods) with plugins provided
    by the [`B2BSynthesisPluginManager`](qiskit_bosons.circuit.library.synthesis.B2BSynthesisPluginManager.html.md#qiskit_bosons.circuit.library.synthesis.B2BSynthesisPluginManager). See `B2BSynthesisConfig`.
  * **apply** ([*bool*](https://docs.python.org/3/builtins/functions.html#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`](#qiskit_bosons.transpiler.passes.B2BSynthesis.apply).
  * **filter** (*Callable* *[* *[**DAGOpNode* *]* *,* [*bool*](https://docs.python.org/3/builtins/functions.html#bool) *]*  *|* *None*) – an optional predicate deciding which nodes to break down. It is called with the
    [`DAGOpNode`](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.dagcircuit.DAGOpNode) of a gate whose type is in [`methods`](#qiskit_bosons.transpiler.passes.B2BSynthesis.methods),
    and the node is left untouched unless it returns `True`. If `None` (the default),
    every such node is selected.
* **Raises:**
  * [**KeyError**](https://docs.python.org/3/builtins/exceptions.html#KeyError) – if `config` names a gate or a method for which no plugin is installed.
  * [**TypeError**](https://docs.python.org/3/builtins/exceptions.html#TypeError) – if `config` gives a value that is none of the forms of
        `B2BSynthesisConfig`.

### Attributes

#### methods *: [dict](https://docs.python.org/3/builtins/stdtypes.html#dict)[[str](https://docs.python.org/3/builtins/stdtypes.html#str), [B2BSynthesisPlugin](qiskit_bosons.circuit.library.synthesis.B2BSynthesisPlugin.html.md#qiskit_bosons.circuit.library.synthesis.B2BSynthesisPlugin)]*

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

The keys of this dictionary correspond to the `__name__` of a [`BosonicGate`](qiskit_bosons.circuit.BosonicGate.html.md#qiskit_bosons.circuit.BosonicGate), and
the values are instances of the [`B2BSynthesisPlugin`](qiskit_bosons.circuit.library.synthesis.B2BSynthesisPlugin.html.md#qiskit_bosons.circuit.library.synthesis.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`](qiskit_bosons.circuit.library.synthesis.B2BSynthesisPluginManager.html.md#qiskit_bosons.circuit.library.synthesis.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()`](qiskit_bosons.circuit.BosonicGate.html.md#qiskit_bosons.circuit.BosonicGate.with_synthesis)), except an [`atomic`](qiskit_bosons.circuit.BosonicGate.html.md#qiskit_bosons.circuit.BosonicGate.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`](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.transpiler.passes.Decompose) or a
[`decompose()`](qiskit_bosons.circuit.BosonicCircuit.html.md#qiskit_bosons.circuit.BosonicCircuit.decompose) call.
Note that a gate which is never expanded reaches the boson-to-qubit stage whole, where it is
mapped without its [`synthesis`](qiskit_bosons.circuit.BosonicGate.html.md#qiskit_bosons.circuit.BosonicGate.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`](#qiskit_bosons.transpiler.passes.B2BSynthesis.methods) and that is accepted by [`filter`](#qiskit_bosons.transpiler.passes.B2BSynthesis.filter) is
replaced by the gates that its plugin produces, or (when [`apply`](#qiskit_bosons.transpiler.passes.B2BSynthesis.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*](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.dagcircuit.DAGCircuit)) – the input circuit with boson-based instructions. Only
  [`DAGOpNode`](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.dagcircuit.DAGOpNode) with [`BosonicGate`](qiskit_bosons.circuit.BosonicGate.html.md#qiskit_bosons.circuit.BosonicGate) instances as their
  [`op`](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.dagcircuit.DAGOpNode#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*](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.dagcircuit.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*](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.passmanager.PassManagerState)) – State associated with workflow execution by the pass manager itself.
  * **callback** ([*Callable*](https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable) *[* *[*[*Task*](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.passmanager.Task) *,* *IR_OUT* *,* [*PropertySet*](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.passmanager.PropertySet) *,* [*float*](https://docs.python.org/3/builtins/functions.html#float) *,* [*int*](https://docs.python.org/3/builtins/functions.html#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](https://docs.python.org/3/builtins/stdtypes.html#tuple)[*IR_OUT*, [*PassManagerState*](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.passmanager.PassManagerState)]

#### name()

Name of the pass.

* **Return type:**
  [str](https://docs.python.org/3/builtins/stdtypes.html#str)

#### update_status(state, run_state)

Update workflow status.

* **Parameters:**
  * **state** ([*PassManagerState*](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.passmanager.PassManagerState)) – Pass manager state to update.
  * **run_state** (*RunState*) – Completion status of current task.
* **Returns:**
  Updated pass manager state.
* **Return type:**
  [*PassManagerState*](https://quantum.cloud.ibm.com/docs/api/qiskit/qiskit.passmanager.PassManagerState)
