From 52a917591d1eb9e7b537fa10767b13221ed28fa4 Mon Sep 17 00:00:00 2001 From: matulni Date: Tue, 23 Dec 2025 15:53:31 +0100 Subject: [PATCH 1/2] Add asv support --- asv_benchmarks/.gitignore | 7 + asv_benchmarks/README.md | 35 +++++ asv_benchmarks/asv.conf.json | 148 +++++++++++++++++++ asv_benchmarks/benchmarks/__init__.py | 0 asv_benchmarks/benchmarks/bench_opengraph.py | 56 +++++++ asv_benchmarks/benchmarks/bench_pattern.py | 53 +++++++ asv_benchmarks/benchmarks/common.py | 9 ++ 7 files changed, 308 insertions(+) create mode 100644 asv_benchmarks/.gitignore create mode 100644 asv_benchmarks/README.md create mode 100644 asv_benchmarks/asv.conf.json create mode 100644 asv_benchmarks/benchmarks/__init__.py create mode 100644 asv_benchmarks/benchmarks/bench_opengraph.py create mode 100644 asv_benchmarks/benchmarks/bench_pattern.py create mode 100644 asv_benchmarks/benchmarks/common.py diff --git a/asv_benchmarks/.gitignore b/asv_benchmarks/.gitignore new file mode 100644 index 000000000..7ff3e47b9 --- /dev/null +++ b/asv_benchmarks/.gitignore @@ -0,0 +1,7 @@ +*__pycache__* +env/ +html/ +results/ +graphix/ +benchmarks/cache/ +*.txt \ No newline at end of file diff --git a/asv_benchmarks/README.md b/asv_benchmarks/README.md new file mode 100644 index 000000000..930c38768 --- /dev/null +++ b/asv_benchmarks/README.md @@ -0,0 +1,35 @@ +# Graphix benchmarks + +Benchmarking Graphix with [Airspeed Velocity](https://asv.readthedocs.io/en/stable/index.html). + +## Basic usage + +Ensure _airspeed velocity_ is installed: + +``` +pip install asv +``` + +To benchmark the last commit run +``` +asv run +``` +on the `asv_benchmarks` folder. + +To visualize the results run +``` +asv publish +asv preview +``` + + +## Further options + +Airspeed velocity allows comparing over various commits. For instance, to benchmark the last `n = 3` commits run: + +``` +git log --format="%H" -n 3 > hashes_to_benchmark.txt +asv run HASHFILE:hashes_to_benchmark.txt +``` + +See the [user reference](https://asv.readthedocs.io/en/stable/user_reference.html) for additional information. diff --git a/asv_benchmarks/asv.conf.json b/asv_benchmarks/asv.conf.json new file mode 100644 index 000000000..2a5920c4d --- /dev/null +++ b/asv_benchmarks/asv.conf.json @@ -0,0 +1,148 @@ +{ + // The version of the config file format. Do not change, unless + // you know what you are doing. + "version": 1, + + // The name of the project being benchmarked + "project": "graphix", + + // The project's homepage + "project_url": "https://graphix.readthedocs.io/", + + // The URL or local path of the source code repository for the + // project being benchmarked + "repo": "..", + + // Customizable commands for building, installing, and + // uninstalling the project. See asv.conf.json documentation. + "install_command": ["python -mpip install {wheel_file}"], + "uninstall_command": ["return-code=any python -mpip uninstall -y {project}"], + "build_command": ["python -m build --wheel -o {build_cache_dir} {build_dir}"], + + // List of branches to benchmark. If not provided, defaults to "main" + // (for git) or "default" (for mercurial). + "branches": ["master"], + + // The DVCS being used. If not set, it will be automatically + // determined from "repo" by looking at the protocol in the URL + // (if remote), or by looking for special directories, such as + // ".git" (if local). + // "dvcs": "git", + + // The tool to use to create environments. May be "conda", + // "virtualenv" or other value depending on the plugins in use. + // If missing or the empty string, the tool will be automatically + // determined by looking for tools on the PATH environment + // variable. + "environment_type": "conda", + + // timeout in seconds for installing any dependencies in environment + // defaults to 10 min + //"install_timeout": 600, + + // timeout in seconds all benchmarks, can be overridden per benchmark + // defaults to 1 min + //"default_benchmark_timeout": 60, + + // the base URL to show a commit for the project. + "show_commit_url": "https://github.com/TeamGraphix/graphix/commit/", + + // The Pythons you'd like to test against. If not provided, defaults + // to the current version of Python used to run `asv`. + // "pythons": ["3.12"], + + // The matrix of dependencies to test. Each key is the name of a + // package (in PyPI) and the values are version numbers. An empty + // list or empty string indicates to just test against the default + // (latest) version. null indicates that the package is to not be + // installed. If the package to be tested is only available from + // PyPi, and the 'environment_type' is conda, then you can preface + // the package name by 'pip+', and the package will be installed via + // pip (with all the conda available packages installed first, + // followed by the pip installed packages). + // + // The versions of the dependencies should be bumped in a dedicated commit + // to easily identify regressions/improvements due to code changes from + // those due to dependency changes. + // + "matrix": { + "numpy": ["2.0.0"], + "scipy": [], + "matplotlib": [], + "networkx": [], + "opt_einsum": [], + "quimb": [], + "sympy": [], + "typing_extensions": [] + }, + + // Combinations of libraries/python versions can be excluded/included + // from the set to test. Each entry is a dictionary containing additional + // key-value pairs to include/exclude. + // + // An exclude entry excludes entries where all values match. The + // values are regexps that should match the whole string. + // + // An include entry adds an environment. Only the packages listed + // are installed. The 'python' key is required. The exclude rules + // do not apply to includes. + // + // In addition to package names, the following keys are available: + // + // - python + // Python version, as in the *pythons* variable above. + // - environment_type + // Environment type, as above. + // - sys_platform + // Platform, as in sys.platform. Possible values for the common + // cases: 'linux2', 'win32', 'cygwin', 'darwin'. + // + // "exclude": [ + // {"python": "3.2", "sys_platform": "win32"}, // skip py3.2 on windows + // {"environment_type": "conda", "six": null}, // don't run without six on conda + // ], + // + // "include": [ + // // additional env for python3.12 + // {"python": "3.12", "numpy": "1.26"}, + // // additional env if run on windows+conda + // {"sys_platform": "win32", "environment_type": "conda", "python": "3.12", "libpython": ""}, + // ], + + // The directory (relative to the current directory) that benchmarks are + // stored in. If not provided, defaults to "benchmarks" + // "benchmark_dir": "benchmarks", + + // The directory (relative to the current directory) to cache the Python + // environments in. If not provided, defaults to "env" + // "env_dir": "env", + + // The directory (relative to the current directory) that raw benchmark + // results are stored in. If not provided, defaults to "results". + // "results_dir": "results", + + // The directory (relative to the current directory) that the html tree + // should be written to. If not provided, defaults to "html". + // "html_dir": "html", + + // The number of characters to retain in the commit hashes. + // "hash_length": 8, + + // `asv` will cache wheels of the recent builds in each + // environment, making them faster to install next time. This is + // number of builds to keep, per environment. + // "build_cache_size": 0 + + // The commits after which the regression search in `asv publish` + // should start looking for regressions. Dictionary whose keys are + // regexps matching to benchmark names, and values corresponding to + // the commit (exclusive) after which to start looking for + // regressions. The default is to start from the first commit + // with results. If the commit is `null`, regression detection is + // skipped for the matching benchmark. + // + // "regressions_first_commits": { + // "some_benchmark": "352cdf", // Consider regressions only after this commit + // "another_benchmark": null, // Skip regression detection altogether + // } +} \ No newline at end of file diff --git a/asv_benchmarks/benchmarks/__init__.py b/asv_benchmarks/benchmarks/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/asv_benchmarks/benchmarks/bench_opengraph.py b/asv_benchmarks/benchmarks/bench_opengraph.py new file mode 100644 index 000000000..f92c0557d --- /dev/null +++ b/asv_benchmarks/benchmarks/bench_opengraph.py @@ -0,0 +1,56 @@ +"""Benchmark `graphix.opengraph.OpenGraph` methods.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from benchmarks.common import fx_rng +from graphix.random_objects import rand_circuit + +if TYPE_CHECKING: + from numpy.random import Generator + + from graphix.measurements import Measurement + from graphix.opengraph import OpenGraph + + +def og_from_rnd_circuit(nqubits: int, depth: int, rng: Generator) -> OpenGraph[Measurement]: + """Generate an open graph from a random circuit. + + Parameters + ---------- + nqubits : int + depth : int + + Returns + ------- + Pattern + """ + circuit = rand_circuit(nqubits, depth, rng) + return circuit.transpile().pattern.extract_opengraph() + + +class FlowExtraction: + """Benchmark flow extraction.""" + + def setup(self) -> None: + """Set up benchmark suit.""" + nqubits = 10 + depth = 5 + ncircuits = 5 + self.og_rnd_circuit = [og_from_rnd_circuit(nqubits, depth, fx_rng()) for _ in range(ncircuits)] + + def time_causal_flow_rnd_circuit(self) -> None: + """Time causal-flow finding algorithm on open graphs generated from random circuits.""" + for og in self.og_rnd_circuit: + og.find_causal_flow() + + def time_gflow_rnd_circuit(self) -> None: + """Time generalised-flow (gflow) finding algorithm on open graphs generated from random circuits.""" + for og in self.og_rnd_circuit: + og.find_gflow() + + def time_pauli_flow_rnd_circuit(self) -> None: + """Time Pauli-flow finding algorithm on open graphs generated from random circuits.""" + for og in self.og_rnd_circuit: + og.extract_pauli_flow() diff --git a/asv_benchmarks/benchmarks/bench_pattern.py b/asv_benchmarks/benchmarks/bench_pattern.py new file mode 100644 index 000000000..5a6e877a7 --- /dev/null +++ b/asv_benchmarks/benchmarks/bench_pattern.py @@ -0,0 +1,53 @@ +"""Benchmark `graphix.pattern.Pattern` methods.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from benchmarks.common import fx_rng +from graphix.random_objects import rand_circuit + +if TYPE_CHECKING: + from numpy.random import Generator + + from graphix.pattern import Pattern + + +def random_pattern(nqubits: int, depth: int, rng: Generator) -> Pattern: + """Generate a random pattern from a random circuit. + + Parameters + ---------- + nqubits : int + depth : int + + Returns + ------- + Pattern + """ + circuit = rand_circuit(nqubits, depth, rng) + pattern = circuit.transpile().pattern + pattern.standardize() + pattern.minimize_space() + return pattern + + +class PatternSimulation: + """Benchmark pattern simulation with various backends.""" + + def setup(self) -> None: + """Set up benchmark suit.""" + nqubits = 3 + depth = 5 + ncircuits = 3 + self.patterns = [random_pattern(nqubits, depth, fx_rng()) for _ in range(ncircuits)] + + def time_statevector(self) -> None: + """Time statevector backend.""" + for pattern in self.patterns: + pattern.simulate_pattern(backend='statevector') + + def time_densitymatrix(self) -> None: + """Time density matrix backend.""" + for pattern in self.patterns: + pattern.simulate_pattern(backend='densitymatrix') diff --git a/asv_benchmarks/benchmarks/common.py b/asv_benchmarks/benchmarks/common.py new file mode 100644 index 000000000..a02da1ba7 --- /dev/null +++ b/asv_benchmarks/benchmarks/common.py @@ -0,0 +1,9 @@ +from __future__ import annotations + +from numpy.random import PCG64, Generator + +SEED = 25 + + +def fx_rng() -> Generator: + return Generator(PCG64(SEED)) From ad932733495ef2511ce1f1631c5bb638ac9149a9 Mon Sep 17 00:00:00 2001 From: matulni Date: Tue, 23 Dec 2025 16:15:23 +0100 Subject: [PATCH 2/2] Fix ruff --- asv_benchmarks/benchmarks/__init__.py | 1 + asv_benchmarks/benchmarks/bench_pattern.py | 4 ++-- asv_benchmarks/benchmarks/common.py | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/asv_benchmarks/benchmarks/__init__.py b/asv_benchmarks/benchmarks/__init__.py index e69de29bb..562b0e91a 100644 --- a/asv_benchmarks/benchmarks/__init__.py +++ b/asv_benchmarks/benchmarks/__init__.py @@ -0,0 +1 @@ +"""Airspeed velocity benchmark of Graphix.""" diff --git a/asv_benchmarks/benchmarks/bench_pattern.py b/asv_benchmarks/benchmarks/bench_pattern.py index 5a6e877a7..5a8186958 100644 --- a/asv_benchmarks/benchmarks/bench_pattern.py +++ b/asv_benchmarks/benchmarks/bench_pattern.py @@ -45,9 +45,9 @@ def setup(self) -> None: def time_statevector(self) -> None: """Time statevector backend.""" for pattern in self.patterns: - pattern.simulate_pattern(backend='statevector') + pattern.simulate_pattern(backend="statevector") def time_densitymatrix(self) -> None: """Time density matrix backend.""" for pattern in self.patterns: - pattern.simulate_pattern(backend='densitymatrix') + pattern.simulate_pattern(backend="densitymatrix") diff --git a/asv_benchmarks/benchmarks/common.py b/asv_benchmarks/benchmarks/common.py index a02da1ba7..1aa6c90a6 100644 --- a/asv_benchmarks/benchmarks/common.py +++ b/asv_benchmarks/benchmarks/common.py @@ -1,3 +1,5 @@ +"""Common utils for airspeed velocity benchmarking.""" + from __future__ import annotations from numpy.random import PCG64, Generator @@ -6,4 +8,5 @@ def fx_rng() -> Generator: + """Return a seeded random generator.""" return Generator(PCG64(SEED))