Qiskit-Extensions / circuit-knitting-toolbox

Tools for knitting quantum circuits with Qiskit
https://qiskit-extensions.github.io/circuit-knitting-toolbox/
Apache License 2.0
74 stars 25 forks source link

IndexError: String index out of range #487

Closed sirgeorgesawcon closed 5 months ago

sirgeorgesawcon commented 6 months ago

I am following the legacy CutQC first tutorial , and I am encountering this issue if I use a different circuit from the one mentioned in the tutorial.

How to recreate the error?

I'm using the following circuit

from qiskit.circuit.library import EfficientSU2

qc = EfficientSU2(4, entanglement="linear", reps=2).decompose()
qc.assign_parameters([0.4] * len(qc.parameters), inplace=True)

qc.draw("mpl",style='iqx', scale=0.8)

output

Then I find the cut automatically, using these set of parameters:

%%capture

from circuit_knitting.cutting.cutqc import cut_circuit_wires

cuts = cut_circuit_wires(
    circuit=qc,
    method="automatic",
    max_subcircuit_width=3,
    max_cuts=5,
    num_subcircuits=[2],
)

It gives me two subcircuits:

2 1

Then When I try to run it using:

from qiskit_ibm_runtime import QiskitRuntimeService  # noqa: F401

# Use local versions of the primitives by default.
service = None

# Uncomment the following line to instead use Qiskit Runtime Service.
# service = QiskitRuntimeService()

from qiskit_ibm_runtime import Options

# Set the Sampler and runtime options
options = Options(execution={"shots": 4000})

# Run 2 parallel qasm simulator threads
backend_names = ["ibmq_qasm_simulator"] * 2

and use the evaluate_subcircuits function

from circuit_knitting.cutting.cutqc import evaluate_subcircuits

subcircuit_instance_probabilities = evaluate_subcircuits(cuts)

# Uncomment the following lines to instead use Qiskit Runtime Service as configured above.
# subcircuit_instance_probabilities = evaluate_subcircuits(cuts,
#                                                          service_args=service.active_account(),
#                                                          backend_names=backend_names,
#                                                          options=options,
#                                                         )

It gives me the error:

IndexError: string index out of range

If I use the circuit in tutorial, it works out smoothly.

System Info

Python : 3.10.11 Qiskit : 0.46.0 qiskit_ibm_runtime : 0.19.1 qiskit_aer : 0.13.3

caleb-johnson commented 6 months ago

I am following the legacy CutQC first tutorial , and I am encountering this issue if I use a different circuit from the one mentioned in the tutorial.

Thanks @sirgeorgesawcon , will try to reproduce

caleb-johnson commented 6 months ago

@sirgeorgesawcon , we have been seeing several different problems with cutqc. It is legacy software, and we are trying to migrate users to the newer wire cutting functionality.

We also have an implementation of an automat cut-finder which supports both gates and wire cuts in a branch here, which should be available in the upcoming release.

I wonder if this new package suits your needs.

sirgeorgesawcon commented 6 months ago

Hi @caleb-johnson

We also have an implementation of an automat cut-finder which supports both gates and wire cuts in a branch here, which should be available in the upcoming release.

Is this version the one that does the gate and wire cut simultaneously in a given quantum circuit? Based on this work.

caleb-johnson commented 6 months ago

Is this version the one that does the gate and wire cut simultaneously in a given quantum circuit? Based on this work.

Yes, our newer cutting package can do both gate and wire cutting. They both use quasi-probability decompositions to decompose 2-qubit gates and wires into local operations. We drew from the references at the bottom of this page during implementation.

In addition to the tutorial(s) I linked above, we have an automatic cut-finder coming soon, which will help users find cut locations to most efficiently partition their circuit. An example of how it will work can be found in a branch here.

Please let us know if you try out the new cutting package! :)

sirgeorgesawcon commented 6 months ago

I tried the cutting package, and from what I see it works with Qiskit 0.45 and less, and doesn't with Qiskit 1.0, but I saw other PR and saw that people are working on it too. Sometimes I get the error of divide by 0 using some random circuit and some random choice of observables, but it still cuts the circuit nevertheless.

But the result from uncut circuit and the one we cut if much different,

For instance

import numpy as np
from qiskit.circuit.random import random_circuit
'''
This function generates a random quantum circuit, of given size and width, and deopth,
and also the max entangled operation
'''

from qiskit.quantum_info import PauliList

circuit = random_circuit(8, 5, max_operands=2, seed=1242)
observables = PauliList(["IZIIIIII", "IIIIZIII", "IIIIIIIZ"])
circuit.draw("mpl",style='iqx', scale=0.8)

makes this circuit output

and giving it these parameters to look for a cut

from circuit_knitting.cutting import find_cuts

# specify the settings
optimization_settings = {"rand_seed": 42}
# specify the constraints
device_constraints = {"num_QPUs": 2, "qubits_per_QPU": 5}
# do the cut
cut_circuit = find_cuts(circuit, optimization_settings, device_constraints)
# draw the cut circuit
cut_circuit.draw("mpl",style='iqx', scale=0.8)

It draws the following

output

and after adding ancilla

output1

It is a nice cut, since the sampling overhead is around 58.

But, the moment I reconstruct the circuit

from qiskit_aer.primitives import Sampler

# Set up a Qiskit Aer Sampler primitive for each circuit partition
samplers = {
    label: Sampler(run_options={"shots": 2**12}) for label in subexperiments.keys()
}

# Retrieve results from each partition's subexperiments
results = {
    label: sampler.run(subexperiments[label]).result()
    for label, sampler in samplers.items()
}

and compare it:

from circuit_knitting.cutting import reconstruct_expectation_values

reconstructed_expvals = reconstruct_expectation_values(
    results,
    coefficients,
    subobservables,
)
from qiskit_aer.primitives import Estimator

estimator = Estimator(run_options={"shots": None}, approximation=True)
exact_expvals = (
    estimator.run([qc] * len(observables), list(observables)).result().values
)
print(
    f"Reconstructed expectation values: {[np.round(reconstructed_expvals[i], 8) for i in range(len(exact_expvals))]}"
)
print(
    f"Exact expectation values: {[np.round(exact_expvals[i], 8) for i in range(len(exact_expvals))]}"
)
print(
    f"Errors in estimation: {[np.round(reconstructed_expvals[i]-exact_expvals[i], 8) for i in range(len(exact_expvals))]}"
)
print(
    f"Relative errors in estimation: {[np.round((reconstructed_expvals[i]-exact_expvals[i]) / exact_expvals[i], 8) for i in range(len(exact_expvals))]}"
)

We get

Reconstructed expectation values: [-0.56650392, 0.6264455, -0.86957383]
Exact expectation values: [0.1948505, 0.47016598, 0.66808262]
Errors in estimation: [-0.76135442, 0.15627952, -1.53765645]
Relative errors in estimation: [-3.90737728, 0.33239223, -2.30159624]

There's a big gap between the uncut and cut values. It might be because I'm doing something wrong during the reconstruction part of the circuit, but I can't figure it out.

Basically I want to contribute a tutorial on solving a big MaxCut problem with QAOA, with using both gate and wire cut, and that's what I'm currently working on. But the initial results don't match, for a random circuit.