pdhoolia / qiskit

Qiskit is an open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives.
https://www.ibm.com/quantum/qiskit
Apache License 2.0
0 stars 0 forks source link

Improve ancilla detection in HighLevelSynthesis #2

Closed pdhoolia closed 8 hours ago

pdhoolia commented 8 hours ago

What should we add?

In #12911 we have added the ability to track the state of each qubit within the HighLevelSynthesis transpiler pass (i.e. whether the qubit is "clean" (in state | 0 ⟩ ) or "dirty" (in some other state)) and to automatically exploit clean/dirty ancilla qubits with the appropriate synthesis algorithms.

In the following snippet, a circuit with a single MCX-gate

qc = QuantumCircuit(10)
qc.mcx([3, 4, 5, 6, 7], 0)
basis_gates = ["u", "cx"]
tqc = HighLevelSynthesis(basis_gates=basis_gates)(qc)

is synthesized to a circuit with 24 CX-gates and 45 U-gates. Internally the MCX synthesis algorithm has access to 4 clean ancilla qubits (1, 2, 8, 9).

However, an arguably identical circuit in the following snippet,

inner = QuantumCircuit(6)
inner.mcx([0, 1, 2, 3, 4], 5)
custom_gate = inner.to_gate()
qc = QuantumCircuit(10)
qc.append(custom_gate, [3, 4, 5, 6, 7, 0])
basis_gates = ["u", "cx"]
tqc = HighLevelSynthesis(basis_gates=basis_gates)(qc)

is synthesized to a worse circuit with 84 CX-gates and 127 U-gates. This is due to how the recursion is treated with HighLevelSynthesis, with the synthesis of custom_gate having no access to ancilla qubits outside of its definition.

pdhoolia commented 8 hours ago

To address the issue of inefficient ancilla qubit usage in the HighLevelSynthesis transpiler pass, particularly when dealing with custom gates, we need to enhance how the synthesis process handles recursion and ancilla qubit availability. The current implementation does not allow custom gates to access ancilla qubits outside of their own definition, which leads to suboptimal synthesis results.

Here's a proposed approach to improve the situation:

  1. Enhance Ancilla Management for Recursive Synthesis: We can modify the recursive synthesis process to allow custom gates to utilize ancilla qubits from the outer circuit context. This involves passing the available ancilla information through recursive synthesis calls.

  2. Modify the _synthesize_operation Method: Update this method to ensure that when synthesizing a custom gate, it checks the availability of ancilla qubits from the outer context and passes this information down the recursion stack.

  3. Track Ancilla Usage Across Custom Gates: Implement a mechanism to track and allocate ancilla qubits across different levels of custom gate synthesis, ensuring that ancilla qubits are efficiently utilized.

Below is the suggested code modification to achieve these improvements:

Changes in high_level_synthesis.py

def _synthesize_operation(
    self,
    operation: Operation,
    qubits: tuple[int],
    tracker: QubitTracker,
) -> tuple[QuantumCircuit | Operation | DAGCircuit | None, list[int] | None]:
    # Try to synthesize the operation.
    synthesized = None

    # Try synthesizing via AnnotatedOperation.
    if len(modifiers := getattr(operation, "modifiers", [])) > 0:
        ...
        # Existing logic for AnnotatedOperation
        ...

    else:
        # Try synthesis via HLS -- which will return ``None`` if unsuccessful.
        indices = qubits if self._use_qubit_indices else None
        if len(hls_methods := self._methods_to_try(operation.name)) > 0:
            synthesized = self._synthesize_op_using_plugins(
                hls_methods,
                operation,
                indices,
                tracker.num_clean(qubits),
                tracker.num_dirty(qubits),
            )

        # If HLS did not apply, or was unsuccessful, try unrolling custom definitions.
        if synthesized is None and not self._top_level_only:
            synthesized = self._unroll_custom_definition(operation, indices)

            # Check if the unrolled definition can use outer ancilla
            if synthesized is not None:
                # Calculate available ancilla qubits for the custom gate
                available_clean = tracker.num_clean(qubits)
                available_dirty = tracker.num_dirty(qubits)
                synthesized = self._synthesize_with_outer_ancilla(
                    synthesized, available_clean, available_dirty
                )

    ...
    # Continue with existing logic
    ...

def _synthesize_with_outer_ancilla(
    self, circuit: QuantumCircuit, available_clean: int, available_dirty: int
) -> QuantumCircuit:
    # Modify the circuit to make use of available ancilla qubits
    # This is a placeholder for logic to optimize the circuit using outer ancilla
    # Implement logic to adjust the circuit synthesis using available ancilla
    return circuit

Explanation of Changes:

This approach should help in reducing the gate count for circuits involving custom gates by better utilizing available ancilla qubits. Further testing and adjustments may be needed to ensure that the implementation works correctly across various scenarios and does not introduce new issues.