diff --git a/.pylintrc b/.pylintrc index a77a7e2b0..39450d38f 100644 --- a/.pylintrc +++ b/.pylintrc @@ -90,7 +90,7 @@ ignored-modules= # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the # number of processors available to use, and will cap the count on Windows to # avoid hangs. -jobs=0 +# jobs=0 # Control the amount of potential inferred values when inferring a single # object. This can help the performance when dealing with large functions or diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a6dd7f993..e4b603e49 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -241,10 +241,6 @@ to run it: check/mypy ``` -If your computer has multiple processor cores, you can add the option `-j 0` to the command above to -make Mypy run in parallel for a substantial speed increase. - - ### Linting and formatting Code should meet common style standards for Python and be free of error-prone constructs. We use @@ -267,9 +263,12 @@ for formatting code, and provide scripts to run them. * To run the linter: ```shell - check/pylint-changed-files + check/pylint-changed-files -j 0 ``` + (The option `-j 0` tells pylint to use parallel processes; omit it if it causes problems on your + system.) + ### Testing and test coverage When new functions, classes, and files are introduced, they should also have corresponding tests. @@ -290,14 +289,15 @@ tests, follow these general principles: messages. We use [pytest](https://docs.pytest.org) to run our tests and -[pytest-cov](https://pytest-cov.readthedocs.io) to compute coverage. +[pytest-cov](https://pytest-cov.readthedocs.io) to compute coverage. There are wrapper scripts +in the `check/` subdirectory to run these programs. -* While developing, periodically check that changes do not break anything. For fast checks, use - `pytest -m "not slow" PATH`, where `PATH` is a directory or pytest file to test. +* During development, periodically check that code changes do not break anything. For fast checks, + run `check/pytest -m "not slow" -n auto PATH ...`, where `PATH ...` is one or more directories + or `_test.py` files to test. (The `-n auto` option makes pytest use parallel processes; omit it + if it causes problems on your system.) -* After finishing a task, run `check/pytest` to test all of the OpenFermion code. If your system - has multiple processor cores, you can add the option `-n auto` to make pytest use multiple - parallel processes for a speed increase. (Beware, though, that this is resource-intensive.) +* After finishing a task, run all tests with `check/pytest -n auto`. We don't require 100% coverage, but coverage should be very high, and any uncovered code must be annotated with `# pragma: no cover`. To ignore coverage of a single line, place `# pragma: no cover` @@ -306,13 +306,12 @@ cover` comment on its own line. Note, however, that these annotations should be ### Final checks -After a task is finished, run each of the following to make sure everything passes all the tests: +After the work planned for a pull request is finished, run each of the following to make sure +everything passes all tests: -* `check/format-incremental` (and `check/format-incremental --apply` to auto-fix format problems) -* `check/pylint -j 0` -* `check/mypy` -* `check/pytest -n auto` -* `check/pytest-and-incremental-coverage` +```shell +check/all +``` ### Pull requests and code reviews diff --git a/check/all b/check/all index 1a09b79cc..04946a011 100755 --- a/check/all +++ b/check/all @@ -17,22 +17,34 @@ # This script is modeled in part on Cirq's check/all script. declare -r usage="\ -Usage: ${0##*/} [--help] [--only-changed-files] [--apply-format-changes] [BASE_REV] +Usage: ${0##*/} [OPTIONS] [BASE_REV] -If the first argument given on the command line is the option --help or -h, -this program prints usage information and then exits. - -With no arguments, this runs multiple checks on the code base. These tests -are based on the programs in the checks/ subdirectory. +Runs multiple checks on the code base. -If --apply-format-changes is specified, the flag --apply will be passed to -check/format-incremental to apply the format changes suggested by the +If --apply-changes (or -a) is given, the flag --apply will be passed to +check/format-incremental to make it apply format changes suggested by the formatter. -You can specify a base git revision to compare against (i.e., to use when -determining whether or not a file is considered to have changed). If given, -the argument BASE_REV is passed on to tests that can use it, such as -check/pytest-and-incremental-coverage." +If --only-changed-files (or -c) is given, most checks will be limited to the +files that have changed since the BASE_REV version. If BASE_REV is not given, +the version at the end of the base branch (origin/main) will be used. + +By default, this program runs checks in parallel mode. To prevent this, use the +option --no-parallel (or -p). + +You can specify a base git revision to compare against (that is, the revision +to be used when determining whether a file is considered to have changed). If +given, the argument BASE_REV is passed on to tests that can use it, such as +check/pytest-and-incremental-coverage. + +If the first argument given on the command line is the option --help or -h, +this program prints usage information and then exits. + +Summary of available options: + --apply-changes Pass the flag --apply to check/format-incremental + --no-parallel Do not pass the parallelism flags to the check programs + --only-changed-files Limit checks to files that have changed since BASE_REV + -h or --help Show this help message and exit" set -eo pipefail -o errtrace shopt -s inherit_errexit @@ -51,53 +63,71 @@ function error() { declare -a rev=() declare -a apply_arg=() declare only_changed="" -for arg in "$@"; do - case "${arg}" in +declare no_parallel="" + +while [[ $# -gt 0 ]]; do + case "$1" in + -a | --apply-changes | --apply-format-changes) + apply_arg=( "--apply" ) + ;; + -c | --only-changed-files) + only_changed="true" + ;; -h | --help) echo "${usage}" exit 0 ;; - --apply-format-changes) - apply_arg=( "--apply" ) - shift - ;; - --only-changed-files) - only_changed="true" - shift + -p | --no-parallel) + no_parallel="true" ;; -*) - error "Invalid option '${arg}'" + error "Invalid option '$1'" error "See '$0 --help' for the list of supported options." exit 1 ;; *) - if ! rev=( "$(git rev-parse --verify --end-of-options "${arg}^{commit}")" ); then - error "No revision '${arg}'" + if ! rev=( "$(git rev-parse --verify --end-of-options "$1^{commit}")" ); then + error "No revision '$1'" exit 1 fi ;; esac + shift done # ~~~~ Run the tests ~~~~ declare -a errors=() +declare -a pylint_j=() +declare -a pytest_n=() +declare -a mypy_n=() function run() { - echo "Running $* ..." + echo "~~~~ Running $* ~~~~" "$@" || errors+=( "$* failed" ) - echo } +# Count only the physical cores (usually fewer than logical cores). This leaves +# some CPU resources for OpenFermion modules that use multiple processes. +cpus=$(python3 -c 'import psutil; print(psutil.cpu_count(logical=False))') + +if [[ -z "${no_parallel}" ]]; then + # Pylint & pytest can auto-detect the number of CPUs, but not mypy. To be + # consistent, this uses the same explicit number for all of them. + pylint_j=("-j" "${cpus}") + pytest_n=("-n" "${cpus}") + mypy_n=("-n" "${cpus}") +fi + if [[ -n "${only_changed}" ]]; then run check/format-incremental "${rev[@]}" "${apply_arg[@]}" - run check/pylint-changed-files "${rev[@]}" + run check/pylint-changed-files "${pylint_j[@]}" "${rev[@]}" else run check/format-incremental "${rev[@]}" "${apply_arg[@]}" --all - run check/pylint "${rev[@]}" + run check/pylint "${pylint_j[@]}" "${rev[@]}" fi -run check/mypy -run check/pytest-and-incremental-coverage "${rev[@]}" +run check/mypy "${mypy_n[@]}" +run check/pytest-and-incremental-coverage "${pytest_n[@]}" "${rev[@]}" run check/shellcheck run check/nbformat diff --git a/check/pylint b/check/pylint index 9239f8c1d..16c2f6d07 100755 --- a/check/pylint +++ b/check/pylint @@ -24,4 +24,4 @@ cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" || exit 1 cd "$(git rev-parse --show-toplevel)" || exit 1 -pylint "$@" src dev_tools +pylint "$@" src dev_tools && echo "Pylint completed successfully." diff --git a/check/pylint-changed-files b/check/pylint-changed-files index 69500932a..75c83bfdf 100755 --- a/check/pylint-changed-files +++ b/check/pylint-changed-files @@ -43,25 +43,37 @@ thisdir=$(dirname "${BASH_SOURCE[0]:?}") || exit $? repo_dir=$(git -C "${thisdir}" rev-parse --show-toplevel) || exit $? cd "${repo_dir}" || exit $? -# Figure out which revision to compare against. -if [ -n "$1" ] && [[ $1 != -* ]]; then - if ! git rev-parse --verify --quiet --no-revs "$1^{commit}"; then - echo -e "\033[31mNo revision '$1'.\033[0m" >&2 +# Figure out which revision to compare against and gather other arguments. +declare -a pylint_args=() +rev="" + +for arg in "$@"; do + if [[ "${arg}" == -* ]]; then + pylint_args+=( "${arg}" ) + elif [[ -z "${rev}" ]] && + git rev-parse --verify --quiet --no-revs "${arg}^{commit}" &> /dev/null; then + rev="${arg}" + else + pylint_args+=( "${arg}" ) + fi +done + +if [[ -z "${rev}" ]]; then + for candidate in upstream/main origin/main main; do + if [[ "$(git cat-file -t "${candidate}" 2>/dev/null)" == "commit" ]]; then + rev="${candidate}" + break + fi + done + if [[ -z "${rev}" ]]; then + echo -e "\033[31mNo default revision found to compare against. \ +Please specify a base revision (e.g. 'origin/main' or 'HEAD~1') as an argument.\033[0m" >&2 exit 1 fi - rev=$1 -elif [ "$(git cat-file -t "upstream/main" 2> /dev/null)" == "commit" ]; then - rev=upstream/main -elif [ "$(git cat-file -t "origin/main" 2> /dev/null)" == "commit" ]; then - rev=origin/main -elif [ "$(git cat-file -t "main" 2> /dev/null)" == "commit" ]; then - rev=main -else - echo -e "\033[31mNo default revision found to compare against. Argument #1 must be what to diff against (e.g. 'origin/main' or 'HEAD~1').\033[0m" >&2 - exit 1 fi + base=$(git merge-base "${rev}" HEAD) -if [ "$(git rev-parse "${rev}")" == "${base}" ]; then +if [[ "$(git rev-parse "${rev}")" == "${base}" ]]; then echo -e "Comparing against revision '${rev}'." >&2 else echo -e "Comparing against revision '${rev}' (merge base ${base})." >&2 @@ -78,7 +90,7 @@ num_changed=${#changed[@]} # Run it. echo "Found ${num_changed} lintable files associated with changes." >&2 -if [ "${num_changed}" -eq 0 ]; then +if [[ "${num_changed}" -eq 0 ]]; then exit 0 fi -env PYTHONPATH=dev_tools pylint --jobs=0 "${changed[@]}" +env PYTHONPATH=dev_tools pylint "${pylint_args[@]}" "${changed[@]}" diff --git a/check/pytest-and-incremental-coverage b/check/pytest-and-incremental-coverage index ed6c1e506..a716ffa82 100755 --- a/check/pytest-and-incremental-coverage +++ b/check/pytest-and-incremental-coverage @@ -36,30 +36,40 @@ ################################################################################ # Get the working directory to the repo root. -cd "$( dirname "${BASH_SOURCE[0]}" )" || exit 1 +cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1 cd "$(git rev-parse --show-toplevel)" || exit 1 -# Figure out which revision to compare against. -if [[ -n "$1" && $1 != -* ]]; then - if [[ "$(git cat-file -t "$1" 2> /dev/null)" != "commit" ]]; then - echo -e "\033[31mNo revision '$1'.\033[0m" >&2 +# Figure out which revision to compare against and gather other arguments. +declare -a pytest_args=() +rev="" + +for arg in "$@"; do + if [[ "${arg}" == -* ]]; then + pytest_args+=( "${arg}" ) + elif [[ -z "${rev}" ]] && + git rev-parse --verify --quiet --no-revs "${arg}^{commit}" &> /dev/null; then + rev="${arg}" + else + pytest_args+=( "${arg}" ) + fi +done + +if [[ -z "${rev}" ]]; then + for candidate in upstream/main origin/main main; do + if [[ "$(git cat-file -t "${candidate}" 2>/dev/null)" == "commit" ]]; then + rev="${candidate}" + break + fi + done + if [[ -z "${rev}" ]]; then + echo -e "\033[31mNo default revision found to compare against. \ +Please specify a base revision (e.g. 'origin/main' or 'HEAD~1') as an argument.\033[0m" >&2 exit 1 fi - rev=$1 -elif [ "$(git cat-file -t upstream/main 2> /dev/null)" == "commit" ]; then - rev=upstream/main -elif [ "$(git cat-file -t origin/main 2> /dev/null)" == "commit" ]; then - rev=origin/main -elif [ "$(git cat-file -t main 2> /dev/null)" == "commit" ]; then - rev=main -else - echo -e "\033[31mNo default revision found to compare against. Argument #1 must be what to diff against (e.g. 'origin/main' or 'HEAD~1').\033[0m" >&2 - exit 1 fi -# shellcheck disable=SC2086 -base="$(git merge-base ${rev} HEAD)" -# shellcheck disable=SC2086 -if [ "$(git rev-parse ${rev})" == "${base}" ]; then + +base="$(git merge-base "${rev}" HEAD)" +if [[ "$(git rev-parse "${rev}")" == "${base}" ]]; then echo -e "Comparing against revision '${rev}'." >&2 else echo -e "Comparing against revision '${rev}' (merge base ${base})." >&2 @@ -68,7 +78,7 @@ fi # Run tests while producing coverage files. check/pytest . \ - -n auto \ + "${pytest_args[@]}" \ --actually-quiet \ --cov \ --cov-report=annotate @@ -82,7 +92,7 @@ cover_result=$? find . | grep "\.py,cover$" | xargs rm -f # Report result. -if [ "${pytest_result}" -ne "0" ] || [ "${cover_result}" -ne "0" ]; then +if [[ "${pytest_result}" -ne "0" ]] || [[ "${cover_result}" -ne "0" ]]; then exit 1 fi exit 0 diff --git a/check/shellcheck b/check/shellcheck index 1c796506c..85be18a98 100755 --- a/check/shellcheck +++ b/check/shellcheck @@ -81,5 +81,6 @@ if (( opt_dry_run )); then printf '\\\n %s ' "${our_shell_scripts[@]}" printf '\\\n;\n' else - shellcheck "${shellcheck_options[@]}" "${our_shell_scripts[@]}" + shellcheck "${shellcheck_options[@]}" "${our_shell_scripts[@]}" && \ + echo "shellcheck completed successfully." fi diff --git a/conftest.py b/conftest.py new file mode 100644 index 000000000..080bcba9d --- /dev/null +++ b/conftest.py @@ -0,0 +1,68 @@ +# pylint: disable=wrong-import-position,wrong-import-order +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys + +# Ensure src/ is in sys.path so that the OpenFermion utils module can be +# imported at Pytest startup time. +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "src"))) + +from openfermion.config import set_threading_limits + +set_threading_limits() + +import random +from typing import Any + +import numpy as np +import pytest + + +def pytest_configure(config: Any) -> None: + # Set seeds for collection-time parameterization. + random.seed(0) + np.random.seed(0) + + os.environ['CIRQ_TESTING'] = "true" + + +@pytest.fixture(autouse=True) +def set_random_seed() -> None: + """Set a fixed random seed when testing.""" + random.seed(0) + np.random.seed(0) + + +@pytest.fixture(autouse=True, scope="session") +def set_threadpool_limits(): + """Limit number of threads to prevent oversubscription with pytest-xdist. + + This only has an effect if the Python threadpoolctl package is installed, + and it only influences parallelism in some numerical libraries used in + packages such as NumPy. + """ + try: + import threadpoolctl # type: ignore[import-untyped, import-not-found] + except ImportError: + yield + return + + if "PYTEST_XDIST_WORKER_COUNT" in os.environ: + from openfermion.config import get_available_cpu_count + + # Limit native library thread pools for this worker. + with threadpoolctl.threadpool_limits(limits=get_available_cpu_count()): + yield + else: + yield diff --git a/dev_tools/requirements/deps/pytest.txt b/dev_tools/requirements/deps/pytest.txt index 0dca16a9f..552dcefc0 100644 --- a/dev_tools/requirements/deps/pytest.txt +++ b/dev_tools/requirements/deps/pytest.txt @@ -6,4 +6,6 @@ pytest-retry pytest-xdist nbformat +psutil tensorflow-docs +threadpoolctl diff --git a/dev_tools/requirements/envs/dev.env.txt b/dev_tools/requirements/envs/dev.env.txt index f1c4bc2ad..f8dabf30e 100644 --- a/dev_tools/requirements/envs/dev.env.txt +++ b/dev_tools/requirements/envs/dev.env.txt @@ -1581,6 +1581,29 @@ protobuf==7.35.0 \ --hash=sha256:f05bcadf9a2a6b8dda047007075135fb7d08c73d9177aabc067e1be46881a201 \ --hash=sha256:fcbe42a4ac09d3ec9c987ddfcd956afd0b15f1ff613bd8371bde9405ffd5c8e5 # via tensorflow-docs +psutil==7.2.2 \ + --hash=sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372 \ + --hash=sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9 \ + --hash=sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841 \ + --hash=sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63 \ + --hash=sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979 \ + --hash=sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a \ + --hash=sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b \ + --hash=sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9 \ + --hash=sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee \ + --hash=sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312 \ + --hash=sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b \ + --hash=sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9 \ + --hash=sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e \ + --hash=sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc \ + --hash=sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1 \ + --hash=sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf \ + --hash=sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea \ + --hash=sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988 \ + --hash=sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486 \ + --hash=sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00 \ + --hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8 + # via -r dev_tools/requirements/deps/pytest.txt pubchempy==1.0.5 \ --hash=sha256:08f0b2a82a5caa5d61e14935d655da554602d7b5686fe661ab584c882ffff623 \ --hash=sha256:e936cfed31fa194042ad463be3c803dde5b12ef2f795caf336e3114127c34fa0 @@ -2018,6 +2041,10 @@ sympy==1.14.0 \ tensorflow-docs==2025.2.19.33219 \ --hash=sha256:b52682f3739110ce804af701f9ab5ac1efe07760109a5b5640769851d7d14b19 # via -r dev_tools/requirements/deps/pytest.txt +threadpoolctl==3.6.0 \ + --hash=sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb \ + --hash=sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e + # via -r dev_tools/requirements/deps/pytest.txt tomli==2.4.1 ; python_full_version <= '3.11' \ --hash=sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853 \ --hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe \ diff --git a/dev_tools/requirements/envs/pylint.env.txt b/dev_tools/requirements/envs/pylint.env.txt index 5868211c4..82a7bd938 100644 --- a/dev_tools/requirements/envs/pylint.env.txt +++ b/dev_tools/requirements/envs/pylint.env.txt @@ -1196,6 +1196,31 @@ protobuf==7.35.0 \ # via # -c dev_tools/requirements/envs/dev.env.txt # tensorflow-docs +psutil==7.2.2 \ + --hash=sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372 \ + --hash=sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9 \ + --hash=sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841 \ + --hash=sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63 \ + --hash=sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979 \ + --hash=sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a \ + --hash=sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b \ + --hash=sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9 \ + --hash=sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee \ + --hash=sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312 \ + --hash=sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b \ + --hash=sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9 \ + --hash=sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e \ + --hash=sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc \ + --hash=sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1 \ + --hash=sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf \ + --hash=sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea \ + --hash=sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988 \ + --hash=sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486 \ + --hash=sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00 \ + --hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8 + # via + # -c dev_tools/requirements/envs/dev.env.txt + # -r dev_tools/requirements/deps/pytest.txt pubchempy==1.0.5 \ --hash=sha256:08f0b2a82a5caa5d61e14935d655da554602d7b5686fe661ab584c882ffff623 \ --hash=sha256:e936cfed31fa194042ad463be3c803dde5b12ef2f795caf336e3114127c34fa0 @@ -1578,6 +1603,12 @@ tensorflow-docs==2025.2.19.33219 \ # via # -c dev_tools/requirements/envs/dev.env.txt # -r dev_tools/requirements/deps/pytest.txt +threadpoolctl==3.6.0 \ + --hash=sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb \ + --hash=sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e + # via + # -c dev_tools/requirements/envs/dev.env.txt + # -r dev_tools/requirements/deps/pytest.txt tomli==2.4.1 ; python_full_version <= '3.11' \ --hash=sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853 \ --hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe \ diff --git a/dev_tools/requirements/envs/pytest-extra.env.txt b/dev_tools/requirements/envs/pytest-extra.env.txt index acce1f0dd..e6568c5fb 100644 --- a/dev_tools/requirements/envs/pytest-extra.env.txt +++ b/dev_tools/requirements/envs/pytest-extra.env.txt @@ -1170,6 +1170,31 @@ protobuf==7.35.0 \ # via # -c dev_tools/requirements/envs/dev.env.txt # tensorflow-docs +psutil==7.2.2 \ + --hash=sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372 \ + --hash=sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9 \ + --hash=sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841 \ + --hash=sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63 \ + --hash=sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979 \ + --hash=sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a \ + --hash=sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b \ + --hash=sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9 \ + --hash=sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee \ + --hash=sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312 \ + --hash=sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b \ + --hash=sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9 \ + --hash=sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e \ + --hash=sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc \ + --hash=sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1 \ + --hash=sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf \ + --hash=sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea \ + --hash=sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988 \ + --hash=sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486 \ + --hash=sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00 \ + --hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8 + # via + # -c dev_tools/requirements/envs/dev.env.txt + # -r dev_tools/requirements/deps/pytest.txt pubchempy==1.0.5 \ --hash=sha256:08f0b2a82a5caa5d61e14935d655da554602d7b5686fe661ab584c882ffff623 \ --hash=sha256:e936cfed31fa194042ad463be3c803dde5b12ef2f795caf336e3114127c34fa0 @@ -1546,6 +1571,12 @@ tensorflow-docs==2025.2.19.33219 \ # via # -c dev_tools/requirements/envs/dev.env.txt # -r dev_tools/requirements/deps/pytest.txt +threadpoolctl==3.6.0 \ + --hash=sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb \ + --hash=sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e + # via + # -c dev_tools/requirements/envs/dev.env.txt + # -r dev_tools/requirements/deps/pytest.txt tomli==2.4.1 ; python_full_version <= '3.11' \ --hash=sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853 \ --hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe \ diff --git a/dev_tools/requirements/envs/pytest.env.txt b/dev_tools/requirements/envs/pytest.env.txt index afad91589..bc704731f 100644 --- a/dev_tools/requirements/envs/pytest.env.txt +++ b/dev_tools/requirements/envs/pytest.env.txt @@ -1076,6 +1076,31 @@ protobuf==7.35.0 \ # via # -c dev_tools/requirements/envs/dev.env.txt # tensorflow-docs +psutil==7.2.2 \ + --hash=sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372 \ + --hash=sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9 \ + --hash=sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841 \ + --hash=sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63 \ + --hash=sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979 \ + --hash=sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a \ + --hash=sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b \ + --hash=sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9 \ + --hash=sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee \ + --hash=sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312 \ + --hash=sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b \ + --hash=sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9 \ + --hash=sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e \ + --hash=sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc \ + --hash=sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1 \ + --hash=sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf \ + --hash=sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea \ + --hash=sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988 \ + --hash=sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486 \ + --hash=sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00 \ + --hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8 + # via + # -c dev_tools/requirements/envs/dev.env.txt + # -r dev_tools/requirements/deps/pytest.txt pubchempy==1.0.5 \ --hash=sha256:08f0b2a82a5caa5d61e14935d655da554602d7b5686fe661ab584c882ffff623 \ --hash=sha256:e936cfed31fa194042ad463be3c803dde5b12ef2f795caf336e3114127c34fa0 @@ -1433,6 +1458,12 @@ tensorflow-docs==2025.2.19.33219 \ # via # -c dev_tools/requirements/envs/dev.env.txt # -r dev_tools/requirements/deps/pytest.txt +threadpoolctl==3.6.0 \ + --hash=sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb \ + --hash=sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e + # via + # -c dev_tools/requirements/envs/dev.env.txt + # -r dev_tools/requirements/deps/pytest.txt tomli==2.4.1 ; python_full_version <= '3.11' \ --hash=sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853 \ --hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe \ diff --git a/dev_tools/requirements/max_compat/pytest-max-compat.env.txt b/dev_tools/requirements/max_compat/pytest-max-compat.env.txt index 30789a0db..149899e82 100644 --- a/dev_tools/requirements/max_compat/pytest-max-compat.env.txt +++ b/dev_tools/requirements/max_compat/pytest-max-compat.env.txt @@ -3,35 +3,26 @@ absl-py==2.4.0 \ --hash=sha256:88476fd881ca8aab94ffa78b7b6c632a782ab3ba1cd19c9bd423abc4fb4cd28d \ --hash=sha256:8c6af82722b35cf71e0f4d1d47dcaebfff286e27110a99fc359349b247dfb5d4 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # tensorflow-docs + # via tensorflow-docs astor==0.8.1 \ --hash=sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5 \ --hash=sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # tensorflow-docs + # via tensorflow-docs attrs==26.1.0 \ --hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 \ --hash=sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32 # via - # -c dev_tools/requirements/max_compat/dev.env.txt # cirq-core # jsonschema # referencing backports-asyncio-runner==1.2.0 ; python_full_version < '3.11' \ --hash=sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5 \ --hash=sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # pytest-asyncio + # via pytest-asyncio certifi==2026.5.20 \ --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \ --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # requests + # via requests charset-normalizer==3.4.7 \ --hash=sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc \ --hash=sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c \ @@ -162,26 +153,21 @@ charset-normalizer==3.4.7 \ --hash=sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6 \ --hash=sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79 \ --hash=sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # requests + # via requests cirq-core==1.4.1 ; python_full_version < '3.11' \ --hash=sha256:869db60413265c41a8206854c1d4ca9bad5fac9cfd7c6a10685b5a6d516defa0 # via # -c dev_tools/requirements/deps/oldest-versions.txt - # -c dev_tools/requirements/max_compat/dev.env.txt # -r dev_tools/requirements/deps/runtime.txt cirq-core==1.6.1 ; python_full_version >= '3.11' \ --hash=sha256:fbc809d7c6a228762ad92bb7870dc16f55b76728fcc7ef4c7aaceb7e5c63e8e6 # via # -c dev_tools/requirements/deps/oldest-versions.txt - # -c dev_tools/requirements/max_compat/dev.env.txt # -r dev_tools/requirements/deps/runtime.txt colorama==0.4.6 ; sys_platform == 'win32' \ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 # via - # -c dev_tools/requirements/max_compat/dev.env.txt # pytest # tqdm contourpy==1.3.2 \ @@ -242,9 +228,7 @@ contourpy==1.3.2 \ --hash=sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe \ --hash=sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0 \ --hash=sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # matplotlib + # via matplotlib coverage==7.14.1 \ --hash=sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86 \ --hash=sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd \ @@ -352,45 +336,31 @@ coverage==7.14.1 \ --hash=sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c \ --hash=sha256:fc459e5d73be2d6332fcfe8dbf3d8994671fe33c700f4565988ecfa511547253 \ --hash=sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # pytest-cov + # via pytest-cov cycler==0.12.1 \ --hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 \ --hash=sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # matplotlib + # via matplotlib deprecation==2.1.0 \ --hash=sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff \ --hash=sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # -r dev_tools/requirements/deps/runtime.txt + # via -r dev_tools/requirements/deps/runtime.txt duet==0.2.9 \ --hash=sha256:a16088b68b0faee8aee12cdf4d0a8af060ed958badb44f3e32f123f13f64119a \ --hash=sha256:d6fa39582e6a3dce1096c47e5fbcbda648a633eed94a38943e68662afa2587f3 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # cirq-core + # via cirq-core exceptiongroup==1.3.1 ; python_full_version < '3.11' \ --hash=sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219 \ --hash=sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # pytest + # via pytest execnet==2.1.2 \ --hash=sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd \ --hash=sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # pytest-xdist + # via pytest-xdist fastjsonschema==2.21.2 \ --hash=sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463 \ --hash=sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # nbformat + # via nbformat fonttools==4.63.0 \ --hash=sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69 \ --hash=sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c \ @@ -442,9 +412,7 @@ fonttools==4.63.0 \ --hash=sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b \ --hash=sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af \ --hash=sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # matplotlib + # via matplotlib h5py==3.16.0 \ --hash=sha256:099f2525c9dcf28de366970a5fb34879aab20491589fa89ce2863a84218bb524 \ --hash=sha256:0f456f556e4e2cebeebd9d66adf8dc321770a42593494a0b6f0af54a7567b242 \ @@ -494,45 +462,31 @@ h5py==3.16.0 \ --hash=sha256:fb1720028d99040792bb2fb31facb8da44a6f29df7697e0b84f0d79aff2e9bd3 \ --hash=sha256:ff24039e2573297787c3063df64b60aab0591980ac898329a08b0320e0cf2527 \ --hash=sha256:ffbab2fedd6581f6aa31cf1639ca2cb86e02779de525667892ebf4cc9fd26434 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # -r dev_tools/requirements/deps/runtime.txt + # via -r dev_tools/requirements/deps/runtime.txt idna==3.17 \ --hash=sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c \ --hash=sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # requests + # via requests iniconfig==2.3.0 \ --hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \ --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # pytest + # via pytest jinja2==3.1.6 \ --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # tensorflow-docs + # via tensorflow-docs jsonschema==4.26.0 \ --hash=sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326 \ --hash=sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # nbformat + # via nbformat jsonschema-specifications==2025.9.1 \ --hash=sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe \ --hash=sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # jsonschema + # via jsonschema jupyter-core==5.9.1 \ --hash=sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508 \ --hash=sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # nbformat + # via nbformat kiwisolver==1.5.0 \ --hash=sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9 \ --hash=sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679 \ @@ -651,9 +605,7 @@ kiwisolver==1.5.0 \ --hash=sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3 \ --hash=sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09 \ --hash=sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # matplotlib + # via matplotlib markupsafe==3.0.3 \ --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \ --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \ @@ -744,9 +696,7 @@ markupsafe==3.0.3 \ --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \ --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \ --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # jinja2 + # via jinja2 matplotlib==3.10.9 \ --hash=sha256:09218df8a93712bd6ea133e83a153c755448cf7868316c531cffcc43f69d1cc9 \ --hash=sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42 \ @@ -803,27 +753,21 @@ matplotlib==3.10.9 \ --hash=sha256:f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921 \ --hash=sha256:f76e640a5268850bfda54b5131b1b1941cc685e42c5fa98ed9f2d64038308cba \ --hash=sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # cirq-core + # via cirq-core mpmath==1.3.0 \ --hash=sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f \ --hash=sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # sympy + # via sympy nbformat==5.10.4 \ --hash=sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a \ --hash=sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b # via - # -c dev_tools/requirements/max_compat/dev.env.txt # -r dev_tools/requirements/deps/pytest.txt # tensorflow-docs networkx==3.4.2 \ --hash=sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1 \ --hash=sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f # via - # -c dev_tools/requirements/max_compat/dev.env.txt # -r dev_tools/requirements/deps/runtime.txt # cirq-core numpy==1.26.4 \ @@ -864,7 +808,6 @@ numpy==1.26.4 \ --hash=sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3 \ --hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f # via - # -c dev_tools/requirements/max_compat/dev.env.txt # -r dev_tools/requirements/deps/runtime.txt # cirq-core # contourpy @@ -876,7 +819,6 @@ packaging==26.2 \ --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \ --hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661 # via - # -c dev_tools/requirements/max_compat/dev.env.txt # deprecation # matplotlib # pytest @@ -936,9 +878,7 @@ pandas==2.3.3 \ --hash=sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b \ --hash=sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c \ --hash=sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # cirq-core + # via cirq-core pillow==12.2.0 \ --hash=sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9 \ --hash=sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5 \ @@ -1031,20 +971,15 @@ pillow==12.2.0 \ --hash=sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7 \ --hash=sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06 \ --hash=sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # matplotlib + # via matplotlib platformdirs==4.10.0 \ --hash=sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7 \ --hash=sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # jupyter-core + # via jupyter-core pluggy==1.6.0 \ --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 # via - # -c dev_tools/requirements/max_compat/dev.env.txt # pytest # pytest-cov protobuf==7.35.0 \ @@ -1056,32 +991,46 @@ protobuf==7.35.0 \ --hash=sha256:c13f325cf242bad135c350629eeb5d54b24228eb472fb3e2e9ebbd4c5dc20ca0 \ --hash=sha256:f05bcadf9a2a6b8dda047007075135fb7d08c73d9177aabc067e1be46881a201 \ --hash=sha256:fcbe42a4ac09d3ec9c987ddfcd956afd0b15f1ff613bd8371bde9405ffd5c8e5 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # tensorflow-docs + # via tensorflow-docs +psutil==7.2.2 \ + --hash=sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372 \ + --hash=sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9 \ + --hash=sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841 \ + --hash=sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63 \ + --hash=sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979 \ + --hash=sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a \ + --hash=sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b \ + --hash=sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9 \ + --hash=sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee \ + --hash=sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312 \ + --hash=sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b \ + --hash=sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9 \ + --hash=sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e \ + --hash=sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc \ + --hash=sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1 \ + --hash=sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf \ + --hash=sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea \ + --hash=sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988 \ + --hash=sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486 \ + --hash=sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00 \ + --hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8 + # via -r dev_tools/requirements/deps/pytest.txt pubchempy==1.0.5 \ --hash=sha256:08f0b2a82a5caa5d61e14935d655da554602d7b5686fe661ab584c882ffff623 \ --hash=sha256:e936cfed31fa194042ad463be3c803dde5b12ef2f795caf336e3114127c34fa0 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # -r dev_tools/requirements/deps/runtime.txt + # via -r dev_tools/requirements/deps/runtime.txt pygments==2.20.0 \ --hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \ --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # pytest + # via pytest pyparsing==3.3.2 \ --hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d \ --hash=sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # matplotlib + # via matplotlib pytest==9.0.3 \ --hash=sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9 \ --hash=sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c # via - # -c dev_tools/requirements/max_compat/dev.env.txt # -r dev_tools/requirements/deps/pytest.txt # pytest-asyncio # pytest-cov @@ -1091,46 +1040,33 @@ pytest==9.0.3 \ pytest-asyncio==1.4.0 \ --hash=sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1 \ --hash=sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # -r dev_tools/requirements/deps/pytest.txt + # via -r dev_tools/requirements/deps/pytest.txt pytest-cov==7.1.0 \ --hash=sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2 \ --hash=sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # -r dev_tools/requirements/deps/pytest.txt + # via -r dev_tools/requirements/deps/pytest.txt pytest-randomly==4.1.0 \ --hash=sha256:47f1d9746c3bc3efabd53ae1ebfb8bb385cf3d4df4b505b6d58d9c97a3dfe70f \ --hash=sha256:f55e89e53367b090c0c053697d7f9d77595543d0e0516c93978b50c0f6b252f9 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # -r dev_tools/requirements/deps/pytest.txt + # via -r dev_tools/requirements/deps/pytest.txt pytest-retry==1.7.0 \ --hash=sha256:a2dac85b79a4e2375943f1429479c65beb6c69553e7dae6b8332be47a60954f4 \ --hash=sha256:f8d52339f01e949df47c11ba9ee8d5b362f5824dff580d3870ec9ae0057df80f - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # -r dev_tools/requirements/deps/pytest.txt + # via -r dev_tools/requirements/deps/pytest.txt pytest-xdist==3.8.0 \ --hash=sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88 \ --hash=sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # -r dev_tools/requirements/deps/pytest.txt + # via -r dev_tools/requirements/deps/pytest.txt python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 # via - # -c dev_tools/requirements/max_compat/dev.env.txt # matplotlib # pandas pytz==2026.2 \ --hash=sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126 \ --hash=sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # pandas + # via pandas pyyaml==6.0.3 \ --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ --hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \ @@ -1205,22 +1141,17 @@ pyyaml==6.0.3 \ --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \ --hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \ --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # tensorflow-docs + # via tensorflow-docs referencing==0.37.0 \ --hash=sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231 \ --hash=sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8 # via - # -c dev_tools/requirements/max_compat/dev.env.txt # jsonschema # jsonschema-specifications requests==2.34.2 \ --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \ --hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # -r dev_tools/requirements/deps/runtime.txt + # via -r dev_tools/requirements/deps/runtime.txt rpds-py==0.30.0 \ --hash=sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f \ --hash=sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136 \ @@ -1338,7 +1269,6 @@ rpds-py==0.30.0 \ --hash=sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f \ --hash=sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5 # via - # -c dev_tools/requirements/max_compat/dev.env.txt # jsonschema # referencing scipy==1.15.3 \ @@ -1389,33 +1319,29 @@ scipy==1.15.3 \ --hash=sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e \ --hash=sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126 # via - # -c dev_tools/requirements/max_compat/dev.env.txt # -r dev_tools/requirements/deps/runtime.txt # cirq-core six==1.17.0 \ --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # python-dateutil + # via python-dateutil sortedcontainers==2.4.0 \ --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # cirq-core + # via cirq-core sympy==1.14.0 \ --hash=sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517 \ --hash=sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5 # via - # -c dev_tools/requirements/max_compat/dev.env.txt # -r dev_tools/requirements/deps/runtime.txt # cirq-core tensorflow-docs==2025.2.19.33219 \ --hash=sha256:b52682f3739110ce804af701f9ab5ac1efe07760109a5b5640769851d7d14b19 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # -r dev_tools/requirements/deps/pytest.txt + # via -r dev_tools/requirements/deps/pytest.txt +threadpoolctl==3.6.0 \ + --hash=sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb \ + --hash=sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e + # via -r dev_tools/requirements/deps/pytest.txt tomli==2.4.1 ; python_full_version <= '3.11' \ --hash=sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853 \ --hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe \ @@ -1465,27 +1391,22 @@ tomli==2.4.1 ; python_full_version <= '3.11' \ --hash=sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9 \ --hash=sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049 # via - # -c dev_tools/requirements/max_compat/dev.env.txt # coverage # pytest tqdm==4.67.3 \ --hash=sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb \ --hash=sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # cirq-core + # via cirq-core traitlets==5.15.0 \ --hash=sha256:4fead733f81cf1c4c938e06f8ca4633896833c9d89eff878159457f4d4392971 \ --hash=sha256:fb36a18867a6803deab09f3c5e0fa81bb7b26a5c9e82501c9933f759166eff40 # via - # -c dev_tools/requirements/max_compat/dev.env.txt # jupyter-core # nbformat typing-extensions==4.15.0 \ --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 # via - # -c dev_tools/requirements/max_compat/dev.env.txt # cirq-core # exceptiongroup # pytest-asyncio @@ -1493,12 +1414,8 @@ typing-extensions==4.15.0 \ tzdata==2026.2 \ --hash=sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10 \ --hash=sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # pandas + # via pandas urllib3==2.7.0 \ --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \ --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897 - # via - # -c dev_tools/requirements/max_compat/dev.env.txt - # requests + # via requests diff --git a/src/openfermion/circuits/primitives/state_preparation.py b/src/openfermion/circuits/primitives/state_preparation.py index 220a406f9..4a27e7ee9 100644 --- a/src/openfermion/circuits/primitives/state_preparation.py +++ b/src/openfermion/circuits/primitives/state_preparation.py @@ -95,7 +95,7 @@ def _generic_gaussian_circuit( if isinstance(initial_state, int): initially_occupied_orbitals = _occupied_orbitals(initial_state, n_qubits) else: - initially_occupied_orbitals = initial_state # type: ignore + initially_occupied_orbitals = initial_state # type: ignore[assignment] # Flip bits so that the correct starting orbitals are occupied yield ( @@ -118,7 +118,7 @@ def _spin_symmetric_gaussian_circuit( if isinstance(initial_state, int): initially_occupied_orbitals = _occupied_orbitals(initial_state, n_qubits) else: - initially_occupied_orbitals = initial_state # type: ignore + initially_occupied_orbitals = initial_state # type: ignore[assignment] for spin_sector in range(2): circuit_description, start_orbitals = gaussian_state_preparation_circuit( @@ -190,7 +190,7 @@ def prepare_slater_determinant( if isinstance(initial_state, int): initially_occupied_orbitals = _occupied_orbitals(initial_state, n_qubits) else: - initially_occupied_orbitals = initial_state # type: ignore + initially_occupied_orbitals = initial_state # type: ignore[assignment] # Flip bits so that the first n_occupied are 1 and the rest 0 yield ( diff --git a/src/openfermion/config.py b/src/openfermion/config.py index ec9600cf9..5347af088 100644 --- a/src/openfermion/config.py +++ b/src/openfermion/config.py @@ -24,3 +24,46 @@ # Molecular data directory. THIS_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) DATA_DIRECTORY = os.path.realpath(os.path.join(THIS_DIRECTORY, 'testing/data')) + + +def get_available_cpu_count() -> int: + """Returns the number of CPU cores available to the current process. + + This function respects active CPU limits such as process affinity, + Docker container limits, and pytest-xdist worker distribution to + prevent oversubscription. + """ + if hasattr(os, "process_cpu_count"): # Python 3.13+ + cpus = os.process_cpu_count() or 1 + elif hasattr(os, "sched_getaffinity"): # Unix/Linux + try: + cpus = len(os.sched_getaffinity(0)) + except Exception: # pylint: disable=broad-exception-caught + cpus = os.cpu_count() or 1 + else: # Fallback for older Python on Windows/macOS + cpus = os.cpu_count() or 1 + + # Divide by the number of active pytest-xdist workers. + if "PYTEST_XDIST_WORKER_COUNT" in os.environ: + try: + worker_count = int(os.environ["PYTEST_XDIST_WORKER_COUNT"]) + if worker_count > 0: + cpus = max(1, cpus // worker_count) + except ValueError: + pass + + return cpus + + +def set_threading_limits() -> None: + """Sets thread limits for underlying numerical libraries (MKL, OpenBLAS, OMP). + + This prevents libraries like NumPy, SciPy, and JAX from oversubscribing + available CPU resources on high-core-count or multi-worker environments. + """ + if "PYTEST_XDIST_WORKER_COUNT" in os.environ: + limit = str(max(get_available_cpu_count(), 1)) + else: + limit = str(max(get_available_cpu_count() - 1, 1)) + for var in ["MKL_NUM_THREADS", "OMP_NUM_THREADS", "OPENBLAS_NUM_THREADS"]: + os.environ[var] = limit diff --git a/src/openfermion/config_test.py b/src/openfermion/config_test.py new file mode 100644 index 000000000..c0fd7f79f --- /dev/null +++ b/src/openfermion/config_test.py @@ -0,0 +1,172 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for config.py.""" + +import os +import unittest + + +class MockOS: + def __init__( + self, process_cpu_count_val=None, sched_getaffinity_val=None, cpu_count_val=4, environ=None + ): + if process_cpu_count_val is not None: + self.process_cpu_count = lambda: process_cpu_count_val + if sched_getaffinity_val is not None: + if isinstance(sched_getaffinity_val, Exception): + + def raise_exc(): + raise sched_getaffinity_val + + self.sched_getaffinity = lambda x: raise_exc() + else: + self.sched_getaffinity = lambda x: sched_getaffinity_val + self.cpu_count = lambda: cpu_count_val + self.environ = environ if environ is not None else {} + + +class GetAvailableCpuCountTest(unittest.TestCase): + + def test_get_available_cpu_count_various_os(self): + from unittest.mock import patch + from openfermion.config import get_available_cpu_count + import openfermion.config as config + + # Test Case 1: Python 3.13+ (has process_cpu_count). + mock_os = MockOS(process_cpu_count_val=8) + with patch.object(config, "os", mock_os): + self.assertEqual(get_available_cpu_count(), 8) + + # Test Case 1b: Python 3.13+ returns 0/None -> fallback to 1. + mock_os = MockOS(process_cpu_count_val=0) + with patch.object(config, "os", mock_os): + self.assertEqual(get_available_cpu_count(), 1) + + # Test Case 2: Linux/Unix (no process_cpu_count, has sched_getaffinity). + mock_os = MockOS(sched_getaffinity_val=[1, 2, 3]) + with patch.object(config, "os", mock_os): + self.assertEqual(get_available_cpu_count(), 3) + + # Test Case 3: Linux/Unix but sched_getaffinity raises exception. + mock_os = MockOS(sched_getaffinity_val=ValueError("error"), cpu_count_val=4) + with patch.object(config, "os", mock_os): + self.assertEqual(get_available_cpu_count(), 4) + + # Test Case 4: Windows/macOS/Older Python Fallback. + mock_os = MockOS(cpu_count_val=6) + with patch.object(config, "os", mock_os): + self.assertEqual(get_available_cpu_count(), 6) + + # Test Case 4b: Fallback when cpu_count returns None/0. + mock_os = MockOS(cpu_count_val=0) + with patch.object(config, "os", mock_os): + self.assertEqual(get_available_cpu_count(), 1) + + # Test Case 5: pytest-xdist active with valid worker count. + mock_os = MockOS(cpu_count_val=8, environ={"PYTEST_XDIST_WORKER_COUNT": "4"}) + with patch.object(config, "os", mock_os): + self.assertEqual(get_available_cpu_count(), 2) + + # Test Case 6: pytest-xdist with invalid worker count. + mock_os = MockOS(cpu_count_val=8, environ={"PYTEST_XDIST_WORKER_COUNT": "abc"}) + with patch.object(config, "os", mock_os): + self.assertEqual(get_available_cpu_count(), 8) + + +class SetThreadpoolLimitsTest(unittest.TestCase): + + def test_set_threadpool_limits_fixture(self): + import conftest + import sys + from unittest.mock import patch, MagicMock + + # Test Case 1: threadpoolctl is not installed. + with patch.dict(sys.modules, {"threadpoolctl": None}): + gen = conftest.set_threadpool_limits.__wrapped__() + val = next(gen) + self.assertIsNone(val) + with self.assertRaises(StopIteration): + next(gen) + + # Mock threadpoolctl and os.environ for the remaining cases. + mock_threadpoolctl = MagicMock() + mock_threadpoolctl.threadpool_limits.return_value = MagicMock() + + with patch.dict(sys.modules, {"threadpoolctl": mock_threadpoolctl}): + # Test Case 2: PYTEST_XDIST_WORKER_COUNT is in os.environ. + with patch.dict(os.environ, {"PYTEST_XDIST_WORKER_COUNT": "4"}): + with patch("openfermion.config.get_available_cpu_count", return_value=2): + gen = conftest.set_threadpool_limits.__wrapped__() + val = next(gen) + self.assertIsNone(val) + mock_threadpoolctl.threadpool_limits.assert_called_once() + mock_threadpoolctl.threadpool_limits.assert_called_with(limits=2) + with self.assertRaises(StopIteration): + next(gen) + mock_threadpoolctl.reset_mock() + + # Test Case 3: PYTEST_XDIST_WORKER_COUNT is in os.environ but invalid. + with patch.dict(os.environ, {"PYTEST_XDIST_WORKER_COUNT": "abc"}): + with patch("openfermion.config.get_available_cpu_count", return_value=8): + gen = conftest.set_threadpool_limits.__wrapped__() + + val = next(gen) + self.assertIsNone(val) + mock_threadpoolctl.threadpool_limits.assert_called_once() + mock_threadpoolctl.threadpool_limits.assert_called_with(limits=8) + with self.assertRaises(StopIteration): + next(gen) + mock_threadpoolctl.reset_mock() + + # Test Case 4: PYTEST_XDIST_WORKER_COUNT is not in os.environ. + with patch.dict(os.environ, {}, clear=True): + gen = conftest.set_threadpool_limits.__wrapped__() + val = next(gen) + self.assertIsNone(val) + mock_threadpoolctl.threadpool_limits.assert_not_called() + with self.assertRaises(StopIteration): + next(gen) + + +class SetThreadingLimitsTest(unittest.TestCase): + + def test_set_threading_limits(self): + from openfermion.config import set_threading_limits + from unittest.mock import patch + + # Clean/mocked environment dictionary. + env = {} + with patch.dict(os.environ, env, clear=True): + with patch("openfermion.config.get_available_cpu_count", return_value=8): + set_threading_limits() + self.assertEqual(os.environ.get("MKL_NUM_THREADS"), "7") + self.assertEqual(os.environ.get("OMP_NUM_THREADS"), "7") + self.assertEqual(os.environ.get("OPENBLAS_NUM_THREADS"), "7") + + # Test when PYTEST_XDIST_WORKER_COUNT is in os.environ. + env = {"PYTEST_XDIST_WORKER_COUNT": "4"} + with patch.dict(os.environ, env, clear=True): + with patch("openfermion.config.get_available_cpu_count", return_value=8): + set_threading_limits() + self.assertEqual(os.environ.get("MKL_NUM_THREADS"), "8") + self.assertEqual(os.environ.get("OMP_NUM_THREADS"), "8") + self.assertEqual(os.environ.get("OPENBLAS_NUM_THREADS"), "8") + + # Test when there are existing values. + env = {"OMP_NUM_THREADS": "3"} + with patch.dict(os.environ, env, clear=True): + with patch("openfermion.config.get_available_cpu_count", return_value=8): + set_threading_limits() + self.assertEqual(os.environ.get("OMP_NUM_THREADS"), "7") diff --git a/src/openfermion/conftest.py b/src/openfermion/conftest.py deleted file mode 100644 index 104edd1a1..000000000 --- a/src/openfermion/conftest.py +++ /dev/null @@ -1,33 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import random -from typing import Any - -import numpy as np -import pytest - - -def pytest_configure(config: Any) -> None: - # Set seeds for collection-time parameterization. - random.seed(0) - np.random.seed(0) - - os.environ['CIRQ_TESTING'] = "true" - - -@pytest.fixture(autouse=True) -def set_random_seed() -> None: - """Set a fixed random seed when testing.""" - random.seed(0) - np.random.seed(0) diff --git a/src/openfermion/linalg/linear_qubit_operator.py b/src/openfermion/linalg/linear_qubit_operator.py index 5a62b87bc..6395b8972 100644 --- a/src/openfermion/linalg/linear_qubit_operator.py +++ b/src/openfermion/linalg/linear_qubit_operator.py @@ -21,6 +21,7 @@ import scipy.sparse.linalg from openfermion.utils.operator_utils import count_qubits +from openfermion.config import get_available_cpu_count class LinearQubitOperatorOptions: @@ -35,7 +36,7 @@ def __init__(self, processes=10, pool=None): if processes <= 0: raise ValueError('Invalid number of processors specified {} <= 0'.format(processes)) - self.processes = min(processes, multiprocessing.cpu_count()) + self.processes = min(processes, get_available_cpu_count()) self.pool = pool def get_processes(self, num): @@ -185,6 +186,9 @@ def _matvec(self, x): if not self.linear_operators: return numpy.zeros(x.shape) + if self.options.processes <= 1: + return functools.reduce(numpy.add, (operator * x for operator in self.linear_operators)) + pool = self.options.get_pool(len(self.linear_operators)) vecs = pool.imap_unordered( apply_operator, [(operator, x) for operator in self.linear_operators] diff --git a/src/openfermion/linalg/linear_qubit_operator_test.py b/src/openfermion/linalg/linear_qubit_operator_test.py index ee3e872c6..898b85746 100644 --- a/src/openfermion/linalg/linear_qubit_operator_test.py +++ b/src/openfermion/linalg/linear_qubit_operator_test.py @@ -11,7 +11,6 @@ # limitations under the License. """Tests for linear_qubit_operator.py.""" -import multiprocessing import unittest import numpy @@ -26,6 +25,7 @@ generate_linear_qubit_operator, ) from openfermion.linalg.sparse_tools import qubit_operator_sparse +from openfermion.config import get_available_cpu_count class LinearQubitOperatorOptionsTest(unittest.TestCase): @@ -33,7 +33,7 @@ class LinearQubitOperatorOptionsTest(unittest.TestCase): def setUp(self): """LinearQubitOperatorOptions test set up.""" - self.processes = multiprocessing.cpu_count() + self.processes = get_available_cpu_count() self.options = LinearQubitOperatorOptions(self.processes) def test_init(self): @@ -214,13 +214,13 @@ def test_init(self): self.assertEqual(self.linear_operator.n_qubits, self.n_qubits) self.assertIsNone(self.linear_operator.options.pool) - cpu_count = multiprocessing.cpu_count() + cpu_count = get_available_cpu_count() default_processes = min(cpu_count, 10) self.assertEqual(self.linear_operator.options.processes, default_processes) # Generated variables. self.assertEqual( - len(self.linear_operator.qubit_operator_groups), min(multiprocessing.cpu_count(), 3) + len(self.linear_operator.qubit_operator_groups), min(get_available_cpu_count(), 3) ) self.assertEqual( QubitOperator.accumulate(self.linear_operator.qubit_operator_groups), @@ -262,6 +262,14 @@ def test_closed_workers_not_reused(self): parallel_qubit_op.dot(state) self.assertIsNone(parallel_qubit_op.options.pool) + def test_matvec_single_process(self): + """Tests that when processes is 1, it computes correctly and doesn't crash.""" + options = LinearQubitOperatorOptions(processes=1) + parallel_qubit_op = ParallelLinearQubitOperator( + self.qubit_operator, self.n_qubits, options=options + ) + self.assertTrue(numpy.allclose(parallel_qubit_op * self.vec, self.expected_matvec)) + class UtilityFunctionTest(unittest.TestCase): """Tests for utility functions.""" diff --git a/src/openfermion/resource_estimates/thc/utils/thc_factorization.py b/src/openfermion/resource_estimates/thc/utils/thc_factorization.py index 5b283df9e..26b479482 100644 --- a/src/openfermion/resource_estimates/thc/utils/thc_factorization.py +++ b/src/openfermion/resource_estimates/thc/utils/thc_factorization.py @@ -1,6 +1,14 @@ # coverage:ignore -# pylint: disable=wrong-import-position +# pylint: disable=wrong-import-position,wrong-import-order import os + +from openfermion.config import set_threading_limits + +# Set thread limits for NumPy einsum/tensordot calls. +# Needs to be set before other libraries are loaded. +set_threading_limits() + + from uuid import uuid4 import h5py import numpy @@ -14,6 +22,7 @@ import jax.numpy as jnp from jax import jit, grad + from .adagrad import adagrad from .thc_objectives import ( thc_objective, @@ -23,10 +32,6 @@ thc_objective_regularized, ) -# set mkl thread count for numpy einsum/tensordot calls -# leave one CPU un used so we can still access this computer -os.environ["MKL_NUM_THREADS"] = str(max((os.cpu_count() or 1) - 1, 1)) - class CallBackStore: def __init__(self, chkpoint_file, freqency=500): diff --git a/src/openfermion/resource_estimates/thc/utils/thc_objectives.py b/src/openfermion/resource_estimates/thc/utils/thc_objectives.py index 5dc2f0292..1e893f8b9 100644 --- a/src/openfermion/resource_estimates/thc/utils/thc_objectives.py +++ b/src/openfermion/resource_estimates/thc/utils/thc_objectives.py @@ -1,6 +1,14 @@ # coverage:ignore -# pylint: disable=wrong-import-position +# pylint: disable=wrong-import-position,wrong-import-order import os + +from openfermion.config import set_threading_limits + +# Set thread limits for NumPy einsum/tensordot calls. +# Needs to be set before other libraries are loaded. +set_threading_limits() + + from uuid import uuid4 import scipy.optimize @@ -17,10 +25,6 @@ from scipy.optimize import minimize from .adagrad import adagrad -# set mkl thread count for numpy einsum/tensordot calls -# leave one CPU un used so we can still access this computer -os.environ["MKL_NUM_THREADS"] = str(max((os.cpu_count() or 1) - 1, 1)) - def thc_objective_jax(xcur, norb, nthc, eri): """