F2FSynthesis

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

Bases: GenericPass[DAGCircuit, DAGCircuit]

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

Some gates can be rewritten into a circuit of smaller fermionic gates before they reach the fermion-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 fermionic counterpart of F2QSynthesis, 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 = FermionicPassManager(
    [
        MergeOrbitalRotations(),
        MergeSlaterDeterminantPreparation(),
        F2FSynthesis({"OrbitalRotation": "Givens", "PrepareSlaterDeterminant": "Givens"}),
    ]
)

Usage

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

  1. Providing a config during initialization:

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

    >>> from qiskit_fermions.circuit.library import synthesis
    >>> from qiskit_fermions.transpiler import passes
    >>>
    >>> synth = passes.F2FSynthesis()
    >>> synth.methods["Evolution"] = synthesis.FermionicSuzukiTrotter(order=2)
    

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

It belongs in the optimization stage, ahead of the layout and the synthesis, and after the passes that consume these gates as such. Placing it earlier would break the gates up before those passes can fuse them, which silently gives up the cheaper Slater-determinant decomposition (see MergeSlaterDeterminantPreparation).

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.

Important

What the replacement is equivalent to is specified by the gate and its plugin, not by this pass: usually it implements the same unitary (or approximates it), but a gate may specify less, and then only a circuit that meets that weaker specification may use this pass. Consult the documentation of the gates you configure, and see F2FSynthesisPlugin.

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 QDriftTrotterization, 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 a decompose() call. Be aware that a gate which is never expanded reaches the fermion-to-qubit stage whole, where it is mapped without its synthesis ever being read, and every method then produces identical output.

Caution

Not every method suits every operator. An Evolution whose operator groups all mutually commute (the diagonal-Coulomb operators of a UCJ, for example) 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 \(a^\dagger_i a_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 (F2FSynthesisConfig | None) – an optional dictionary to pre-populate the methods with plugins provided by the F2FSynthesisPluginManager. See F2FSynthesisConfig.

  • 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 F2FSynthesisConfig.

Attributes

methods: dict[str, F2FSynthesisPlugin]

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

The keys of this dictionary correspond to the __name__ of a FermionicGate, and the values are instances of the F2FSynthesisPlugin 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 F2FSynthesisPluginManager 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 FermionicGate.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 a decompose() call. Note that a gate which is never expanded reaches the fermion-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 fermion-based instructions. Only DAGOpNode with FermionicGate instances as their op are supported, plus Barrier and FermionicMeasure, which are carried through untouched (a measurement keeps the classical bit it writes into).

Returns:

The output circuit which is still acting on a fermionic 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