diff --git a/BuildMemo.md b/BuildMemo.md index e4e599a..84e8eb2 100644 --- a/BuildMemo.md +++ b/BuildMemo.md @@ -13,7 +13,7 @@ If you prefer to install Anaconda, please make sure to download the correct vers Download and install CMake https://cmake.org/download/ -CMake version must > 3.16.0 to compile igraph. +CMake 3.18 or newer is required to compile the native extension. Remember to add CMake to your path. @@ -99,12 +99,11 @@ cppsrc/lib/ └── simulator-core.lib # simulator-core library for Windows ``` -Then install or pack the package +Then install or build a wheel. These commands use the active Python interpreter and install the build-only dependencies declared in `pyproject.toml` in an isolated environment. ``` -python setup.py install # install at local -python setup.py bdist_wheel # pack to a binary wheel, must first pip install wheel -python setup.py sdist # pack to a tar.gz of src +python -m pip install . +python -m pip wheel --no-deps . ``` ### Issues @@ -120,40 +119,7 @@ Call Stack (most recent call first): /Users/spinq/Desktop/CMake.app/Contents/share/cmake-3.22/Modules/FindPackageHandleStandardArgs.cmake:594 (_FPHSA_FAILURE_MESSAGE) ``` -To fix this, you need to add `PYTHON_INCLUDE_DIR` and `PYTHON_LIBRARY` at line 56 of `setup.py` - -Windows Sample - -```python -cmake_args = [ - '-DPYTHON_INCLUDE_DIR=D:/tools/miniconda3/envs/python38/include', - '-DPYTHON_LIBRARY=D:/tools/miniconda3/envs/python38/libs/python38.lib', - '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, - '-DPYTHON_EXECUTABLE=' + sys.executable -] -``` - -Mac Sample - -```python -cmake_args = [ - '-DPYTHON_INCLUDE_DIR=/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/include/python3.8', - '-DPYTHON_LIBRARY=/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/libpython3.8.dylib', - '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, - '-DPYTHON_EXECUTABLE=' + sys.executable -] -``` - -Linux Sample - -```python -cmake_args = [ - '-DPYTHON_INCLUDE_DIR=/home/hx/.conda/envs/python38/include/python3.8', - '-DPYTHON_LIBRARY=/home/hx/.conda/envs/python38/lib/libpython3.8.so', - '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, - '-DPYTHON_EXECUTABLE=' + sys.executable -] -``` +Use `python -m pip install .` from the intended environment. The build passes that interpreter and the isolated `pybind11` CMake directory explicitly; manual edits to `setup.py` should not be necessary. #### 2 @@ -180,4 +146,4 @@ ERROR: Could not install packages due to an OSError: [Errno 13] Permission denie Consider using the `--user` option or check the permissions. ``` -Check premission, use `sudo` or `--user` for installation \ No newline at end of file +Check premission, use `sudo` or `--user` for installation diff --git a/CMakeLists.txt b/CMakeLists.txt index 55b588e..8be4b4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.18) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED True) @@ -11,29 +11,15 @@ endif () project(spinqit VERSION 0.0.1) +find_package(Python 3.8 COMPONENTS Interpreter Development.Module REQUIRED) +set(PYBIND11_FINDPYTHON ON) +find_package(pybind11 CONFIG REQUIRED) + add_subdirectory(cppsrc) set(WRAPPER_DIR "spinqit/backend/wrapper") set(SOURCES "${WRAPPER_DIR}/backend_binding.cpp") -find_package(PythonLibs 3 REQUIRED) -set(_find_pybind_cmake_command " -import sys -import pybind11 -sys.stdout.write(pybind11.get_cmake_dir()) -") - -execute_process(COMMAND "${PYTHON_EXECUTABLE}" -c "${_find_pybind_cmake_command}" - OUTPUT_VARIABLE _pybind_cmake - RESULT_VARIABLE _pybind_cresult) - -message(STATUS "PYBIND CMAKE: ${_pybind_cmake}") - -set(pybind11_DIR "${_pybind_cmake}") - -message(STATUS "PYBIND11 dir: ${pybind11_DIR}") -find_package(pybind11 REQUIRED) - pybind11_add_module(spinq_backends ${SOURCES}) target_include_directories(spinq_backends PRIVATE "${CMAKE_CURRENT_LIST_DIR}/cppsrc/include" "${CMAKE_CURRENT_LIST_DIR}/cppsrc/basic_simulator/include" @@ -45,4 +31,4 @@ set_target_properties(spinq_backends PROPERTIES BUILD_RPATH "\$ORIGIN" ) -target_link_libraries(spinq_backends PRIVATE spinq-simulator) \ No newline at end of file +target_link_libraries(spinq_backends PRIVATE spinq-simulator) diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..7e72bc7 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include CMakeLists.txt +graft cppsrc +graft spinqit/backend/wrapper +graft tests +global-exclude __pycache__ *.py[cod] .DS_Store diff --git a/README.md b/README.md index f7182f6..df3f9da 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ SpinQit is the quantum software development kit from SpinQ Technology Co., Ltd. ## Installation and Documentation - SpinQit is available on Windows, Linux and MacOS. Only the **Windows** version can use a local quantum computer as a backend. This package has been tested on Ubuntu 20.04 & 22.04 (x86_64), Windows 10 (x86_64), MacOS Ventura 13.0 (M1, M2) and MacOS Mojave 10.14.6 (x86_64). -- SpinQit requires Python 3.8+. This package has been tested with Python 3.8.13 and 3.9.12. +- SpinQit supports Python 3.8 through 3.12. - We suggest you use Anaconda to set up your Python environment. SpinQit can be installed using the following command: diff --git a/cppsrc/CMakeLists.txt b/cppsrc/CMakeLists.txt index 358e028..8648f5d 100644 --- a/cppsrc/CMakeLists.txt +++ b/cppsrc/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.18) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -9,28 +9,6 @@ endif () project(spinq-simulator VERSION 0.0.1) -find_package(PythonLibs REQUIRED) - -message(STATUS "PYTHON include dirs: ${PYTHON_INCLUDE_DIRS}") - -set(_find_pybind_includes_command " -import sys -import pybind11 -sys.stdout.write(pybind11.get_include()) -") - -execute_process(COMMAND "${PYTHON_EXECUTABLE}" -c "${_find_pybind_includes_command}" - OUTPUT_VARIABLE _pybind_output - RESULT_VARIABLE _pybind_result) - -if(_pybind_result EQUAL "0") - message(STATUS "PYCOMM RAW: ${_pybind_output}") - set(PYBIND_INCLUDE_DIRS "${_pybind_output}") -else() - message(WARNING "(NAIVE) CHECK COULD NOT FIND PYBIND!") - set(PYBIND_INCLUDE_DIRS ${PYTHON_INCLUDE_DIRS}) -endif() - # Source files file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS "*.cpp" "*.h") @@ -79,8 +57,7 @@ add_library(spinq-simulator STATIC ${SOURCE_FILES}) target_include_directories(spinq-simulator PUBLIC ${CMAKE_CURRENT_LIST_DIR}/basic_simulator/include ${CMAKE_CURRENT_LIST_DIR}/nmr/include ${CMAKE_CURRENT_LIST_DIR}/include - ${PYTHON_INCLUDE_DIRS} - ${PYBIND_INCLUDE_DIRS}) + ${Python_INCLUDE_DIRS}) set_target_properties(spinq-simulator PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE @@ -92,6 +69,6 @@ target_link_libraries(spinq-simulator PUBLIC ${CORE_LIB} ${IGRAPH_LIB} ${QUASAR_LIB} + pybind11::headers Threads::Threads ) - diff --git a/cppsrc/basic_simulator/include/basic_simulator.h b/cppsrc/basic_simulator/include/basic_simulator.h index 1c5ddd4..419b140 100644 --- a/cppsrc/basic_simulator/include/basic_simulator.h +++ b/cppsrc/basic_simulator/include/basic_simulator.h @@ -42,10 +42,7 @@ namespace py = pybind11; using namespace std; -extern "C" { -#include "util/graph_attributes.h" -#include "igraph/igraph.h" -} +#include "util/graph_data.h" class BasicSimulator { @@ -53,13 +50,14 @@ class BasicSimulator BasicSimulator(); ~BasicSimulator(); - Result execute(py::capsule graph, py::dict config) + Result execute(py::dict graph_data, py::dict config) { Result re; vector sv; vector ps; - igraph_t *gptr = (igraph_t *)graph.get_pointer(); + NativeGraph graph(graph_data); + igraph_t *gptr = graph.get(); igraph_vector_t vs_res; igraph_vector_init(&vs_res, 0); igraph_topological_sorting(gptr, &vs_res, IGRAPH_OUT); @@ -153,4 +151,4 @@ class BasicSimulator }; -#endif \ No newline at end of file +#endif diff --git a/cppsrc/include/util/graph_attributes.h b/cppsrc/include/util/graph_attributes.h index 78e102b..9116377 100644 --- a/cppsrc/include/util/graph_attributes.h +++ b/cppsrc/include/util/graph_attributes.h @@ -22,8 +22,8 @@ #include #define PyBaseString_Check(o) (PyUnicode_Check(o) || PyBytes_Check(o)) -#define ATTR_STRUCT(graph) ((igraphmodule_i_attribute_struct*)((graph)->attr)) -#define ATTR_STRUCT_DICT(graph) ((igraphmodule_i_attribute_struct*)((graph)->attr))->attrs +#define ATTR_STRUCT(graph) ((spinqit_attribute_struct*)((graph)->attr)) +#define ATTR_STRUCT_DICT(graph) ((spinqit_attribute_struct*)((graph)->attr))->attrs #define ATTRHASH_IDX_GRAPH 0 #define ATTRHASH_IDX_VERTEX 1 @@ -32,7 +32,7 @@ typedef struct { PyObject* attrs[3]; PyObject* vertex_name_index; -} igraphmodule_i_attribute_struct; +} spinqit_attribute_struct; /* * Copy unicode bytes to a string @@ -418,4 +418,4 @@ static int topological_sorting_from_vertex(const igraph_t *graph, igraph_vector_destroy(&dfs_res); return 0; -} \ No newline at end of file +} diff --git a/cppsrc/include/util/graph_data.h b/cppsrc/include/util/graph_data.h new file mode 100644 index 0000000..86dd563 --- /dev/null +++ b/cppsrc/include/util/graph_data.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +#include + +extern "C" { +#include "igraph/igraph.h" +#include "util/graph_attributes.h" +} + +class NativeGraph +{ +public: + explicit NativeGraph(pybind11::dict data) : data_(data) + { + pybind11::list edges = data_["edges"]; + igraph_vector_t edge_vector; + if (igraph_vector_init(&edge_vector, edges.size() * 2) != IGRAPH_SUCCESS) { + throw std::runtime_error("Could not allocate the native graph edge vector."); + } + + int status; + try { + for (pybind11::ssize_t i = 0; i < edges.size(); ++i) { + pybind11::sequence edge = edges[i]; + VECTOR(edge_vector)[i * 2] = edge[0].cast(); + VECTOR(edge_vector)[i * 2 + 1] = edge[1].cast(); + } + + status = igraph_create( + &graph_, + &edge_vector, + data_["vertex_count"].cast(), + data_["directed"].cast() + ); + } catch (...) { + igraph_vector_destroy(&edge_vector); + throw; + } + igraph_vector_destroy(&edge_vector); + if (status != IGRAPH_SUCCESS) { + throw std::runtime_error("Could not create the native graph."); + } + + attributes_.attrs[ATTRHASH_IDX_GRAPH] = data_["graph_attrs"].ptr(); + attributes_.attrs[ATTRHASH_IDX_VERTEX] = data_["vertex_attrs"].ptr(); + attributes_.attrs[ATTRHASH_IDX_EDGE] = data_["edge_attrs"].ptr(); + attributes_.vertex_name_index = nullptr; + graph_.attr = &attributes_; + } + + ~NativeGraph() + { + graph_.attr = nullptr; + igraph_destroy(&graph_); + } + + NativeGraph(const NativeGraph&) = delete; + NativeGraph& operator=(const NativeGraph&) = delete; + + igraph_t* get() + { + return &graph_; + } + +private: + pybind11::dict data_; + spinqit_attribute_struct attributes_; + igraph_t graph_; +}; diff --git a/cppsrc/nmr/include/nmr.h b/cppsrc/nmr/include/nmr.h index 79056ba..3dba939 100644 --- a/cppsrc/nmr/include/nmr.h +++ b/cppsrc/nmr/include/nmr.h @@ -44,10 +44,7 @@ using namespace std; #include "SpinQuasar.h" -extern "C" { -#include "util/graph_attributes.h" -#include "igraph/igraph.h" -} +#include "util/graph_data.h" inline string convert_to_binary(size_t i, size_t n) { @@ -62,7 +59,7 @@ class Nmr { public: Nmr() {} - Result execute(py::capsule graph, py::dict config) + Result execute(py::dict graph_data, py::dict config) { Result re; string ip = "127.0.0.1"; @@ -113,7 +110,8 @@ class Nmr verbose = pcobj.cast(); } - igraph_t *gptr = (igraph_t *)graph.get_pointer(); + NativeGraph graph(graph_data); + igraph_t *gptr = graph.get(); vector gate_map; int qnum = translate(gptr, gate_map); @@ -167,4 +165,4 @@ class Nmr int translate(const igraph_t *g, vector & gate_map); }; -#endif \ No newline at end of file +#endif diff --git a/doc/GettingStarted.md b/doc/GettingStarted.md index e7f8b91..cacef37 100644 --- a/doc/GettingStarted.md +++ b/doc/GettingStarted.md @@ -2,7 +2,7 @@ ## Requirements - SpinQit is available on Windows, Linux and MacOS. Only the **Windows** version can use a local quantum computer as a backend. This package has been tested on Ubuntu 20.04 & 22.04 (x86_64), Windows 10 (x86_64), MacOS Ventura 13.0 (M1, M2) and MacOS Mojave 10.14.6 (x86_64). -- SpinQit requires Python 3.8+. This package has been tested with Python 3.8.13 and 3.9.12. +- SpinQit supports Python 3.8 through 3.12. - We suggest you use Anaconda to set up your Python environment. Please refer to [https://docs.anaconda.com/anaconda/](https://docs.anaconda.com/anaconda/) about how to install and use a conda environment. On Windows, we recommend adding Anaconda to your PATH environment viarable to avoid unnecessary troubles. ## Installation SpinQit can be installed using the following command: @@ -2136,4 +2136,3 @@ html.writer-html5 .rst-content table td:nth-child(2) p{ margin-bottom: 12px !important; } - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..043c366 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[build-system] +requires = [ + "setuptools>=61", + "wheel", + "cmake>=3.18", + "pybind11>=2.11,<4", +] +build-backend = "setuptools.build_meta" diff --git a/requirements.txt b/requirements.txt index 07ae16c..7d4b803 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,10 +5,10 @@ matplotlib>=3.5 numpy<2.0.0 noisyopt==0.2.2 psutil==5.9.1 -pybind11==2.9.2 +pybind11>=2.11,<4 pycryptodome==3.11.0 python-constraint==1.4.0 -python-igraph==0.9.10 +igraph>=0.10.8,<2 autoray==0.6.1 sympy requests @@ -16,4 +16,4 @@ retworkx scipy scikit-learn torch -autograd \ No newline at end of file +autograd diff --git a/setup.py b/setup.py index 15a999c..d52beef 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ import platform import subprocess -from distutils.version import LooseVersion +import pybind11 from setuptools import setup, find_packages, Extension from setuptools.command.build_ext import build_ext from glob import glob @@ -42,10 +42,10 @@ def run(self): ", ".join(e.name for e in self.extensions)) if platform.system() == "Windows": - cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', - out.decode()).group(1)) - if cmake_version < '3.1.0': - raise RuntimeError("CMake >= 3.1.0 is required on Windows") + cmake_version = tuple(int(part) for part in re.search( + r'version\s*([\d.]+)', out.decode()).group(1).split('.')) + if cmake_version < (3, 18, 0): + raise RuntimeError("CMake >= 3.18.0 is required on Windows") for ext in self.extensions: self.build_extension(ext) @@ -55,7 +55,8 @@ def build_extension(self, ext): os.path.dirname(self.get_ext_fullpath(ext.name))) cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, - '-DPYTHON_EXECUTABLE=' + sys.executable] + '-DPython_EXECUTABLE=' + sys.executable, + '-Dpybind11_DIR=' + pybind11.get_cmake_dir()] cfg = 'Debug' if self.debug else 'Release' build_args = ['--config', cfg] @@ -84,7 +85,7 @@ def build_extension(self, ext): date_files_list = [] final_place = '' -if sys.argv[1] in ['install', 'bdist_egg']: +if len(sys.argv) > 1 and sys.argv[1] in ['install', 'bdist_egg']: final_place = 'spinqit' if platform.system() == 'Windows': @@ -113,13 +114,18 @@ def build_extension(self, ext): classifiers=[ 'Development Status :: 4 - Beta', "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Operating System :: MacOS" ], ext_modules=[CMakeExtension('spinqit.spinq_backends')], - install_requires=['numpy<2.0.0', 'scipy', 'scikit-learn', 'torch', 'autograd==1.5.0', 'psutil', 'retworkx', 'python-igraph==0.9.10', 'pybind11', 'antlr4-python3-runtime==4.9.2', 'python-constraint', 'requests', 'matplotlib>=3.5', 'pycryptodome==3.11.0', 'autoray==0.6.1', 'noisyopt==0.2.2', 'sympy'], - python_requires='>=3.8', + install_requires=['numpy<2.0.0', 'scipy', 'scikit-learn', 'torch', 'autograd==1.5.0', 'psutil', 'retworkx', 'igraph>=0.10.8,<2', 'antlr4-python3-runtime==4.9.2', 'python-constraint', 'requests', 'matplotlib>=3.5', 'pycryptodome==3.11.0', 'autoray==0.6.1', 'noisyopt==0.2.2', 'sympy'], + python_requires='>=3.8,<3.13', cmdclass=dict(build_ext=CMakeBuild), package_data={'spinqit': package_data_files}, data_files=date_files_list, diff --git a/spinqit/backend/backend_util.py b/spinqit/backend/backend_util.py index a372e5f..12965b4 100644 --- a/spinqit/backend/backend_util.py +++ b/spinqit/backend/backend_util.py @@ -17,8 +17,15 @@ from spinqit.compiler import IntermediateRepresentation, NodeType from spinqit.compiler.translator.gate_converter import decompose_single_qubit_gate, decompose_multi_qubit_gate -def get_graph_capsule(graph: Graph): - return graph.__graph_as_capsule() +def get_graph_data(graph: Graph): + return { + 'directed': graph.is_directed(), + 'vertex_count': graph.vcount(), + 'edges': graph.get_edgelist(), + 'graph_attrs': {name: graph[name] for name in graph.attributes()}, + 'vertex_attrs': {name: graph.vs[name] for name in graph.vs.attributes()}, + 'edge_attrs': {name: graph.es[name] for name in graph.es.attributes()}, + } def map_results(probabilities: List, qubit_mapping: List) -> List: qubit_num = len(qubit_mapping) @@ -90,4 +97,3 @@ def _add_pauli_gate(gate, qubits, ir): node_idx_list = ir.substitute_nodes([idx], ilist, 0) ir.remove_nodes([idx]) return node_idx_list - diff --git a/spinqit/backend/basic_simulator_backend.py b/spinqit/backend/basic_simulator_backend.py index 8d6fbc3..cbb4f6f 100644 --- a/spinqit/backend/basic_simulator_backend.py +++ b/spinqit/backend/basic_simulator_backend.py @@ -19,7 +19,7 @@ from autoray import numpy as ar from scipy import sparse -from .backend_util import get_graph_capsule, _add_pauli_gate +from .backend_util import get_graph_data, _add_pauli_gate from spinqit.compiler import IntermediateRepresentation, NodeType from spinqit.model import Instruction from spinqit.model import I, H, X, Y, Z, Rx, Ry, Rz, T, Td, S, Sd, P, CX, CY, CZ, SWAP, CCX, U @@ -133,7 +133,7 @@ def __substitute_callee_U(v, ir, qubits, clbits): def execute(self, ir: IntermediateRepresentation, config): self.assemble(ir) - return self.simulator.execute(get_graph_capsule(ir.dag), config.metadata) + return self.simulator.execute(get_graph_data(ir.dag), config.metadata) def get_value_and_grad_fn(self, ir, config, measure_op=None, place_holder=None, grad_method=None): def value_and_grad_fn(params): diff --git a/spinqit/backend/nmr_backend.py b/spinqit/backend/nmr_backend.py index 5f5039a..20b19c4 100644 --- a/spinqit/backend/nmr_backend.py +++ b/spinqit/backend/nmr_backend.py @@ -20,7 +20,7 @@ from scipy import sparse from autoray import numpy as ar -from .backend_util import get_graph_capsule, _add_pauli_gate +from .backend_util import get_graph_data, _add_pauli_gate from .basebackend import BaseBackend from ..utils import requires_grad from ..primitive import PauliBuilder, calculate_pauli_expectation, pauli_decompose @@ -157,7 +157,7 @@ def execute(self, ir: IntermediateRepresentation, config: NMRConfig): self.assemble(ir) for i in range(NMRBackend.MAX_RETRIES): try: - result = self.machine.execute(get_graph_capsule(ir.dag), config.metadata) + result = self.machine.execute(get_graph_data(ir.dag), config.metadata) break except Exception as e: if i < NMRBackend.MAX_RETRIES - 1: diff --git a/spinqit/backend/spinq_cloud_backend.py b/spinqit/backend/spinq_cloud_backend.py index dde4c74..1acba8d 100644 --- a/spinqit/backend/spinq_cloud_backend.py +++ b/spinqit/backend/spinq_cloud_backend.py @@ -36,7 +36,7 @@ from spinqit.model import Instruction from spinqit.compiler.ir import NodeType, IntermediateRepresentation from spinqit.grad import grad_func_hardware -from .backend_util import get_graph_capsule, _add_pauli_gate +from .backend_util import _add_pauli_gate from .layout import generate_direct_layout, collect_gate_qubits from ..primitive import PauliBuilder, calculate_pauli_expectation, pauli_decompose from ..utils.function import requires_grad @@ -571,4 +571,4 @@ def check_node(self, ir, place_holder): record_function.append(p.get_function(place_holder)) else: record_function.append(p) - v['func'] = record_function if len(record_function) > 0 else None \ No newline at end of file + v['func'] = record_function if len(record_function) > 0 else None diff --git a/spinqit/primitive/tapering.py b/spinqit/primitive/tapering.py index 55ecc41..594a84b 100644 --- a/spinqit/primitive/tapering.py +++ b/spinqit/primitive/tapering.py @@ -19,7 +19,7 @@ def to_binary_repr(pauli_str: str) -> np.ndarray: qnum = len(pauli_str) - repr = np.zeros(2*qnum, dtype=np.int) + repr = np.zeros(2*qnum, dtype=int) for idx in range(qnum): op = pauli_str[idx] if op == 'X': @@ -210,4 +210,3 @@ def taper_off_qubits(hamiltonian: List, generators: List, sector: List) -> List: tapered = [(o, c) for o, c in zip(ops, coeffs)] return combine_terms(tapered) - \ No newline at end of file diff --git a/tests/test_python_compatibility.py b/tests/test_python_compatibility.py new file mode 100644 index 0000000..43bfbe0 --- /dev/null +++ b/tests/test_python_compatibility.py @@ -0,0 +1,114 @@ +import json +import subprocess +import sys +import tempfile +import textwrap +import unittest + + +class PythonCompatibilityTest(unittest.TestCase): + def test_graph_is_exported_through_public_igraph_api(self): + script = textwrap.dedent( + """ + import json + + from igraph import Graph + from spinqit.backend.backend_util import get_graph_data + + graph = Graph(3, [(0, 1), (1, 2)], directed=True) + graph["qnum"] = 2 + graph.vs["kind"] = ["input", "gate", "output"] + graph.es["qubit"] = [0, 1] + print(json.dumps(get_graph_data(graph))) + """ + ) + completed = subprocess.run( + [sys.executable, "-c", script], + cwd=tempfile.gettempdir(), + capture_output=True, + text=True, + timeout=180, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + data = json.loads(completed.stdout) + self.assertTrue(data["directed"]) + self.assertEqual(data["vertex_count"], 3) + self.assertEqual(data["edges"], [[0, 1], [1, 2]]) + self.assertEqual(data["graph_attrs"], {"qnum": 2}) + self.assertEqual(data["vertex_attrs"]["kind"], ["input", "gate", "output"]) + self.assertEqual(data["edge_attrs"]["qubit"], [0, 1]) + + def test_basic_simulator_does_not_depend_on_igraph_binary_layout(self): + script = textwrap.dedent( + """ + import json + from spinqit import BasicSimulatorConfig, Circuit, H, get_basic_simulator, get_compiler + + circuit = Circuit("python-compatibility") + qubit = circuit.allocateQubits(1)[0] + circuit << (H, qubit) + + result = get_basic_simulator().execute( + get_compiler("native").compile(circuit, 0), + BasicSimulatorConfig(), + ) + print(json.dumps(result.probabilities), flush=True) + """ + ) + completed = subprocess.run( + [sys.executable, "-c", script], + cwd=tempfile.gettempdir(), + capture_output=True, + text=True, + timeout=180, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + probabilities = json.loads(completed.stdout) + self.assertAlmostEqual(probabilities["0"], 0.5) + self.assertAlmostEqual(probabilities["1"], 0.5) + + def test_autograd_crosses_the_native_simulator_boundary(self): + script = textwrap.dedent( + """ + import json + + from autograd import grad + from spinqit import Circuit, Parameter, Ry, generate_hamiltonian_matrix + from spinqit.algorithm.loss import expval + from spinqit.interface.qlayer import QLayer + + circuit = Circuit("autograd-compatibility") + qubit = circuit.allocateQubits(1)[0] + theta = circuit.add_params(shape=(1,)) + circuit << (Ry, qubit, theta[0]) + layer = QLayer( + circuit, + measure=expval(generate_hamiltonian_matrix([("Z", 1.0)])), + backend_mode="spinq", + grad_method="adjoint_differentiation", + ) + + params = Parameter([0.4]) + value = float(layer(params)) + derivative = float(grad(lambda values: layer(values))(params)[0][0]) + print(json.dumps({"value": value, "derivative": derivative})) + """ + ) + completed = subprocess.run( + [sys.executable, "-c", script], + cwd=tempfile.gettempdir(), + capture_output=True, + text=True, + timeout=180, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + result = json.loads(completed.stdout) + self.assertAlmostEqual(result["value"], 0.9210609940028851) + self.assertAlmostEqual(result["derivative"], -0.3894183423086505) + + +if __name__ == "__main__": + unittest.main()