Skip to content

Commit fee620b

Browse files
authored
Algo Benchmark Library card catalog (#235)
1 parent fc2ab80 commit fee620b

File tree

4 files changed

+183
-4
lines changed

4 files changed

+183
-4
lines changed

.github/workflows/pythonpackage.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ jobs:
5151
run: |
5252
pip install pytest
5353
# RECIRQ_CHESS_TEST_SEED: make quantum_chess tests determnistic
54-
# RECIRQ_IGNORE_PYTKET: for 'next' cirq version, skip pytket tests
55-
RECIRQ_CHESS_TEST_SEED=789 RECIRQ_IGNORE_PYTKET=y pytest -v
54+
# RECIRQ_IMPORT_FAILSAFE: skip tests on unsupported Cirq configurations
55+
RECIRQ_CHESS_TEST_SEED=789 RECIRQ_IMPORT_FAILSAFE=y pytest -v
5656
nbformat:
5757
name: Notebook formatting
5858
runs-on: ubuntu-latest
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import dataclasses
2+
from dataclasses import dataclass
3+
from functools import lru_cache
4+
from typing import Type, Callable, List, Tuple, Any
5+
6+
import pandas as pd
7+
8+
try:
9+
from cirq_google.workflow import ExecutableSpec, QuantumExecutableGroup
10+
11+
workflow = True
12+
except ImportError as e:
13+
import os
14+
15+
if 'RECIRQ_IMPORT_FAILSAFE' in os.environ:
16+
workflow = False
17+
else:
18+
raise ImportError(f"This functionality requires a pre-release version of Cirq: {e}")
19+
20+
21+
@dataclass
22+
class AlgorithmicBenchmark:
23+
"""A record of relevant elements that comprise a benchmark based on a quantum computing
24+
algorithm of interest.
25+
26+
Args:
27+
domain: The problem domain represented as the corresponding high-level ReCirq module.
28+
This must be a valid module name beginning with "recirq.".
29+
name: The benchmark name. Must be unique within the domain.
30+
executable_family: A globally unique identifier for this AlgorithmicBenchmark.
31+
This should match up with this Benchmark's `spec_class.executable_family`.
32+
The executable family is the fully-qualified leaf-module where the code
33+
for this AlgorithmicBenchmark lives.
34+
spec_class: The ExecutableSpec subclass for this AlgorithmicBenchmark.
35+
data_class: The class which can contain ETL-ed data for this AlgorithmicBenchmark.
36+
executable_generator_func: The function that returns a QuantumExecutableGroup for a
37+
given Config.
38+
configs: A list of available `BenchmarkConfig` for this benchmark.
39+
"""
40+
41+
domain: str
42+
name: str
43+
executable_family: str
44+
spec_class: Type['ExecutableSpec']
45+
data_class: Type
46+
executable_generator_func: Callable[[Any], 'QuantumExecutableGroup']
47+
configs: List['BenchmarkConfig']
48+
49+
def as_strings(self):
50+
"""Get values of this class as strings suitable for printing."""
51+
ret = {k: str(v) for k, v in dataclasses.asdict(self).items()}
52+
ret['spec_class'] = self.spec_class.__name__
53+
ret['data_class'] = self.data_class.__name__
54+
ret['executable_generator_func'] = self.executable_generator_func.__name__
55+
return ret
56+
57+
58+
@dataclass
59+
class BenchmarkConfig:
60+
"""A particular configuration of an AlgorithmicBenchmark
61+
62+
Args:
63+
short_name: The short name for this config. Unique within an AlgorithmicBenchmark.
64+
full_name: A globally unique name for this config.
65+
gen_script: The script filename that generates the QuantumExecutableGroup for this Config.
66+
Should begin with prefix "gen-".
67+
run_scripts: A list of script filenames that execute (or can execute) this config.
68+
"""
69+
70+
short_name: str
71+
full_name: str
72+
gen_script: str
73+
run_scripts: List[str]
74+
75+
76+
BENCHMARKS = [
77+
]
78+
79+
80+
@lru_cache()
81+
def get_all_algo_configs() -> List[Tuple[AlgorithmicBenchmark, BenchmarkConfig]]:
82+
"""Return a tuple of (AlgorithmsBenchmark, BenchmarkConfig) for each BenchmarkConfig in
83+
`BENCHMARKS`.
84+
85+
This "flattens" the list of BenchmarkConfig from their nested structure in `BENCHMARKS`.
86+
"""
87+
ret = []
88+
for algo in BENCHMARKS:
89+
for config in algo.configs:
90+
ret.append((algo, config))
91+
return ret
92+
93+
94+
def print_table_of_benchmarks():
95+
"""Print the AlgorithmicBenchmarks in a tabular form."""
96+
df = pd.DataFrame([algo.as_strings() for algo in BENCHMARKS]).set_index('executable_family')
97+
pd.set_option('display.width', None)
98+
pd.set_option('display.max_colwidth', None)
99+
print(df.transpose())
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import os
2+
from importlib import import_module
3+
from types import ModuleType
4+
5+
import pytest
6+
7+
from recirq.algorithmic_benchmark_library import BENCHMARKS, get_all_algo_configs, workflow
8+
9+
RECIRQ_DIR = os.path.abspath(os.path.dirname(__file__) + '/../')
10+
11+
if not workflow:
12+
pytestmark = pytest.mark.skip('algorithmic_benchmark_library requires pre-release of Cirq.')
13+
14+
15+
@pytest.mark.parametrize('algo', BENCHMARKS)
16+
def test_domain(algo):
17+
# By convention, the domain should be a recirq module.
18+
assert algo.domain.startswith('recirq.'), 'domain should be a recirq module.'
19+
mod = import_module(algo.domain)
20+
assert isinstance(mod, ModuleType), 'domain should be a recirq module.'
21+
22+
23+
def test_benchmark_name_unique_in_domain():
24+
# In a given domain, all benchmark names should be unique
25+
pairs = [(algo.domain, algo.name) for algo in BENCHMARKS]
26+
assert len(set(pairs)) == len(pairs)
27+
28+
29+
@pytest.mark.parametrize('algo', BENCHMARKS)
30+
def test_executable_family_is_formulaic(algo):
31+
# Check consistency in the AlgorithmicBenchmark dataclass:
32+
assert algo.executable_family == algo.spec_class.executable_family, \
33+
"benchmark's executable_family should match that of the spec_class"
34+
35+
# By convention, we set this to be the module name. By further convention,
36+
# {algo.domain}.{algo.name} should be the module name.
37+
assert algo.executable_family == f'{algo.domain}.{algo.name}', \
38+
"The executable family should be set to the benchmarks's domain.name"
39+
40+
# Check the convention that it should give a module
41+
mod = import_module(algo.executable_family)
42+
assert isinstance(mod, ModuleType), \
43+
"The executable family should specify an importable module."
44+
45+
46+
@pytest.mark.parametrize('algo', BENCHMARKS)
47+
def test_classes_and_funcs(algo):
48+
# The various class objects should exist in the module
49+
mod = import_module(algo.executable_family)
50+
assert algo.spec_class == getattr(mod, algo.spec_class.__name__), \
51+
"The spec_class must exist in the benchmark's module"
52+
assert algo.data_class == getattr(mod, algo.data_class.__name__), \
53+
"The data_class must exist in the benchmark's module"
54+
assert algo.executable_generator_func == getattr(mod, algo.executable_generator_func.__name__), \
55+
"the executable_generator_func must exist in the benchmark's module"
56+
57+
58+
def test_globally_unique_executable_family():
59+
# Each entry should have a unique executable family
60+
fams = [algo.executable_family for algo in BENCHMARKS]
61+
assert len(set(fams)) == len(fams)
62+
63+
64+
def test_globally_unique_config_full_name():
65+
full_names = [config.full_name for algo, config in get_all_algo_configs()]
66+
assert len(set(full_names)) == len(full_names)
67+
68+
69+
@pytest.mark.parametrize('algo_config', get_all_algo_configs())
70+
def test_gen_script(algo_config):
71+
algo, config = algo_config
72+
73+
# Make sure it's formulaic
74+
assert config.gen_script == f'gen-{config.short_name}.py', \
75+
"The gen_script should be of the form 'gen-{short_name}'"
76+
77+
# Make sure it exists
78+
gen_script_path = (f"{RECIRQ_DIR}/{algo.domain.replace('.', '/')}/"
79+
f"{algo.name.replace('.', '/')}/{config.gen_script}")
80+
assert os.path.exists(gen_script_path)

recirq/qaoa/placement.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import cirq_google as cg
1212

1313
try:
14-
# Set the 'RECIRQ_IGNORE_PYTKET' environment variable to treat PyTket as an optional
14+
# Set the 'RECIRQ_IMPORT_FAILSAFE' environment variable to treat PyTket as an optional
1515
# dependency. We do this for CI testing against the next, pre-release Cirq version.
1616
import pytket
1717
import pytket.extensions.cirq
@@ -20,7 +20,7 @@
2020
from pytket.predicates import CompilationUnit, ConnectivityPredicate
2121
from pytket.routing import GraphPlacement
2222
except ImportError as e:
23-
if 'RECIRQ_IGNORE_PYTKET' in os.environ:
23+
if 'RECIRQ_IMPORT_FAILSAFE' in os.environ:
2424
pytket = NotImplemented
2525
else:
2626
raise e

0 commit comments

Comments
 (0)