Skip to content

Commit a8c0cf7

Browse files
committed
Algo Benchmark Library card catalog
Change-Id: I4ca692cd92ddbee95d0d2aeed5a4027fcd0f2aa4
1 parent 06febaa commit a8c0cf7

File tree

2 files changed

+153
-0
lines changed

2 files changed

+153
-0
lines changed

recirq/algo_benchmark_library.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import dataclasses
2+
from dataclasses import dataclass
3+
from functools import lru_cache
4+
from typing import Type, Callable, List, Tuple
5+
6+
import pandas as pd
7+
8+
from cirq_google.workflow import ExecutableSpec, QuantumExecutableGroup
9+
10+
11+
@dataclass
12+
class AlgoBenchmark:
13+
domain: str
14+
"""The problem domain. Usually a high-level ReCirq module."""
15+
16+
name: str
17+
"""The benchmark name. Must be unique within the domain."""
18+
19+
executable_family: str
20+
"""A globally unique identifier for this AlgoBenchmark.
21+
22+
This should match up with this Benchmark's `spec_class.executable_family`.
23+
24+
By convention, the executable family is the fully-qualified leaf-module where the code
25+
for this AlgoBenchmark lives.
26+
"""
27+
28+
spec_class: Type[ExecutableSpec]
29+
"""The ExecutableSpec subclass for this AlgoBenchmark."""
30+
31+
data_class: Type
32+
"""The class which can contain ETL-ed data for this AlgoBenchmark."""
33+
34+
gen_func: Callable[[...], QuantumExecutableGroup]
35+
"""The function that returns a QuantumExecutableGroup for a given Config."""
36+
37+
configs: List['BenchmarkConfig']
38+
39+
def as_strings(self):
40+
ret = {k: str(v) for k, v in dataclasses.asdict(self).items()}
41+
ret['spec_class'] = self.spec_class.__name__
42+
ret['data_class'] = self.data_class.__name__
43+
ret['gen_func'] = self.gen_func.__name__
44+
return ret
45+
46+
47+
@dataclass
48+
class BenchmarkConfig:
49+
short_name: str
50+
"""The short name for this config. Unique within an AlgoBenchmark."""
51+
52+
full_name: str
53+
"""A globally unique name for this config."""
54+
55+
gen_script: str
56+
"""The script filename that generates the QuantumExecutableGroup for this Config."""
57+
58+
run_scripts: List[str]
59+
"""A list of script filenames that execute (or can execute) this config."""
60+
61+
62+
BENCHMARKS = [
63+
]
64+
65+
66+
@lru_cache()
67+
def get_all_algo_configs() -> List[Tuple[AlgoBenchmark, BenchmarkConfig]]:
68+
ret = []
69+
for algo in BENCHMARKS:
70+
for config in algo.configs:
71+
ret.append((algo, config))
72+
return ret
73+
74+
75+
def main():
76+
df = pd.DataFrame([algo.as_strings() for algo in BENCHMARKS]).set_index('executable_family')
77+
pd.set_option('display.width', None)
78+
pd.set_option('display.max_colwidth', None)
79+
print(df.transpose())
80+
81+
82+
if __name__ == '__main__':
83+
main()

recirq/algo_benchmark_library_test.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import os
2+
from importlib import import_module
3+
from types import ModuleType
4+
5+
import pytest
6+
7+
from recirq.algo_benchmark_library import BENCHMARKS, get_all_algo_configs
8+
9+
RECIRQ_DIR = os.path.abspath(os.path.dirname(__file__) + '/../')
10+
11+
12+
@pytest.mark.parametrize('algo', BENCHMARKS)
13+
def test_domain(algo):
14+
# By convention, the domain should be a recirq module.
15+
assert algo.domain.startswith('recirq.')
16+
mod = import_module(algo.domain)
17+
assert isinstance(mod, ModuleType)
18+
19+
20+
def test_benchmark_name_unique_in_domain():
21+
# In a given domain, all benchmark names should be unique
22+
pairs = [(algo.domain, algo.name) for algo in BENCHMARKS]
23+
assert len(set(pairs)) == len(pairs)
24+
25+
26+
@pytest.mark.parametrize('algo', BENCHMARKS)
27+
def test_executable_family_is_formulaic(algo):
28+
# Check consistency in the AlgoBenchmark dataclass:
29+
assert algo.executable_family == algo.spec_class.executable_family
30+
31+
# By convention, we set this to be the module name. By further convention,
32+
# {algo.domain}.{algo.name} should be the module name.
33+
assert algo.executable_family == f'{algo.domain}.{algo.name}'
34+
35+
# Check the convention that it should give a module
36+
mod = import_module(algo.executable_family)
37+
assert isinstance(mod, ModuleType)
38+
39+
40+
@pytest.mark.parametrize('algo', BENCHMARKS)
41+
def test_classes_and_funcs(algo):
42+
# The various class objects should exist in the module
43+
mod = import_module(algo.executable_family)
44+
assert algo.spec_class == getattr(mod, algo.spec_class.__name__)
45+
assert algo.data_class == getattr(mod, algo.data_class.__name__)
46+
assert algo.gen_func == getattr(mod, algo.gen_func.__name__)
47+
48+
49+
def test_globally_unique_executable_family():
50+
# Each entry should have a unique executable family
51+
fams = [algo.executable_family for algo in BENCHMARKS]
52+
assert len(set(fams)) == len(fams)
53+
54+
55+
def test_globally_unique_config_full_name():
56+
full_names = [config.full_name for algo, config in get_all_algo_configs()]
57+
assert len(set(full_names)) == len(full_names)
58+
59+
60+
@pytest.mark.parametrize('algo_config', get_all_algo_configs())
61+
def test_gen_script(algo_config):
62+
algo, config = algo_config
63+
64+
# Make sure it's formulaic
65+
assert config.gen_script == f'gen-{config.short_name}.py'
66+
67+
# Make sure it exists
68+
gen_script_path = (f"{RECIRQ_DIR}/{algo.domain.replace('.', '/')}/"
69+
f"{algo.name.replace('.', '/')}/{config.gen_script}")
70+
assert os.path.exists(gen_script_path)

0 commit comments

Comments
 (0)