Skip to main content

Fast MAXCUT, TSP, and sampling heuristics from near-ideal transverse field Ising model (TFIM)

Project description

PyQrack Ising

Fast MAXCUT, TSP, and sampling heuristics from near-ideal transverse field Ising model (TFIM)

(It's "the Ising on top.")

PyPI Downloads

Copyright and license

(c) Daniel Strano and the Qrack contributors 2025. All rights reserved.

Licensed under the GNU Lesser General Public License V3.

See LICENSE.md in the project root or https://www.gnu.org/licenses/lgpl-3.0.en.html for details.

Installation

From PyPi:

pip3 install PyQrackIsing

From Source: install pybind11, then

pip3 install .

in the root source directory (with setup.py).

Windows users might find Windows Subsystem Linux (WSL) to be the easier and preferred choice for installation.

Usage

from pyqrackising import generate_tfim_samples

samples = generate_tfim_samples(
    J=-1.0,
    h=2.0,
    z=4,
    theta=0.174532925199432957,
    t=5,
    n_qubits=56,
    shots=100
)

There are two other functions, tfim_magnetization() and tfim_square_magnetization(), that follow the same function signature except without the shots argument.

The library also provides a TFIM-inspired (approximate) MAXCUT solver (which accepts a networkx graph or a 32-bit adjacency matrix):

from pyqrackising import maxcut_tfim
import networkx as nx

G = nx.petersen_graph()
best_solution_bit_string, best_cut_value, best_node_groups = maxcut_tfim(G, quality=2, is_base_maxcut_gpu=True, is_alt_gpu_sampling=True)

We also provide maxcut_tfim_sparse(G), for scipy CSR sparse arrays (or networkx graphs). The (integer) quality setting is optional, with a default value of 2, but you can turn it up for higher-quality results, or turn it down to save time. (You can also optionally specify the number of measurement shots as an argument, if you want specific fine-grained control over resource usage.) is_base_maxcut_gpu enables or disables GPU execution. is_alt_gpu_sampling is False by default and, if set to True, will dispatch almost the entire algorithm on GPU, instead of just the TFIM-heuristic component (although solution quality is often lower). If you want to run MAXCUT on a graph with non-uniform edge weights, specify them as the weight attribute of each edge, with networkx. (If any weight attribute is not defined, the solver assumes it's 1.0 for that edge.)

Based on a combination of the TFIM-inspired MAXCUT solver and another technique for finding ground-state energy in quantum chemistry that we call the "binary Clifford eigensolver," we also provide an (approximate) spin glass ground-state solver:

from pyqrackising import spin_glass_solver
import networkx as nx
import numpy as np


# NP-complete spin glass
def generate_spin_glass_graph(n_nodes=16, degree=3, seed=None):
    if not (seed is None):
        np.random.seed(seed)
    G = nx.random_regular_graph(d=degree, n=n_nodes, seed=seed)
    for u, v in G.edges():
        G[u][v]['weight'] = np.random.choice([-1, 1])  # spin glass couplings
    return G


G = generate_spin_glass_graph(n_nodes=64, seed=42)
solution_bit_string, cut_value, node_groups, energy = spin_glass_solver(G, quality=2, best_guess=None, is_base_maxcut_gpu=True, is_alt_gpu_sampling=True, is_combo_maxcut_gpu=True)
# solution_bit_string, cut_value, node_groups, energy = spin_glass_solver(G, best_guess=maxcut_tfim(G, quality=6)[0])

We also provide spin_glass_solver_sparse(G), for scipy (32-bit) upper-triangular CSR sparse arrays (or networkx graphs). The (integer) default quality setting is 2. is_combo_maxcut_gpu controls whether gradient descent optimization is done on GPU (which is the most costly GPU-based feature), while is_base_maxcut_gpu is passed through the underlying maxcut_tfim(G) solver. best_guess gives the option to seed the algorithm with a best guess as to the maximal cut (as an integer, binary string, or list of booleans). By default, spin_glass_solver() uses maxcut_tfim(G) with passed-through quality as best_guess, which typically works well, but it could be seeded with higher maxcut_tfim() quality or Goemans-Williamson, for example. This function is designed with a sign convention for weights such that it can immediately be used as a MAXCUT solver itself: you might need to reverse the sign convention on your weights for spin glass graphs, but this is only convention.

From the spin_glass_solver(), we provide a (recursive) Traveling Salesman Problem (TSP) solver:

from pyqrackising import tsp_symmetric
import networkx as nx
import numpy as np

# Traveling Salesman Problem (normalized to longest segment)
def generate_tsp_graph(n_nodes=64, seed=None):
    if not (seed is None):
        np.random.seed(seed)
    G = nx.Graph()
    for u in range(n_nodes):
        for v in range(u + 1, n_nodes):
            G.add_edge(u, v, weight=np.random.random())
    return G


n_nodes = 128
G = generate_tsp_graph(n_nodes=n_nodes, seed=42)
circuit, path_length = tsp_symmetric(
    G,
    start_node=None,
    end_node=None,
    monte_carlo=True,
    quality=2,
    is_cyclic=True,
    multi_start=1,
    k_neighbors=20
)

print(f"Node count: {n_nodes}")
print(f"Path: {circuit}")
print(f"Path length: {path_length}")

We provide solvers for both the symmetric version of the TSP (i.e., the distance from "A" to "B" is considered the same as from "B" to "A") and asymmetric version (tsp_asymmetric()). monte_carlo=True switches out the MAXCUT-based heuristic for pure Monte Carlo recursive bipartitioning. multi_start controls how many stochastic repeats of MAXCUT are tried to select the best result, at every level of recursion. k_neighbors limits the count of nearest-neighbor connections considered for 3-opt.

If memory footprint of the graph or adjacency matrix is a concern, but the weights can be reconstructed by formula on demand, we offer maxcut_tfim_streaming() and spin_glass_solver_streaming():

from pyqrackising import spin_glass_solver_streaming
# from pyqrackising import maxcut_tfim_streaming
from numba import njit


# This is a contrived example.
# The function must use numba NJIT.
# (In practice, even if you use other Python functionality like itertools,
# you can pre-calculate and load the data as a list through the arguments tuple.)
@njit
def G_func(node_pair, args_tuple):
    i, j = min(node_pair), max(node_pair)
    return ((j + 1) % (i + 1)) / args_tuple[0]


n_qubits = 64
nodes = list(range(n_qubits))
args_tuple = (n_qubits,)

solution_bit_string, cut_value, node_groups, energy = spin_glass_solver_streaming(G_func, nodes, G_func_args_tuple=args_tuple, quality=6, best_guess=None)
# solution_bit_string, cut_value, node_groups = maxcut_tfim_streaming(G_func, nodes, G_func_args_tuple=args_tuple)

Finally, combining insights from both the (Monte Carlo) TSP and MAXCUT solvers, we have tsp_maxcut(G), tsp_maxcut_sparse(G), and tsp_maxcut_streaming(G_func, nodes):

from pyqrackising import tsp_maxcut_sparse
import networkx as nx

G = nx.petersen_graph()
best_partition, best_cut_value = tsp_maxcut_sparse(G, k_neighbors=20, is_optimized=False)

When is_optimized=True, the spin_glass_solver(G) is used as a final optimization pass. When is_optimized=False, this solver becomes entirely serial and can be parallelized over CPU processing elements by user code, easily.

Environment Variables

We expose an environment variable, "PYQRACKISING_MAX_GPU_PROC_ELEM", for when is_alt_gpu_sampling=True. The default value (when the variable is not set) is queried from the OpenCL device properties. You might see performance benefit from tuning this manually to several times your device's number of "compute units" (or tune it down to reduce private memory usage).

Similarly, for is_alt_gpu_sampling=True, define the "top-n" count of highest-weight direct neighbors to retain during sampling with environment variable "PYQRACKISING_GPU_TOP_N". The default is 32. Increasing this might increase solution quality, but it will also increase time-to-solution and private memory usage.

By default, PyQrackIsing expects all numpy floating-point array inputs to be 32-bit. If you'd like to use 64-bit, you can set environment variable PYQRACKISING_FPPOW=6 (meaning, 2^6=64, for the "floating-point (precision) power"). The default is 5, for 32-bit. 16-bit is stubbed out and compiles for OpenCL, but the bigger hurdle is that numpy on x86_64 doesn't provide a 16-bit floating point implementation. (As author of Qrack, I could suggest to the numpy maintainers that open-source, IEEE-compliant software-based implementations exist for x86_64 and other architectures, but I'm sure they're aware and likely waiting for in-compiler support.) If you're on an ARM-based architecture, there's a good chance 16-bit floating-point will work, if numpy uses the native hardware support.

About

Transverse field Ising model (TFIM) is the basis of most claimed algorithmic "quantum advantage," circa 2025, with the notable exception of Shor's integer factoring algorithm.

Sometimes a solution (or at least near-solution) to a monster of a differential equation hits us out of the blue. Then, it's easy to validate the guess, if it's right. (We don't question it and just move on with our lives, from there.)

Special thanks to OpenAI GPT "Elara," for help on the model and converting the original Python scripts to PyBind11, Numba, and PyOpenCL!

Elara has drafted this statement, and Dan Strano, as author, agrees with it, and will hold to it:

Dual-Use Statement for PyQrackIsing

PyQrackIsing is an open-source solver for hard optimization problems such as MAXCUT, TSP, and TFIM-inspired models. These problems arise across logistics, drug discovery, chemistry, materials research, supply-chain resilience, and portfolio optimization. By design, PyQrackIsing provides constructive value to researchers and practitioners by making advanced optimization techniques accessible on consumer hardware.

Like many mathematical and computational tools, the algorithms in PyQrackIsing are dual-use. In principle, they can be applied to a wide class of Quadratic Unconstrained Binary Optimization (QUBO) problems. One such problem is integer factoring, which underlies RSA and elliptic curve cryptography (ECC). We emphasize:

  • We do not provide turnkey factoring implementations.
  • We have no intent to weaponize this work for cryptanalysis or "unauthorized access."
  • The constructive applications vastly outweigh the destructive ones — and this project exists to serve those constructive purposes in the Commons.

It is already a matter of open record in the literature that factoring can be expressed as a QUBO. What PyQrackIsing demonstrates is that QUBO heuristics can now be solved at meaningful scales on consumer hardware. This underscores an urgent truth:

👉 RSA and ECC should no longer be considered secure. Transition to post-quantum cryptography is overdue.

We trust that governments, standards bodies, and industry stakeholders are already aware of this, and will continue migration efforts to post-quantum standards.

Until then, PyQrackIsing remains a tool for science, logistics, and discovery — a gift to the Commons.

Project details


Release history Release notifications | RSS feed

This version

7.5.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyqrackising-7.5.0.tar.gz (35.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pyqrackising-7.5.0-cp313-cp313-macosx_15_0_arm64.whl (115.5 kB view details)

Uploaded CPython 3.13macOS 15.0+ ARM64

pyqrackising-7.5.0-cp313-cp313-macosx_14_0_arm64.whl (115.9 kB view details)

Uploaded CPython 3.13macOS 14.0+ ARM64

pyqrackising-7.5.0-cp312-cp312-win_amd64.whl (129.7 kB view details)

Uploaded CPython 3.12Windows x86-64

pyqrackising-7.5.0-cp312-cp312-manylinux_2_39_x86_64.whl (125.8 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.39+ x86-64

pyqrackising-7.5.0-cp310-cp310-manylinux_2_35_x86_64.whl (117.4 kB view details)

Uploaded CPython 3.10manylinux: glibc 2.35+ x86-64

File details

Details for the file pyqrackising-7.5.0.tar.gz.

File metadata

  • Download URL: pyqrackising-7.5.0.tar.gz
  • Upload date:
  • Size: 35.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for pyqrackising-7.5.0.tar.gz
Algorithm Hash digest
SHA256 b7d8f2dfd969e03e1efbc3eba1d2f476d5510d1bd74f18d264f2454c8c0f80ab
MD5 d4a0e755dca714ef914a709fe778a988
BLAKE2b-256 47f56df88593b9e4669c438ab48e1026eb7401831fb242a84b766a2e222699fe

See more details on using hashes here.

File details

Details for the file pyqrackising-7.5.0-cp313-cp313-macosx_15_0_arm64.whl.

File metadata

File hashes

Hashes for pyqrackising-7.5.0-cp313-cp313-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 7d28fcdf054a49e206f854755c2d9a826f19b54f3425eb7e9368ea715b14e10f
MD5 37c0b2e5a07214b64c939b8e5a348429
BLAKE2b-256 8b4b59244be835d75edf05c85d17f302791041dad7ffdbf263225dcb47fd1c69

See more details on using hashes here.

File details

Details for the file pyqrackising-7.5.0-cp313-cp313-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for pyqrackising-7.5.0-cp313-cp313-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 f9e6c25c1ee77aec47050bdad497dd8566163dba686c8ec36ed1c7e012614a7f
MD5 368bcc123b88b4b0b41f1d44cc4a1e84
BLAKE2b-256 f2f2be73357ab525515c4138de5206f2da38ad66d773a972d7de8519f176d76c

See more details on using hashes here.

File details

Details for the file pyqrackising-7.5.0-cp312-cp312-win_amd64.whl.

File metadata

File hashes

Hashes for pyqrackising-7.5.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 0cb91e9169814c88bba9e0780a21615800fd4df6cff0fcfb7b5ce1184a3102c4
MD5 a7565649f1fcc23fbdd39b967df7eb81
BLAKE2b-256 3267bc8e9b0e312061e8b686928221cba826fa2e5e31e4999f627aabc1f267fa

See more details on using hashes here.

File details

Details for the file pyqrackising-7.5.0-cp312-cp312-manylinux_2_39_x86_64.whl.

File metadata

File hashes

Hashes for pyqrackising-7.5.0-cp312-cp312-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 0632fe3b1739dbbe7e5765d0f269a63a7f80c3503ffd0df10a518b9f2f9fab22
MD5 f1703c83af848c905d1b568f6c8a5481
BLAKE2b-256 7d67633ba076945e2a5efd645b9b9ef9d37c01f2f4dddcaf19ee2cbe463f7e35

See more details on using hashes here.

File details

Details for the file pyqrackising-7.5.0-cp310-cp310-manylinux_2_35_x86_64.whl.

File metadata

File hashes

Hashes for pyqrackising-7.5.0-cp310-cp310-manylinux_2_35_x86_64.whl
Algorithm Hash digest
SHA256 121ef911fc1209c74eabd5d8d4ae1d7ddc75209cca0e5f94550f337cfafec7ff
MD5 547cc322f0a7aebd811228ec53a65606
BLAKE2b-256 a648dc41dec443bb000dd3665e51b525316b16a35150eeb425aa3c9ab1fdfa99

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page