Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/codspeed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: CodSpeed

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

jobs:
benchmarks:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
with:
show-progress: false

- uses: astral-sh/setup-uv@v5

- name: Sync test deps
run: uv sync --extra test

- name: Run benchmarks
uses: CodSpeedHQ/action@v4
with:
mode: simulation
token: ${{ secrets.CODSPEED_TOKEN }}
run: uv run pytest tests/benchmarks/ --codspeed
11 changes: 10 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Changelog = "https://delaynet.readthedocs.io/en/latest/changelog/"

[project.optional-dependencies]
lint = ["pre-commit", "ruff"]
test = ["pytest", "pytest-cov", "coverage", "pytest-xdist"]
test = ["pytest", "pytest-cov", "coverage", "pytest-xdist", "pytest-codspeed"]
doc = [
"sphinx",
"myst-nb",
Expand Down Expand Up @@ -89,6 +89,15 @@ select = ["NPY201"]
minversion = "7.0"
addopts = "-ra"
testpaths = ["tests"]
filterwarnings = [
"error",
# eigenvector_centrality warns about symmetric matrix with directed=True
'default:Parameter .*irected.* was passed buts weight matrix is symmetric.*:UserWarning',
# igraph C extension runtime warnings (disconnected graphs in eigenvector centrality)
'default::RuntimeWarning',
# numpy longdouble probe warning on uncommon CPUs (e.g. CodSpeed CI runner)
'default:Signature.*numpy.longdouble.*does not match any known type:UserWarning',
]

[tool.coverage.run]
branch = true
Expand Down
Empty file added tests/benchmarks/__init__.py
Empty file.
37 changes: 37 additions & 0 deletions tests/benchmarks/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Shared fixtures and constants for benchmarks."""

import numpy as np
import pytest

from delaynet.preparation.data_generator import gen_delayed_causal_network

TIME_POINTS = 200
LAG_STEPS = 3
NODE_SIZES = [5, 10]

CONTINUOUS_METRICS = ["lc", "rc", "gv"]


def gen_continuous(n_nodes: int, rng: int = 19425) -> np.ndarray:
_, _, ts = gen_delayed_causal_network(
ts_len=TIME_POINTS, n_nodes=n_nodes, l_dens=0.3, rng=rng
)
return np.ascontiguousarray(ts.T)


@pytest.fixture(scope="module", params=NODE_SIZES, ids=lambda n: f"nodes{n}")
def continuous_data(request) -> np.ndarray:
return gen_continuous(request.param)


def pytest_collection_modifyitems(config, items):
if not config.getoption("codspeed", False):
try:
from pytest_codspeed.plugin import has_benchmark_fixture

msg = pytest.mark.skip(reason="use --codspeed to run benchmarks")
for item in items:
if has_benchmark_fixture(item):
item.add_marker(msg)
except ImportError:
pass
69 changes: 69 additions & 0 deletions tests/benchmarks/test_bench_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Isolated single-pair metric benchmarks (no pair-loop overhead)."""

import numpy as np
import pytest

from delaynet.connectivities.continuous_ordinal_patterns import random_patterns
from delaynet.connectivities.granger import gt_multi_lag
from delaynet.connectivities.linear_correlation import linear_correlation
from delaynet.connectivities.rank_correlation import rank_correlation
from delaynet.connectivities.mutual_information import mutual_information
from delaynet.connectivities.transfer_entropy import transfer_entropy
from delaynet.preparation.data_generator import gen_delayed_causal_network

from .conftest import LAG_STEPS

_ts = np.ascontiguousarray(
gen_delayed_causal_network(ts_len=200, n_nodes=2, l_dens=0.5, rng=0)[2].T
)
TS1, TS2 = _ts[:, 0], _ts[:, 1]

_disc = np.random.default_rng(0).integers(0, 4, size=(200, 2))
DTS1, DTS2 = _disc[:, 0], _disc[:, 1]

N_TESTS = 2


@pytest.mark.benchmark(group="metrics")
@pytest.mark.parametrize(
"metric_func,kwargs",
[
(linear_correlation, {}),
(rank_correlation, {}),
],
ids=["lc", "rc"],
)
def test_continuous_metric(benchmark, metric_func, kwargs):
benchmark(metric_func, TS1, TS2, lag_steps=LAG_STEPS, **kwargs)


@pytest.mark.benchmark(group="metrics")
def test_granger_f_test(benchmark):
ts = gen_delayed_causal_network(ts_len=50, n_nodes=5, l_dens=0.3, rng=0)[2].T
ts1, ts2 = ts[:, 0], ts[:, 1]
benchmark(gt_multi_lag, ts1, ts2, lag_steps=LAG_STEPS)


@pytest.mark.benchmark(group="metrics")
def test_ordinal_patterns(benchmark):
ts = gen_delayed_causal_network(ts_len=50, n_nodes=5, l_dens=0.3, rng=0)[2].T
ts1, ts2 = ts[:, 0], ts[:, 1]
random_patterns(ts1, ts2, p_size=3, num_rnd_patterns=2, lag_steps=1)
benchmark(random_patterns, ts1, ts2, p_size=3, num_rnd_patterns=2, lag_steps=1)


@pytest.mark.benchmark(group="metrics")
@pytest.mark.parametrize(
"metric_func",
[mutual_information, transfer_entropy],
ids=["mi", "te"],
)
def test_discrete_metric(benchmark, metric_func):
benchmark(
metric_func,
DTS1,
DTS2,
approach="discrete",
lag_steps=LAG_STEPS,
n_tests=N_TESTS,
)
28 changes: 28 additions & 0 deletions tests/benchmarks/test_bench_reconstruct_network.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""reconstruct_network benchmarks: full pipeline across metrics and sizes."""

import pytest

from delaynet.network_reconstruction import reconstruct_network

from .conftest import LAG_STEPS, gen_continuous

_RECON_METRICS = ["lc", "rc", "gc", "gv"]


@pytest.mark.benchmark(group="reconstruct-network")
@pytest.mark.parametrize("metric", _RECON_METRICS)
def test_reconstruct(benchmark, continuous_data, metric):
benchmark(reconstruct_network, continuous_data, metric, lag_steps=LAG_STEPS)


@pytest.mark.benchmark(group="parallel-scaling")
@pytest.mark.parametrize("workers", [1, 2], ids=["seq", "par2"])
def test_reconstruct_workers(benchmark, workers):
data = gen_continuous(5)
benchmark(
reconstruct_network,
data,
"lc",
lag_steps=LAG_STEPS,
workers=workers,
)
23 changes: 23 additions & 0 deletions tests/benchmarks/test_bench_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""find_optimal_lag wrapper overhead, measured with a lightweight metric."""

import numpy as np
import pytest

from delaynet.preparation.data_generator import gen_delayed_causal_network
from delaynet.utils.lag_steps import find_optimal_lag

from .conftest import LAG_STEPS

_ts = np.ascontiguousarray(
gen_delayed_causal_network(ts_len=500, n_nodes=2, l_dens=0.5, rng=0)[2].T
)
TS1, TS2 = _ts[:, 0], _ts[:, 1]


def _l2_metric(ts1, ts2, lag):
return float(np.sum((ts1[:-lag] - ts2[lag:]) ** 2))


@pytest.mark.benchmark(group="utils")
def test_find_optimal_lag(benchmark):
benchmark(find_optimal_lag, _l2_metric, TS1, TS2, list(range(1, LAG_STEPS + 1)))
Loading
Loading