Home Reference Source

src/backends/ibm/ibmhttpclient.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 axios from 'axios'
const _api_url = 'https://quantumexperience.ng.bluemix.net/api/'
/**
 * @class IBMHTTPClient
 */
export default class IBMHTTPClient {
  static async isOnline(device) {
    const url = `Backends/${device}/queue/status`
    const result = await axios.get(`${_api_url}${url}`)
    return result.state
  }

  /**
  Retrieves a previously run job by its ID.

    @param {string} device Device on which the code was run / is running.
    @param {string} user IBM quantum experience user (e-mail)
    @param {string} password IBM quantum experience password
    @param {string} jobid Id of the job to retrieve
  */
  static async retrieve(device, user, password, jobid) {
    const [user_id, access_token] = await IBMHTTPClient.authenticate(user, password)
    return IBMHTTPClient.getResult(device, jobid, access_token)
  }

  static async sleep(interval) {
    return new Promise((resolve) => {
      setTimeout(resolve, interval)
    })
  }

  /**
  Sends QASM through the IBM API and runs the quantum circuit.

   @param {string} info Contains QASM representation of the circuit to run.
   @param {string} device Either 'simulator', 'ibmqx4', or 'ibmqx5'.
   @param {string} user IBM quantum experience user.
   @param {string} password IBM quantum experience user password.
   @param {number} shots Number of runs of the same circuit to collect statistics.
   @param {boolean} verbose If true, additional information is printed, such as
measurement statistics. Otherwise, the backend simply registers
one measurement result (same behavior as the projectq Simulator).
   */
  static async send(info, device = 'sim_trivial_2', user = '', password = '', shots = 1, verbose = false) {
    try {
      // check if the device is online
      if (['ibmqx4', 'ibmqx5'].includes(device)) {
        const online = await IBMHTTPClient.isOnline(device)
        if (!online) {
          console.log('The device is offline (for maintenance?). Use the simulator instead or try again later.')
          throw new Error('Device is offline')
        }
      }
      if (verbose) {
        console.log('- Authenticating...')
      }
      const [user_id, access_token] = await IBMHTTPClient.authenticate(user, password)
      if (verbose) {
        const obj = JSON.parse(info)
        console.log(`- Running code: ${obj.qasms[0].qasm}`)
      }
      const execution_id = await IBMHTTPClient.run(info, device, user_id, access_token, shots)
      if (verbose) {
        console.log('- Waiting for results...')
      }
      const res = await IBMHTTPClient.getResult(device, execution_id, access_token)
      if (verbose) {
        console.log('- Done.')
      }
      return res
    } catch (e) {
      console.log(e)
    }
  }

  static async authenticate(email = '', password = '') {
    const result = await axios.post(`${_api_url}users/login`, {email, password})
    const {userId, id} = result.data
    return [userId, id]
  }

  static async run(qasm, device, user_id, access_token, shots) {
    const suffix = 'Jobs'
    const params = {
      'access_token': access_token,
      'deviceRunType': device,
      'fromCache': 'false',
      'shots': shots
    }
    const resp = await axios({
      method: 'post',
      url: `${_api_url}${suffix}`,
      headers: {'Content-Type': 'application/json'},
      data: qasm,
      params
    })

    return resp.data.id
  }

  static async getResult(device, execution_id, access_token, num_retries = 3000,
    interval = 1) {
    const suffix = `Jobs/${execution_id}`
    const status_url = `${_api_url}Backends/${device}/queue/status`

    console.log(`Waiting for results. [Job ID: ${execution_id}]`)

    for (let retries = 0; retries < num_retries; ++retries) {
      const resp = await axios.get(`${_api_url}${suffix}`, {params: {access_token}})
      const {data} = resp
      const {qasms} = data
      if (qasms) {
        const {result} = qasms[0]
        if (result) {
          return result
        }
      }
      await IBMHTTPClient.sleep(interval)
      if (['ibmqx4', 'ibmqx5'].includes(device) && retries % 60 === 0) {
        const stateResp = await axios.get(status_url)
        const {state, lengthQueue} = stateResp.data
        if (typeof state !== 'undefined' && !state) {
          throw new Error(`Device went offline. The ID of your submitted job is ${execution_id}`)
        }

        if (lengthQueue) {
          console.log(`Currently there are ${lengthQueue} jobs queued for execution on ${device}.`)
        }
      }
    }
    throw new Error(`Timeout. The ID of your submitted job is ${execution_id}.`)
  }
}