Python Examples

cQASM examples

Grover’s Algorithm: implementation in cQASM and performance analysis

This example demonstrates how to use the SDK to create a more complex Grover’s algorithm in cQASM, and simulate the circuit on Quantum Inspire.

import os

from quantuminspire.api import QuantumInspireAPI
from quantuminspire.credentials import get_authentication

from src.run import generate_sat_qasm, execute_sat_qasm

QI_URL = os.getenv('API_URL', 'https://api.quantum-inspire.com/')


# Authenticate, create project and define backend
name = 'SAT_Problem'
authentication = get_authentication()
qi = QuantumInspireAPI(QI_URL, authentication, project_name=name)
backend = qi.get_backend_type_by_name(
    'QX single-node simulator')  # if the project already exists in My QI, the existing backend will be used and this line will not overwrite the backend information.
shot_count = 512

# define SAT problem
boolean_expr = "(a and not(b) and c and d) or (not(a) and b and not(c) and d)"

# choose variables; see src.run.generate_sat_qasm() for details.
# recommended settings for a simulator
cnot_mode = "normal"
sat_mode = "reuse qubits"
apply_optimization_to_qasm = False
connected_qubit = None

# # recommended settings for Starmon-5. As of now, any sat problem involving more than one variable will contain too many gates,
# # and decoherence will occur. If you try this, be sure to use a boolean_expr and sat_mode that initiates a total of 5 qubits.
# cnot_mode = "no toffoli"
# sat_mode = "reuse qubits"
# apply_optimization_to_qasm = False
# connected_qubit = '2'


# generate the qasm code that will solve the SAT problem
qasm, _, qubit_count, data_qubits = generate_sat_qasm(expr_string=boolean_expr, cnot_mode=cnot_mode, sat_mode=sat_mode,
                                                      apply_optimization=apply_optimization_to_qasm,
                                                      connected_qubit=connected_qubit)

print(qasm)

# execute the qasm code and show the results
execute_sat_qasm(qi=qi, qasm=qasm, shot_count=shot_count, backend=backend, qubit_count=qubit_count,
                 data_qubits=data_qubits, plot=True)

Code for generating and executing the cQASM

from src.optimizer import *
from src.sat_utilities import *
import math


def generate_sat_qasm(expr_string, cnot_mode, sat_mode, apply_optimization=True, connected_qubit=None):
    """
    Generate the QASM needed to evaluate the SAT problem for a given boolean expression.

    Args:
        expr_string: A boolean expression as a string
        cnot_mode: The mode for CNOTs. 'normal' and 'no toffoli' are verified to be working. 'crot' and 'fancy cnot' are experimental.
              - normal: use toffoli gates and ancillary qubits for max speed
              - no toffoli: same as normal, but replace toffoli gates for 2-gate equivalent circuits. uses ancillary qubits. This mode must be used if using a backend that doesn't support toffoli gates, like starmon-5.
              - crot: no ancillary qubits or toffoli gates, but scales with 3^n gates for n bits
              - fancy cnot: no ancillary qubits or toffoli gates, scales 2^n
        sat_mode: The mode for the SAT solving circuit:
              - reuse gates: use minimal amount of gates
              - reuse qubits: use minimal amount of ancillary qubits
        apply_optimization: Whether to apply the optimization algorithm to our generated QASM, saving ~20-50% lines of code
        connected_qubit: This functionallity is meant for backends in which not all qubits are connected to each other.
        For example, in Starmon-5, only the third qubit is connected to all other qubits. In order to make the qasm work on
        this backend, connected_qubit='2' should be given as argument. Qubits are then swapped during the algorithm such that every gate is
        between the connected_qubit and another one. This function can only be used in combination with
        cnot_mode='no toffoli', to ensure no three qubit gates are present.
              - None: do not swap any gates
              - '2': swap qubits to ensure all gates involve qubit 2.

    Returns: A tuple of the following values:
        - qasm: The QASM representing the requested Grover search
        - line count: The total number of parallel lines that is executed (including grover loops)
        - qubit count: The total number of qubits required
        - data qubits: The number of data qubits
    """

    algebra = boolean.BooleanAlgebra()
    expr = algebra.parse(expr_string)

    control_names = sorted(list(expr.symbols), reverse=True)

    # note that the number of data qubits also includes an extra bit which must be 1 for the algorithm to succeed
    data_qubits = len(control_names) + 1

    expr = split_expression_evenly(expr.simplify())

    if sat_mode == "reuse gates":
        oracle_qasm, _, last_qubit_index = generate_sat_oracle_reuse_gates(expr, control_names, is_toplevel=True,
                                                                           mode=cnot_mode)
    elif sat_mode == "reuse qubits":
        oracle_qasm, _, last_qubit_index = generate_sat_oracle_reuse_qubits(expr, control_names, [], is_toplevel=True,
                                                                            mode=cnot_mode)
    else:
        raise ValueError("Invalid SAT mode: {} instead of 'reuse gates' or 'reuse qubits'".format(sat_mode))

    qubit_count = last_qubit_index + 1

    # some modes may require many ancillary qubits for the diffusion operator!
    if cnot_mode in ["normal", "no toffoli"]:
        qubit_count = max(qubit_count, data_qubits * 2 - 3)

    qasm = "version 1.0\n" \
           "qubits {}\n".format(qubit_count)

    # initialisation
    qasm += fill("H", data_qubits)

    # looping grover
    iterations = int(math.pi * math.sqrt(2 ** data_qubits - 1) / 4)
    qasm += ".grover_loop({})\n".format(iterations)

    qasm += oracle_qasm + "\n"

    # diffusion
    qasm += fill("H", data_qubits)
    qasm += fill("X", data_qubits)
    qasm += cnot_pillar(cnot_mode, data_qubits)
    qasm += fill("X", data_qubits)
    qasm += fill("H", data_qubits)

    if apply_optimization:
        qasm = apply_optimizations(qasm, qubit_count, data_qubits)

    if connected_qubit is not None:
        qasm = swap_qubits(qasm=qasm, cnot_mode=cnot_mode, apply_optimization=apply_optimization,
                           connected_qubit=connected_qubit)

    # remove blank lines
    qasm_lines = qasm.split("\n")
    qasm_lines = list(filter(lambda x: x not in ["", "{}", " "], qasm_lines))
    qasm = "\n".join(qasm_lines).replace("\n\n", "\n")

    return qasm + "\n.do_measurement\nmeasure_all", iterations * qasm.count("\n"), qubit_count, data_qubits


def execute_sat_qasm(qi, qasm, shot_count, backend, qubit_count, data_qubits, plot):
    """
    Execute the given QASM code and parse the results as though we are evaluating a SAT problem.

    Args:
        qi: An instance of the Quantum Inspire API
        qasm: The qasm program
        shot_count: The number of shots to execute on the circuit
        backend: An instance a QI API backend
        qubit_count: The total number of qubits used in the qasm program
        data_qubits: The number of qubits used by Grover's Algorithm (aka non-ancillary)
        plot: Whether to plot the results of this run

    Returns: A tuple of the following values:
        - histogram_list: a list of pairs, specifying a name and probability, as returned from QI
        - likely_solutions: a list of bit strings, all of which seem to solve the given formula, according to grover
        - runtime: The execution time on the QI backend
    """

    line_count = qasm.count("\n")
    print("Executing QASM code ({} instructions, {} qubits, {} shots)".format(line_count, qubit_count, shot_count))
    result = qi.execute_qasm(qasm, backend_type=backend, number_of_shots=shot_count)
    runtime = result["execution_time_in_seconds"]
    print("Ran on simulator in {} seconds".format(str(runtime)[:5]))

    if qubit_count > 15:
        print("No plot because of large qubit count")
        histogram_list = interpret_results(result, qubit_count, data_qubits, False)
    else:
        histogram_list = interpret_results(result, qubit_count, data_qubits, plot)

    likely_solutions = []
    print("Interpreting SAT results:")
    highest_prob = max(map(lambda _: _[1], histogram_list))
    for h in histogram_list:
        # remove all ancillaries and the first data bit
        name, prob = h[0][-data_qubits + 1:], h[1]
        if prob > highest_prob / 2:
            print("{} satisfies the SAT problem".format(name))
            likely_solutions.append(name)

    return histogram_list, likely_solutions, runtime

Code for the optimizer

from src.sat_utilities import alternative_and

optimizations = {
    "HXH": "Z",
    "HH": "",
    "XX": "",
    "ZX": "Y"
}
largest_opt = 3

forbidden = [
    ".grover_loop",
    "Toffoli",
    "CNOT",
    "CR"
]


def apply_optimizations(qasm, qubit_count, data_qubits):
    """
    Apply 3 types of optimization to the given QASM code:
        Combine groups of gates, such as H-X-H, to faster equivalent gates, Z in this case.
        Shift gates to be executed in parallel.
        Clean QASM code itself (q[1,2,3] becomes q[1:3])

    Args:
        qasm: Valid QASM code to optimize
        qubit_count: The total number of qubits
        data_qubits: The number of qubits that are not ancillaries

    Returns: A equivalent piece of QASM with optimizations applied
    """

    # run "speed" mode until QASM does not change
    prev_qasm = ""
    while prev_qasm != qasm:
        prev_qasm = qasm[:]
        qasm = optimize(qasm, qubit_count, data_qubits, mode="speed")

    # run "style" mode until QASM does not change
    prev_qasm = ""
    while prev_qasm != qasm:
        prev_qasm = qasm[:]
        qasm = optimize(qasm, qubit_count, data_qubits, mode="style")

    prev_qasm = ""
    while prev_qasm != qasm:
        prev_qasm = qasm[:]
        qasm = optimize_toffoli(qasm)

    # tidy up "ugly" optimized code
    qasm = clean_code(qasm)

    return qasm


def remove_gate_from_line(local_qasm_line, gate_symbol, qubit_index):
    """
    Removes the application of a specific gate on a specific qubit.

    Args:
        local_qasm_line: The line from which this call should be removed.
        gate_symbol: The symbol representing the gate
        qubit_index: The index of the target qubit

    Returns: The same line of QASM, with the gate removed.
    """

    # if gate applied to single qubit, remove gate call entirely
    single_application = "{} q[{}]".format(gate_symbol, qubit_index)
    if single_application in local_qasm_line:
        # if there is a parallel bar right
        local_qasm_line = local_qasm_line.replace(single_application + " | ", "")
        # else: if there is a parallel bar left
        local_qasm_line = local_qasm_line.replace(" | " + single_application, "")
        # else: if it is the only gate in parallelized brackets
        local_qasm_line = local_qasm_line.replace("{" + single_application + "}", "")
        # else: if it is not parellelized at all
        local_qasm_line = local_qasm_line.replace(single_application, "")

    # else remove just the number
    else:
        local_qasm_line = local_qasm_line.replace(",{},".format(qubit_index), ",")
        local_qasm_line = local_qasm_line.replace("[{},".format(qubit_index), "[")
        local_qasm_line = local_qasm_line.replace(",{}]".format(qubit_index), "]")

    return local_qasm_line


def add_gate_to_line(local_qasm_line, gate_symbol, qubit_index):
    """
    Add in parallel the application of a gate on a qubit.
    Args:
        local_qasm_line: The existing line of QASM to add the gate in.
        gate_symbol: The symbol representing the gate.
        qubit_index: The index of the target qubit.

    Returns: The same line of QASM with the gate added.
    """

    # if another operation is already called on this qubit, we have to put the new gate on a new line
    if "[" + str(qubit_index) + "]" in local_qasm_line \
            or "[" + str(qubit_index) + "," in local_qasm_line \
            or "," + str(qubit_index) + "," in local_qasm_line \
            or "," + str(qubit_index) + "]" in local_qasm_line:
        local_qasm_line += "\n{} q[{}]\n".format(gate_symbol, qubit_index)

    # if the line is not empty, we need to consider what's already present
    elif local_qasm_line != "":
        # a bracket indicates this line is parallelized with the { gate | gate | gate } syntax
        if "{" in local_qasm_line:
            # remove } from the line and add it back at the end
            local_qasm_line = local_qasm_line.rstrip("}| \n") + \
                              " | " + \
                              "{} q[{}]".format(gate_symbol, qubit_index) + \
                              "}\n"

        # no bracket means we have to add the parallelization syntax ourselves
        else:
            local_qasm_line = "{" + local_qasm_line.rstrip("\n") + \
                              " | " + \
                              "{} q[{}]".format(gate_symbol, qubit_index) + "}\n"

    # else, if the line IS empty, we can just put this gate in directly
    else:
        local_qasm_line = "{} q[{}]\n".format(gate_symbol, qubit_index)
    return local_qasm_line


def remove_toffoli_from_line(local_qasm_line, qubit_1, qubit_2, target_qubit):
    """
    Remove a specific Toffoli gate from a line of qasm.

    Args:
        local_qasm_line: The line of qasm
        qubit_1: The first control qubit of the Toffoli gate
        qubit_2: The second control qubit
        target_qubit: The target qubit

    Returns: The same line of qasm without the Toffoli gate call

    """
    single_application = "Toffoli q[{}],q[{}],q[{}]".format(qubit_1, qubit_2, target_qubit)

    # if there is a parallel bar right
    local_qasm_line = local_qasm_line.replace(single_application + " | ", "")
    # else: if there is a parallel bar left
    local_qasm_line = local_qasm_line.replace(" | " + single_application, "")
    # else: if it is the only gate in parallelized brackets
    local_qasm_line = local_qasm_line.replace("{" + single_application + "}", "")
    # else: if it is not parellelized at all
    local_qasm_line = local_qasm_line.replace(single_application, "")
    return local_qasm_line


def add_toffoli_to_line(local_qasm_line, qubit_1, qubit_2, target_qubit):
    """
    Add a single Toffoli gate application to the given line of qasm.

    Args:
        local_qasm_line: The line of qasm
        qubit_1: The first control qubit
        qubit_2: The second control qubit
        target_qubit: The target qubit

    Returns: The same line of qasm with the Toffoli gate added in parallel
    """

    single_application = "Toffoli q[{}],q[{}],q[{}]".format(qubit_1, qubit_2, target_qubit)

    # if the line is not empty, we need to consider what's already present
    if local_qasm_line != "":
        # a bracket indicates this line is parallelized with the { gate_1 | gate_2 | gate_3 } syntax
        if "{" in local_qasm_line:
            # remove } from the line and add it back at the end
            local_qasm_line = local_qasm_line.rstrip("}| \n") + \
                              " | " + \
                              single_application + \
                              "}\n"

        # no bracket means we have to add the parallelization syntax ourselves
        else:
            local_qasm_line = "{" + local_qasm_line.rstrip("\n") + \
                              " | " + \
                              single_application + "}\n"

    # else, if the line IS empty, we can just put this gate in directly
    else:
        local_qasm_line = single_application + "\n"
    return local_qasm_line


def optimize(qasm, qubit_count, data_qubits, mode="speed"):
    """
    Apply a single pass of performance-oriented optimizations to the given QASM.

    Args:
        qasm: A valid QASM program to optimize.
        qubit_count: The total number of qubits
        data_qubits: The number of qubits that are not ancillaries
        mode: Setting that determines the type of optimization:
            "speed" -> combine gates into equivalent smaller gates
            "style" -> parallelize gates for speedup and aesthetics

    Returns: Functionally the same QASM code, with one run of optimizations applied.
    """

    qasm_lines = qasm.split("\n")
    gates_applied = []
    for i in range(qubit_count):
        gates_applied.append([])

    for qasm_line_index in range(len(qasm_lines)):
        line = qasm_lines[qasm_line_index]
        if len(line) == 0:
            continue

        gate = line.split()[0].lstrip("{")
        if gate not in ["H", "X", "Y", "Z"]:
            gate = "_"

        if ":" in line:
            raise ValueError("Optimizer does not work well with q[0:3] notation!\n"
                             "Line: {}".format(line))

        if "[" in line:
            for element in line.split(" | "):
                gate = element.split()[0].lstrip("{")
                if gate not in ["H", "X", "Y", "Z"]:
                    gate = "_"

                alt_affected_qubits = []
                for possibly_affected in range(qubit_count):
                    hits = [
                        ",{},".format(possibly_affected),
                        "[{},".format(possibly_affected),
                        ",{}]".format(possibly_affected),
                        "[{}]".format(possibly_affected)
                    ]
                    if any(h in element for h in hits):
                        alt_affected_qubits.append(possibly_affected)

                for a in alt_affected_qubits:
                    gates_applied[a].append((qasm_line_index, gate))
        else:
            for a in range(data_qubits):
                gates_applied[a].append((qasm_line_index, gate))

    for qubit_index in range(len(gates_applied)):
        gates = gates_applied[qubit_index]
        skip_counter = 0
        for gate_index in range(len(gates)):
            if skip_counter > 0:
                skip_counter -= 1
                continue

            if mode == "speed":
                for offset in range(0, largest_opt - 1):
                    next_gates = "".join(map(lambda _: _[1], gates[gate_index:gate_index + largest_opt - offset]))
                    if next_gates in optimizations:
                        replacement = optimizations[next_gates]

                        line_indices = list(map(lambda _: _[0], gates[gate_index:gate_index + largest_opt - offset]))
                        # first, remove all gates that are to be replaced
                        for idx, line_number in enumerate(line_indices):
                            qasm_lines[line_number] = remove_gate_from_line(qasm_lines[line_number],
                                                                            next_gates[idx],
                                                                            qubit_index)

                        # add replacement gate to first line index
                        # unless there is no replacement gate, of course
                        if replacement != "":
                            qasm_lines[line_indices[0]] = add_gate_to_line(qasm_lines[line_indices[0]],
                                                                           replacement,
                                                                           qubit_index)

                        # ensure we skip a few gates
                        skip_counter += len(next_gates)

            elif mode == "style":
                # check if we can shift left to align with other gates
                current_line, current_gate = gates[gate_index]
                prev_line = current_line - 1

                if any(f in qasm_lines[current_line] or f in qasm_lines[prev_line] for f in forbidden):
                    continue
                # if this or the previous line has a break statement, no shifting possible
                if current_gate == "_" or (gates[gate_index - 1][1] == "_" and gates[gate_index - 1][0] == prev_line):
                    continue

                if qasm_lines[prev_line] == "":
                    continue

                # having passed these checks, we can try to actually shift
                if current_gate in ["H", "X", "Y", "Z"] and str(qubit_index) not in qasm_lines[prev_line]:
                    # remove from current line
                    qasm_lines[current_line] = remove_gate_from_line(qasm_lines[current_line], current_gate,
                                                                     qubit_index)
                    # add to left
                    qasm_lines[prev_line] = add_gate_to_line(qasm_lines[prev_line], current_gate, qubit_index)

    # remove blank lines
    qasm_lines = list(filter(lambda x: x not in ["", "{}", " "], qasm_lines))
    return "\n".join(qasm_lines).replace("\n\n", "\n")


def optimize_toffoli(qasm):
    """
    Specific style optimizer capable of left-shifting and annihilating Toffoli gates.

    Args:
        qasm: QASM to optimize

    Returns: Equivalent QASM with Toffoli gates optimized.
    """

    qasm_lines = qasm.split("\n")
    for current_line_index in range(1, len(qasm_lines)):
        cur_line = qasm_lines[current_line_index]
        prev_line = qasm_lines[current_line_index - 1]
        if "Toffoli" in cur_line and "Toffoli" in prev_line and ".grover_loop" not in prev_line:
            # find all Toffoli triplets in both lines
            prev_line_gates = prev_line.strip("{} |").split(" | ")
            prev_line_toffolis = list(filter(lambda x: "Toffoli" in x, prev_line_gates))

            cur_line_gates = cur_line.strip("{} |").split(" | ")
            cur_line_toffolis = list(filter(lambda x: "Toffoli" in x, cur_line_gates))

            any_updated = False
            for c_t in cur_line_toffolis:
                if any_updated:
                    break

                c_qubit_1, c_qubit_2, c_target_qubit = tuple(map(int, c_t.strip("Toffoli q[]").split("],q[")))
                shiftable = True

                for p_t in prev_line_toffolis:
                    p_qubit_1, p_qubit_2, p_target_qubit = tuple(map(int, p_t.strip("Toffoli q[]").split("],q[")))

                    if {c_qubit_1, c_qubit_2} == {p_qubit_1, p_qubit_2} and c_target_qubit == p_target_qubit:
                        # remove toffolis from both lines
                        cur_line = remove_toffoli_from_line(cur_line, c_qubit_1, c_qubit_2, c_target_qubit)
                        prev_line = remove_toffoli_from_line(prev_line, p_qubit_1, p_qubit_2, p_target_qubit)
                        shiftable = False
                        any_updated = True
                        break
                    elif len({c_qubit_1, c_qubit_2, c_target_qubit}.intersection(
                            {p_qubit_1, p_qubit_2, p_target_qubit})) != 0:
                        shiftable = False
                        break

                # check if anything has blocked us from shifting left
                if shiftable:
                    # otherwise we can go!
                    cur_line = remove_toffoli_from_line(cur_line, c_qubit_1, c_qubit_2, c_target_qubit)
                    prev_line = add_toffoli_to_line(prev_line, c_qubit_1, c_qubit_2, c_target_qubit)

                    any_updated = True

            if any_updated:
                qasm_lines[current_line_index] = cur_line
                qasm_lines[current_line_index - 1] = prev_line

    # remove blank lines
    qasm_lines = list(filter(lambda x: x not in ["", "{}", " "], qasm_lines))

    return "\n".join(qasm_lines).replace("\n\n", "\n")


def replace_toffoli_with_alt(qasm):
    """
    Replace all Toffoli gates (including parallelized ones) by their alternative representation.
    See src.grover.search_utilies.alternative_toffoli for more details.

    Args:
        qasm: The full qasm program that contains Toffoli gates to replace

    Returns: The same qasm with Toffoli gates replaced.
    """
    qasm_lines = qasm.split("\n")
    for current_line_index in range(len(qasm_lines)):
        cur_line = qasm_lines[current_line_index]
        if "Toffoli" in cur_line:
            # find all Toffoli triplets in this line

            cur_line_gates = cur_line.strip("{} |").split(" | ")
            cur_line_toffolis = list(filter(lambda x: "Toffoli" in x, cur_line_gates))

            multiple = len(cur_line_toffolis) > 1

            line_strings = []
            for i in range(7):
                if multiple:
                    line_strings.append("{")
                else:
                    line_strings.append("")

            for current_t in cur_line_toffolis:
                qubit_1, qubit_2, target_qubit = tuple(map(int, current_t.strip("Toffoli q[]").split("],q[")))
                alt_qasm_lines = alternative_and(qubit_1, qubit_2, target_qubit).split("\n")
                for j in range(7):
                    line_strings[j] += alt_qasm_lines[j]
                    if multiple:
                        line_strings[j] += " | "

            if multiple:
                for i in range(7):
                    line_strings[i] = line_strings[i][:-3] + "}"

            cur_line = "\n".join(line_strings)

            qasm_lines[current_line_index] = cur_line

    # remove blank lines
    qasm_lines = list(filter(lambda x: x not in ["", "{}", " "], qasm_lines))
    return "\n".join(qasm_lines).replace("\n\n", "\n")


def clean_code(qasm):
    """
    Clean given QASM by rewriting each line to a more readable format.
    For example, "{ X q[0] | H q[3,4,5] | X q[1] | X q[2] }"
    Would become "{ X q[0:2] | H q[3:5]"

    Args:
        qasm: Valid QASM code to clean

    Returns: The same QASM code with improved formatting.
    """

    qasm_lines = qasm.split("\n")
    for idx in range(len(qasm_lines)):
        line = qasm_lines[idx]
        gate_dict = {}
        new_line = ""
        if "Toffoli" not in line and "CR" not in line and "CNOT" not in line and ("{" in line or "," in line):
            line = line.strip("{}")
            elements = line.split("|")
            for e in elements:
                gate, target = e.split()
                indices = list(map(int, target.strip("q[]").split(",")))
                if gate not in gate_dict:
                    gate_dict[gate] = indices
                else:
                    gate_dict[gate] += indices

            parallel = len(gate_dict.keys()) > 1
            if parallel:
                new_line += "{ "
            for gate, indices in gate_dict.items():
                if max(indices) - min(indices) + 1 == len(indices) > 1:
                    new_line += "{} q[{}:{}]".format(gate, min(indices), max(indices))
                else:
                    new_line += "{} q[{}]".format(gate, ",".join(map(str, indices)))
                new_line += " | "

            new_line = new_line[:-3]
            if parallel:
                new_line += " }"
        else:
            new_line = line

        qasm_lines[idx] = new_line

    return "\n".join(qasm_lines)


Code for the SAT-utilities

import boolean
from boolean.boolean import AND, OR, NOT, Symbol
import random

import matplotlib.pyplot as plt
import math


def apply(gate, qubit):
    """
    Simply apply a gate to a single qubit

    Args:
        gate: The gate to apply
        qubit: The target qubit

    Returns: Valid QASM that represents this application

    """
    return "{} q[{}]\n".format(gate, qubit)


def fill(character, data_qubits):
    """
    Apply a specific gate to all data qubits
    Args:
        character: The QASM gate to apply
        data_qubits: The number of data qubits

    Returns: Valid QASM to append to the program
    """
    # create a list of the qubit indices that need the gate applied
    indices = ",".join(map(str, range(data_qubits)))

    return "{} q[{}]\n".format(character, indices)


def normal_n_size_cnot(n, mode):
    """
    Generate a CNOT with n control bits.
    It is assumed the control bits have indices [0:n-1],
    and the target bit is at index [n].

    Args:
        n: The number of control bits
        mode: The method by which we will make CNOT gates

    Returns: Valid QASM to append to the program
    """

    if n == 1:
        local_qasm = "CNOT q[0],q[1]\n"
    elif n == 2:
        if mode == "no toffoli":
            local_qasm = alternative_and(0, 1, 2)
        else:
            local_qasm = "Toffoli q[0],q[1],q[2]\n"
    else:
        # for n > 2, there is no direct instruction in QASM, so we must generate an equivalent circuit
        # the core idea of a large CNOT is that we must AND-gate together all the control bits
        # we do this with Toffoli gates, and store the result of each Toffoli on ancillary qubits

        # we keep a list of all the bits that should be AND-ed together
        bits_to_and = list(range(n))
        ancillary_count = 0

        # make a list of all the Toffoli gates to eventually write to the program
        gate_list = []

        # we will continue looping until all bits are and-ed together
        while len(bits_to_and) > 0:
            # take the first two
            a, b = bits_to_and[:2]
            # if these are the only two elements to AND, we're almost done!
            # just combine these 2 in a Toffoli...
            # ...which targets the global "target bit" of this n-CNOT
            if len(bits_to_and) == 2:
                target = n
                bits_to_and = []

            # the default case is to write the result to an ancillary bit
            else:
                target = n + 1 + ancillary_count
                ancillary_count += 1
                # remove the used qubits from the list of bits to AND
                bits_to_and = bits_to_and[2:] + [target]

            if mode == "no toffoli":
                gate_list.append(alternative_and(a, b, target))
            else:
                gate_list.append("Toffoli q[{}],q[{}],q[{}]".format(a, b, target))
            # gate_list.append("Toffoli q[{}],q[{}],q[{}]".format(a, b, target))

        # Apply the complete list of gates in reverse after the target is flipped
        # This undoes all operations on the ancillary qubits (so they remain 0)
        gate_list = gate_list + gate_list[-2::-1]
        local_qasm = "\n".join(gate_list) + "\n"

    return local_qasm


def n_size_crot(n, start_n, target, angle):
    """
    Generate a controlled rotation with n control bits without using Toffoli gates.
    It is assumed the control bits have indices [start_n : start_n + n-1].

    Args:
        n: The number of control bits
        start_n: The first index that is a control bit
        target: The target bit index
        angle: The angle in radians by which to shift the phase
               An angle of pi gives an H-Z-H aka X gate
               An angle of pi/2 gives an H-S-H gate
               An angle of pi/4 gives an H-T-H gate
               Etc.

    Returns: Valid QASM to append to the program
    """
    local_qasm = ""

    if n == 1:
        # Simply a CROT with the given angle
        local_qasm += apply("H", target)
        local_qasm += "CR q[{}],q[{}],{}\n".format(start_n, target, angle)
        local_qasm += apply("H", target)
    else:
        # V gate using the lowest control bit
        local_qasm += n_size_crot(1, start_n + n - 1, target, angle / 2)

        # n-1 CNOT on highest bits (new_angle = angle)
        local_qasm += n_size_crot(n - 1, 0, start_n + n - 1, math.pi)

        # V dagger gate on lowest two bits
        local_qasm += n_size_crot(1, start_n + n - 1, target, -angle / 2)

        # n-1 CNOT on highest bits (new_angle = angle)
        local_qasm += n_size_crot(n - 1, 0, start_n + n - 1, math.pi)

        # controlled V gate using highest as controls and lowest as target (new_angle = angle / 2)
        local_qasm += n_size_crot(n - 1, 0, target, angle / 2)

    return local_qasm


def cnot_pillar(mode, data_qubits):
    """
    Generate a common structure that applies a Hadamard, CNOT, and Hadamard again to the lowest data bit

    Returns: Valid QASM to append to the program
    """
    local_qasm = "H q[{}]\n".format(data_qubits - 1)
    if mode in ["normal", "no toffoli"]:
        local_qasm += normal_n_size_cnot(data_qubits - 1, mode)
    elif mode == "crot":
        local_qasm += n_size_crot(data_qubits - 1, 0, data_qubits - 1, math.pi)
    elif mode == "fancy cnot":
        local_qasm += fancy_cnot(data_qubits - 1)
    local_qasm += "H q[{}]\n".format(data_qubits - 1)
    return local_qasm


def search_oracle(search_term, data_qubits):
    """
    Generate a common structure that is used in the oracle circuit.
    It flips all bits that correspond to a 0 in the search target.
    Args:
        search_term: The search term for which to generate an oracle
        data_qubits: The number of data qubits

    Returns: Valid QASM to append to the program
    """
    if "0" not in search_term:
        return "\n"

    local_qasm = "X q["
    for i in range(data_qubits):
        if search_term[i] == "0":
            local_qasm += str(i) + ","

    # remove last comma and add closing bracket
    local_qasm = local_qasm[:-1] + "]\n"
    return local_qasm


def int_to_bits(int_str, qubit_count):
    """
    Convert a number (possibly in string form) to a readable bit format.
    For example, the result '11', which means both qubits were measured as 1, is returned by the API as "3".
    This converts that output to the readable version.

    Args:
        int_str: A string or integer in base 10 that represents the measurement outcome.

    Returns: A string of 0's and 1's
    """
    # convert to an integer, then generate the binary string
    # remove the "0b" prefix from the binary string
    # then pad (using zfill) with 0's
    return str(bin(int(int_str)))[2:].zfill(qubit_count)


def interpret_results(result_dict, qubit_count, data_qubits, plot=True):
    """
    Parse the result dictionary given by the API into a readable format, and plot it.

    Args:
        result_dict: The dictionary given by qi.execute_qasm
        plot: Whether to plot the results in a bar chart

    Returns: Parsed result
    """

    num_of_measurements = 2 ** qubit_count

    # we still store the histogram bars in here, and later sort them in ascending order
    ordered_bars = [None for _ in range(num_of_measurements)]

    for i in range(num_of_measurements):
        # find result in dictionary and add to list of bars
        # zero-valued bars don't show up in the dictionary, hence the try-except
        try:
            bar = result_dict["histogram"][str(i)]
        except KeyError:
            bar = 0

        # generate corresponding binary name (so "11" instead of "3")
        name = int_to_bits(i, qubit_count)

        ordered_bars[i] = (name, bar)

    if plot:
        for b in ordered_bars:
            # check if the given bar has 0's for all the ancillary qubits
            # if it does not, we assume it is irrelevant for the histogram, so we don't plot it
            if int(b[0], 2) < 2 ** data_qubits:
                plt.bar(b[0][-data_qubits:], b[1])
            # if a result is returned where some ancillary qubits were not zero, we have a problem
            elif b[1] != 0:
                raise ValueError("\tNonzero result from 'impossible' measurement:\n"
                                 "\tColumn {} has fraction {}. This means not all control bits were 0!".format(b[0],
                                                                                                               b[1]))

        # set styling for the x-axis markers
        plt.xticks(fontsize=6, rotation=45, ha="right")
        plt.title("Measurements, discarding ancilla qubits")
        plt.show()

    return ordered_bars


def gray_code(n):
    """
    Generate a Gray code sequence of bit string with length n.

    Args:
        n: The size for each element in the Gray code

    Returns: An array of strings forming a Gray code
    """

    if n == 1:
        return ["0", "1"]
    else:
        g_previous = gray_code(n - 1)
        mirrored_paste = g_previous + g_previous[::-1]
        g_current = mirrored_paste[:]
        for i in range(2 ** n):
            if i < 2 ** (n - 1):
                g_current[i] = g_current[i] + "0"
            else:
                g_current[i] = g_current[i] + "1"
        return g_current


def fancy_cnot(n):
    """
    Generate a circuit equivalent to an n-bit CNOT.
    This avoids using Toffoli gates or ancillary qubits.
    Args:
        n: Number of control bits

    Returns: Valid QASM that represents a CNOT

    """
    gray_code_list = gray_code(n)[1:]
    local_qasm = apply("H", n)

    for i in range(len(gray_code_list)):
        if i == 0:
            local_qasm += "CR q[0],q[{}],{}\n".format(n, math.pi / (2 ** (n - 1)))
        else:
            prev_gray = gray_code_list[i - 1]
            cur_gray = gray_code_list[i]

            flip_idx = -1
            for j in range(len(cur_gray)):
                if cur_gray[j] != prev_gray[j]:
                    flip_idx = j
                    break

            last_1_bit_cur = len(cur_gray) - 1 - cur_gray[::-1].index("1")
            last_1_bit_prev = len(prev_gray) - 1 - prev_gray[::-1].index("1")

            bit_a = flip_idx

            if flip_idx == last_1_bit_cur:
                bit_b = last_1_bit_prev
            else:
                bit_b = last_1_bit_cur

            control_bit = min(bit_a, bit_b)
            target_bit = max(bit_a, bit_b)

            local_qasm += "CNOT q[{}],q[{}]\n".format(control_bit, target_bit)

            parity = cur_gray.count("1") % 2
            if parity == 0:
                angle = -math.pi / (2 ** (n - 1))
            else:
                angle = math.pi / (2 ** (n - 1))

            local_qasm += "CR q[{}],q[{}],{}\n".format(target_bit, n, angle)

    local_qasm += apply("H", n)
    return local_qasm


def alternative_and(control_1, control_2, target):
    """
    Generate a circuit from 1 and 2 qubit gates that performs an operation equivalent to a Toffoli gate.

    Args:
        control_1: First control bit index
        control_2: Second control bit index
        target: Target bit index

    Returns: Valid QASM that performs a CCNOT
    """

    if control_1 == control_2:
        return "CNOT q[{}],q[{}]\n".format(control_1, target)

    local_qasm = ""
    local_qasm += apply("H", target)
    local_qasm += f"CNOT q[{control_2}],q[{target}]\n"
    local_qasm += apply("Tdag", target)
    local_qasm += f"CNOT q[{control_1}],q[{target}]\n"
    local_qasm += apply("T", target)
    local_qasm += f"CNOT q[{control_2}],q[{target}]\n"
    local_qasm += apply("Tdag", target)
    local_qasm += f"CNOT q[{control_1}],q[{target}]\n"
    local_qasm += apply("T", control_2)
    local_qasm += apply("T", target)
    local_qasm += apply("H", target)
    local_qasm += f"CNOT q[{control_1}],q[{control_2}]\n"
    local_qasm += apply("T", control_1)
    local_qasm += apply("Tdag", control_2)
    local_qasm += f"CNOT q[{control_1}],q[{control_2}]\n"

    return local_qasm


def generate_sat_oracle_reuse_gates(expr: boolean.Expression, control_names, is_toplevel=False, mode='normal'):
    """
    Generate the circuit needed for an oracle solving the SAT problem, given a boolean expression.
    This uses a new ancillary qubit for every boolean gate, UNLESS that sub-expression has already been calculated before.

    Args:
        expr: The boolean expression (instance of boolean.Expression, not a string)
        control_names: The names of the control variables
        is_toplevel: Whether this is the main call, or a recursive call

    Returns: A tuple of the following values:
        - qasm: The QASM for this expression
        - target_qubit: The qubit line on which the output of this expression is placed
    """

    global highest_qubit_used
    global expressions_calculated

    if is_toplevel:
        highest_qubit_used = len(control_names)
        expressions_calculated = {}
    local_qasm = ""

    # go through possible types
    if type(expr) == AND or type(expr) == OR:
        # left side
        left_qasm, left_qubit, _ = generate_sat_oracle_reuse_gates(expr.args[0], control_names, mode=mode)
        expressions_calculated[expr.args[0]] = left_qubit
        right_qasm, right_qubit, _ = generate_sat_oracle_reuse_gates(expr.args[1], control_names, mode=mode)
        expressions_calculated[expr.args[1]] = right_qubit
        local_qasm += left_qasm
        local_qasm += right_qasm
    elif type(expr) == NOT:
        inner_qasm, inner_qubit, _ = generate_sat_oracle_reuse_gates(expr.args[0], control_names, mode=mode)
        local_qasm += inner_qasm
        local_qasm += "X q[{}]\n".format(inner_qubit)
        return local_qasm, inner_qubit, highest_qubit_used
    elif type(expr) == Symbol:
        # nothing to do here
        return local_qasm, control_names.index(expr), highest_qubit_used
    else:
        raise ValueError("Unknown boolean expr type: {}".format(type(expr)))

    if expr in expressions_calculated:
        already_calculated_index = expressions_calculated[expr]
        # we don't need to add any qasm, just say where this expression can be found
        return "", already_calculated_index, highest_qubit_used

    if is_toplevel:
        target_qubit = len(control_names)
        local_qasm += "H q[{}]\n".format(len(control_names))
        left_half_qasm = local_qasm[:]
    else:
        # we need another ancillary bit
        highest_qubit_used += 1
        target_qubit = highest_qubit_used

    if type(expr) == AND:
        if mode == 'normal':
            local_qasm += generate_and(left_qubit, right_qubit, target_qubit)
        else:
            local_qasm += alternative_and(left_qubit, right_qubit, target_qubit)
    elif type(expr) == OR:
        if mode == 'normal':
            local_qasm += generate_or(left_qubit, right_qubit, target_qubit)
        else:
            local_qasm += alternative_or(left_qubit, right_qubit, target_qubit)

    # undo NOT applications
    if len(expr.args) == 2 and not is_toplevel:
        if type(expr.args[0]) == NOT:
            local_qasm += "X q[{}]\n".format(left_qubit)
        if type(expr.args[1]) == NOT:
            local_qasm += "X q[{}]\n".format(right_qubit)

    # indicate to other calls of this function that this expression has been generated already
    expressions_calculated[expr] = target_qubit

    if is_toplevel:
        local_qasm += "\n".join(left_half_qasm.split("\n")[::-1])
        return local_qasm, target_qubit, highest_qubit_used

    return local_qasm, target_qubit, highest_qubit_used


def generate_sat_oracle_reuse_qubits(expr, control_names, avoid, last_qubit=-1, is_toplevel=False, mode='normal'):
    """
    Generate a SAT oracle that saves on ancillary qubits by resetting them, so that they can be reused.

    Args:
        expr: The boolean expression to generate an oracle for
        avoid: The ancillary lines that we can't use because they already contain data (default is an empty list)
        control_names: The names of the variables in the expression (such as "a", "b" and "c")
        last_qubit: The highest qubit index we have ever used. This is needed to calculate the total number of ancillaries
        is_toplevel: Whether this function call is the "original". In that case, its output is a specific qubit line

    Returns: A tuple of the following values:
        - target_line: The output line of the qasm representing the input expression
                       All other lines are guaranteed to be reset to 0
        - qasm: The QASM code
        - last_qubit: The highest ancillary qubit index encountered in this expression
    """

    first_ancillary_bit = len(control_names) + 1

    if len(expr.args) > 2:
        raise ValueError("Fancy SAT Oracle expects only 1 and 2-argument expressions, but got {}".format(expr.args))

    if type(expr) == Symbol:
        return "", control_names.index(expr), last_qubit
    elif type(expr) == NOT:
        qubit_index = control_names.index(expr.args[0])
        return "X q[{}]".format(qubit_index), qubit_index, last_qubit
    elif type(expr) == AND:
        if mode == 'normal':
            generate_func = generate_and
        else:
            generate_func = alternative_and
    elif type(expr) == OR:
        if mode == 'normal':
            generate_func = generate_or
        else:
            generate_func = alternative_or
    else:
        raise ValueError("Unknown type in Boolean expression: {}".format(type(expr)))

    left_expr = expr.args[0]
    right_expr = expr.args[1]

    left_qasm, left_target_qubit, left_last_qubit = generate_sat_oracle_reuse_qubits(left_expr, control_names,
                                                                                     avoid[:],
                                                                                     last_qubit, mode=mode)
    avoid.append(left_target_qubit)
    right_qasm, right_target_qubit, right_last_qubit = generate_sat_oracle_reuse_qubits(right_expr, control_names,
                                                                                        avoid[:],
                                                                                        last_qubit, mode=mode)
    avoid.append(right_target_qubit)

    target_qubit = -1
    # if toplevel, we know what to target: the specific line that is set to the |1> state
    if is_toplevel:
        target_qubit = first_ancillary_bit - 1
        my_qasm = "H q[{}]\n".format(target_qubit) + \
                  generate_func(left_target_qubit, right_target_qubit, target_qubit) + \
                  "H q[{}]\n".format(target_qubit)
    else:
        # find the lowest line we can use
        # if necessary, we can target an entirely new line (if all the others are used)
        for i in range(first_ancillary_bit, first_ancillary_bit + max(avoid) + 1):
            if i not in avoid:
                target_qubit = i
                break
        my_qasm = generate_func(left_target_qubit, right_target_qubit, target_qubit)

    last_qubit = max(last_qubit, max(avoid), left_last_qubit, right_last_qubit, target_qubit)

    local_qasm = "\n".join([
        left_qasm,
        right_qasm,
        my_qasm,
        *right_qasm.split("\n")[::-1],
        *left_qasm.split("\n")[::-1]
    ])

    return local_qasm, target_qubit, last_qubit


def generate_and(qubit_1, qubit_2, target_qubit):
    """
    Generate an AND in qasm code (just a Toffoli).
    """
    if qubit_1 == qubit_2:
        return "CNOT q[{}],q[{}]\n".format(qubit_1, target_qubit)

    return "Toffoli q[{}],q[{}],q[{}]\n".format(qubit_1, qubit_2, target_qubit)


def generate_or(qubit_1, qubit_2, target_qubit):
    """
    Generate an OR in qasm code (Toffoli with X gates).
    """
    if qubit_1 == qubit_2:
        return "CNOT q[{}],q[{}]\n".format(qubit_1, target_qubit)

    local_qasm = "X q[{},{}]\n".format(qubit_1, qubit_2)
    local_qasm += generate_and(qubit_1, qubit_2, target_qubit)
    local_qasm += "X q[{},{},{}]\n".format(qubit_1, qubit_2, target_qubit)
    return local_qasm


def alternative_or(qubit_1, qubit_2, target_qubit):
    if qubit_1 == qubit_2:
        return "CNOT q[{}],q[{}]\n".format(qubit_1, target_qubit)

    local_qasm = "X q[{},{}]\n".format(qubit_1, qubit_2)
    local_qasm += alternative_and(qubit_1, qubit_2, target_qubit)
    local_qasm += "X q[{},{},{}]\n".format(qubit_1, qubit_2, target_qubit)
    return local_qasm


def split_expression_evenly(expr):
    """
    Split a Boolean expression as evenly as possible into a binary tree.

    Args:
        expr: The Boolean expression to split

    Returns: The same expression, where all gates are applied to exactly 2 elements
    """

    expr_type = type(expr)
    if len(expr.args) > 2:
        halfway = int(len(expr.args) / 2)
        right_expanded = split_expression_evenly(expr_type(*expr.args[halfway:]))

        if len(expr.args) > 3:
            left_expanded = split_expression_evenly(expr_type(*expr.args[:halfway]))
        else:
            left_expanded = split_expression_evenly(expr.args[0])

        return expr_type(left_expanded, right_expanded)
    elif len(expr.args) == 2:
        return expr_type(split_expression_evenly(expr.args[0]),
                         split_expression_evenly(expr.args[1]))
    else:
        return expr


def generate_ksat_expression(n, m, k):
    """
    Generate an arbitrary k-SAT expression according to the given parameters.

    Args:
        n: The number of groups
        m: The number of variables in a group
        k: The number of variables

    Returns: A Boolean expression
    """
    if m > k:
        raise ValueError("m > k not possible for kSAT")

    alphabet = []
    for i in range(k):
        alphabet.append(chr(97 + i))

    expression = ""

    for i in range(n):
        literals = random.sample(alphabet, m)
        expression += " and ({}".format(literals[0])
        for l in literals[1:]:
            if random.random() < 0.5:
                expression += " or not({})".format(l)
            else:
                expression += " or {}".format(l)
        expression += ")"

    return expression.lstrip("and ")


def swap_qubits(qasm, cnot_mode, apply_optimization, connected_qubit='2'):
    """
    Implement swap gates to ensure all gates are between the connected_qubit and some other qubit. This is necesarry in
    for example starmon-5 QPU backend, as only qubit 2 is connected to all other qubits.
    For example, "CNOT q[1],q[4]"
    Would become "SWAP q[1],q[2]
                  CNOT q[2],q[4]
                  SWAP q[1],q[2]"   if connected_qubit='2'

    Args:
        qasm: Valid QASM code
        connected_qubit: The qubit that is connected to all other qubits. For starmon-5 this is the third qubit, so connected_qubit='2'.

    Returns: functionally equal QASM code that only has gates between connected qubits.

    This function should not be used on optimized code, or on code that contains three qubit gates. In practice, this
    means you should only apply the function when using the mode cnot_mode='no toffoli' and apply_optimization=False
    """
    if connected_qubit is None:
        return qasm

    if cnot_mode != 'no toffoli' or apply_optimization:
        raise ValueError(
            "Invalid mode: can only use swap_qubits() in combination with cnot_mode='no toffoli' and apply_optimization=False")

    new_lines = []
    for line in qasm.split('\n'):
        if ' ' in line:
            args = line.split(' ')[1].split(',')
            if len(args) > 1:
                if args[0][0] == 'q' and args[1][0] == 'q':
                    q0 = args[0][2]
                    q1 = args[1][2]
                    if q0 != connected_qubit and q1 != connected_qubit:
                        to_swap = min([q0, q1])
                        swap_arg = f'SWAP q[{to_swap}],q[{connected_qubit}]'
                        new_lines += [swap_arg, line.replace(to_swap, connected_qubit), swap_arg]
                        continue
        new_lines += [line]
    return '\n'.join(new_lines)

ProjectQ examples

ProjectQ example 1

A simple example that demonstrates how to use the SDK to create a circuit to create a Bell state, and simulate the circuit on Quantum Inspire.

"""
This example is copied from https://github.com/ProjectQ-Framework/ProjectQ
and is covered under the Apache 2.0 license.
"""
import os

from projectq import MainEngine
from projectq.backends import ResourceCounter
from projectq.ops import CNOT, H, Measure, All
from projectq.setups import restrictedgateset

from quantuminspire.api import QuantumInspireAPI
from quantuminspire.credentials import get_authentication
from quantuminspire.projectq.backend_qx import QIBackend

QI_URL = os.getenv('API_URL', 'https://api.quantum-inspire.com/')


project_name = 'ProjectQ-entangle'
authentication = get_authentication()
qi_api = QuantumInspireAPI(QI_URL, authentication, project_name=project_name)
qi_backend = QIBackend(quantum_inspire_api=qi_api)

compiler_engines = restrictedgateset.get_engine_list(one_qubit_gates=qi_backend.one_qubit_gates,
                                                     two_qubit_gates=qi_backend.two_qubit_gates)
compiler_engines.extend([ResourceCounter()])
engine = MainEngine(backend=qi_backend, engine_list=compiler_engines)

qubits = engine.allocate_qureg(2)
q1 = qubits[0]
q2 = qubits[1]

H | q1
CNOT | (q1, q2)
All(Measure) | qubits

engine.flush()

print('\nMeasured: {0}'.format([int(q) for q in qubits]))
print('Probabilities {0}'.format(qi_backend.get_probabilities(qubits)))

ProjectQ example 2

An example that demonstrates how to use the SDK to create a more complex circuit to run Grover’s algorithm and simulate the circuit on Quantum Inspire.

"""
This example is copied from https://github.com/ProjectQ-Framework/ProjectQ
and is covered under the Apache 2.0 license.
"""
import os
import math

from projectq import MainEngine
from projectq.backends import ResourceCounter, Simulator
from projectq.meta import Compute, Control, Loop, Uncompute
from projectq.ops import CNOT, CZ, All, H, Measure, X, Z
from projectq.setups import restrictedgateset

from quantuminspire.api import QuantumInspireAPI
from quantuminspire.credentials import get_authentication
from quantuminspire.projectq.backend_qx import QIBackend

QI_URL = os.getenv('API_URL', 'https://api.quantum-inspire.com/')


def run_grover(eng, n, oracle):
    """
    Runs Grover's algorithm on n qubit using the provided quantum oracle.

    Args:
        eng (MainEngine): Main compiler engine to run Grover on.
        n (int): Number of bits in the solution.
        oracle (function): Function accepting the engine, an n-qubit register,
            and an output qubit which is flipped by the oracle for the correct
            bit string.

    Returns:
        solution (list<int>): Solution bit-string.
    """
    x = eng.allocate_qureg(n)

    # start in uniform superposition
    All(H) | x

    # number of iterations we have to run:
    num_it = int(math.pi / 4. * math.sqrt(1 << n))

    # prepare the oracle output qubit (the one that is flipped to indicate the
    # solution. start in state 1/sqrt(2) * (|0> - |1>) s.t. a bit-flip turns
    # into a (-1)-phase.
    oracle_out = eng.allocate_qubit()
    X | oracle_out
    H | oracle_out

    # run num_it iterations
    with Loop(eng, num_it):
        # oracle adds a (-1)-phase to the solution
        oracle(eng, x, oracle_out)

        # reflection across uniform superposition
        with Compute(eng):
            All(H) | x
            All(X) | x

        with Control(eng, x[0:-1]):
            Z | x[-1]

        Uncompute(eng)

    All(Measure) | x
    Measure | oracle_out

    eng.flush()
    # return result
    return [int(qubit) for qubit in x]


def alternating_bits_oracle(eng, qubits, output):
    """
    Marks the solution string 1,0,1,0,...,0,1 by flipping the output qubit,
    conditioned on qubits being equal to the alternating bit-string.

    Args:
        eng (MainEngine): Main compiler engine the algorithm is being run on.
        qubits (Qureg): n-qubit quantum register Grover search is run on.
        output (Qubit): Output qubit to flip in order to mark the solution.
    """
    with Compute(eng):
        All(X) | qubits[1::2]
    with Control(eng, qubits):
        X | output
    Uncompute(eng)


# Remote Quantum-Inspire backend
authentication = get_authentication()
qi = QuantumInspireAPI(QI_URL, authentication)
qi_backend = QIBackend(quantum_inspire_api=qi)

compiler_engines = restrictedgateset.get_engine_list(one_qubit_gates=qi_backend.one_qubit_gates,
                                                     two_qubit_gates=qi_backend.two_qubit_gates,
                                                     other_gates=qi_backend.three_qubit_gates)
compiler_engines.extend([ResourceCounter()])
qi_engine = MainEngine(backend=qi_backend, engine_list=compiler_engines)

# Run remote Grover search to find a n-bit solution
result_qi = run_grover(qi_engine, 3, alternating_bits_oracle)
print("\nResult from the remote Quantum-Inspire backend: {}".format(result_qi))

# Local ProjectQ simulator backend
compiler_engines = restrictedgateset.get_engine_list(one_qubit_gates="any", two_qubit_gates=(CNOT, CZ))
compiler_engines.append(ResourceCounter())
local_engine = MainEngine(Simulator(), compiler_engines)

# Run local Grover search to find a n-bit solution
result_local = run_grover(local_engine, 3, alternating_bits_oracle)
print("Result from the local ProjectQ simulator backend: {}\n".format(result_local))

Qiskit examples

Qiskit example 1

A simple example that demonstrates how to use the SDK to create a circuit to create a Bell state, and simulate the circuit on Quantum Inspire.

"""Example usage of the Quantum Inspire backend with the Qiskit SDK.

A simple example that demonstrates how to use the SDK to create
a circuit to create a Bell state, and simulate the circuit on
Quantum Inspire.

For documentation on how to use Qiskit we refer to
[https://qiskit.org/](https://qiskit.org/).

Specific to Quantum Inspire is the creation of the QI instance, which is used to set the authentication
of the user and provides a Quantum Inspire backend that is used to execute the circuit.

Copyright 2018-19 QuTech Delft. Licensed under the Apache License, Version 2.0.
"""
import os

from qiskit import execute
from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit

from quantuminspire.credentials import get_authentication
from quantuminspire.qiskit import QI

QI_URL = os.getenv('API_URL', 'https://api.quantum-inspire.com/')


project_name = 'Qiskit-entangle'
authentication = get_authentication()
QI.set_authentication(authentication, QI_URL, project_name=project_name)
qi_backend = QI.get_backend('QX single-node simulator')

q = QuantumRegister(2)
b = ClassicalRegister(2)
circuit = QuantumCircuit(q, b)

circuit.h(q[0])
circuit.cx(q[0], q[1])
circuit.measure(q, b)

qi_job = execute(circuit, backend=qi_backend, shots=256)
qi_result = qi_job.result()
histogram = qi_result.get_counts(circuit)
print('\nState\tCounts')
[print('{0}\t\t{1}'.format(state, counts)) for state, counts in histogram.items()]
# Print the full state probabilities histogram
probabilities_histogram = qi_result.get_probabilities(circuit)
print('\nState\tProbabilities')
[print('{0}\t\t{1}'.format(state, val)) for state, val in probabilities_histogram.items()]

Qiskit example 2

A simple example that demonstrates how to use the SDK to create a circuit to demonstrate conditional gate execution.

"""Example usage of the Quantum Inspire backend with the Qiskit SDK.

A simple example that demonstrates how to use the SDK to create
a circuit to demonstrate conditional gate execution.

For documentation on how to use Qiskit we refer to
[https://qiskit.org/](https://qiskit.org/).

Specific to Quantum Inspire is the creation of the QI instance, which is used to set the authentication
of the user and provides a Quantum Inspire backend that is used to execute the circuit.

Copyright 2018-19 QuTech Delft. Licensed under the Apache License, Version 2.0.
"""
import os

from qiskit import BasicAer, execute
from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit

from quantuminspire.credentials import get_authentication
from quantuminspire.qiskit import QI

QI_URL = os.getenv('API_URL', 'https://api.quantum-inspire.com/')


authentication = get_authentication()
QI.set_authentication(authentication, QI_URL)
qi_backend = QI.get_backend('QX single-node simulator')

q = QuantumRegister(3, "q")
c0 = ClassicalRegister(1, "c0")
c1 = ClassicalRegister(1, "c1")
c2 = ClassicalRegister(1, "c2")
qc = QuantumCircuit(q, c0, c1, c2, name="conditional")

qc.h(q[0])
qc.h(q[1]).c_if(c0, 0)  # h-gate on q[1] is executed
qc.h(q[2]).c_if(c1, 1)  # h-gate on q[2] is not executed

qc.measure(q[0], c0)
qc.measure(q[1], c1)
qc.measure(q[2], c2)

qi_job = execute(qc, backend=qi_backend, shots=1024)
qi_result = qi_job.result()
histogram = qi_result.get_counts(qc)
print("\nResult from the remote Quantum Inspire backend:\n")
print('State\tCounts')
[print('{0}\t{1}'.format(state, counts)) for state, counts in histogram.items()]

print("\nResult from the local Qiskit simulator backend:\n")
backend = BasicAer.get_backend("qasm_simulator")
job = execute(qc, backend=backend, shots=1024)
result = job.result()
print(result.get_counts(qc))

Back to the main page.