qsim icon indicating copy to clipboard operation
qsim copied to clipboard

Update docs and error messages for qsim flags

Open kevinsung opened this issue 4 years ago • 5 comments

Qsimh sometimes gives empty output, seemingly on small circuits:

import numpy as np
import cirq
from cirq.experiments import (
    random_rotations_between_grid_interaction_layers_circuit)
import qsimcirq

qsim_simulator = qsimcirq.QSimSimulator()
qsimh_options = {'k': [0], 'w': 0, 'p': 1, 'r': 1}
qsimh_simulator = qsimcirq.QSimhSimulator(qsimh_options)

a = cirq.LineQubit(0)
circuit = cirq.Circuit(
    cirq.X(a)**0.5
)
qsim_circuit = qsimcirq.QSimCircuit(circuit)

bitstrings = ['0', '1']
qsim_amplitudes = qsim_simulator.compute_amplitudes(qsim_circuit, bitstrings)
qsimh_amplitudes = qsimh_simulator.compute_amplitudes(qsim_circuit, bitstrings)

print('Circuit:')
print(circuit)
print()
print(f'Qsim amplitudes: {qsim_amplitudes}')
print(f'Qsimh amplitudes: {qsimh_amplitudes}')

Output:

Circuit:
0: ───X^0.5───

Qsim amplitudes: [(0.5+0.5j), (0.5-0.5j)]
Qsimh amplitudes: []

When Qsimh does produce output (I seem to have to make the circuit big enough for this to happen), it is incorrect:

def optimized_for_qsim(circuit: cirq.Circuit):
    optimized_circuit = cirq.Circuit()
    for op in circuit.all_operations():
        if isinstance(op.gate, cirq.PhasedXPowGate):
            optimized_circuit.append(cirq.decompose(op))
        else:
            optimized_circuit.append(op)
    return optimized_circuit

qubits = cirq.GridQubit.square(2)
circuit = random_rotations_between_grid_interaction_layers_circuit(
    qubits=qubits, depth=3, seed=1234)
circuit = optimized_for_qsim(circuit)
qsim_circuit = qsimcirq.QSimCircuit(circuit)

bitstrings = ['0000', '0001']
qsim_amplitudes = qsim_simulator.compute_amplitudes(qsim_circuit, bitstrings)
qsimh_amplitudes = qsimh_simulator.compute_amplitudes(qsim_circuit, bitstrings)

print('Circuit:')
print(circuit)
print()
print(f'Qsim amplitudes: {qsim_amplitudes}')
print(f'Qsimh amplitudes: {qsimh_amplitudes}')
cirq.testing.assert_allclose_up_to_global_phase(np.array(qsim_amplitudes),
                                                np.array(qsimh_amplitudes),
                                                atol=1e-5)

Output:

Circuit:
                                   ┌────┐   ┌────────┐
(0, 0): ───T^-1────X^0.5───T────────SYC──────X^0.5───────T^-1────X^0.5───T───────Y^0.5──────────────────────
                                    │
(0, 1): ───Y^0.5───T^-1────X^0.5────┼──T──────────SYC────X^0.5───Y^0.5──────────────────────────────────────
                                    │             │
(1, 0): ───X^0.5────────────────────SYC──────T^-1─┼──────X^0.5───T───────X^0.5───SYC─────T^-1───X^0.5───T───
                                                  │                              │
(1, 1): ───X^0.5───T^-1────X^0.5────T─────────────SYC────X^0.5───────────────────SYC─────T^-1───X^0.5───T───
                                   └────┘   └────────┘

Qsim amplitudes: [(0.22928695380687714-0.1546979546546936j), (0.0881662517786026+0.3213808536529541j)]
Qsimh amplitudes: [(0.12853418290615082+0.01285102590918541j), (-0.031721945852041245+0.05422064661979675j)]
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-2-fc37687fd8cd> in <module>
     25 cirq.testing.assert_allclose_up_to_global_phase(np.array(qsim_amplitudes),
     26                                                 np.array(qsimh_amplitudes),
---> 27                                                 atol=1e-5)

~/Projects/Cirq/cirq/testing/lin_alg_utils.py in assert_allclose_up_to_global_phase(actual, desired, rtol, atol, equal_nan, err_msg, verbose)
    193                                equal_nan=equal_nan,
    194                                err_msg=err_msg,
--> 195                                verbose=verbose)

/usr/lib/python3/dist-packages/numpy/testing/_private/utils.py in assert_allclose(actual, desired, rtol, atol, equal_nan, err_msg, verbose)
   1501     header = 'Not equal to tolerance rtol=%g, atol=%g' % (rtol, atol)
   1502     assert_array_compare(compare, actual, desired, err_msg=str(err_msg),
-> 1503                          verbose=verbose, header=header, equal_nan=equal_nan)
   1504 
   1505 

/usr/lib/python3/dist-packages/numpy/testing/_private/utils.py in assert_array_compare(comparison, x, y, err_msg, verbose, header, precision, equal_nan, equal_inf)
    827                                 verbose=verbose, header=header,
    828                                 names=('x', 'y'), precision=precision)
--> 829             raise AssertionError(msg)
    830     except ValueError:
    831         import traceback

AssertionError: 
Not equal to tolerance rtol=1e-07, atol=1e-05

Mismatch: 100%
Max absolute difference: 0.27085387
Max relative difference: 4.31169146
 x: array([ 0.276594-2.775558e-17j, -0.10666 +3.157253e-01j])
 y: array([ 0.129175+1.734723e-18j, -0.02617 +5.710753e-02j])

Could this be due to the options passed to the initialization of the Qsimh simulator? I just copied the options from https://github.com/quantumlib/qsim/blob/master/docs/cirq_interface.md; I don't know what they mean and I can't find their documentation.

kevinsung avatar Apr 28 '20 00:04 kevinsung

Could this be due to the options passed to the initialization of the Qsimh simulator? I just copied the options from https://github.com/quantumlib/qsim/blob/master/docs/cirq_interface.md; I don't know what they mean and I can't find their documentation.

These flags are shared with the C++ apps; definitions can be found in the "qsimh_base" docs. I suspect that the empty output is a result of this; if the prefix/root/suffix gate count doesn't make sense with the provided circuit, qsimh will likely misbehave. We should have an error for this, though...

It's possible that the flag issue is also causing the non-empty output to be incorrect, although I'm less certain about this. @kevinsung, could you retry with new flag values based on the docs and update this issue with the results?

=====

TODO items identified from this:

  • Copy docs from apps to qsimcirq to clarify that flag usage is the same
  • Return a useful error if qsimh is configured with too few/many prefix/root/suffix gates

95-martin-orion avatar Apr 28 '20 15:04 95-martin-orion

qsimh is a hybrid Schrödinger-Feynman simulator. The circuit is split into two parts. So there should be at least two qubits for non-empty output. qsimh option 'p' specifies the number of "prefix" gates. To sum over all the paths, p should be equal to 0. If p > 0 then only partial summation is performed. The results still should be correct but differ from qsim results in general.

sergeisakov avatar Apr 28 '20 16:04 sergeisakov

The results still should be correct but differ from qsim results in general.

@sergeisakov In the second example that I gave above, the output is actually incorrect.

Changing p to 0 does seem to give correct results, but still returns empty output on some circuits with at least 2 qubits. For example:

import numpy as np
import cirq
from cirq.experiments import (
    random_rotations_between_grid_interaction_layers_circuit)
import qsimcirq

qsimh_options = {'k': [0], 'w': 0, 'p': 0, 'r': 1}
qsimh_simulator = qsimcirq.QSimhSimulator(qsimh_options)

def optimized_for_qsim(circuit: cirq.Circuit):
    optimized_circuit = cirq.Circuit()
    for op in circuit.all_operations():
        if isinstance(op.gate, cirq.PhasedXPowGate):
            optimized_circuit.append(cirq.decompose(op))
        else:
            optimized_circuit.append(op)
    return optimized_circuit

qubits = cirq.GridQubit.square(2)
circuit = random_rotations_between_grid_interaction_layers_circuit(
    qubits=qubits, depth=1, seed=1234)
circuit = optimized_for_qsim(circuit)
qsim_circuit = qsimcirq.QSimCircuit(circuit)

bitstrings = [0, 1]
qsimh_amplitudes = qsimh_simulator.compute_amplitudes(qsim_circuit, bitstrings)

print('Circuit:')
print(circuit)
print()
print(f'Qsimh amplitudes: {qsimh_amplitudes}')

Output:

Circuit:
                                   ┌────┐
(0, 0): ───T^-1────X^0.5───T────────SYC─────X^0.5───────────────
                                    │
(0, 1): ───Y^0.5───T^-1────X^0.5────┼──T────────────────────────
                                    │
(1, 0): ───X^0.5────────────────────SYC─────T^-1────X^0.5───T───

(1, 1): ───X^0.5───T^-1────X^0.5────T───────────────────────────
                                   └────┘

Qsimh amplitudes: []

kevinsung avatar Apr 28 '20 22:04 kevinsung

The results still should be correct but differ from qsim results in general.

@sergeisakov In the second example that I gave above, the output is actually incorrect.

To clarify @sergeisakov's comment, the output from a p > 0 run is effectively a reduced-fidelity simulation. It is "correct" in the sense that the sum-over-subset-of-paths it performs is accurate, but its results will necessarily differ from a p = 0 "sum-over-all-paths" simulation.

95-martin-orion avatar Apr 28 '20 22:04 95-martin-orion

The circuit is split into two parts. The number of prefix gates p plus the number of root gates r should not exceed the number of gates on the cut between the two parts. It seems that there are no gates on the cut in the last example. This leads to empty output because r = 1. You can set r to zero or you can specify a different cut, say, 'k': [0, 1].

As @95-martin-orion pointed out, we need to update qsimcirq docs and to show a useful error message.

sergeisakov avatar Apr 29 '20 11:04 sergeisakov