Qiskit / 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
5.18k stars 2.35k forks source link

Simulator Interfaces #281

Closed ajavadia closed 6 years ago

ajavadia commented 6 years ago

Summary:

This issue is regarding the way we expose simulator backends to the user. From a user's perspective, it would make sense to group these simulators by functionality. This is separate from how they are actually implemented "under the hood". Functionally, we have the following simulators:

  1. qasm_simulator: This is to mimic how things are done in a real experiment.

    • To be meaningful, it should contain measurements (measure qr->cr) as the result will only be a bit-string, similar to experiment results. No measurement means you just read out 0s.
    • It can contain reset and if.
    • Multiple measurements to the same classical register will rewrite the bits.
    • It can include 1 or many shots. At the end, the returned result will be a histogram of the classical register for that many shots.
      result = {
      "data": {
      "counts": {
          "00010": 40,
          "00111": 24
      }
      }
      ...
      }
  2. wavefunction_simulator: This is for obtaining wavefunction from qubit registers.

    • The wavefunction can be requested by a snapshot qr->sr command, where qr is the register to be queried and sr is a (to-be-implemented) snapshot_register (or wavefunction_register).
    • It does not need measurements. Including them will collapse the wavefunction.
    • For the first implementation, we can disable measure, if and shots.
    • Later, we may include them, but then we have to return a list of possible snapshot outcomes for each snapshot_register.
      result = {
      "data": {
      "snapshots": {
          "sr1": [0.5+0.5J, 0, 0, -0.707J],
          "sr2": [0.2, 0.1, 0.25+0.25J, 0.908J]
      }
      }
      ...
      }
  3. unitary_simulator: for getting the 2^n x 2^n unitary matrix equivalent of the circuit.

    • measure, if and shots are disabled.
      result = {
      "data": {
          'unitary': array([
              [-0.5, 0.5, 0.5, 0.5],
              [0.5, 0.5, 0.5, -0.5],
              [0.5, 0.5, -0.5, 0.5],
              [0.5, -0.5, 0.5, 0.5]
          ])
      }
      }
      ...
      }
  4. stabilizer_simulator: for efficient simulation of stabilizer circuits, according to Gottesman's paper.

    • snapshot qr->sr will record the 2n+1 X 2n dimensional matrix of a clifford tableau.
      result = {
      "data": {
          'tableu': array([
              [1, 0, 0, 0, 0],
              [0, 1, 0, 0, 0],
              [0, 0, 1, 0, 0],
              [0, 0, 0, 1, 0]
          ]),
      }
      }
      ...
      }
  5. tgate_simulator: for fast simulation of circuits dominated by Clifford gates with relatively few T gates, according to Bravyi and Gosset's paper

Expected Behavior

Implementation optimizations:

a. There are currently 3 implementations for qasm simulator: python, c++ and an external one by projectq. We should make the c++ one the standard local_qasm_simulator, and call the others local_qasm_simulator_py, local_qasm_simulator_projectq. There isn't really any reason for the latter 2 to be exposed through get_available_backends(), but someone who knows they absolutely want to use one of them can know they exist through documentation.

b. For the local_qasm_simulator, if there are no intermediate measures, then the compiler should be smart and not call local_qasm_simulator many times. Instead, it should call the local_wavefunction_simulator, then sample the output wavefunction to produce the counts histogram. Similarly, if there is an intermediate measure but that qubit is not modified later (it can be a control), then the measurement should be commuted to the end and the whole simulation, again, only done once.

c. For the local_qasm_simulator, if the circuit is entirely made up of CNOT, Hadamard, Phase, the compiler should be smart and call the local_stabalizer_simulator which is much more scalable. Similarly for a Clifford-dominated circuit with some Ts, it should call the local_tgate_simulator.

Current Behavior

Context

This is a summary of some discussions with @jaygambetta, @awcross1, @levbishop. Everyone, please comment if you have any thoughts, and feel free to correct any mistakes by editing my comment.

jaygambetta commented 6 years ago

@ajavadia i think the current Clifford vs stabilizer needs some thinking. I think we want to have two. One does the tableu and one does the vector for the stablizer (current). I am also not sure we need to return counts for this unless it is used by the local_qasm_simulator.

For the snapshot in wavefunction I am a little confused. I am assuming it means that we are saving the wavefunction at the points in time when snapshot qr->sr. Does this map the full quantum state to the sr label or can i select a register. If i select a register is it a reduced state?

For projectq maybe we should make it just local_wavefunction_simulator_projectq as the part that turns it into a qasm simulator is the slow part and im not sure that they support if and reset anyway. (@ewinston is this correct). It is really a wavefunction simulator.

jaygambetta commented 6 years ago

Also i think this makes #244 obsolete so we could close that issue.

ajavadia commented 6 years ago

@jaygambetta ok updated my comment with your suggestions about clifford and stabilizer.

@chriseclectic: does the #save command in you simulator record reduced states, or it has to be for the full qubits? (see jay's comment above) Probably hard to do without separable states, in which case the syntax should become snapshot sr

jaygambetta commented 6 years ago

Also with the snapshot extensions we should also add it to the unitary. Also what happens if i dont put a snapshot in?

chriseclectic commented 6 years ago

If you use the save command and the appropriate config option to return the saves states the output from the C++ simulator will return information about the full state vector (across all registers) regardless of which registers the gate is defined to "act" on. This was done to make it compatible with QASM since you can't have a QASM gate definition that doesn't act on a quantum register.

How the snapshot is returned depends on your config settings. Currently it can be returned as

The clifford simulator currently returns its internal representation of tableaus in the form :

three_qubit_tableau_example=
{
    "destabilizers": [
        {'X': [1], "Z": [0], "phase": 0},
        {'X': [2], "Z": [0], "phase": 0},
        {'X': [4], "Z": [0], "phase": 0}
    ],
   "stabilizers": [
        {"X": [0], "Z": [1], "phase": 1},
        {"X": [0], "Z": [2], "phase": 1},
        {"X": [0], "Z": [4], "phase": 1}
    ]
}

So the stabilizers and destabilizers are each a list of length num_qubits, and in each are binary vector for the X and Z.

Here is an example for the qiskit simulator:

import qiskit
from qiskit import QuantumProgram
import qiskit.extensions.qiskit_simulator

qp = QuantumProgram()
qr = qp.create_quantum_register('qr', 2);
cr = qp.create_classical_register('cr', 2);
circ = qp.create_circuit('bell', [qr], [cr])
circ.h(qr[0])
circ.cx(qr[0], qr[1])
circ.barrier(qr)
circ.save(1, qr)
circ.barrier(qr)
circ.measure(qr, cr)

backend = 'local_qiskit_simulator'
config = {'data': ['saved_quantum_state',
                   'saved_density',
                   'saved_quantum_state_ket',
                   'saved_probabilities']}
result = qp.execute('bell', shots=10, backend=backend, config=config)
result.get_data('bell')

The output is

{'counts': {'00': 5, '11': 5},
 'saved_probabilities': {'1': [0.5, 0.0, 0.0, 0.5]},
 'saved_quantum_states': [{1: array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j])},
  {1: array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j])},
  {1: array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j])},
  {1: array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j])},
  {1: array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j])},
  {1: array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j])},
  {1: array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j])},
  {1: array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j])},
  {1: array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j])},
  {1: array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j])}],
 'saved_quantum_states_ket': [{'1': {'00': [0.707106781186548, 0.0],
    '11': [0.707106781186547, 0.0]}},
  {'1': {'00': [0.707106781186548, 0.0], '11': [0.707106781186547, 0.0]}},
  {'1': {'00': [0.707106781186548, 0.0], '11': [0.707106781186547, 0.0]}},
  {'1': {'00': [0.707106781186548, 0.0], '11': [0.707106781186547, 0.0]}},
  {'1': {'00': [0.707106781186548, 0.0], '11': [0.707106781186547, 0.0]}},
  {'1': {'00': [0.707106781186548, 0.0], '11': [0.707106781186547, 0.0]}},
  {'1': {'00': [0.707106781186548, 0.0], '11': [0.707106781186547, 0.0]}},
  {'1': {'00': [0.707106781186548, 0.0], '11': [0.707106781186547, 0.0]}},
  {'1': {'00': [0.707106781186548, 0.0], '11': [0.707106781186547, 0.0]}},
  {'1': {'00': [0.707106781186548, 0.0], '11': [0.707106781186547, 0.0]}}],
 'time_taken': 0.000511}

For the clifford simulator:

backend = 'local_clifford_simulator'
config = {'data': ['saved_quantum_state']}
result = qp.execute('bell', shots=5, backend=backend, config=config)
result.get_data('bell')

returns

{'counts': {'00': 4, '11': 1},
 'saved_quantum_states': [{'1': {'destabilizers': [{'X': [0],
      'Z': [1],
      'phase': 0},
     {'X': [2], 'Z': [0], 'phase': 0}],
    'stabilizers': [{'X': [3], 'Z': [0], 'phase': 0},
     {'X': [0], 'Z': [3], 'phase': 0}]}},
  {'1': {'destabilizers': [{'X': [0], 'Z': [1], 'phase': 0},
     {'X': [2], 'Z': [0], 'phase': 0}],
    'stabilizers': [{'X': [3], 'Z': [0], 'phase': 0},
     {'X': [0], 'Z': [3], 'phase': 0}]}},
  {'1': {'destabilizers': [{'X': [0], 'Z': [1], 'phase': 0},
     {'X': [2], 'Z': [0], 'phase': 0}],
    'stabilizers': [{'X': [3], 'Z': [0], 'phase': 0},
     {'X': [0], 'Z': [3], 'phase': 0}]}},
  {'1': {'destabilizers': [{'X': [0], 'Z': [1], 'phase': 0},
     {'X': [2], 'Z': [0], 'phase': 0}],
    'stabilizers': [{'X': [3], 'Z': [0], 'phase': 0},
     {'X': [0], 'Z': [3], 'phase': 0}]}},
  {'1': {'destabilizers': [{'X': [0], 'Z': [1], 'phase': 0},
     {'X': [2], 'Z': [0], 'phase': 0}],
    'stabilizers': [{'X': [3], 'Z': [0], 'phase': 0},
     {'X': [0], 'Z': [3], 'phase': 0}]}}],
 'time_taken': 0.000404}
ewinston commented 6 years ago

@jaygambetta I believe that is true about the projectq simulator.

1ucian0 commented 6 years ago

I think values (like in the saved_quantum_states) should be strings. There is no difference and we will be allowed to use symbols, such as pi.

PS: complex number are not supported in JSON. Another reason to make them strings?