Ansatz generation (qiskit_addon_aqc_tensor.ansatz_generation)

Tools for generating ansatz circuits.

AnsatzBlock

Ansatz block.

OneQubitAnsatzBlock

One-qubit ansatz block.

TwoQubitAnsatzBlock

Two-qubit ansatz block.

ZXZ

One-qubit ansatz block based on the ZXZ decomposition.

KAK

Two-qubit ansatz block based on the KAK decomposition.

generate_ansatz_from_circuit(qc, /, *, qubits_initially_zero=False, parameter_name='theta')[source][source]

Generate an ansatz from the two-qubit connectivity structure of a circuit.

See the explanatatory material for motivation.

Parameters:
  • qc (QuantumCircuit) – A circuit, which is assumed to be unitary. Barriers are ignored.

  • qubits_initially_zero (bool) – If True, the first Z rotation on each qubit is fixed to zero because such a rotation has no effect on the state \(|0\rangle\).

  • parameter_name (str) – Name for the ParameterVector representing the free parameters in the returned ansatz circuit.

Return type:

tuple[QuantumCircuit, list[float]]

Returns:

(ansatz, parameter_values) such that ansatz.assign_parameters(parameter_values) is equivalent to qc up to a global phase.

Example:

Consider the following circuit as an example:

from qiskit import QuantumCircuit

qc = QuantumCircuit(6)
qc.rx(0.4, 0)
qc.ryy(0.2, 2, 3)
qc.h(2)
qc.rz(0.1, 2)
qc.rxx(0.3, 0, 1)
qc.rzz(0.3, 0, 1)
qc.cx(2, 1)
qc.s(1)
qc.h(4)
qc.draw("mpl")

(Source code)

Circuit diagram output by the previous code.

If the above circuit is passed to generate_ansatz_from_circuit(), it will return an ansatz with parametrized two-qubit KAK rotations in the same locations as the input:

from qiskit_addon_aqc_tensor import generate_ansatz_from_circuit

ansatz, initial_params = generate_ansatz_from_circuit(
    qc, qubits_initially_zero=True, parameter_name="x"
)
ansatz.draw("mpl")

(Source code)

Circuit diagram output by the previous code.

Note that in the generated ansatz, all consecutive single-qubit gates are collapsed into the same ZXZ block, and all consecutive two-qubit gates are collapsed into a single KAK block, up to single-qubit rotations.

Further, the generate_ansatz_from_circuit() function provides parameters which, when bound to the ansatz, will result in a circuit equivalent to the original one, up to a global phase:

ansatz.assign_parameters(initial_params).draw("mpl")

(Source code)

Circuit diagram output by the previous code.

A 1D Trotter circuit leads to a similar result, with its characteristic brickwork structure:

from rustworkx.generators import path_graph
from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit, generate_xyz_hamiltonian

hamiltonian = generate_xyz_hamiltonian(
    path_graph(6),
    coupling_constants=(0.0, 0.0, 1.0),
    ext_magnetic_field=(0.4, 0.0, 0.0),
)

good_circuit = generate_time_evolution_circuit(
    hamiltonian,
    synthesis=SuzukiTrotter(reps=2),
    time=1.0,
)

good_circuit.draw("mpl", initial_state=True)

(Source code)

Circuit diagram output by the previous code.
from qiskit_addon_aqc_tensor import generate_ansatz_from_circuit

ansatz, initial_params = generate_ansatz_from_circuit(
    good_circuit, qubits_initially_zero=True, parameter_name="x"
)
ansatz.assign_parameters(initial_params).draw("mpl", initial_state=True)

(Source code)

Circuit diagram output by the previous code.
parametrize_circuit(qc, /, *, parameter_name='theta')[source][source]

Create a parametrized version of a circuit.

Given a quantum circuit, constructs another quantum circuit which is identical except that any gates with numerical parameters are replaced by gates (of the same type) with free parameters. The new circuit is returned along with a list containing the original values of the parameters.

Parameters:
  • qc (QuantumCircuit) – The quantum circuit to parametrize.

  • parameter_name (str) – Name for the ParameterVector representing the free parameters in the returned ansatz circuit.

Return type:

tuple[QuantumCircuit, list[float | None]]

Returns:

(ansatz, parameter_values) such that ansatz.assign_parameters(parameter_values) is identical to qc as long as qc did not already contain parameters. If qc already had parameters, then parameter_values will contain None at the entries corresponding to those parameters.

Example:

Consider the following circuit as an example:

from qiskit import QuantumCircuit

qc = QuantumCircuit(6)
qc.rx(0.4, 0)
qc.ryy(0.2, 2, 3)
qc.h(2)
qc.rz(0.1, 2)
qc.rxx(0.3, 0, 1)
qc.rzz(0.3, 0, 1)
qc.cx(2, 1)
qc.s(1)
qc.h(4)
qc.draw("mpl")

(Source code)

Circuit diagram output by the previous code.

If the above circuit is passed to parametrize_circuit(), it will return an ansatz obtained from this circuit by replacing numerical parameters with free parameters:

from qiskit_addon_aqc_tensor import parametrize_circuit

ansatz, initial_params = parametrize_circuit(qc)
ansatz.draw("mpl")

(Source code)

Circuit diagram output by the previous code.

Further, the parametrize_circuit() function provides parameters which, when bound to the ansatz, will result in a circuit identical to the original one:

ansatz.assign_parameters(initial_params).draw("mpl")

(Source code)

Circuit diagram output by the previous code.

If the original circuit already contained parameters, then the returned parameter values will contain None at the entries corresponding to those parameters, and the preceding code will not work. The following example shows how to recover the original circuit in this case.

from qiskit.circuit import Parameter

qc = QuantumCircuit(3)
alpha1 = Parameter("alpha1")
alpha2 = Parameter("alpha2")
qc.ry(alpha1, [0])
qc.rz(0.1, [0])
qc.ry(alpha2, [1])
qc.rz(alpha1, [1])
qc.ry(0.2, [2])
qc.rz(0.3, [2])
ansatz, initial_params = parametrize_circuit(qc)
ansatz.assign_parameters(
    {
        param: val
        for param, val in zip(ansatz.parameters, initial_params)
        if val is not None
    },
    inplace=True,
)
ansatz.draw("mpl")

(Source code)

Circuit diagram output by the previous code.