diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 77109b428..e0968df6a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -73,10 +73,15 @@ jobs: cache: pip cache-dependency-path: | requirements.txt - dev-requirements.txt + pyproject.toml + + - name: Upgrade pip + run: python -m pip install --upgrade pip - name: Install dependencies - run: pip install -r requirements.txt -r dev-requirements.txt + run: | + pip install -r requirements.txt + pip install --group dev - name: Check format continue-on-error: ${{inputs.soft-linting == 'true'}} @@ -237,10 +242,15 @@ jobs: cache: pip cache-dependency-path: | requirements.txt - dev-requirements.txt + pyproject.toml + + - name: Upgrade pip + run: python -m pip install --upgrade pip - name: Install dependencies - run: pip install -r requirements.txt -r dev-requirements.txt + run: | + pip install -r requirements.txt + pip install --group dev - name: Set up Bazel uses: './.github/actions/set-up-bazel' @@ -322,10 +332,15 @@ jobs: cache: pip cache-dependency-path: | requirements.txt - dev-requirements.txt + pyproject.toml + + - name: Upgrade pip + run: python -m pip install --upgrade pip - name: Install dependencies - run: pip install -r requirements.txt -r dev-requirements.txt + run: | + pip install -r requirements.txt + pip install --group dev - name: Set up Bazel uses: './.github/actions/set-up-bazel' @@ -377,10 +392,15 @@ jobs: cache: pip cache-dependency-path: | requirements.txt - dev-requirements.txt + pyproject.toml + + - name: Upgrade pip + run: python -m pip install --upgrade pip - name: Install dependencies - run: pip install -r requirements.txt -r dev-requirements.txt + run: | + pip install -r requirements.txt + pip install --group dev - name: Set up Bazel uses: './.github/actions/set-up-bazel' diff --git a/.github/workflows/cirq_compatibility.yml b/.github/workflows/cirq_compatibility.yml index 5fc76ff0f..18d974bef 100644 --- a/.github/workflows/cirq_compatibility.yml +++ b/.github/workflows/cirq_compatibility.yml @@ -52,7 +52,7 @@ jobs: cache: pip cache-dependency-path: | requirements.txt - dev-requirements.txt + pyproject.toml - name: Install latest dev version of Cirq run: pip install --upgrade cirq~=1.0.dev @@ -60,7 +60,7 @@ jobs: - name: Install qsim dev requirements run: | pip install -r requirements.txt - pip install -r dev-requirements.txt + pip install --group dev - name: Run Python tests env: diff --git a/Dockerfile b/Dockerfile index 7d82f2eb5..100c68e92 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,21 +38,23 @@ COPY ./lib/ /qsim/lib/ COPY ./pybind_interface/ /qsim/lib/ COPY ./qsimcirq_tests/ /qsim/qsimcirq_tests/ COPY ./requirements.txt /qsim/requirements.txt -COPY ./dev-requirements.txt /qsim/dev-requirements.txt +COPY ./pyproject.toml /qsim/pyproject.toml # Create venv to avoid collision between system packages and what we install. -RUN python3 -m venv --upgrade-deps test_env +RUN python3 -m venv --upgrade-deps test_env && \ + . test_env/bin/activate + +WORKDIR /qsim/ # Activate venv. ENV PATH="/test_env/bin:$PATH" # Install qsim requirements. -# hadolint ignore=DL3042 -RUN python3 -m pip install -r /qsim/requirements.txt && \ - python3 -m pip install -r /qsim/dev-requirements.txt +# hadolint ignore=DL3013 +RUN python3 -m pip install --no-cache-dir --upgrade pip && \ + python3 -m pip install --no-cache-dir -r requirements.txt # Compile qsim. -WORKDIR /qsim/ RUN make -j qsim ENTRYPOINT ["/qsim/apps/qsim_base.x"] diff --git a/MANIFEST.in b/MANIFEST.in index 4b487267f..2968589be 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,4 @@ include requirements.txt -include dev-requirements.txt include CMakeLists.txt graft pybind_interface diff --git a/dev-requirements.txt b/dev-requirements.txt deleted file mode 100644 index 2a0fc0a36..000000000 --- a/dev-requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -cmake~=3.28.1 -black~=25.9.0 -flynt~=1.0 -isort[colors]~=6.0.1 -# The global option to pybind11 makes it include CMake files in a location where -# CMake will find them. It makes a crucial difference in some environments. -pybind11[global] -pylint~=4.0.2 -pytest -pytest-xdist -py-cpuinfo -setuptools>=78.1.1 diff --git a/docs/install_qsimcirq.md b/docs/install_qsimcirq.md index 6887a57f1..441dc3ead 100644 --- a/docs/install_qsimcirq.md +++ b/docs/install_qsimcirq.md @@ -10,18 +10,24 @@ directly in C++ code without building and installing the qsimcirq interface. ## Before installation -Prior to installation, consider opening a +Prior to installation, consider creating a [virtual environment](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/). -Prerequisites are included in the +Prerequisites for installing and running qsim are included in the [`requirements.txt`](https://github.com/quantumlib/qsim/blob/main/requirements.txt) -file, and will be automatically installed along with qsimcirq. +file, and will be automatically installed along with qsimcirq when you install +it with pip. -If you'd like to develop qsimcirq, a separate set of dependencies are includes +If you'd like to develop qsimcirq, a separate set of dependencies are defined in the -[`dev-requirements.txt`](https://github.com/quantumlib/qsim/blob/main/dev-requirements.txt) -file. You can install them with `pip3 install -r dev-requirements.txt` or -`pip3 install qsimcirq[dev]`. +[`pyproject.toml`](https://github.com/quantumlib/qsim/blob/main/pyproject.toml) +file. Using pip version 25.1 or higher, you can install them with the following +commands: + +```shell +pip install -r requirements.txt +pip install --group dev +``` ## Linux installation diff --git a/pybind_interface/Dockerfile b/pybind_interface/Dockerfile index 54a4ff511..2b1cb4fd0 100644 --- a/pybind_interface/Dockerfile +++ b/pybind_interface/Dockerfile @@ -26,5 +26,9 @@ WORKDIR /qsim/ # Build pybind code early to cache the results RUN make -j -C /qsim/ pybind +# Install Python development dependencies. +# hadolint ignore=DL3013 +RUN pip install --no-cache-dir --group dev + # Compile and run qsim tests ENTRYPOINT ["make", "-C", "/qsim/", "run-py-tests"] diff --git a/pyproject.toml b/pyproject.toml index 37d714455..a4f3e4613 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,28 +12,135 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Note: there are altogether 3 types of dependencies listed in this file: +# +# [build-system].requires: the packages needed for the build system. This list +# is not stored in the package metadata. +# +# [project].dependencies: other packages are minimally needed to be able to +# install and run qsimcirq. These are things like Cirq, NumPy, etc. Equivalent +# to "install_requires" in setuptools' setup.py. The list gets stored in the +# metadata of the package; when the project is installed by pip, this is the +# specification that is used to install its dependencies. +# +# [dependency-groups].dev: the development dependencies; i.e., what a +# developer needs in order to run unit tests, linters, and formatters. The +# "[dependency-groups]" section is a Python packaging feature introduced in +# 2025. This list is not stored in the metadata of the package. To install the +# development dependencies, use "pip install --group dev". + [build-system] +build-backend = "setuptools.build_meta" requires = [ - "packaging", - "setuptools>=78.1.1", - "pybind11[global]", - # "pip install" from sources needs to build Pybind, which needs CMake too. - "cmake~=3.28.1", + "setuptools>=78.1.1", + "setuptools-scm[toml]>=6.2", + "wheel", ] -build-backend = "setuptools.build_meta" + +[project] +name = "qsimcirq" +description = "High-performance quantum circuit simulator for C++ and Python." +authors = [ + { name = "The qsim/qsimh Developers", email = "qsim-qsimh-dev@googlegroups.com" } +] +maintainers = [ + { name = "Google Quantum AI", email = "quantum-oss-maintainers@google.com" } +] +readme = {file = "README.md", content-type = "text/markdown"} +license = "Apache-2.0" +requires-python = ">=3.10.0" +dynamic = ["version", "dependencies"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: GPU :: NVIDIA CUDA", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Programming Language :: C++", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Scientific/Engineering :: Quantum Computing", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed", +] +keywords = [ + "algorithms", + "cirq", + "nisq", + "quantum algorithm development", + "quantum circuit simulator", + "quantum computer simulator", + "quantum computing", + "quantum programming", + "quantum simulation", + "quantum", + "schrödinger-feynman simulation", + "simulation", + "state vector simulator", +] + +[project.urls] +documentation = "https://quantumai.google/qsim" +download = "https://pypi.org/project/qsimcirq/#files" +homepage = "https://quantumai.google/qsim" +issues = "https://github.com/quantumlib/qsim/issues" +source = "https://github.com/quantumlib/qsim" + +[dependency-groups] +# Development dependencies. Install these with "pip install --group dev". +dev = [ + "black~=25.9.0", + "cibuildwheel", + # Distutils was removed from Python in 3.12. + "setuptools; python_version >= '3.12'", + "flynt~=1.0", + "isort[colors]~=6.0.1", + "py-cpuinfo", + "pylint~=4.0.2", + "pytest", + "pytest-xdist", +] + +[tool.setuptools] +packages = ["qsimcirq"] +package-data = {"qsimcirq" = ["py.typed"]} + +[tool.setuptools.dynamic] +# The next one becomes the value of [project].version. +version = {attr = "qsimcirq._version.__version__"} +# The next one becomes [project].dependencies, equivalent to "install_requires" +# in setuptools' setup.py. "pip install qsim" installs these automatically. +dependencies = {file = ["requirements.txt"] } [tool.cibuildwheel] -test-extras = "dev" +build = "cp310-* cp311-* cp312-* cp313-*" dependency-versions = "latest" enable = ["cpython-prerelease"] environment.PIP_PREFER_BINARY = "1" # Due to package & module name conflict, temporarily move it away to run tests: -before-test = "mv {package}/qsimcirq /tmp" -test-command = "pytest -s -v {package}/qsimcirq_tests/qsimcirq_test.py && mv /tmp/qsimcirq {package}" +before-test = "pip install --group dev && mv {package}/qsimcirq /tmp" +test-command = """ +pytest -s -v {package}/qsimcirq_tests/qsimcirq_test.py && +mv /tmp/qsimcirq {package} +""" [tool.cibuildwheel.macos] -before-build = "brew install -q libomp llvm@19 && brew unlink libomp && brew unlink llvm@19 && brew link --force libomp && brew link --force llvm@19" -repair-wheel-command = "delocate-listdeps {wheel} && delocate-wheel --verbose --require-archs {delocate_archs} -w {dest_dir} {wheel}" +before-build = """ +brew install -q libomp llvm@19 && +brew unlink libomp && +brew unlink llvm@19 && +brew link --force libomp && +brew link --force llvm@19 +""" +repair-wheel-command = """ +delocate-listdeps {wheel} && +delocate-wheel --verbose --require-archs {delocate_archs} -w {dest_dir} {wheel} +""" [tool.cibuildwheel.linux] manylinux-x86_64-image = "manylinux2014" diff --git a/requirements.txt b/requirements.txt index 21e8a4ade..a927b3363 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,17 @@ +# Installation and run-time dependencies for qsimcirq. This file is read +# by pyproject.toml. + absl-py cirq-core~=1.0 -numpy>=1.26.0 +numpy>=1.26.0,<2.0; python_version < '3.11' +numpy>=2.0; python_version >= '3.11' + +# These are needed because installing qsimcirq in some environments may require +# pip to compile Pybind for that specific platform: +cmake~=3.28.1 +pybind11[global] + +# These are transitive dependencies we need to constrain to avoid unresolvable +# installation conflicts due to them requiring higher Python versions: +scipy<1.16; python_version < '3.11' +contourpy<1.3; python_version < '3.11' diff --git a/setup.py b/setup.py index 328b27cdd..d3ccfa54c 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,10 @@ from setuptools import Extension, setup from setuptools.command.build_ext import build_ext +# qsimcirq/_version.py contains the source of truth for the version nhumber. +__version__ = runpy.run_path("qsimcirq/_version.py")["__version__"] +assert __version__, "The version string must not be empty" + class CMakeExtension(Extension): def __init__(self, name, sourcedir=""): @@ -67,6 +71,8 @@ def build_extension(self, ext): "-DCMAKE_CUDA_COMPILER=nvcc", ] + # Append additional CMake arguments from the environment variable. + # This is e.g. used by cibuildwheel to force a certain C++ standard. additional_cmake_args = os.environ.get("CMAKE_ARGS", "") if additional_cmake_args: cmake_args += additional_cmake_args.split() @@ -110,9 +116,7 @@ def build_extension(self, ext): env = os.environ.copy() cxxflags = env.get("CXXFLAGS", "") - env["CXXFLAGS"] = ( - f'{cxxflags} -DVERSION_INFO=\\"{self.distribution.get_version()}\\"' - ) + env["CXXFLAGS"] = f'{cxxflags} -DVERSION_INFO=\\"{__version__}\\"' if not os.path.exists(self.build_temp): os.makedirs(self.build_temp) subprocess.check_call( @@ -124,42 +128,7 @@ def build_extension(self, ext): ) -with open("requirements.txt") as f: - requirements = [ - line.strip() for line in f if line.strip() and not line.strip().startswith("#") - ] -with open("dev-requirements.txt") as f: - dev_requirements = [ - line.strip() for line in f if line.strip() and not line.strip().startswith("#") - ] - -description = "Schrödinger and Schrödinger-Feynman simulators for quantum circuits." - -# README file as long_description. -with open("README.md", encoding="utf-8") as f: - long_description = f.read() - -__version__ = runpy.run_path("qsimcirq/_version.py")["__version__"] -if not __version__: - raise ValueError("Version string cannot be empty") - setup( - name="qsimcirq", - version=__version__, - url="https://github.com/quantumlib/qsim", - author="The qsim/qsimh Developers", - author_email="qsim-qsimh-dev@googlegroups.com", - maintainer="Google Quantum AI", - maintainer_email="quantum-oss-maintainers@google.com", - python_requires=">=3.10.0", - install_requires=requirements, - extras_require={ - "dev": dev_requirements, - }, - license="Apache-2.0", - description=description, - long_description=long_description, - long_description_content_type="text/markdown", ext_modules=[ CMakeExtension("qsimcirq/qsim_avx512"), CMakeExtension("qsimcirq/qsim_avx2"), @@ -170,49 +139,5 @@ def build_extension(self, ext): CMakeExtension("qsimcirq/qsim_decide"), CMakeExtension("qsimcirq/qsim_hip"), ], - cmdclass=dict(build_ext=CMakeBuild), - zip_safe=False, - packages=["qsimcirq"], - package_data={"qsimcirq": ["py.typed"]}, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Environment :: GPU :: NVIDIA CUDA", - "Intended Audience :: Developers", - "Intended Audience :: Science/Research", - "Operating System :: MacOS :: MacOS X", - "Operating System :: Microsoft :: Windows", - "Operating System :: POSIX :: Linux", - "Programming Language :: C++", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Topic :: Scientific/Engineering :: Quantum Computing", - "Topic :: Software Development :: Libraries :: Python Modules", - "Typing :: Typed", - ], - keywords=[ - "algorithms", - "api", - "application programming interface", - "cirq", - "google quantum", - "google", - "nisq", - "python", - "quantum algorithm development", - "quantum circuit simulator", - "quantum computer simulator", - "quantum computing", - "quantum computing research", - "quantum programming", - "quantum simulation", - "quantum", - "schrödinger-feynman simulation", - "sdk", - "simulation", - "state vector simulator", - "software development kit", - ], + cmdclass={"build_ext": CMakeBuild}, )