Home Reference Source

src/cengines/basics.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 {Qureg, Qubit} from '../types/qubit'
import Command from '../ops/command'
import {Allocate, Deallocate} from '../ops/gates'
import {DirtyQubitTag} from '../meta/tag'
import { LastEngineError } from '../meta/error'

/**
 * @class BasicEngine
 * @abstract
 * @desc
Basic compiler engine: All compiler engines are derived from this class.
It provides basic functionality such as qubit allocation/deallocation and
functions that provide information about the engine's position (e.g., next
engine).

This information is provided by the MainEngine, which initializes all
further engines.

    Attributes:
next_engine (BasicEngine): Next compiler engine (or the back-end).
main_engine (MainEngine): Reference to the main compiler engine.
is_last_engine (bool): true for the last engine, which is the back-end.
 */
export class BasicEngine {
  /**
   * @constructor
   Initialize the basic engine.

    Initializes local variables such as _next_engine, _main_engine, etc. to None.
  */
  constructor() {
    this.isLastEngine = false
  }

  /**
    Default implementation of isAvailable:
    Ask the next engine whether a command is available, i.e.,
    whether it can be executed by the next engine(s).

    @param {Command} cmd Command for which to check availability.
    @return {boolean} true if the command can be executed.

    @throws {LastEngineError} If is_last_engine is true but isAvailable is not implemented.
     */
  isAvailable(cmd) {
    if (!this.isLastEngine) {
      return this.next.isAvailable(cmd)
    }
    throw new LastEngineError('Should not be last!')
  }

  /**
    Return a new qubit as a list containing 1 qubit object (quantum
register of size 1).

Allocates a new qubit by getting a (new) qubit id from the MainEngine,
    creating the qubit object, and then sending an AllocateQubit command
down the pipeline. If dirty=true, the fresh qubit can be replaced by
a pre-allocated one (in an unknown, dirty, initial state). Dirty qubits
must be returned to their initial states before they are deallocated /
freed.

    All allocated qubits are added to the MainEngine's set of active
qubits as weak references. This allows proper clean-up at the end of
the JavaScript program (using atexit), deallocating all qubits which are
still alive. Qubit ids of dirty qubits are registered in MainEngine's
dirty_qubits set.

    @param {boolean} dirty If true, indicates that the allocated qubit may be
    dirty (i.e., in an arbitrary initial state).

    @return {Qureg} Qureg of length 1, where the first entry is the allocated qubit.
  */
  allocateQubit(dirty = false) {
    const new_id = this.main.getNewQubitID()
    const qubit = new Qubit(this, new_id)
    const qb = new Qureg(qubit)
    const cmd = new Command(this, Allocate, [qb])
    if (dirty) {
      if (this.isMetaTagSupported(DirtyQubitTag)) {
        cmd.tags.push(new DirtyQubitTag())
        this.main.dirtyQubits.add(qubit.id)
      }
    }
    this.main.activeQubits.add(qubit)
    this.send([cmd])
    return qb
  }

  /**
    Allocate n qubits and return them as a quantum register, which is a
list of qubit objects.

    @param {number} n Number of qubits to allocate
    @return {Qureg} Qureg of length n, a list of n newly allocated qubits.
  */
  allocateQureg(n) {
    const array = []
    for (let i = 0; i < n; ++i) {
      const q = this.allocateQubit()[0]
      array.push(q)
    }
    return new Qureg(array)
  }

  /**
    Deallocate a qubit (and sends the deallocation command down the
pipeline). If the qubit was allocated as a dirty qubit, add
DirtyQubitTag() to Deallocate command.

    @param {BasicQubit} qubit Qubit to deallocate.
    @throws {Error} Qubit already deallocated. Caller likely has a bug.
  */
  deallocateQubit(qubit) {
    if (qubit.id === -1) {
      throw new Error('Already deallocated.')
    }
    const is_dirty = this.main.dirtyQubits.has(qubit.id)
    const cmds = [new Command(this, Deallocate, [new Qureg([qubit])], [], is_dirty ? [new DirtyQubitTag()] : [])]
    this.send(cmds)
  }

  /**
    Check if there is a compiler engine handling the meta tag

    @param {function} metaTag Meta tag class for which to check support

    @return {boolean} true if one of the further compiler engines is a
meta tag handler, i.e., engine.is_meta_tag_handler(meta_tag)
returns true.
     */
  isMetaTagSupported(metaTag) {
    let engine = this
    try {
      while (true) {
        if (typeof engine.isMetaTagHandler === 'function' && engine.isMetaTagHandler(metaTag)) {
          return true
        }
        engine = engine.next
      }
    } catch (e) {
      return false
    }
  }

  /**
    Forward the list of commands to the next engine in the pipeline.
   @param {Command[]} commandList
  */
  send(commandList) {
    this.next.receive(commandList)
  }

  receive() {
    // do nothing
  }
}

/**
 * @class ForwarderEngine
 * @desc
    A ForwarderEngine is a trivial engine which forwards all commands to the next engine.

    It is mainly used as a substitute for the MainEngine at lower levels such
that meta operations still work (e.g., with Compute).
 */
export class ForwarderEngine extends BasicEngine {
  /**
   * @constructor

    @param {BasicEngine} engine Engine to forward all commands to.
    @param {function} cmdModFunc Function which is called before sending a
command. Each command cmd is replaced by the command it
returns when getting called with cmd.
     */
  constructor(engine, cmdModFunc) {
    super()
    this.main = engine.main
    this.next = engine
    if (!cmdModFunc) {
      cmdModFunc = x => x
    }
    this.cmdModFunc = cmdModFunc
  }

  receive(commandList) {
    const newCommandList = commandList.map(cmd => this.cmdModFunc(cmd))
    this.send(newCommandList)
  }

  /**
   * internal usaged for deallocate qubits after `Uncompute`
   */
  autoDeallocateQubits() {
    const copy = new Set(this.main.activeQubits)
    copy.forEach((qb) => {
      if (qb.engine === this) {
        // need to
        qb.deallocate()
      }
    })
  }
}