{ "cells": [ { "cell_type": "markdown", "id": "speed_on_your_machine-0", "metadata": {}, "source": "# Speed on your machine\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Abel-ai-lab/andrey/blob/main/examples/speed_on_your_machine.ipynb) {download}`Download this notebook <../../../examples/speed_on_your_machine.ipynb>`\n\nRun PC with Andrey and causal-learn on the same observations. The size ladder is deliberately\nsmall; increase it to explore your machine's limits. Each fit runs sequentially with one numeric\nthread. Imports, warm-up, graph conversion, and accuracy scoring are outside the timed region.\n\n**The fastest causal discovery package.** That claim comes from\nthe [release cards](https://andrey.abel.ai/docs/benchmarks.html), not a promised result\non this machine. The live demo labels its server; your hardware and data determine your times.\n\nRun in a fresh kernel. Outside Colab, the install cell adds causal-learn with uv.\n\nIn Colab, these are timings of the Colab runtime. Download the notebook to measure your own CPU.\n" }, { "cell_type": "markdown", "id": "7fb27b941602401d91542211134fc71a", "metadata": {}, "source": [ "## Start\n", "\n", "Choose a CPU runtime and run this cell first. In Colab it installs `andrey-core==0.9.0` from PyPI;\n", "local kernels use the installed environment." ] }, { "cell_type": "code", "execution_count": null, "id": "acae54e37e7d407bbb7b55eff062a284", "metadata": { "tags": [ "colab-setup" ] }, "outputs": [], "source": [ "import os\n", "import sys\n", "\n", "IN_COLAB = \"google.colab\" in sys.modules\n", "\n", "if IN_COLAB:\n", " os.environ[\"ANDREY_DEVICE\"] = \"cpu\"\n", " !pip install --quiet \"andrey-core[viz]==0.9.0\" pandas \"causal-learn==0.1.4.8\"" ] }, { "cell_type": "code", "execution_count": null, "id": "speed_on_your_machine-install", "metadata": {}, "outputs": [], "source": [ "if not IN_COLAB:\n", " !uv pip install --python {sys.executable} causal-learn==0.1.4.8" ] }, { "cell_type": "code", "execution_count": null, "id": "speed_on_your_machine-1", "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", "for variable in (\n", " \"OMP_NUM_THREADS\",\n", " \"OPENBLAS_NUM_THREADS\",\n", " \"MKL_NUM_THREADS\",\n", " \"NUMEXPR_NUM_THREADS\",\n", " \"NUMBA_NUM_THREADS\",\n", " \"ANDREY_NUM_WORKERS\",\n", "):\n", " os.environ[variable] = \"1\"\n", "os.environ[\"ANDREY_BACKEND\"] = \"numpy\"\n", "os.environ[\"ANDREY_DEVICE\"] = \"cpu\"" ] }, { "cell_type": "code", "execution_count": null, "id": "speed_on_your_machine-2", "metadata": {}, "outputs": [], "source": [ "import platform\n", "from importlib.metadata import version\n", "from time import perf_counter\n", "\n", "import numpy as np\n", "import pandas as pd\n", "from causallearn.search.ConstraintBased.PC import pc as causal_learn_pc\n", "from threadpoolctl import threadpool_limits\n", "\n", "import andrey\n", "import andrey.metrics as metrics\n", "from andrey.data import benchmarks\n", "\n", "print(\"Execution environment:\", \"Google Colab runtime\" if IN_COLAB else \"Local kernel\")\n", "print(f\"Hardware: {platform.machine()}, {os.cpu_count()} logical CPUs; one thread per fit\")\n", "print(f\"Andrey {andrey.__version__}; causal-learn {version('causal-learn')}\")\n", "\n", "\n", "def convert_causal_learn(result):\n", " # causal-learn stores -1=tail, 1=arrow at the same matrix positions as Andrey.\n", " native = result.G.graph\n", " marks = np.zeros(native.shape, dtype=np.uint8)\n", " marks[native == -1] = 1\n", " marks[native == 1] = 2\n", " return andrey.GraphStructure.from_numpy(marks, kind=\"cpdag\")" ] }, { "cell_type": "markdown", "id": "speed_on_your_machine-3", "metadata": {}, "source": [ "## Warm both packages, then time fits\n", "\n", "Both use stable PC, Fisher-Z, and `alpha=0.05`. The same three seeds are used at each size.\n", "Speed and accuracy are reported separately: every paired ratio, and each package's SHD beside\n", "the empty graph's." ] }, { "cell_type": "code", "execution_count": null, "id": "speed_on_your_machine-4", "metadata": {}, "outputs": [], "source": [ "sizes = [10, 20, 40]\n", "seeds = [7, 11, 19]\n", "rows = []\n", "with threadpool_limits(limits=1):\n", " warm = benchmarks.scm(\"linear_gauss_er\", 5).sample(n=100, seed=0).data\n", " andrey.pc(warm)\n", " causal_learn_pc(warm, alpha=0.05, stable=True, show_progress=False)\n", " for d in sizes:\n", " for seed in seeds:\n", " sample = benchmarks.scm(\"linear_gauss_er\", d).sample(\n", " n=10 * d, seed=seed, scale=\"standardize\"\n", " )\n", " empty = andrey.GraphStructure.from_numpy(np.zeros((d, d)), kind=\"cpdag\")\n", " floor = metrics.score(empty, sample.graph)[\"shd\"]\n", " for package in (\"Andrey\", \"causal-learn\"):\n", " start = perf_counter()\n", " if package == \"Andrey\":\n", " result = andrey.pc(sample.data, alpha=0.05)\n", " else:\n", " result = causal_learn_pc(\n", " sample.data, alpha=0.05, stable=True, show_progress=False\n", " )\n", " seconds = perf_counter() - start\n", " graph = result.structure if package == \"Andrey\" else convert_causal_learn(result)\n", " shd = metrics.score(graph, sample.graph)[\"shd\"]\n", " rows.append(\n", " dict(d=d, seed=seed, package=package, seconds=seconds, SHD=shd, empty_SHD=floor)\n", " )\n", "measurements = pd.DataFrame(rows)\n", "measurements" ] }, { "cell_type": "code", "execution_count": null, "id": "speed_on_your_machine-5", "metadata": {}, "outputs": [], "source": [ "summary = measurements.groupby([\"d\", \"package\"])[[\"seconds\", \"SHD\", \"empty_SHD\"]].median()\n", "display(summary)\n", "paired = measurements.pivot(\n", " index=[\"d\", \"seed\"], columns=\"package\", values=[\"seconds\", \"SHD\", \"empty_SHD\"]\n", ")\n", "ratios = paired[\"seconds\"][\"causal-learn\"] / paired[\"seconds\"][\"Andrey\"]\n", "ratios.groupby(level=\"d\").agg([\"median\", \"count\"]).rename(\n", " columns={\"median\": \"Paired speed ratio\", \"count\": \"Paired seeds\"}\n", ")" ] }, { "cell_type": "markdown", "id": "speed_on_your_machine-6", "metadata": {}, "source": [ "Ratios are calculated per shared dataset and then aggregated. They need not equal ratios of\n", "median times. Increase the ladder only when the preceding sizes finish comfortably;\n", "causal-learn can take minutes at larger sizes.\n", "\n", "The headline workload is PC at 400 variables and 4,000 observations. Add `400` to `sizes` only\n", "when ready for a longer run. The benchmark page records the campaign builds and hardware; this\n", "notebook measures the installed packages with one numeric thread on your machine." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 5 }