Home Reference Source

src/cengines/swapandcnotflipper.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, ForwarderEngine} from './basics'
import {NOT, H, Swap} from '../ops/gates'
import {All} from '../ops/metagates'
import {instanceOf, tuple} from '../libs/util';
import {CNOT} from '../ops/shortcuts'
import CommandModifier from './cmdmodifier'

/**
 * @class SwapAndCNOTFlipper
 * @desc
Flips CNOTs and translates Swaps to CNOTs where necessary.

    Warning:
This engine assumes that CNOT and Hadamard gates are supported by
the following engines.

    Warning:
This engine cannot be used as a backend.
 */
export default class SwapAndCNOTFlipper extends BasicEngine {
  /**
   * @constructor
   * @param {Set<string> | Set<Array.<number>>} connectivity Set of tuples (c, t) where if (c, t) is an
   *   element of the set means that a CNOT can be performed between the physical ids (c, t)
   *   with c being the control and t being the target qubit.
   */
  constructor(connectivity) {
    super()
    if (connectivity instanceof Set) {
      const newMap = {}
      connectivity.forEach(v => newMap[v] = 1)
      connectivity = newMap
    }
    this.connectivity = connectivity
  }

  /**
   * Check if the IBM backend can perform the Command cmd and return true if so.
   * @param {Command} cmd The command to check
   */
  isAvailable(cmd) {
    return this.isSwap(cmd) || this.next.isAvailable(cmd)
  }

  isCNOT(cmd) {
    return instanceOf(cmd.gate, NOT.constructor) && cmd.controlCount === 1
  }

  isSwap(cmd) {
    const n = cmd.controlCount
    const f = cmd.gate.equal(Swap)
    return n === 0 && f
  }

  needsFlipping(cmd) {
    if (!this.isCNOT(cmd)) {
      return false
    }

    const target = cmd.qubits[0][0].id
    const control = cmd.controlQubits[0].id
    const key = [control, target]
    const rkey = [target, control]
    const v = this.connectivity[key]
    const rv = this.connectivity[rkey]
    const is_possible = typeof v !== 'undefined'
    if (!is_possible && typeof rv === 'undefined') {
      throw new Error(`The provided connectivity does not allow to execute the CNOT gate ${cmd.toString()}.`)
    }
    return !is_possible
  }

  sendCNOT(cmd, control, target, flip = false) {
    const cmd_mod = (command) => {
      command.tags = cmd.tags.slice(0).concat(command.tags)
      command.engine = this.main
      return command
    }

    // We'll have to add all meta tags before sending on
    const cmd_mod_eng = new CommandModifier(cmd_mod)
    cmd_mod_eng.next = this.next
    cmd_mod_eng.main = this.main
    // forward everything to the command modifier
    const forwarder_eng = new ForwarderEngine(cmd_mod_eng)
    target[0].engine = forwarder_eng
    control[0].engine = forwarder_eng
    if (flip) {
      // flip the CNOT using Hadamard gates:
      new All(H).or(control.concat(target))
      CNOT.or(tuple(target, control))
      new All(H).or(control.concat(target))
    } else {
      CNOT.or(tuple(control, target))
    }
  }

  /**
     Receives a command list and if the command is a CNOT gate, it flips
    it using Hadamard gates if necessary; if it is a Swap gate, it
    decomposes it using 3 CNOTs. All other gates are simply sent to the next engine.
    @param {Command[]} commandList list of commands to receive.
   */
  receive(commandList) {
    commandList.forEach((cmd) => {
      if (this.needsFlipping(cmd)) {
        this.sendCNOT(cmd, cmd.controlQubits, cmd.qubits[0], true)
      } else if (this.isSwap(cmd)) {
        const qubits = []
        cmd.qubits.forEach(qr => qr.forEach(qb => qubits.push(qb)))
        const ids = qubits.map(qb => qb.id)
        assert(ids.length === 2)
        let key = ids
        let v = this.connectivity[key]
        let control
        let target
        if (typeof v !== 'undefined') {
          control = [qubits[0]]
          target = [qubits[1]]
        } else {
          key = key.slice(0).reverse()
          v = this.connectivity[key]
          if (typeof v !== 'undefined') {
            control = [qubits[1]]
            target = [qubits[0]]
          } else {
            throw new Error(`The provided connectivity does not allow to execute the Swap gate ${cmd.toString()}.`)
          }
        }

        this.sendCNOT(cmd, control, target)
        this.sendCNOT(cmd, target, control, true)
        this.sendCNOT(cmd, control, target)
      } else {
        this.next.receive([cmd])
      }
    })
  }
}