Skip to content
Open
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
61 changes: 42 additions & 19 deletions .github/workflows/buildwheels.yml → .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
name: Build Wheels
name: Build and Deploy to PyPI

on:
workflow_dispatch:
pull_request:
push:
tags:
- "v*.*.*"
release:
types:
- published

jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
name: Build wheels for ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
Expand All @@ -28,38 +31,58 @@ jobs:
persist-credentials: false

- name: Install uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
with:
version: "0.10.11"
version: "0.11.20"

- name: Build wheels
uses: pypa/cibuildwheel@v3.4.0
# env:
# CIBW_BUILD_FRONTEND: value
# ...
# with:
# package-dir: .
# output-dir: wheelhouse
# config-file: "{package}/pyproject.toml"
- name: Build Wheels
run: uvx cibuildwheel==4.0.0

# - name: Build wheels
# uses: pypa/cibuildwheel@v3.4.0
# env:
# CIBW_BUILD_FRONTEND: value
# ...
# with:
# package-dir: .
# output-dir: wheelhouse
# config-file: "{package}/pyproject.toml"

- uses: actions/upload-artifact@v4
with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl

publish:
build_sdist:
name: Build Source Distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Build sdist
run: uvx build --sdist
- uses: actions/upload-artifact@v4
with:
name: cibw-sdist
path: dist/*.tar.gz

upload_pypi:
name: Publish to PyPI
needs: build_wheels
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/respondpy/
permissions:
id-token: write

if: github.event_name == 'release' && github.event.action == 'published'
steps:
- name: Download wheel artifacts
uses: actions/download-artifact@v5
with:
path: dist
pattern: cibw-wheels-*
pattern: cibw-*
merge-multiple: true

- name: Publish distributions to PyPI with attestations
Expand Down
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ set(Python_ARTIFACTS_INTERACTIVE TRUE)
FetchContent_Declare(
pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11.git
GIT_TAG f5fbe867d2d26e4a0a9177a51f6e568868ad3dc8 # v3.0.1
GIT_TAG d03662f0984f652b60e7ddce53d3868002275197 # v3.0.4
FIND_PACKAGE_ARGS NAMES pybind11)

FetchContent_Declare(
Expand All @@ -62,7 +62,7 @@ set(SPDLOG_INSTALL ON)
FetchContent_Declare(
respond
GIT_REPOSITORY https://github.com/SyndemicsLab/respond.git
GIT_TAG 538f5b228f7b8781562057d390c378f0775b50fb # v2.3.1
GIT_TAG f54256ce2b27ea09a19bfdf2395d46c2f04e99c8 # feature/documentation-and-logging
OVERRIDE_FIND_PACKAGE
)
set(RESPOND_BUILD_DOCS OFF)
Expand Down
242 changes: 242 additions & 0 deletions benchmarks/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
################################################################################
# File: conftest.py #
# Project: respondpy #
# Created Date: 2026-05-06 #
# Author: Matthew Carroll #
# ----- #
# Last Modified: 2026-05-06 #
# Modified By: Matthew Carroll #
# ----- #
# Copyright (c) 2026 Syndemics Lab at Boston Medical Center #
################################################################################

"""
Shared fixtures for respondpy benchmarks.

The database is created once per session (scope="session") so DB setup cost
is paid only once across all benchmark functions.
"""

from __future__ import annotations

import sqlite3
from configparser import ConfigParser
from pathlib import Path

import pytest


# ---------------------------------------------------------------------------
# Schema & seed data
# ---------------------------------------------------------------------------

_DB_SCHEMA = """
DROP TABLE IF EXISTS "intervention";
CREATE TABLE "intervention" (
"id" INTEGER NOT NULL UNIQUE,
"name" TEXT NOT NULL UNIQUE,
PRIMARY KEY("id" AUTOINCREMENT)
);
DROP TABLE IF EXISTS "behavior";
CREATE TABLE "behavior" (
"id" INTEGER NOT NULL UNIQUE,
"name" TEXT NOT NULL UNIQUE,
PRIMARY KEY("id" AUTOINCREMENT)
);
DROP TABLE IF EXISTS "background_mortality";
CREATE TABLE "background_mortality" (
"sample" INTEGER NOT NULL,
"time" INTEGER NOT NULL,
"probability" REAL NOT NULL DEFAULT 0.0,
PRIMARY KEY("sample","time")
);
DROP TABLE IF EXISTS "behavior_transition";
CREATE TABLE "behavior_transition" (
"sample" INTEGER NOT NULL,
"intervention" INTEGER NOT NULL,
"time" INTEGER NOT NULL,
"initial_behavior" INTEGER NOT NULL,
"new_behavior" INTEGER NOT NULL,
"probability" REAL NOT NULL DEFAULT 0.0,
PRIMARY KEY("sample","intervention","time","initial_behavior","new_behavior"),
FOREIGN KEY("initial_behavior") REFERENCES "behavior"("id"),
FOREIGN KEY("intervention") REFERENCES "intervention"("id"),
FOREIGN KEY("new_behavior") REFERENCES "behavior"("id")
);
DROP TABLE IF EXISTS "initial_population";
CREATE TABLE "initial_population" (
"sample" INTEGER NOT NULL,
"intervention" INTEGER NOT NULL,
"behavior" INTEGER NOT NULL,
"count" REAL NOT NULL DEFAULT 0.0,
PRIMARY KEY("sample","intervention","behavior")
);
DROP TABLE IF EXISTS "intervention_transition";
CREATE TABLE "intervention_transition" (
"sample" INTEGER NOT NULL,
"behavior" INTEGER NOT NULL,
"time" INTEGER NOT NULL,
"initial_intervention" INTEGER NOT NULL,
"new_intervention" INTEGER NOT NULL,
"probability" REAL NOT NULL DEFAULT 0.0,
PRIMARY KEY("sample","behavior","initial_intervention","new_intervention","time"),
FOREIGN KEY("behavior") REFERENCES "behavior"("id"),
FOREIGN KEY("initial_intervention") REFERENCES "intervention"("id"),
FOREIGN KEY("new_intervention") REFERENCES "intervention"("id")
);
DROP TABLE IF EXISTS "overdose";
CREATE TABLE "overdose" (
"intervention" INTEGER NOT NULL,
"sample" INTEGER NOT NULL,
"behavior" INTEGER NOT NULL,
"time" INTEGER NOT NULL,
"probability" REAL NOT NULL DEFAULT 0.0,
PRIMARY KEY("intervention","sample","behavior","time"),
FOREIGN KEY("behavior") REFERENCES "behavior"("id"),
FOREIGN KEY("intervention") REFERENCES "intervention"("id")
);
DROP TABLE IF EXISTS "overdose_fatality";
CREATE TABLE "overdose_fatality" (
"sample" INTEGER NOT NULL,
"intervention" INTEGER NOT NULL,
"behavior" INTEGER NOT NULL,
"time" INTEGER NOT NULL,
"probability" REAL NOT NULL DEFAULT 0.0,
PRIMARY KEY("sample","intervention","behavior","time"),
FOREIGN KEY("behavior") REFERENCES "behavior"("id"),
FOREIGN KEY("intervention") REFERENCES "intervention"("id")
);
DROP TABLE IF EXISTS "population_change";
CREATE TABLE "population_change" (
"sample" INTEGER NOT NULL,
"intervention" INTEGER NOT NULL,
"behavior" INTEGER NOT NULL,
"time" INTEGER NOT NULL,
"count" REAL NOT NULL DEFAULT 0.0,
PRIMARY KEY("sample","intervention","behavior","time"),
FOREIGN KEY("behavior") REFERENCES "behavior"("id"),
FOREIGN KEY("intervention") REFERENCES "intervention"("id")
);
DROP TABLE IF EXISTS "smr";
CREATE TABLE "smr" (
"sample" INTEGER NOT NULL,
"intervention" INTEGER NOT NULL,
"behavior" INTEGER NOT NULL,
"time" INTEGER NOT NULL,
"ratio" REAL NOT NULL DEFAULT 1.0,
PRIMARY KEY("sample","time","behavior","intervention"),
FOREIGN KEY("behavior") REFERENCES "behavior"("id"),
FOREIGN KEY("intervention") REFERENCES "intervention"("id")
);
DROP TABLE IF EXISTS "cohort";
CREATE TABLE "cohort" (
"id" INTEGER NOT NULL UNIQUE,
"description" TEXT,
"background_mortality_sample" INTEGER NOT NULL,
"behavior_transition_sample" INTEGER NOT NULL,
"initial_population_sample" INTEGER NOT NULL,
"intervention_transition_sample" INTEGER NOT NULL,
"overdose_sample" INTEGER NOT NULL,
"overdose_fatality_sample" INTEGER NOT NULL,
"population_change_sample" INTEGER NOT NULL,
"smr_sample" INTEGER NOT NULL,
PRIMARY KEY("id" AUTOINCREMENT)
);
DROP TABLE IF EXISTS "demographics";
CREATE TABLE "demographics" (
"id" INTEGER NOT NULL UNIQUE,
"type" TEXT NOT NULL,
"value" TEXT NOT NULL,
PRIMARY KEY("id" AUTOINCREMENT)
);
DROP TABLE IF EXISTS "cohort_demographics";
CREATE TABLE "cohort_demographics" (
"cohort_id" INTEGER NOT NULL,
"demographic_id" INTEGER NOT NULL,
PRIMARY KEY("cohort_id","demographic_id"),
FOREIGN KEY("cohort_id") REFERENCES "cohort"("id"),
FOREIGN KEY("demographic_id") REFERENCES "demographics"("id")
);
"""

_SEED_DATA = """
INSERT INTO cohort (id, description, background_mortality_sample, behavior_transition_sample, initial_population_sample, intervention_transition_sample, overdose_sample, overdose_fatality_sample, population_change_sample, smr_sample)
VALUES (1, "Benchmark Cohort", 1, 1, 1, 1, 1, 1, 1, 1);

INSERT INTO intervention (id, name)
VALUES (1, "no_treatment"), (2, "early_buprenorphine"), (3, "buprenorphine"), (4, "post_buprenorphine");

INSERT INTO behavior (id, name)
VALUES (1, "active_injection"), (2, "nonactive_injection");

INSERT INTO initial_population (sample, intervention, behavior, count)
VALUES (1,1,1,100),(1,1,2,150),(1,2,1,200),(1,2,2,250),(1,3,1,0),(1,3,2,0),(1,4,1,0),(1,4,2,0);

INSERT INTO population_change (sample, intervention, behavior, time, count)
VALUES (1,1,1,1,100),(1,1,2,1,150),(1,2,1,1,200),(1,2,2,1,250),(1,3,1,1,0),(1,3,2,1,0),(1,4,1,1,0),(1,4,2,1,0);

INSERT INTO intervention_transition (sample, behavior, time, initial_intervention, new_intervention, probability)
VALUES (1,1,1,1,1,0.8),(1,2,1,1,1,0.7),(1,1,1,1,2,0.2),(1,2,1,1,2,0.3),
(1,1,1,1,3,0.0),(1,2,1,1,3,0.0),(1,1,1,1,4,0.0),(1,2,1,1,4,0.0),
(1,1,1,2,1,0.0),(1,2,1,2,1,0.0),(1,1,1,2,2,0.7),(1,2,1,2,2,0.6),
(1,1,1,2,3,0.2),(1,2,1,2,3,0.1),(1,1,1,2,4,0.1),(1,2,1,2,4,0.3),
(1,1,1,3,1,0.0),(1,2,1,3,1,0.0),(1,1,1,3,2,0.0),(1,2,1,3,2,0.0),
(1,1,1,3,3,0.8),(1,2,1,3,3,0.8),(1,1,1,3,4,0.2),(1,2,1,3,4,0.2),
(1,1,1,4,1,0.8),(1,2,1,4,1,0.8),(1,1,1,4,2,0.0),(1,2,1,4,2,0.0),
(1,1,1,4,3,0.0),(1,2,1,4,3,0.0),(1,1,1,4,4,0.2),(1,2,1,4,4,0.2);

INSERT INTO behavior_transition (sample, intervention, time, initial_behavior, new_behavior, probability)
VALUES (1,1,1,1,1,0.8),(1,1,1,1,2,0.2),(1,1,1,2,1,0.1),(1,1,1,2,2,0.9),
(1,2,1,1,1,0.9),(1,2,1,1,2,0.1),(1,2,1,2,1,0.7),(1,2,1,2,2,0.3),
(1,3,1,1,1,0.3),(1,3,1,1,2,0.7),(1,3,1,2,1,0.4),(1,3,1,2,2,0.6),
(1,4,1,1,1,0.3),(1,4,1,1,2,0.7),(1,4,1,2,1,0.2),(1,4,1,2,2,0.8);

INSERT INTO smr (sample, intervention, behavior, time, ratio)
VALUES (1,1,1,1,2.0),(1,1,2,1,2.1),(1,2,1,1,2.0),(1,2,2,1,2.1),
(1,3,1,1,2.0),(1,3,2,1,2.1),(1,4,1,1,2.0),(1,4,2,1,2.1);

INSERT INTO background_mortality (sample, time, probability)
VALUES (1,1,0.25);

INSERT INTO overdose (sample, intervention, behavior, time, probability)
VALUES (1,1,1,1,0.8),(1,1,2,1,0.7),(1,2,1,1,0.8),(1,2,2,1,0.7),
(1,3,1,1,0.8),(1,3,2,1,0.7),(1,4,1,1,0.8),(1,4,2,1,0.7);

INSERT INTO overdose_fatality (sample, intervention, behavior, time, probability)
VALUES (1,1,1,1,0.1),(1,1,2,1,0.2),(1,2,1,1,0.1),(1,2,2,1,0.2),
(1,3,1,1,0.1),(1,3,2,1,0.2),(1,4,1,1,0.1),(1,4,2,1,0.2);
"""


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture(scope="session")
def benchmark_db(tmp_path_factory) -> Path:
"""Create and seed a SQLite database once for the entire benchmark session."""
db_path = tmp_path_factory.mktemp("bench-data") / "benchmark.db"
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.executescript(_DB_SCHEMA)
cursor.executescript(_SEED_DATA)
conn.commit()
conn.close()
return db_path


@pytest.fixture(scope="session")
def benchmark_config() -> ConfigParser:
"""Fixed simulation config used across all benchmarks."""
cfg = ConfigParser()
cfg["simulation"] = {
"duration": "52",
"parameter_change_times": "52",
"stratify_entering_cohort": "false",
}
cfg["output"] = {
"build_summary_stats": "true",
"save_state_history": "true",
"timesteps_to_report": "52",
}
return cfg
Loading
Loading