From dc52158bf02697c0b0f3022829f0df78665cfcc6 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Wed, 15 Dec 2021 14:28:13 -0800 Subject: [PATCH] Add dev_tools/check_notebooks.py (#244) * Add dev_tools/check_notebooks.py Change-Id: I578c7a45d1946d89b8de1ca15d3353ccc8578f76 * docs Change-Id: I07c189f27b2c7c2adcf184e56437abab2e8eafe8 * nbfmt Change-Id: I4308d149b9f088f565c68aad304ad6ee420e976b --- dev_tools/.gitignore | 1 + dev_tools/check_notebooks.py | 109 ++++++++++++++++++++++++++ docs/qaoa/optimization_analysis.ipynb | 25 ++++++ 3 files changed, 135 insertions(+) create mode 100644 dev_tools/.gitignore create mode 100644 dev_tools/check_notebooks.py diff --git a/dev_tools/.gitignore b/dev_tools/.gitignore new file mode 100644 index 00000000..fd02abca --- /dev/null +++ b/dev_tools/.gitignore @@ -0,0 +1 @@ +notebook_envs \ No newline at end of file diff --git a/dev_tools/check_notebooks.py b/dev_tools/check_notebooks.py new file mode 100644 index 00000000..04f15c18 --- /dev/null +++ b/dev_tools/check_notebooks.py @@ -0,0 +1,109 @@ +import os +import shutil +import time +from multiprocessing import Pool +from subprocess import run as _run, CalledProcessError + +BASE_PACKAGES = [ + # for running the notebooks + "jupyter", + # assumed to be part of colab + "seaborn~=0.11.1", +] + + +def _check_notebook(notebook_fn: str, notebook_id: str, stdout, stderr): + """Helper function to actually do the work in `check_notebook`. + + `check_notebook` has all the context managers and exception handling, + which would otherwise result in highly indented code. + + Args: + notebook_fn: The notebook filename + notebook_id: A unique string id for the notebook that does not include `/` + stdout: A file-like object to redirect stdout + stderr: A file-like object to redirect stderr + """ + print(f'Starting {notebook_id}') + + def run(*args, **kwargs): + return _run(*args, check=True, stdout=stdout, stderr=stderr, **kwargs) + + # 1. create venv + venv_dir = os.path.abspath(f'./notebook_envs/{notebook_id}') + run(['python', '-m', 'venv', '--clear', venv_dir]) + + # 2. basic colab-like environment + pip = f'{venv_dir}/bin/pip' + run([pip, 'install'] + BASE_PACKAGES) + + # 3. execute + jupyter = f'{venv_dir}/bin/jupyter' + env = os.environ.copy() + env['PATH'] = f'{venv_dir}/bin:{env["PATH"]}' + run([jupyter, 'nbconvert', '--to', 'html', '--execute', notebook_fn], cwd='../', env=env) + + # 4. clean up + shutil.rmtree(venv_dir) + + +def check_notebook(notebook_fn: str): + """Check a notebook. + + 1. Create a venv just for that notebook + 2. Verify the notebook executes without error (and that it installs its own dependencies!) + 3. Clean up venv dir + + A scratch directory dev_tools/notebook_envs will be created containing tvenv as well as + stdout and stderr logs for each notebook. Each of these files and directories will be + named according to the "notebook_id", which is `notebook_fn.replace('/', '-')`. + + The executed notebook will be rendered to html alongside its original .ipynb file for + spot-checking. + + Args: + notebook_fn: The filename of the notebook relative to the repo root. + """ + notebook_id = notebook_fn.replace('/', '-') + start = time.perf_counter() + with open(f'./notebook_envs/{notebook_id}.stdout', 'w') as stdout, \ + open(f'./notebook_envs/{notebook_id}.stderr', 'w') as stderr: + try: + _check_notebook(notebook_fn, notebook_id, stdout, stderr) + except CalledProcessError: + print('ERROR!', notebook_id) + end = time.perf_counter() + print(f'{notebook_id} {end - start:.1f}s') + + +NOTEBOOKS = [ + 'docs/otoc/otoc_example.ipynb', + 'docs/guide/data_analysis.ipynb', + 'docs/guide/data_collection.ipynb', + 'docs/qaoa/example_problems.ipynb', + 'docs/qaoa/precomputed_analysis.ipynb', + 'docs/qaoa/hardware_grid_circuits.ipynb', + 'docs/qaoa/optimization_analysis.ipynb', + 'docs/qaoa/tasks.ipynb', + 'docs/qaoa/landscape_analysis.ipynb', + 'docs/qaoa/routing_with_tket.ipynb', + 'docs/quantum_chess/concepts.ipynb', + # 'docs/quantum_chess/quantum_chess_rest_api.ipynb', # runs a server, never finishes. + # 'docs/quantum_chess/quantum_chess_client.ipynb', # uses the server, requires modification. + 'docs/hfvqe/molecular_data.ipynb', + 'docs/hfvqe/quickstart.ipynb', + 'docs/fermi_hubbard/publication_results.ipynb', + 'docs/fermi_hubbard/experiment_example.ipynb', +] + + +def main(): + os.chdir(os.path.dirname(__file__)) + os.makedirs('./notebook_envs', exist_ok=True) + with Pool(4) as pool: + results = pool.map(check_notebook, NOTEBOOKS) + print(results) + + +if __name__ == '__main__': + main() diff --git a/docs/qaoa/optimization_analysis.ipynb b/docs/qaoa/optimization_analysis.ipynb index 6fb6e173..1241317c 100644 --- a/docs/qaoa/optimization_analysis.ipynb +++ b/docs/qaoa/optimization_analysis.ipynb @@ -62,6 +62,31 @@ "" ] }, + { + "cell_type": "markdown", + "metadata": { + "id": "314b4703b85b" + }, + "source": [ + "## Setup\n", + "\n", + "Install the ReCirq package:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ca9285a3a162" + }, + "outputs": [], + "source": [ + "try:\n", + " import recirq\n", + "except ImportError:\n", + " !pip install -q git+https://github.com/quantumlib/ReCirq sympy~=1.6" + ] + }, { "cell_type": "markdown", "metadata": {