Home Reference Source

src/backends/ibm/ibm.js

/*
 * Copyright (c) 2018 Isaac Phoenix ([email protected]).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import assert from 'assert'
import {BasicEngine} from '../../cengines/basics'
import Gates, {
  Allocate, Barrier, Deallocate, FlushGate, H, Measure, NOT, Rx, Ry, Rz, S, T, Y, Z
} from '../../ops/gates'
import {LogicalQubitIDTag} from '../../meta/tag';
import IBMHTTPClient from './ibmhttpclient'
import {instanceOf} from '../../libs/util'
import '../../ops/metagates'

const {Tdag, Sdag} = Gates
/**
 * @class IBMBackend
 * @desc
The IBM Backend class, which stores the circuit, transforms it to JSON
QASM, and sends the circuit through the IBM API.
 */
export default class IBMBackend extends BasicEngine {
  /**
   * @constructor
  @param {{use_hardware: boolean, num_runs: number, verbose: boolean, user: string, password: string, device: string, retrieve_execution: ?string}} args
    use_hardware: If true, the code is run on the IBM quantum chip (instead of using the IBM simulator)
    num_runs: Number of runs to collect statistics. (default is 1024)
    verbose: If true, statistics are printed, in addition to the measurement result being registered (at the end of the circuit).
    user: IBM Quantum Experience user name
    password: IBM Quantum Experience password
    device: Device to use ('ibmqx4', or 'ibmqx5') if use_hardware is set to true. Default is ibmqx4.
    retrieve_execution: Job ID to retrieve instead of re-running the circuit (e.g., if previous run timed out).
  */
  constructor(...args) {
    super()
    this._reset()
    this._errors = []
    let use_hardware = false
    let num_runs = 1024
    let verbose = false
    let user = null
    let password = null
    let device = 'ibmqx4'
    let retrieve_execution = null

    if (typeof args[0] === 'object') {
      const obj = args[0]
      use_hardware = use_hardware || obj.use_hardware
      num_runs = num_runs || obj.num_runs
      verbose = verbose || obj.verbose
      user = user || obj.user
      password = password || obj.password
      device = device || obj.device
      retrieve_execution = retrieve_execution || obj.retrieve_execution
    } else {
      [use_hardware, num_runs, verbose, user, password, device, retrieve_execution] = args
    }

    num_runs = num_runs || 1024
    device = device || 'ibmqx4'
    if (use_hardware) {
      this.device = device
    } else {
      this.device = 'simulator'
    }
    this._num_runs = num_runs
    this._verbose = verbose
    this._user = user
    this._password = password
    this._probabilities = {}
    this.qasm = ''
    this._measured_ids = []
    this._allocated_qubits = new Set()
    this._retrieve_execution = retrieve_execution
  }

  /**
  Return true if the command can be executed.

    The IBM quantum chip can do X, Y, Z, T, Tdag, S, Sdag,
    rotation gates, barriers, and CX / CNOT.

    @param {Command} cmd Command for which to check availability
    @return {boolean}
   */
  isAvailable(cmd) {
    const g = cmd.gate
    const controlCount = cmd.controlCount
    if (g.equal(NOT) && controlCount <= 1) {
      return true
    }
    if (controlCount === 0) {
      const set = [T, Tdag, S, Sdag, H, Y, Z]
      if (set.includes(g)) {
        return true
      }
      if (g instanceof Rx || g instanceof Ry || g instanceof Rz) {
        return true
      }
    }
    const set = [Measure, Allocate, Deallocate, Barrier]
    if (set.includes(g)) {
      return true
    }
    return false
  }

  // Reset all temporary variables (after flush gate).
  _reset() {
    this._clear = true
    this._measured_ids = []
  }

  /**
Temporarily store the command cmd.

  Translates the command and stores it in a local variable (this._cmds).

  @param {Command} cmd Command to store
  */
  _store(cmd) {
    if (this._clear) {
      this._probabilities = {}
      this._clear = false
      this.qasm = ''
      this._allocated_qubits = new Set()
    }

    const {gate} = cmd

    if (gate.equal(Allocate)) {
      this._allocated_qubits.add(cmd.qubits[0][0].id)
      return
    }

    if (gate.equal(Deallocate)) {
      return
    }

    if (gate.equal(Measure)) {
      assert(cmd.qubits.length === 1 && cmd.qubits[0].length === 1)
      const qb_id = cmd.qubits[0][0].id
      let logical_id
      for (let i = 0; i < cmd.tags.length; ++i) {
        const t = cmd.tags[i]
        if (t instanceof LogicalQubitIDTag) {
          logical_id = t.logical_qubit_id
          break
        }
      }
      assert(typeof logical_id !== 'undefined')
      this._measured_ids.push(logical_id)
    } else if (gate === NOT && cmd.controlCount === 1) {
      const ctrl_pos = cmd.controlQubits[0].id
      const qb_pos = cmd.qubits[0][0].id
      this.qasm += `\ncx q[${ctrl_pos}], q[${qb_pos}];`
    } else if (gate === Barrier) {
      const qb_pos = []
      cmd.qubits.forEach(qr => qr.forEach(qb => qb_pos.push(qb.id)))
      this.qasm += '\nbarrier '
      let qb_str = ''
      qb_pos.forEach((pos) => {
        qb_str += `q[${pos}]`
      })

      this.qasm += `${qb_str.substring(0, qb_str.length - 2)};`
    } else if (instanceOf(gate, [Rx, Ry, Rz])) {
      assert(cmd.controlCount === 0)
      const qb_pos = cmd.qubits[0][0].id
      const u_strs = {
        Rx: a => `u3(${a}, -pi/2, pi/2)`,
        Ry: a => `u3(${a}, 0, 0)`,
        Rz: a => `u1(${a})`
      }
      const gateASM = u_strs[gate.toString().substring(0, 2)](gate.angle)
      this.qasm += `\n${gateASM} q[${qb_pos}];`
    } else {
      if (cmd.controlCount !== 0) {
        console.log(187, cmd.toString())
      }
      assert(cmd.controlCount === 0)
      const key = gate.toString()
      const v = IBMBackend.gateNames[key]
      let gate_str
      if (typeof v !== 'undefined') {
        gate_str = v
      } else {
        gate_str = key.toLowerCase()
      }

      const qb_pos = cmd.qubits[0][0].id
      this.qasm += `\n${gate_str} q[${qb_pos}];`
    }
  }


  /**
  Return the physical location of the qubit with the given logical id.

    @param {number} qbID ID of the logical qubit whose position should be returned.
   */
  _logicalToPhysical(qbID) {
    assert(!!this.main.mapper)
    const mapping = this.main.mapper.currentMapping
    const v = mapping[qbID]
    if (typeof v === 'undefined') {
      throw new Error(`Unknown qubit id ${qbID}. Please make sure 
      eng.flush() was called and that the qubit 
      was eliminated during optimization.`)
    }
    return v
  }

  /**
  Return the list of basis states with corresponding probabilities.

    The measured bits are ordered according to the supplied quantum
register, i.e., the left-most bit in the state-string corresponds to
the first qubit in the supplied quantum register.

    Warning:
Only call this function after the circuit has been executed!

    @param {Array.<Qubit>|Qureg} qureg Quantum register determining the order of the qubits.

    @return {Object} Dictionary mapping n-bit strings to probabilities.

    @throws {Error} If no data is available (i.e., if the circuit has
not been executed). Or if a qubit was supplied which was not
present in the circuit (might have gotten optimized away).
   */
  getProbabilities(qureg) {
    if (Object.keys(this._probabilities).length === 0) {
      throw new Error('Please, run the circuit first!')
    }

    const probability_dict = {}

    this._probabilities.forEach((state) => {
      const mapped_state = []
      for (let i = 0; i < qureg.length; ++i) {
        mapped_state.push('0')
      }

      for (let i = 0; i < qureg.length; ++i) {
        mapped_state[i] = state[this._logicalToPhysical(qureg[i].id)]
      }
      const probability = this._probabilities[state]
      probability_dict[mapped_state.join('')] = probability
    })

    return probability_dict
  }

  /**
  Run the circuit.

    Send the circuit via the IBM API (JSON QASM) using the provided user
data / ask for username & password.
   */
  async run() {
    if (this.qasm.length === 0) {
      return
    }
    // finally: add measurements (no intermediate measurements are allowed)
    this._measured_ids.forEach((measured_id) => {
      const qb_loc = this.main.mapper.currentMapping[measured_id]
      this.qasm += `measure q[${qb_loc}] -> c[${qb_loc}];`
    })
    let max_qubit_id = -1
    this._allocated_qubits.forEach((id) => {
      if (id > max_qubit_id) {
        max_qubit_id = id
      }
    })

    const nq = max_qubit_id + 1
    const qasm = `\ninclude \"qelib1.inc\";\nqreg q[${nq}];\ncreg c[${nq}];${this.qasm}`
    const info = {}
    info.qasms = [{qasm}]
    info.shots = this._num_runs
    info.maxCredits = 5
    info.backend = {'name': this.device}
    const infoJSON = JSON.stringify(info)

    try {
      let res
      if (!this._retrieve_execution) {
        res = await IBMHTTPClient.send(infoJSON, this.device, this._user, this._password, this._num_runs, this._verbose)
      } else {
        res = await IBMHTTPClient.retrieve(this.device, this._user, this._password, this._retrieve_execution)
      }
      const {counts} = res.data
      // Determine random outcome
      const P = Math.random()
      let p_sum = 0.0
      let measured = ''
      Object.keys(counts).forEach((state) => {
        const probability = counts[state] * 1.0 / this._num_runs
        if (Array.isArray(state)) {
          state = state.slice(0).reverse()
          state = ''.join(state)
        }
        p_sum += probability
        let star = ''
        if (p_sum >= P && measured === '') {
          measured = state
          star = '*'
        }
        this._probabilities[state] = probability
        if (this._verbose && probability > 0) {
          console.log(`${state.toString()} with p = ${probability.toString()}${star}`)
        }
      })

      class QB {
        constructor(ID) {
          this.id = ID
        }
      }

      // register measurement result
      this._measured_ids.forEach((ID) => {
        const location = this._logicalToPhysical(ID)
        const result = measured[location]
        this.main.setMeasurementResult(new QB(ID), result)
      })
      this._reset()
    } catch (e) {
      throw new Error('Failed to run the circuit. Aborting.')
    }
  }

  /**
  Receives a command list and, for each command, stores it until
completion.

    @param {Command[]} commandList List of commands to execute
   */
  receive(commandList) {
    commandList.forEach((cmd) => {
      if (!(cmd.gate instanceof FlushGate)) {
        this._store(cmd)
      } else {
        this.run()
          .then(() => this._reset())
          .catch((e) => {
            console.log(e)
            this.addError(e)
          }).finally(() => {
            if (this.didRunCallback) {
              this.didRunCallback()
            }
          })
      }
    })
  }

  /**
   * @return {Error[]}
   */
  get errors() {
    return this._errors
  }

  addError(error) {
    this._errors.push(error)
  }

  /**
   * @return {function}
   */
  get didRunCallback() {
    return this._didRunCallback
  }

  /**
   * @param {function} callback
   */
  set didRunCallback(callback) {
    this._didRunCallback = callback
  }
}

IBMBackend.gateNames = {
  [Tdag.toString()]: 'tdg',
  [Sdag.toString()]: 'sdg'
}