Skip to main content

View on GitHub

Open this notebook in GitHub to run it yourself

Oracle generation for 3-SAT problems

This notebook demonstrates Classiq’s capabilities in the framework of phase oracles. The focus is 3-SAT problems on a growing number of variables. To highlight the advantage of generation times, we skip transpilation for the synthesis output. The following utility functions generate random 3-SAT problems for NN boolean variables, consisting of NN clauses.
import numpy as np

from classiq.qmod.symbolic import logical_and, logical_not, logical_or


def generate_permutation_for_3sat_expression(num_qubits, max_samples=1000):
    """
    A function that generates two permutations on a list of num_qubits variables,
    for introducing a random and valid 3-SAT problem
    """

    direct_arr = np.array([k for k in range(num_qubits)])
    for k in range(max_samples):
        permut1 = np.random.permutation(num_qubits)
        permut2 = np.random.permutation(num_qubits)
        if (
            (0 not in permut2 - direct_arr)
            and (0 not in permut1 - direct_arr)
            and (0 not in permut1 - permut2)
        ):
            break

    assert (
        k < max_samples
    ), "Could not find a random 3-SAT problem, try to increase max_samples"
    return direct_arr, permut1, permut2


def generate_3sat_qbit_expression(vars, s0, s1, s2):
    """
    A function that generates a 3-SAT problem on a list of QBit variables.

The returned expression contains num_qubits=len(vars) clauses and contains
    triplets of the form (x_k or ~x_s1(k) or x_s2(k)), where s1, s2 are permutations.
    """

    num_qubits = len(vars)
    k = 0
    y = logical_or(logical_or(vars[s0[k]], logical_not(vars[s1[k]])), vars[s2[k]])
    for k in range(1, num_qubits):
        temp = logical_or(
            logical_or(vars[s0[k]], logical_not(vars[s1[k]])), vars[s2[k]]
        )
        y = logical_and(y, temp)
    return y

  1. Generating Phase Oracles
For each 3-SAT problem we generate an oracle with Classiq and save the generation time, as well as the circuits’ width.
from classiq import *

qmods = []
qprogs = []


def get_generation_time_classiq(s0, s1, s2, num_qubits):
    start_cl = time.time()

    @qfunc
    def main(qbv: Output[QArray]):

        def inner_call(aux: QNum):
            aux ^= generate_3sat_qbit_expression(
                [qbv[k] for k in range(num_qubits)], s0, s1, s2
            )

        allocate(num_qubits, qbv)
        aux = QNum("aux")
        allocate(1, aux)
        within_apply(
            lambda: (X(aux), H(aux)),
            lambda: inner_call(aux),
        )
        free(aux)

    qmod = create_model(main)
    qmod = set_preferences(qmod, preferences=Preferences(transpilation_option="none"))
    qmods.append(qmod)
    qprog = synthesize(qmod)
    qprogs.append(qprog)

    return qprog.data.width, time.time() - start_cl
The following function generates a phase oracle with qiskit.
def get_generation_time_qiskit(s0, s1, s2, num_qubits):
    start_qs = time.time()
    dict_of_qnums = {f"x{k}": QNum(f"x{k}") for k in range(num_qubits)}
    expression = str(
        generate_3sat_qbit_expression(
            [dict_of_qnums[f"x{k}"] for k in range(num_qubits)], s0, s1, s2
        )
    )
    expression = expression.replace("or", "|")
    expression = expression.replace("not", "~")
    expression = expression.replace("and", "&")
    oracle = PhaseOracle(expression, var_order=None)
    q = QuantumRegister(num_qubits)
    qc = QuantumCircuit(q)
    qc.append(oracle, q[:])

    return time.time() - start_qs
For generating the same data with Qiskit please uncomment the commented lines (including the pip install command). We work with qiskit version 1.0.
import time

from qiskit import QuantumCircuit, QuantumRegister, transpile
from qiskit.circuit.library import PhaseOracle
We skip generating data with Qiskit for N>23N>23, as generation times exponentially diverge with the number of variables.
np.random.seed(128)
cl_times = []
num_qubits_list = [k for k in range(10, 23)] + [
    int(l) for l in np.logspace(np.log2(24), np.log2(68), 10, base=2)
]

# from importlib.metadata import version
# try:
#     import qiskit
#     if version('qiskit') != "1.0.0":
#       !pip uninstall qiskit -y
#       !pip install qiskit==1.0.0
# except ImportError:
#     !pip install qiskit==1.0.0

# ! pip install tweedledum
# qs_times = []

for l in num_qubits_list:
    num_qubits = l
    print("num_qubits:", num_qubits)
    s0, s1, s2 = generate_permutation_for_3sat_expression(num_qubits)

    cl_width, classiq_generation_time = get_generation_time_classiq(
        s0, s1, s2, num_qubits
    )
    cl_times.append(classiq_generation_time)
    print("classiq_width:", cl_width, ",   classiq_time:", classiq_generation_time)

    # if l<23:
    #     qiskit_generation_time = get_generation_time_qiskit(s0, s1, s2, num_qubits)
    #     qs_times.append(qiskit_generation_time)
    #     print("qiskit_time:", qiskit_generation_time)
Output:
num_qubits: 10
  classiq_width: 25 ,   classiq_time: 5.598961114883423
  num_qubits: 11
  classiq_width: 25 ,   classiq_time: 4.382137060165405
  num_qubits: 12
  classiq_width: 27 ,   classiq_time: 3.538400173187256
  num_qubits: 13
  classiq_width: 33 ,   classiq_time: 3.414907217025757
  num_qubits: 14
  classiq_width: 35 ,   classiq_time: 4.9529290199279785
  num_qubits: 15
  classiq_width: 34 ,   classiq_time: 4.352447032928467
  num_qubits: 16
  classiq_width: 36 ,   classiq_time: 4.4613118171691895
  num_qubits: 17
  classiq_width: 37 ,   classiq_time: 3.462131977081299
  num_qubits: 18
  classiq_width: 40 ,   classiq_time: 4.390230894088745
  num_qubits: 19
  classiq_width: 48 ,   classiq_time: 4.343130826950073
  num_qubits: 20
  classiq_width: 47 ,   classiq_time: 4.476085901260376
  num_qubits: 21
  classiq_width: 45 ,   classiq_time: 4.27609920501709
  num_qubits: 22
  classiq_width: 53 ,   classiq_time: 4.421467065811157
  num_qubits: 24
  classiq_width: 55 ,   classiq_time: 5.765365123748779
  num_qubits: 26
  classiq_width: 64 ,   classiq_time: 4.636790990829468
  num_qubits: 30
  classiq_width: 72 ,   classiq_time: 5.937633037567139
  num_qubits: 33
  classiq_width: 70 ,   classiq_time: 5.995665073394775
  num_qubits: 38
  classiq_width: 82 ,   classiq_time: 5.880221843719482
  num_qubits: 42
  classiq_width: 101 ,   classiq_time: 8.07465410232544
  num_qubits: 48
  classiq_width: 110 ,   classiq_time: 7.8774120807647705
  num_qubits: 53
  classiq_width: 118 ,   classiq_time: 8.30559492111206
  num_qubits: 60
  classiq_width: 136 ,   classiq_time: 10.575138092041016
  num_qubits: 67
  classiq_width: 137 ,   classiq_time: 10.804642915725708
  

  1. Plotting the Data
Since generating the data takes time we hard-coded the Qiskit results in the notebook. If you run this notebook by yourself please comment out the following cell.
qs_times = [
    0.23147010803222656,
    0.2850170135498047,
    2.6256730556488037,
    0.75693678855896,
    5.783859968185425,
    3.3723957538604736,
    3.9280269145965576,
    39.92809295654297,
    60.67643904685974,
    16.551968097686768,
    31.536834955215454,
    31.086618900299072,
    794.9081449508667,
]

import matplotlib.pyplot as plt

classiq_color = "#D7F75B"
qiskit_color = "#6FA4FF"
plt.rcParams["font.family"] = "serif"
plt.rc("savefig", dpi=300)

plt.rcParams["axes.linewidth"] = 1
plt.rcParams["xtick.major.size"] = 5
plt.rcParams["xtick.minor.size"] = 5
plt.rcParams["ytick.major.size"] = 5
plt.rcParams["ytick.minor.size"] = 5


plt.loglog(
    [n for n in num_qubits_list if n < 23],
    qs_times,
    "s",
    label="qiskit",
    markerfacecolor=qiskit_color,
    markeredgecolor="k",
    markersize=7,
    markeredgewidth=1.5,
    linewidth=1.5,
    color=qiskit_color,
)
plt.loglog(
    num_qubits_list,
    cl_times,
    "o",
    label="classiq",
    markerfacecolor=classiq_color,
    markeredgecolor="k",
    markersize=8.5,
    markeredgewidth=1.5,
    linewidth=1.5,
    color=classiq_color,
)

plt.legend(fontsize=16, loc="upper right")


plt.ylabel("generation time [sec]", fontsize=16)
plt.xlabel("number of variables", fontsize=16)
plt.yticks(fontsize=16)
plt.xticks(fontsize=16)
Output:
(array([1.e-01, 1.e+00, 1.e+01, 1.e+02, 1.e+03]),
   [Text(0.1, 0, '$\\mathdefault{10^{-1}}$'),
    Text(1.0, 0, '$\\mathdefault{10^{0}}$'),
    Text(10.0, 0, '$\\mathdefault{10^{1}}$'),
    Text(100.0, 0, '$\\mathdefault{10^{2}}$'),
    Text(1000.0, 0, '$\\mathdefault{10^{3}}$')])
  

output