From c4894770c11593874c9492eb84ced6526e5b6d7e Mon Sep 17 00:00:00 2001 From: Iga Narendra Date: Mon, 4 May 2026 14:36:31 +0700 Subject: [PATCH 01/13] init docker setups --- .dockerignore | 8 ++++++++ docker-compose.yml | 13 +++++++++++++ docker/linux/Dockerfile | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 .dockerignore create mode 100644 docker-compose.yml create mode 100644 docker/linux/Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c4732a3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.git +.venv +bin/results +bin/simulation_cpu/results +bin/simulation_cpu_postprocessing/results +**/*.o +**/__pycache__ +*.pyc diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1601e96 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +services: + drugsim-linux: + platform: linux/amd64 + build: + context: . + dockerfile: docker/linux/Dockerfile + container_name: drugsim-linux + working_dir: /workspace/DrugSimulation + stdin_open: true + tty: true + volumes: + - .:/workspace/DrugSimulation + - ../libCML:/workspace/libCML diff --git a/docker/linux/Dockerfile b/docker/linux/Dockerfile new file mode 100644 index 0000000..faec0a1 --- /dev/null +++ b/docker/linux/Dockerfile @@ -0,0 +1,21 @@ +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y --no-install-recommends \ + bash \ + build-essential \ + ca-certificates \ + cmake \ + git \ + libcurl4-openssl-dev \ + libjson-c-dev \ + libopenmpi-dev \ + make \ + openmpi-bin \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /workspace/DrugSimulation + +CMD ["/bin/bash"] From 1577064496a435e2e6ffc01d402d9cc5ad3fd008 Mon Sep 17 00:00:00 2001 From: Iga Narendra Date: Mon, 4 May 2026 14:38:58 +0700 Subject: [PATCH 02/13] add readme, and tutorial on how to run on macos Co-authored-by: Copilot --- README.md | 494 +++++++++++++++++++++++++++++ docs/SOLUTION2_MACOS_SELF_GUIDE.md | 423 ++++++++++++++++++++++++ 2 files changed, 917 insertions(+) create mode 100644 README.md create mode 100644 docs/SOLUTION2_MACOS_SELF_GUIDE.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..1c4a2e4 --- /dev/null +++ b/README.md @@ -0,0 +1,494 @@ +# DrugSimulation + +A high-performance, MPI-parallel in-silico cardiac drug safety simulation framework based on the [CiPA (Comprehensive in vitro Proarrhythmia Assay)](https://cipaproject.org/) initiative. The tool simulates the electrophysiological effects of drugs on human ventricular cardiomyocytes across multiple cell models and drug concentrations, then extracts quantitative biomarkers (CiPA features) for cardiotoxicity assessment. + +--- + +## Table of Contents + +- [Overview](#overview) +- [Cell Models](#cell-models) +- [Prerequisites](#prerequisites) +- [Building](#building) +- [Project Structure](#project-structure) +- [Configuration (`param.txt`)](#configuration-paramtxt) +- [Input Files](#input-files) +- [Running a Simulation Locally on Your Machine](#running-a-simulation-locally-on-your-machine) +- [Running in Dockerized Linux (Recommended for macOS)](#running-in-dockerized-linux-recommended-for-macos) +- [Running a Simulation on an HPC Cluster (PBS/Torque)](#running-a-simulation-on-an-hpc-cluster-pbstorque) +- [Post-processing & Reporting](#post-processing--reporting) +- [Output Files](#output-files) +- [Python Scripts](#python-scripts) +- [AI Report Generation](#ai-report-generation) + +--- + +## Overview + +DrugSimulation runs a multi-pace action-potential (AP) simulation for each drug sample and concentration. It computes steady-state electrical behaviour over up to 1000 paces, then extracts the following CiPA biomarker features from the final paces: + +| Feature | Description | +|---|---| +| `qNet` | Net charge (area under net current) | +| `qInward` | Inward charge | +| `INaLAUC` | Area under the late sodium current | +| `ICaLAUC` | Area under the L-type calcium current | +| `APD90` / `APD50` | Action potential duration at 90% / 50% repolarisation | +| `APDTri` | AP triangulation (APD90 − APD50) | +| `VmMax` / `VmRest` | Peak and resting membrane potential | +| `dVmdtMax` | Maximum upstroke velocity | +| `dVmdtMaxRepol` | Maximum repolarisation rate | +| `CaTD90` / `CaTD50` | Calcium transient duration at 90% / 50% | +| `CaTDTri` | Calcium transient triangulation | +| `CaMax` / `CaRest` | Peak and resting intracellular calcium | + +MPI is used to distribute samples across CPU cores. The simulation supports both a full in-silico run and a faster postprocessing-only mode that reads pre-computed steady-state values. + +--- + +## Cell Models + +Select the cardiac cell model at compile time using one of the preprocessor macros below: + +| Macro | Binary suffix | Cell model | +|---|---|---| +| *(none)* | `ORd-static` | O'Hara–Rudy 2011 (default) | +| `-DCIPAORDV1` | `CiPAORdv1.0` | O'Hara–Rudy CiPA v1.0 (2017) | +| `-DTOR_ORD` | `ToR-ORd` | Tomek–O'Hara–Rudy (ToR-ORd) | +| `-DTOR_ORD_DYNCL` | `ToR-ORd-dynCl` | ToR-ORd with dynamic chloride | +| `-DGRANDI` | `Grandi` | Grandi 2011 atrial | +| `-DORD_STATIC_LAND` | `ORd-static_Land` | O'Hara–Rudy + Land contraction | +| `-DCIPAORDV1_LAND` | `CiPAORdv1.0_Land` | CiPAORdv1.0 + Land contraction | +| `-DTOR_ORD_LAND` | `ToR-ORd_Land` | ToR-ORd + Land contraction | + +Add `-DPOSTPROCESSING` to any of the above to build the postprocessing variant. + +--- + +## Prerequisites + +| Dependency | Notes | +|---|---| +| C++11 compiler with MPI (`mpicxx` / `mpicc`) | e.g. OpenMPI or MPICH | +| [libCML](../libCML/) | Cardiac Modelling Library (expected at `../libCML/`) | +| [SUNDIALS CVode 5.7.0](https://computing.llnl.gov/projects/sundials) | ODE solver (expected at `../libCML/libs/sundials-5.7.0/`) | +| `libcurl` | Used by the report generator | +| `libjson-c` | Used by the report generator | +| Python ≥ 3.6 | For post-processing scripts | +| Python packages | See [`bin/scripts/requirements.txt`](bin/scripts/requirements.txt) | + +Install Python dependencies: + +```bash +pip install -r bin/scripts/requirements.txt +``` + +--- + +## Building + +### Build all variants at once + +```bash +bash compile_binaries.sh +``` + +This builds all eight cell-model variants plus their postprocessing counterparts and places the binaries in `bin/`. + +### Build a single variant manually + +```bash +# Example: CiPAORdv1.0 +make all EXTRA_CXXFLAGS+="-std=c++11 -DCIPAORDV1" + +# Example: ToR-ORd postprocessing +make all EXTRA_CXXFLAGS+="-std=c++11 -DTOR_ORD -DPOSTPROCESSING" + +# Clean build artefacts for a variant +make EXTRA_CXXFLAGS+="-DCIPAORDV1" clean +``` + +Compiled binaries are written to `bin/drugsim_`. + +--- + +## Project Structure + +``` +DrugSimulation/ +├── main.cpp # Entry point – MPI init, parameter loading, simulation dispatch +├── Makefile # Build system +├── compile_binaries.sh # Builds all cell-model variants in one step +├── modules/ +│ ├── drug_simulation_bench.cpp/hpp # Top-level simulation orchestrator (MPI work distribution) +│ ├── insilico.cpp/hpp # Single-sample in-silico AP simulation loop +│ ├── postprocessing.cpp/hpp # Feature extraction from pre-computed steady states +│ ├── report_drug.cpp/hpp # Invokes the AI report-generation pipeline +│ └── show_param_logs.cpp/hpp # Prints parameter summary to stdout +└── bin/ + ├── drugsim_ # Compiled binaries (git-ignored) + ├── scripts/ # Shared utility scripts + │ ├── combine_and_average_features.py # Merges per-core feature CSVs; computes per-concentration averages + │ ├── compute_mean_ci.py # Bootstrap median + 95% CI across concentrations + │ ├── plot_features.py # Dose–response feature plots + │ ├── plot_time_series.py # Time-series plots (Vm, dVm/dt, Cai, currents) + │ ├── convert_params.sh # Converts param.txt keys to camelCase + │ ├── create_concs_directories.sh # Creates result sub-directories per concentration + │ ├── create_escaped_b64_file.sh # Escapes & Base64-encodes files for API upload + │ ├── unzip_files.sh / zip_files.sh # Archive helpers for result files + │ └── requirements.txt # Python package pinned versions + ├── simulation_cpu/ # Ready-to-run working directory for a standard CPU simulation + │ ├── param.txt # Simulation parameters (edit this before running) + │ ├── exec_bash.sh # Launch simulation locally via mpiexec + │ ├── exec_pbs.sh # Launch simulation on a PBS/Torque HPC cluster + │ ├── kill_bash.sh # Kill a running local simulation + │ ├── generate_report.sh # Run post-processing and generate figures + │ ├── exec_perplexity_report_generator.sh # AI narrative report via Perplexity API + │ ├── chantest_hill/ # IC50 / Hill coefficient input files per drug + │ ├── chantest_herg/ # hERG-specific parameter files per drug + │ ├── population/ # Conductance variability (virtual population) files + │ ├── initial_states/ # Pre-computed steady-state initial conditions + │ ├── kitox_error_data/ # Reference IC50 data + │ └── requests/ # Perplexity API prompt templates + └── simulation_cpu_postprocessing/ # Working directory for postprocessing-only runs + └── (same layout as simulation_cpu/) +``` + +--- + +## Configuration (`param.txt`) + +Edit `bin/simulation_cpu/param.txt` before running. Key parameters: + +| Parameter | Example | Description | +|---|---|---| +| `user_name` | `marcell` | Label used in output filenames | +| `number_pacing` | `1000` | Total number of paces to simulate | +| `number_pacing_write` | `10` | Number of final paces written to disk | +| `cycle_length` | `2000` | Pacing cycle length (ms) | +| `cell_model` | `CiPAORdv1.0_endo` | Cell model + cell type (`endo`, `epi`, `myo`) | +| `stimulus_duration` | `0.5` | Stimulus current duration (ms) | +| `stimulus_amplitude_scale` | `1.0` | Scaling factor for stimulus amplitude | +| `solver_type` | `CVode` | ODE solver: `CVode` or `Euler` | +| `time_step_min` | `0.5` | Minimum adaptive time step for CVode (ms) | +| `time_step_max` | `2.0` | Maximum adaptive time step for CVode (ms) | +| `writing_step` | `1.0` | Output writing interval (ms) | +| `drug_name` | `quinidine` | Drug name (used in filenames) | +| `drug_concentrations` | `3237,6474,9711,12948` | Comma-separated concentrations to simulate (µM) | +| `hill_file` | `./chantest_hill/quinidine/IC50_samples30.csv` | IC50 & Hill coefficient samples | +| `herg_file` | `./chantest_herg/quinidine/boot_pars10.csv` | hERG bootstrapped parameters | +| `is_cvar` | `0` | Enable conductance variability (`1` = on) | +| `cvar_file` | `./population/indi_1sample.csv` | Conductance variability population file | +| `number_of_cpu` | `10` | Number of MPI processes for `mpiexec` | + +--- + +## Input Files + +### IC50 / Hill coefficients (`hill_file`) + +CSV file where each row is one virtual sample. Columns define per-channel IC50 and Hill coefficient pairs: + +``` +ICaL_IC50, ICaL_h, IK1_IC50, IK1_h, IKs_IC50, IKs_h, +INa_IC50, INa_h, INaL_IC50, INaL_h, Ito_IC50, Ito_h, +hERG_IC50, hERG_h +``` + +### hERG parameters (`herg_file`) + +CSV file with bootstrapped hERG kinetic parameters for the CiPAORdv1.0 model. + +### Conductance variability (`cvar_file`) + +CSV file providing per-sample scaling factors for ionic conductances (e.g., `GNa_fast`, `GKr`, `PCa`, …). Enables virtual population simulations. + +--- + +## Running a Simulation Locally on Your Machine + +This is a complete end-to-end walkthrough for running a simulation on your local machine. + +### Step 1 — Build the binaries + +From the repository root, build all variants: + +```bash +bash compile_binaries.sh +``` + +Or build only the variant you need (example: CiPAORdv1.0): + +```bash +make all EXTRA_CXXFLAGS+="-std=c++11 -DCIPAORDV1" +``` + +Binaries are placed in `bin/`. + +### Step 2 — Install Python dependencies + +```bash +pip install -r bin/scripts/requirements.txt +``` + +### Step 3 — Configure the simulation + +```bash +cd bin/simulation_cpu +``` + +Open `param.txt` and set the parameters for your run. The most important ones: + +```ini +user_name = your_name +cell_model = CiPAORdv1.0_endo # choose model + cell type (endo/epi/myo) +drug_name = quinidine +drug_concentrations = 3237,6474,9711,12948 +hill_file = ./chantest_hill/quinidine/IC50_samples30.csv +herg_file = ./chantest_herg/quinidine/boot_pars10.csv +number_pacing = 1000 +number_of_cpu = 4 # set to the number of cores you want to use +``` + +> **Tip:** Set `number_of_cpu` to the number of physical/logical cores available on your machine. Run `nproc` (Linux) or `sysctl -n hw.logicalcpu` (macOS) to check. + +### Step 4 — Launch the simulation + +```bash +bash exec_bash.sh +``` + +`exec_bash.sh` will: +1. Read `param.txt` to determine the cell model, drug, concentrations, and CPU count. +2. Verify that `number_of_cpu` does not exceed the machine's available cores. +3. Create result sub-directories under `./results//`. +4. Run `mpiexec -np ../drugsim_ -input_deck param.txt`, logging all output to `./results/logfile`. +5. Zip time-series, feature, and initial-values CSVs per concentration. +6. Automatically call `generate_report.sh` to produce plots and summary statistics. + +Progress is printed to the terminal; detailed logs are in `./results/logfile`. + +### Step 5 — Monitor and stop + +Tail the log in a separate terminal: + +```bash +tail -f ./results/logfile +``` + +To kill a running simulation cleanly: + +```bash +bash kill_bash.sh +``` + +### Step 6 — View results + +After completion, results are in `./results//`: + +``` +results/ +├── 0.00/ # control (no drug) +├── 3237.00/ # Cmax1 +│ ├── quinidine_3237.00_time_series_smp0.csv +│ ├── quinidine_3237.00_features_core0.csv +│ └── … +├── quinidine-median-ci.csv # bootstrap median + 95% CI across concentrations +└── logfile +``` + +Open the generated plots (`.png` / `.pdf`) in the concentration directories, or run `generate_report.sh` again separately if you want to regenerate figures without re-running the full simulation. + +--- + +## Running in Dockerized Linux (Recommended for macOS) + +This repository now includes a Linux container workflow that preserves the expected original layout where `libCML` is a sibling directory. + +The Docker service is pinned to `linux/amd64` so it matches the bundled SUNDIALS archives under `../libCML/libs/sundials-5.7.0/lib64`. + +Expected host layout: + +```text +/Users/iganarendra/ + DrugSimulation/ + libCML/ +``` + +### 1. Build the container image + +From the repository root: + +```bash +docker compose build drugsim-linux +``` + +### 2. Build and run simulation in one command (inside container) + +```bash +docker compose run --rm \ + -e VARIANT_FLAG=-DCIPAORDV1 \ + -e NP=10 \ + -e SIM_DIR=bin/simulation_cpu \ + drugsim-linux \ + bash -lc "./docker/linux/build_and_run.sh" +``` + +What this does: +1. Builds `libCML/build/libcml.a` inside Linux. +2. Builds DrugSimulation binary using bundled Linux SUNDIALS libs from `../libCML/libs/sundials-5.7.0/lib64`. +3. Runs `mpiexec` on `bin/drugsim_CiPAORdv1.0` with `param.txt` from `bin/simulation_cpu/`. +4. Writes runtime logs to `bin/simulation_cpu/results/logfile.docker`. + +### 3. Build only (skip simulation) + +```bash +docker compose run --rm \ + -e VARIANT_FLAG=-DCIPAORDV1 \ + -e RUN_SIM=0 \ + drugsim-linux \ + bash -lc "./docker/linux/build_and_run.sh" +``` + +### 3a. Run a short pacing test without editing `param.txt` + +```bash +docker compose run --rm \ + -e VARIANT_FLAG=-DCIPAORDV1 \ + -e NP=2 \ + -e PACING_OVERRIDE=5 \ + -e PACING_WRITE_OVERRIDE=5 \ + -e SOLVER_OVERRIDE=Euler \ + drugsim-linux \ + bash -lc "./docker/linux/build_and_run.sh" +``` + +This creates a temporary parameter file inside the container run, leaves your checked-in `bin/simulation_cpu/param.txt` unchanged, and runs the full simulation flow with only 5 paces using the Euler solver. + +### 4. Supported variant flags + +- `-DCIPAORDV1` +- `-DTOR_ORD` +- `-DTOR_ORD_DYNCL` +- `-DGRANDI` +- `-DORD_STATIC_LAND` +- `-DCIPAORDV1_LAND` +- `-DTOR_ORD_LAND` +- empty string (`""`) for ORd-static default + +### 5. Rollback / cleanup + +Container-only cleanup (safe): + +```bash +docker compose down --remove-orphans +docker image prune -f +``` + +Reset build artifacts in both repos: + +```bash +cd /Users/iganarendra/DrugSimulation && make clean || true +cd /Users/iganarendra/libCML && make clean || true +``` + +Revert dockerization file changes in this repo: + +```bash +cd /Users/iganarendra/DrugSimulation +git restore --source=HEAD --staged --worktree -- Makefile README.md docker-compose.yml docker/ +``` + +--- + +## Running a Simulation on an HPC Cluster (PBS/Torque) + +1. Navigate to the working directory: + ```bash + cd bin/simulation_cpu + ``` + +2. Edit `param.txt` (same as above). + +3. Submit to the queue: + ```bash + qsub exec_pbs.sh + ``` + +The PBS script requests 1 node with 10 processors (`ppn=10`) by default. Adjust `#PBS -l nodes=1:ppn=N` and `number_of_cpu` in `param.txt` to match your allocation. + +To stop a running local simulation: +```bash +bash kill_bash.sh +``` + +--- + +## Post-processing & Reporting + +From the `simulation_cpu` working directory, after a simulation completes: + +```bash +bash generate_report.sh +``` + +This script performs four steps: + +1. **Time-series plots** — calls `plot_time_series.py` to generate plots of Vm, dVm/dt, Cai, and key ionic currents for each concentration. +2. **Feature plots** — calls `plot_features.py` to produce dose–response plots for all 16 CiPA biomarkers. +3. **Feature aggregation** — calls `combine_and_average_features.py` to merge per-core CSV fragments and compute per-concentration sample averages. +4. **Median & confidence intervals** — calls `compute_mean_ci.py` to compute bootstrap median and 95% CI across concentrations, writing `-median-ci.csv`. + +--- + +## Output Files + +Results are written under `./results//`: + +| File pattern | Contents | +|---|---| +| `__time_series_smp.csv` | Full time-series (Vm, Cai, currents) for sample N | +| `__features_core.csv` | CiPA feature values computed by MPI core K | +| `__initial_values_smp.csv` | Steady-state initial conditions for sample N | +| `--features-all.csv` | Combined features for all samples at a concentration | +| `sample-averages-across-concentrations.csv` | Mean feature value per sample across concentrations | +| `-median-ci.csv` | Bootstrap median + 95% CI for each feature | +| `-depol-failure-count.csv` | Count of depolarisation failures per concentration | + +--- + +## Python Scripts + +All Python utilities live in [`bin/scripts/`](bin/scripts/). + +| Script | Purpose | +|---|---| +| `plot_time_series.py` | Plot Vm, dVm/dt, Cai, INaL, ICaL, IKr, etc. vs. time | +| `plot_features.py` | Plot 16 CiPA features as dose–response curves | +| `combine_and_average_features.py` | Merge per-core feature CSVs; compute sample averages | +| `compute_mean_ci.py` | Bootstrap median and 95% CI from averaged samples | +| `convert_params.sh` | Convert `param.txt` keys to camelCase for API use | +| `create_concs_directories.sh` | Create output directory tree per concentration | +| `create_escaped_b64_file.sh` | Escape special characters and Base64-encode a file | +| `zip_files.sh` / `unzip_files.sh` | Zip/unzip simulation output files | + +--- + +## AI Report Generation + +DrugSimulation includes an optional pipeline to automatically generate a scientific manuscript (LaTeX/PDF) using the [Perplexity](https://www.perplexity.ai/) Sonar Pro API. + +After running `generate_report.sh`, execute: + +```bash +bash exec_perplexity_report_generator.sh +``` + +This pipeline: +1. Converts and Base64-encodes the parameter file, median-CI CSV, time-series CSVs, and a LaTeX template. +2. Sends structured prompts to the Perplexity API for each manuscript section (Methods, Results parts 1–7, Discussion) in both English and Korean. +3. Assembles the API responses into a complete LaTeX document. + +Prompt templates are stored in [`bin/simulation_cpu/requests/`](bin/simulation_cpu/requests/). Responses are saved to `bin/simulation_cpu_postprocessing/responses/`. + +> **Note:** A Perplexity API key must be configured in the environment before running the report generator. diff --git a/docs/SOLUTION2_MACOS_SELF_GUIDE.md b/docs/SOLUTION2_MACOS_SELF_GUIDE.md new file mode 100644 index 0000000..0de0cda --- /dev/null +++ b/docs/SOLUTION2_MACOS_SELF_GUIDE.md @@ -0,0 +1,423 @@ +# Solution 2 (macOS Native) Self-Execution and Rollback Guide + +## Goal +Build and run DrugSimulation on macOS using one consistent clang-based toolchain, while keeping safe rollback points after every risky step. + +This guide assumes: +- DrugSimulation repo path: `/Users/iganarendra/DrugSimulation` +- libCML repo path: `/Users/iganarendra/libCML` +- macOS with Xcode command line tools installed + +--- + +## High-Level Decision Tree + +1. If both repos are clean, continue. +2. Else, snapshot/stash/backup first, then continue. +3. Build SUNDIALS 5.x from source inside libCML. +4. If SUNDIALS build fails, stop and fix dependency/toolchain issue before touching simulation code. +5. Patch `tm` enum collisions in libCML (clang compatibility). +6. Build libCML with clang. +7. If libCML build fails, rollback patch set and retry from a clean point. +8. Build DrugSimulation with matching clang path. +9. If link fails with ABI/symbol mismatch, verify both sides were truly rebuilt with clang and SUNDIALS 5.x. +10. Run simulation (skip Python/report stage). +11. If runtime fails, inspect logs and rollback only the minimal layer needed. + +--- + +## Step 0: Preflight Checks + +Run: + +```bash +xcode-select -p +clang++ --version +cmake --version +make --version +mpicxx --version +``` + +If `xcode-select -p` fails: + +```bash +xcode-select --install +``` + +If `cmake` is missing: + +```bash +brew install cmake +``` + +Rollback impact: none (tool installation only). + +--- + +## Step 1: Safety Snapshot (Mandatory) + +### 1.1 Check git status in both repos + +```bash +cd /Users/iganarendra/DrugSimulation +git status --short + +cd /Users/iganarendra/libCML +git status --short +``` + +### 1.2 Branch by condition + +- If status is clean in both repos: + - Create safety tags/branches anyway. +- Else: + - Stash or commit before proceeding. + +Recommended: + +```bash +cd /Users/iganarendra/DrugSimulation +git checkout -b backup/pre-solution2-$(date +%Y%m%d-%H%M%S) + +git add -A +git commit -m "backup: pre solution2 attempt" || true + +git stash push -u -m "pre-solution2-drugsim" || true + +cd /Users/iganarendra/libCML +git checkout -b backup/pre-solution2-$(date +%Y%m%d-%H%M%S) + +git add -A +git commit -m "backup: pre solution2 attempt" || true + +git stash push -u -m "pre-solution2-libcml" || true +``` + +### 1.3 Optional file-system backup (extra safety) + +```bash +cd /Users/iganarendra +mkdir -p backup_solution2 + +tar -czf backup_solution2/DrugSimulation-pre-solution2.tar.gz DrugSimulation +tar -czf backup_solution2/libCML-pre-solution2.tar.gz libCML +``` + +Rollback impact: complete restore possible even if git history gets messy. + +--- + +## Step 2: Build Native SUNDIALS 5.x for macOS (inside libCML) + +Reason: bundled prebuilt archives are Linux ELF, not usable on macOS. + +### 2.1 Configure build dir + +```bash +cd /Users/iganarendra/libCML/libs/sundials-5.7.0 +rm -rf build-macos-clang +mkdir -p build-macos-clang +cd build-macos-clang +``` + +### 2.2 CMake configure + +```bash +cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DBUILD_SHARED_LIBS=OFF \ + -DEXAMPLES_ENABLE=OFF \ + -DSUNDIALS_BUILD_WITH_MONITORING=OFF +``` + +If configure fails: +- If message indicates missing CMake modules/toolchain: + - confirm Xcode tools and CMake install, then rerun. +- Else: + - stop here and fix before moving on. + +### 2.3 Build + +```bash +cmake --build . -j$(sysctl -n hw.ncpu) +``` + +### 2.4 Verify generated static libs are Mach-O + +```bash +find . -name "libsundials*.a" | head -n 10 +file src/cvode/libsundials_cvode.a +file src/nvector/serial/libsundials_nvecserial.a +``` + +Expected: output contains `current ar archive random library` on macOS (not ELF object references from Linux builds). + +Rollback for Step 2: + +```bash +cd /Users/iganarendra/libCML/libs/sundials-5.7.0 +rm -rf build-macos-clang +``` + +--- + +## Step 3: Patch `tm` Enum Collisions in libCML (clang fix) + +Reason: some model headers use `tm` as enum identifier; clang conflicts with `struct tm` contexts more strictly. + +### 3.1 Locate collisions + +```bash +cd /Users/iganarendra/libCML +rg -n "\btm\b" cellmodels | head -n 200 +``` + +### 3.2 Apply minimal rename pattern + +Use one consistent replacement for enum token, for example `tm_state`. + +Important rules: +- Rename only enum member identifiers and direct references. +- Do not rename unrelated variable names unless compilation requires it. +- Keep changes scoped to files that fail or are clearly part of the collision set. + +### 3.3 Verify no accidental broad replacement + +```bash +git diff --stat +git diff -- cellmodels +``` + +If diff looks too broad: + +```bash +git restore --source=HEAD --staged --worktree -- cellmodels +``` + +Then redo with targeted edits. + +Rollback for Step 3: + +```bash +cd /Users/iganarendra/libCML +git restore --source=HEAD --staged --worktree -- . +# or selective restore for touched files only +``` + +--- + +## Step 4: Build libCML with clang + native SUNDIALS includes/libs + +### 4.1 Clean old build artifacts + +```bash +cd /Users/iganarendra/libCML +rm -rf build +mkdir -p build +``` + +### 4.2 Build command (template) + +Adjust include/lib paths if your SUNDIALS build output differs. + +```bash +make \ + CXX=clang++ \ + CC=clang \ + CXXFLAGS="-fPIC -fpermissive -std=c++11 -I./libs/sundials-5.7.0/include -I./libs/sundials-5.7.0/build-macos-clang/include" \ + LDFLAGS="-L./libs/sundials-5.7.0/build-macos-clang/src/cvode -L./libs/sundials-5.7.0/build-macos-clang/src/nvector/serial" \ + build/libcml.a +``` + +If build fails: +- If failure is still `tm`-related: + - go back to Step 3 and finish renames in remaining files. +- If failure is missing SUNDIALS headers: + - verify include path and generated headers in build folder. +- If failure is unrelated model source issue: + - fix one model source at a time, rebuild. + +### 4.3 Verify archive exists and is current + +```bash +ls -lh build/libcml.a +``` + +Rollback for Step 4: + +```bash +cd /Users/iganarendra/libCML +rm -rf build +# optionally restore source edits if needed +# git restore --source=HEAD --staged --worktree -- . +``` + +--- + +## Step 5: Wire DrugSimulation to Correct CML/SUNDIALS Artifacts + +Goal: ensure DrugSimulation links against the newly rebuilt clang-compatible `libcml.a` and matching native SUNDIALS static libs. + +### 5.1 Build clean + +```bash +cd /Users/iganarendra/DrugSimulation +make clean || true +``` + +### 5.2 Build with explicit linker overrides (preferred, less invasive) + +Use your Makefile variable names as applicable: + +```bash +make CXX=mpicxx \ + LDFLAGS="-g ../libCML/build/libcml.a \ + ../libCML/libs/sundials-5.7.0/build-macos-clang/src/cvode/libsundials_cvode.a \ + ../libCML/libs/sundials-5.7.0/build-macos-clang/src/nvector/serial/libsundials_nvecserial.a \ + -lcurl -ljson-c" +``` + +If your build needs additional SUNDIALS components, append the exact generated `.a` paths from your local build. + +If link fails with `std::__1` vs `std::__cxx11` symbols: +- libCML was not fully rebuilt with clang, or stale objects were reused. +- Do full clean of libCML build dir and rebuild Step 4. + +If link fails with SUNDIALS symbols missing: +- wrong SUNDIALS lib set or wrong order. +- inspect undefined symbol names and add corresponding SUNDIALS modules from your build output. + +Rollback for Step 5: + +```bash +cd /Users/iganarendra/DrugSimulation +make clean || true +git restore --source=HEAD --staged --worktree -- Makefile +``` + +(If you only used command-line overrides and did not edit Makefile, rollback is just `make clean`.) + +--- + +## Step 6: Run the Simulation (Skip Python) + +From simulation folder: + +```bash +cd /Users/iganarendra/DrugSimulation/bin/simulation_cpu +``` + +Run executable directly or with MPI depending on your project expectation. + +Examples: + +```bash +../drugsim_CiPAORdv1.0 +``` + +or + +```bash +mpirun -np 10 ../drugsim_CiPAORdv1.0 +``` + +Use `param.txt` already present in this folder. + +If run fails immediately: +- Check binary exists and has execute permission. +- Confirm current working directory is `bin/simulation_cpu`. +- Validate input files referenced by `param.txt` exist. + +Rollback for Step 6: none required (runtime only). + +--- + +## Step 7: Validation Checklist + +Mark complete only when all are true: + +- `libCML/build/libcml.a` exists and was rebuilt in current attempt. +- DrugSimulation binary (for your target model) is produced. +- No `std::__1` vs `std::__cxx11` unresolved symbol errors. +- Simulation starts and progresses through pacing/concentrations. +- Output files appear in expected output directories. + +--- + +## Fast Rollback Recipes + +## A) Undo local edits only (keep history) + +```bash +cd /Users/iganarendra/libCML +git restore --source=HEAD --staged --worktree -- . +rm -rf build libs/sundials-5.7.0/build-macos-clang + +cd /Users/iganarendra/DrugSimulation +git restore --source=HEAD --staged --worktree -- . +make clean || true +``` + +## B) Return to stash snapshot + +```bash +cd /Users/iganarendra/libCML +git stash list +git stash apply stash@{0} + +cd /Users/iganarendra/DrugSimulation +git stash list +git stash apply stash@{0} +``` + +## C) Nuclear restore from tar backups + +```bash +cd /Users/iganarendra +mv DrugSimulation DrugSimulation.failed.$(date +%Y%m%d-%H%M%S) +mv libCML libCML.failed.$(date +%Y%m%d-%H%M%S) + +tar -xzf backup_solution2/DrugSimulation-pre-solution2.tar.gz +tar -xzf backup_solution2/libCML-pre-solution2.tar.gz +``` + +Use this only if git state is beyond easy repair. + +--- + +## Practical Execution Strategy (Recommended) + +1. Perform Steps 0-2 fully. +2. Commit checkpoint in libCML: + +```bash +cd /Users/iganarendra/libCML +git add -A +git commit -m "checkpoint: native sundials built" +``` + +3. Perform Step 3 patching and commit: + +```bash +git add -A +git commit -m "fix: rename tm enum identifiers for clang" +``` + +4. Perform Step 4 and commit if build succeeds. +5. Perform Step 5 in DrugSimulation, preferably with CLI overrides first (avoid permanent Makefile edits). +6. Run Step 6. + +This gives clean, reversible checkpoints at each major stage. + +--- + +## When to Stop and Ask for Help + +Stop immediately if: +- You see massive unintended diffs in libCML model files. +- You get a new class of compiler errors unrelated to `tm` or SUNDIALS. +- You accidentally mix Homebrew SUNDIALS 7.x headers with SUNDIALS 5.x libs. + +At that point, rollback to last checkpoint and re-run from the previous stable step. From 2bb6c5d6d5821e4953b86c736ac51711c5e1a39e Mon Sep 17 00:00:00 2001 From: Iga Narendra Date: Mon, 4 May 2026 14:39:08 +0700 Subject: [PATCH 03/13] add run utils for docker --- docker/linux/build_and_run.sh | 115 ++++++++++++++++++++++++++++++++++ docker/linux/run_only.sh | 102 ++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100755 docker/linux/build_and_run.sh create mode 100755 docker/linux/run_only.sh diff --git a/docker/linux/build_and_run.sh b/docker/linux/build_and_run.sh new file mode 100755 index 0000000..8b18af5 --- /dev/null +++ b/docker/linux/build_and_run.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Tunables (can be overridden by environment variables) +VARIANT_FLAG="${VARIANT_FLAG:--DCIPAORDV1}" +SIM_DIR="${SIM_DIR:-bin/simulation_cpu}" +NP="${NP:-10}" +RUN_SIM="${RUN_SIM:-1}" +PARAM_FILE="${PARAM_FILE:-param.txt}" +PACING_OVERRIDE="${PACING_OVERRIDE:-}" +PACING_WRITE_OVERRIDE="${PACING_WRITE_OVERRIDE:-}" +SOLVER_OVERRIDE="${SOLVER_OVERRIDE:-}" + +variant_suffix_from_flag() { + case "$1" in + "") + echo "ORd-static" + ;; + "-DCIPAORDV1") + echo "CiPAORdv1.0" + ;; + "-DTOR_ORD") + echo "ToR-ORd" + ;; + "-DTOR_ORD_DYNCL") + echo "ToR-ORd-dynCl" + ;; + "-DGRANDI") + echo "Grandi" + ;; + "-DORD_STATIC_LAND") + echo "ORd-static_Land" + ;; + "-DCIPAORDV1_LAND") + echo "CiPAORdv1.0_Land" + ;; + "-DTOR_ORD_LAND") + echo "ToR-ORd_Land" + ;; + *) + echo "" + ;; + esac +} + +BIN_SUFFIX="$(variant_suffix_from_flag "$VARIANT_FLAG")" +if [[ -z "$BIN_SUFFIX" ]]; then + echo "Unsupported VARIANT_FLAG: $VARIANT_FLAG" + exit 2 +fi + +echo "[1/3] Building libCML static library in Linux container..." +cd /workspace/libCML +make clean || true +make build/libcml.a + +echo "[2/3] Building DrugSimulation binary for variant: $BIN_SUFFIX" +cd /workspace/DrugSimulation +make clean || true +make all \ + EXTRA_CXXFLAGS+="-std=c++11 $VARIANT_FLAG" \ + SUNDIALS_LIB_DIR="../libCML/libs/sundials-5.7.0/lib64" \ + EXTRA_LINK_DIRS="" + +if [[ "$RUN_SIM" != "1" ]]; then + echo "RUN_SIM=$RUN_SIM, skipping simulation run." + exit 0 +fi + +echo "[3/3] Running simulation with mpiexec in $SIM_DIR" +cd "/workspace/DrugSimulation/$SIM_DIR" + +PARAM_TO_USE="$PARAM_FILE" +TEMP_PARAM_FILE="" + +if [[ -n "$PACING_OVERRIDE" || -n "$PACING_WRITE_OVERRIDE" || -n "$SOLVER_OVERRIDE" ]]; then + TEMP_PARAM_FILE=".docker-test-param.txt" + cp "$PARAM_FILE" "$TEMP_PARAM_FILE" + + if [[ -n "$PACING_OVERRIDE" ]]; then + sed -E -i "s#^(number_pacing[[:space:]]*=[[:space:]]*)[0-9]+#\1${PACING_OVERRIDE}#" "$TEMP_PARAM_FILE" + fi + + if [[ -n "$PACING_WRITE_OVERRIDE" ]]; then + sed -E -i "s#^(number_pacing_write[[:space:]]*=[[:space:]]*)[0-9]+#\1${PACING_WRITE_OVERRIDE}#" "$TEMP_PARAM_FILE" + elif [[ -n "$PACING_OVERRIDE" ]]; then + sed -E -i "s#^(number_pacing_write[[:space:]]*=[[:space:]]*)[0-9]+#\1${PACING_OVERRIDE}#" "$TEMP_PARAM_FILE" + fi + + if [[ -n "$SOLVER_OVERRIDE" ]]; then + sed -E -i "s#^(solver_type[[:space:]]*=[[:space:]]*).* *//?#\1${SOLVER_OVERRIDE} // #" "$TEMP_PARAM_FILE" + fi + + PARAM_TO_USE="$TEMP_PARAM_FILE" + trap 'rm -f "$TEMP_PARAM_FILE"' EXIT +fi + +DRUG_CONCENTRATIONS_RAW="$(grep '^drug_concentrations' "$PARAM_TO_USE" | cut -d'=' -f2 | sed 's#//.*##' | xargs || true)" +mkdir -p results/0.00 +if [[ -n "$DRUG_CONCENTRATIONS_RAW" ]]; then + IFS=',' read -r -a concs <<< "$DRUG_CONCENTRATIONS_RAW" + for conc in "${concs[@]}"; do + conc_trimmed="$(echo "$conc" | xargs)" + if [[ -n "$conc_trimmed" ]]; then + conc_dir="$(printf "%.2f" "$conc_trimmed")" + mkdir -p "results/$conc_dir" + fi + done +fi + +echo "Using parameter file: $PARAM_TO_USE" +mpiexec --allow-run-as-root -np "$NP" "../drugsim_${BIN_SUFFIX}" -input_deck "$PARAM_TO_USE" \ + | tee results/logfile.docker + +echo "Simulation finished. Logs: $SIM_DIR/results/logfile.docker" diff --git a/docker/linux/run_only.sh b/docker/linux/run_only.sh new file mode 100755 index 0000000..1a3a92e --- /dev/null +++ b/docker/linux/run_only.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Tunables (can be overridden by environment variables) +VARIANT_FLAG="${VARIANT_FLAG:--DCIPAORDV1}" +SIM_DIR="${SIM_DIR:-bin/simulation_cpu}" +NP="${NP:-10}" +PARAM_FILE="${PARAM_FILE:-param.txt}" +PACING_OVERRIDE="${PACING_OVERRIDE:-}" +PACING_WRITE_OVERRIDE="${PACING_WRITE_OVERRIDE:-}" +SOLVER_OVERRIDE="${SOLVER_OVERRIDE:-}" + +variant_suffix_from_flag() { + case "$1" in + "") + echo "ORd-static" + ;; + "-DCIPAORDV1") + echo "CiPAORdv1.0" + ;; + "-DTOR_ORD") + echo "ToR-ORd" + ;; + "-DTOR_ORD_DYNCL") + echo "ToR-ORd-dynCl" + ;; + "-DGRANDI") + echo "Grandi" + ;; + "-DORD_STATIC_LAND") + echo "ORd-static_Land" + ;; + "-DCIPAORDV1_LAND") + echo "CiPAORdv1.0_Land" + ;; + "-DTOR_ORD_LAND") + echo "ToR-ORd_Land" + ;; + *) + echo "" + ;; + esac +} + +BIN_SUFFIX="$(variant_suffix_from_flag "$VARIANT_FLAG")" +if [[ -z "$BIN_SUFFIX" ]]; then + echo "Unsupported VARIANT_FLAG: $VARIANT_FLAG" + exit 2 +fi + +echo "[1/1] Running simulation with mpiexec in $SIM_DIR" +cd "/workspace/DrugSimulation/$SIM_DIR" + +if [[ ! -x "../drugsim_${BIN_SUFFIX}" ]]; then + echo "Missing binary ../drugsim_${BIN_SUFFIX}" + echo "Build first with docker/linux/build_and_run.sh or make in the container." + exit 3 +fi + +PARAM_TO_USE="$PARAM_FILE" +TEMP_PARAM_FILE="" + +if [[ -n "$PACING_OVERRIDE" || -n "$PACING_WRITE_OVERRIDE" || -n "$SOLVER_OVERRIDE" ]]; then + TEMP_PARAM_FILE=".docker-test-param.txt" + cp "$PARAM_FILE" "$TEMP_PARAM_FILE" + + if [[ -n "$PACING_OVERRIDE" ]]; then + sed -E -i "s#^(number_pacing[[:space:]]*=[[:space:]]*)[0-9]+#\1${PACING_OVERRIDE}#" "$TEMP_PARAM_FILE" + fi + + if [[ -n "$PACING_WRITE_OVERRIDE" ]]; then + sed -E -i "s#^(number_pacing_write[[:space:]]*=[[:space:]]*)[0-9]+#\1${PACING_WRITE_OVERRIDE}#" "$TEMP_PARAM_FILE" + elif [[ -n "$PACING_OVERRIDE" ]]; then + sed -E -i "s#^(number_pacing_write[[:space:]]*=[[:space:]]*)[0-9]+#\1${PACING_OVERRIDE}#" "$TEMP_PARAM_FILE" + fi + + if [[ -n "$SOLVER_OVERRIDE" ]]; then + sed -E -i "s#^(solver_type[[:space:]]*=[[:space:]]*).*#\1${SOLVER_OVERRIDE}#" "$TEMP_PARAM_FILE" + fi + + PARAM_TO_USE="$TEMP_PARAM_FILE" + trap 'rm -f "$TEMP_PARAM_FILE"' EXIT +fi + +DRUG_CONCENTRATIONS_RAW="$(grep '^drug_concentrations' "$PARAM_TO_USE" | cut -d'=' -f2 | sed 's#//.*##' | xargs || true)" +mkdir -p results/0.00 +if [[ -n "$DRUG_CONCENTRATIONS_RAW" ]]; then + IFS=',' read -r -a concs <<< "$DRUG_CONCENTRATIONS_RAW" + for conc in "${concs[@]}"; do + conc_trimmed="$(echo "$conc" | xargs)" + if [[ -n "$conc_trimmed" ]]; then + conc_dir="$(printf "%.2f" "$conc_trimmed")" + mkdir -p "results/$conc_dir" + fi + done +fi + +echo "Using parameter file: $PARAM_TO_USE" +mpiexec --allow-run-as-root -np "$NP" "../drugsim_${BIN_SUFFIX}" -input_deck "$PARAM_TO_USE" \ + | tee results/logfile.docker + +echo "Simulation finished. Logs: $SIM_DIR/results/logfile.docker" From d3065f800e45719a350eca096e814d2149fefff3 Mon Sep 17 00:00:00 2001 From: Iga Narendra Date: Mon, 4 May 2026 14:50:39 +0700 Subject: [PATCH 04/13] remove macos related sundial --- docker/linux/build_and_run.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docker/linux/build_and_run.sh b/docker/linux/build_and_run.sh index 8b18af5..47fc508 100755 --- a/docker/linux/build_and_run.sh +++ b/docker/linux/build_and_run.sh @@ -58,9 +58,7 @@ echo "[2/3] Building DrugSimulation binary for variant: $BIN_SUFFIX" cd /workspace/DrugSimulation make clean || true make all \ - EXTRA_CXXFLAGS+="-std=c++11 $VARIANT_FLAG" \ - SUNDIALS_LIB_DIR="../libCML/libs/sundials-5.7.0/lib64" \ - EXTRA_LINK_DIRS="" + EXTRA_CXXFLAGS+="-std=c++11 $VARIANT_FLAG" if [[ "$RUN_SIM" != "1" ]]; then echo "RUN_SIM=$RUN_SIM, skipping simulation run." From 094ca6ba0f4649049bf49ffb4738f769be3a2bde Mon Sep 17 00:00:00 2001 From: Iga Narendra Date: Thu, 18 Jun 2026 15:34:22 +0700 Subject: [PATCH 05/13] add test param for docker --- bin/simulation_cpu/.docker-test-param.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 bin/simulation_cpu/.docker-test-param.txt diff --git a/bin/simulation_cpu/.docker-test-param.txt b/bin/simulation_cpu/.docker-test-param.txt new file mode 100644 index 0000000..252f61e --- /dev/null +++ b/bin/simulation_cpu/.docker-test-param.txt @@ -0,0 +1,18 @@ +user_name = marcell // User Name that will be used in the file name. +number_pacing = 5 // Number of paces. +number_pacing_write = 5 // Last X paces to be written in the file. +cycle_length = 2000 // Time period of one cycle (msec). +cell_model = CiPAORdv1.0_endo // Name of the cell model. Refer to the guide to fill it +stimulus_duration = 0.5 // Duration of stimulus current applied (msec). +stimulus_amplitude_scale = 1.0 // Scaling factor of stimulus current amplitude. +solver_type = CVode // Type of ODE solver used. Only CVode or Euler are available. +time_step_min = 0.5 // The minimum time step of the computation (msec). Only for CVode solver. +time_step_max = 2.0 // The maximum time step of the computation (msec). Only for CVode solver. +writing_step = 1.0 // The interval time to write the result into the file (msec). +drug_name = quinidine // Name of the drug +drug_concentrations = 3237,6474,9711,12948 // Concentration of the drug, Cmax1 to Cmax4 values (uMolar) +hill_file = ./chantest_hill/quinidine/IC50_samples30.csv // File that contains the IC50 information for each ionic channel. +herg_file = ./chantest_herg/quinidine/boot_pars10.csv // File that contains the hERG parameters. +is_cvar = 0 // Conductance variability. Set true to activate and assign the file to Cvar_File +cvar_file = ./population/indi_1sample.csv // the file for conductance variability. +number_of_cpu = 10 // Number of CPU used for mpiexec call From 46288f520e9802113dfbce1ddcf045361b822dd0 Mon Sep 17 00:00:00 2001 From: Iga Narendra Date: Wed, 15 Jul 2026 12:00:10 +0700 Subject: [PATCH 06/13] add time stamp minimum override --- docker/linux/build_and_run.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docker/linux/build_and_run.sh b/docker/linux/build_and_run.sh index 47fc508..983b2c4 100755 --- a/docker/linux/build_and_run.sh +++ b/docker/linux/build_and_run.sh @@ -10,6 +10,7 @@ PARAM_FILE="${PARAM_FILE:-param.txt}" PACING_OVERRIDE="${PACING_OVERRIDE:-}" PACING_WRITE_OVERRIDE="${PACING_WRITE_OVERRIDE:-}" SOLVER_OVERRIDE="${SOLVER_OVERRIDE:-}" +TIME_STEP_MIN_OVERRIDE="${TIME_STEP_MIN_OVERRIDE:-}" variant_suffix_from_flag() { case "$1" in @@ -71,7 +72,7 @@ cd "/workspace/DrugSimulation/$SIM_DIR" PARAM_TO_USE="$PARAM_FILE" TEMP_PARAM_FILE="" -if [[ -n "$PACING_OVERRIDE" || -n "$PACING_WRITE_OVERRIDE" || -n "$SOLVER_OVERRIDE" ]]; then +if [[ -n "$PACING_OVERRIDE" || -n "$PACING_WRITE_OVERRIDE" || -n "$SOLVER_OVERRIDE" || -n "$TIME_STEP_MIN_OVERRIDE" ]]; then TEMP_PARAM_FILE=".docker-test-param.txt" cp "$PARAM_FILE" "$TEMP_PARAM_FILE" @@ -89,6 +90,12 @@ if [[ -n "$PACING_OVERRIDE" || -n "$PACING_WRITE_OVERRIDE" || -n "$SOLVER_OVERRI sed -E -i "s#^(solver_type[[:space:]]*=[[:space:]]*).* *//?#\1${SOLVER_OVERRIDE} // #" "$TEMP_PARAM_FILE" fi + if [[ -n "$TIME_STEP_MIN_OVERRIDE" ]]; then + # Euler uses this as a fixed integration step for the whole run (time_step_max is ignored + # by the Euler branch), so this needs to be small (e.g. 0.005) to stay numerically stable. + sed -E -i "s#^(time_step_min[[:space:]]*=[[:space:]]*)[0-9.]+#\1${TIME_STEP_MIN_OVERRIDE}#" "$TEMP_PARAM_FILE" + fi + PARAM_TO_USE="$TEMP_PARAM_FILE" trap 'rm -f "$TEMP_PARAM_FILE"' EXIT fi From 662bdfed1c0b0c91acf6911c30312c0b3441a2e1 Mon Sep 17 00:00:00 2001 From: Iga Narendra Date: Mon, 20 Jul 2026 22:42:58 +0700 Subject: [PATCH 07/13] update readme and docker execution preference --- .gitignore | 48 +++++++++++++++++++++++++++++++++++ README.md | 33 ++++++++---------------- docker/linux/build_and_run.sh | 12 ++++++++- 3 files changed, 70 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index d20aa13..6d2db6f 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,51 @@ bin/simulation_cpu_postprocessing/responses/*.json *.swp logfile results + +# CiPA drug parameter data pulled from FDA/CiPA GitHub repo (new drugs beyond quinidine) +bin/simulation_cpu/chantest_hill/bepridil/ +bin/simulation_cpu/chantest_hill/chlorpromazine/ +bin/simulation_cpu/chantest_hill/cisapride/ +bin/simulation_cpu/chantest_hill/diltiazem/ +bin/simulation_cpu/chantest_hill/dofetilide/ +bin/simulation_cpu/chantest_hill/mexiletine/ +bin/simulation_cpu/chantest_hill/ondansetron/ +bin/simulation_cpu/chantest_hill/ranolazine/ +bin/simulation_cpu/chantest_hill/sotalol/ +bin/simulation_cpu/chantest_hill/terfenadine/ +bin/simulation_cpu/chantest_hill/verapamil/ +bin/simulation_cpu/chantest_hill/CiPA_training_drugs_Cmax.csv +bin/simulation_cpu/chantest_herg/bepridil/ +bin/simulation_cpu/chantest_herg/chlorpromazine/ +bin/simulation_cpu/chantest_herg/cisapride/ +bin/simulation_cpu/chantest_herg/diltiazem/ +bin/simulation_cpu/chantest_herg/dofetilide/ +bin/simulation_cpu/chantest_herg/mexiletine/ +bin/simulation_cpu/chantest_herg/ondansetron/ +bin/simulation_cpu/chantest_herg/ranolazine/ +bin/simulation_cpu/chantest_herg/sotalol/ +bin/simulation_cpu/chantest_herg/terfenadine/ +bin/simulation_cpu/chantest_herg/verapamil/ +bin/simulation_cpu_postprocessing/chantest_hill/bepridil/ +bin/simulation_cpu_postprocessing/chantest_hill/chlorpromazine/ +bin/simulation_cpu_postprocessing/chantest_hill/cisapride/ +bin/simulation_cpu_postprocessing/chantest_hill/diltiazem/ +bin/simulation_cpu_postprocessing/chantest_hill/dofetilide/ +bin/simulation_cpu_postprocessing/chantest_hill/mexiletine/ +bin/simulation_cpu_postprocessing/chantest_hill/ondansetron/ +bin/simulation_cpu_postprocessing/chantest_hill/ranolazine/ +bin/simulation_cpu_postprocessing/chantest_hill/sotalol/ +bin/simulation_cpu_postprocessing/chantest_hill/terfenadine/ +bin/simulation_cpu_postprocessing/chantest_hill/verapamil/ +bin/simulation_cpu_postprocessing/chantest_hill/CiPA_training_drugs_Cmax.csv +bin/simulation_cpu_postprocessing/chantest_herg/bepridil/ +bin/simulation_cpu_postprocessing/chantest_herg/chlorpromazine/ +bin/simulation_cpu_postprocessing/chantest_herg/cisapride/ +bin/simulation_cpu_postprocessing/chantest_herg/diltiazem/ +bin/simulation_cpu_postprocessing/chantest_herg/dofetilide/ +bin/simulation_cpu_postprocessing/chantest_herg/mexiletine/ +bin/simulation_cpu_postprocessing/chantest_herg/ondansetron/ +bin/simulation_cpu_postprocessing/chantest_herg/ranolazine/ +bin/simulation_cpu_postprocessing/chantest_herg/sotalol/ +bin/simulation_cpu_postprocessing/chantest_herg/terfenadine/ +bin/simulation_cpu_postprocessing/chantest_herg/verapamil/ diff --git a/README.md b/README.md index 1c4a2e4..76bd1c1 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,8 @@ pip install -r bin/scripts/requirements.txt ## Building +Native builds are intended for Linux environments that have matching `libCML` and SUNDIALS static archives. On macOS, use the Dockerized Linux workflow below because the bundled archives under `../libCML/libs/sundials-5.7.0/lib64` are Linux target files and will not link natively on macOS. + ### Build all variants at once ```bash @@ -303,7 +305,7 @@ Open the generated plots (`.png` / `.pdf`) in the concentration directories, or ## Running in Dockerized Linux (Recommended for macOS) -This repository now includes a Linux container workflow that preserves the expected original layout where `libCML` is a sibling directory. +This repository includes a Linux container workflow that preserves the expected original layout where `libCML` is a sibling directory. The Docker service is pinned to `linux/amd64` so it matches the bundled SUNDIALS archives under `../libCML/libs/sundials-5.7.0/lib64`. @@ -320,18 +322,14 @@ Expected host layout: From the repository root: ```bash -docker compose build drugsim-linux +docker compose up -d --build ``` ### 2. Build and run simulation in one command (inside container) ```bash -docker compose run --rm \ - -e VARIANT_FLAG=-DCIPAORDV1 \ - -e NP=10 \ - -e SIM_DIR=bin/simulation_cpu \ - drugsim-linux \ - bash -lc "./docker/linux/build_and_run.sh" +docker compose exec -T drugsim-linux env VARIANT_FLAG=-DCIPAORDV1 NP=10 RUN_SIM=1 \ + bash docker/linux/build_and_run.sh ``` What this does: @@ -343,24 +341,15 @@ What this does: ### 3. Build only (skip simulation) ```bash -docker compose run --rm \ - -e VARIANT_FLAG=-DCIPAORDV1 \ - -e RUN_SIM=0 \ - drugsim-linux \ - bash -lc "./docker/linux/build_and_run.sh" +docker compose exec -T drugsim-linux env RUN_SIM=0 VARIANT_FLAG=-DCIPAORDV1 \ + bash docker/linux/build_and_run.sh ``` -### 3a. Run a short pacing test without editing `param.txt` +### 3a. Run a short pacing test (smoke test) without editing `param.txt` ```bash -docker compose run --rm \ - -e VARIANT_FLAG=-DCIPAORDV1 \ - -e NP=2 \ - -e PACING_OVERRIDE=5 \ - -e PACING_WRITE_OVERRIDE=5 \ - -e SOLVER_OVERRIDE=Euler \ - drugsim-linux \ - bash -lc "./docker/linux/build_and_run.sh" +docker compose exec -T drugsim-linux env VARIANT_FLAG=-DCIPAORDV1 NP=1 PACING_OVERRIDE=5 PACING_WRITE_OVERRIDE=5 TIME_STEP_MIN_OVERRIDE=0.001 SOLVER_OVERRIDE=Euler \ + RUN_SIM=1 bash docker/linux/build_and_run.sh ``` This creates a temporary parameter file inside the container run, leaves your checked-in `bin/simulation_cpu/param.txt` unchanged, and runs the full simulation flow with only 5 paces using the Euler solver. diff --git a/docker/linux/build_and_run.sh b/docker/linux/build_and_run.sh index 983b2c4..fb345a2 100755 --- a/docker/linux/build_and_run.sh +++ b/docker/linux/build_and_run.sh @@ -11,6 +11,8 @@ PACING_OVERRIDE="${PACING_OVERRIDE:-}" PACING_WRITE_OVERRIDE="${PACING_WRITE_OVERRIDE:-}" SOLVER_OVERRIDE="${SOLVER_OVERRIDE:-}" TIME_STEP_MIN_OVERRIDE="${TIME_STEP_MIN_OVERRIDE:-}" +HILL_FILE_OVERRIDE="${HILL_FILE_OVERRIDE:-}" +CONC_OVERRIDE="${CONC_OVERRIDE:-}" variant_suffix_from_flag() { case "$1" in @@ -72,7 +74,7 @@ cd "/workspace/DrugSimulation/$SIM_DIR" PARAM_TO_USE="$PARAM_FILE" TEMP_PARAM_FILE="" -if [[ -n "$PACING_OVERRIDE" || -n "$PACING_WRITE_OVERRIDE" || -n "$SOLVER_OVERRIDE" || -n "$TIME_STEP_MIN_OVERRIDE" ]]; then +if [[ -n "$PACING_OVERRIDE" || -n "$PACING_WRITE_OVERRIDE" || -n "$SOLVER_OVERRIDE" || -n "$TIME_STEP_MIN_OVERRIDE" || -n "$HILL_FILE_OVERRIDE" || -n "$CONC_OVERRIDE" ]]; then TEMP_PARAM_FILE=".docker-test-param.txt" cp "$PARAM_FILE" "$TEMP_PARAM_FILE" @@ -96,6 +98,14 @@ if [[ -n "$PACING_OVERRIDE" || -n "$PACING_WRITE_OVERRIDE" || -n "$SOLVER_OVERRI sed -E -i "s#^(time_step_min[[:space:]]*=[[:space:]]*)[0-9.]+#\1${TIME_STEP_MIN_OVERRIDE}#" "$TEMP_PARAM_FILE" fi + if [[ -n "$HILL_FILE_OVERRIDE" ]]; then + sed -E -i "s#^(hill_file[[:space:]]*=[[:space:]]*)\S+#\1${HILL_FILE_OVERRIDE}#" "$TEMP_PARAM_FILE" + fi + + if [[ -n "$CONC_OVERRIDE" ]]; then + sed -E -i "s#^(drug_concentrations[[:space:]]*=[[:space:]]*)\S+#\1${CONC_OVERRIDE}#" "$TEMP_PARAM_FILE" + fi + PARAM_TO_USE="$TEMP_PARAM_FILE" trap 'rm -f "$TEMP_PARAM_FILE"' EXIT fi From 7f9e19303648753271eb3cd1c33d6825a9471405 Mon Sep 17 00:00:00 2001 From: Iga Narendra Date: Mon, 20 Jul 2026 22:58:44 +0700 Subject: [PATCH 08/13] add benchmarking script and needed endpoints --- docker/linux/benchmark.sh | 219 ++++++++++++++++++++++++++++++++++ docker/linux/build_and_run.sh | 12 +- docker/linux/run_only.sh | 31 ++++- 3 files changed, 259 insertions(+), 3 deletions(-) create mode 100755 docker/linux/benchmark.sh diff --git a/docker/linux/benchmark.sh b/docker/linux/benchmark.sh new file mode 100755 index 0000000..b9334a2 --- /dev/null +++ b/docker/linux/benchmark.sh @@ -0,0 +1,219 @@ +#!/usr/bin/env bash +# Hardware-performance benchmark harness for DrugSimulation. +# +# Sweeps the drugsim container across drugs x solvers x MPI process counts, +# holding every other hyperparameter fixed, and records wall-clock elapsed +# time for each run to a CSV so runs are comparable across hardware. +# +# Run from the repository root (or anywhere; it cd's to the repo root itself): +# bash docker/linux/benchmark.sh +# +# Everything is configured via environment variables (same style as +# build_and_run.sh / run_only.sh). See `bash docker/linux/benchmark.sh -h`. +set -u -o pipefail + +usage() { + cat <<'EOF' +Usage: [ENV=value ...] bash docker/linux/benchmark.sh + +Sweeps DRUGS x SOLVERS x NP_LIST, running each combination REPEATS times via +`docker compose exec ... run_only.sh`, and appends one row per run to a CSV +under OUT_DIR. All other simulation hyperparameters are held fixed across the +whole sweep so timings are comparable. + +Environment variables (defaults shown): + DRUGS (auto-discovered from chantest_hill/*) comma/space separated + SOLVERS CVode,Euler comma/space separated + NP_LIST 1 comma/space separated MPI process counts + REPEATS 1 repeats per combination + VARIANT_FLAG -DCIPAORDV1 cell-model build variant (fixed for the whole sweep) + PACING 5 number_pacing override + PACING_WRITE 5 number_pacing_write override + TIME_STEP_MIN 0.001 time_step_min override + CONC (unset -> use param.txt's own concentrations for every drug) + HILL_SAMPLES 30 -> chantest_hill//IC50_samples.csv + HERG_SAMPLES 10 -> chantest_herg//boot_pars.csv + SIM_DIR bin/simulation_cpu + SKIP_BUILD 0 set 1 to reuse an already-built binary + FAIL_FAST 0 set 1 to stop the sweep on first failure + OUT_DIR benchmark_results/ where results.csv/summary.txt/logs/ are written + DRY_RUN 0 set 1 to print the commands without running them + +Example - sweep all drugs across both solvers at 1 and 4 MPI ranks, 3 repeats: + NP_LIST="1,4" REPEATS=3 bash docker/linux/benchmark.sh + +Example - just two drugs, Euler only, for a quick sanity check: + DRUGS="quinidine,bepridil" SOLVERS="Euler" bash docker/linux/benchmark.sh +EOF +} + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + usage + exit 0 +fi + +REPO_ROOT="$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse --show-toplevel)" +cd "$REPO_ROOT" + +SIM_DIR="${SIM_DIR:-bin/simulation_cpu}" +VARIANT_FLAG="${VARIANT_FLAG:--DCIPAORDV1}" +SOLVERS="${SOLVERS:-CVode,Euler}" +NP_LIST="${NP_LIST:-1}" +REPEATS="${REPEATS:-1}" +PACING="${PACING:-5}" +PACING_WRITE="${PACING_WRITE:-5}" +TIME_STEP_MIN="${TIME_STEP_MIN:-0.001}" +CONC="${CONC:-}" +HILL_SAMPLES="${HILL_SAMPLES:-30}" +HERG_SAMPLES="${HERG_SAMPLES:-10}" +SKIP_BUILD="${SKIP_BUILD:-0}" +FAIL_FAST="${FAIL_FAST:-0}" +DRY_RUN="${DRY_RUN:-0}" +OUT_DIR="${OUT_DIR:-benchmark_results/$(date +%Y%m%d_%H%M%S)}" + +if [[ -z "${DRUGS:-}" ]]; then + DRUGS="$(find "$SIM_DIR/chantest_hill" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort | tr '\n' ',')" +fi + +# Accept comma- and/or space-separated lists. +split_list() { echo "$1" | tr ',' ' '; } +read -r -a DRUG_ARR <<< "$(split_list "$DRUGS")" +read -r -a SOLVER_ARR <<< "$(split_list "$SOLVERS")" +read -r -a NP_ARR <<< "$(split_list "$NP_LIST")" + +if [[ ${#DRUG_ARR[@]} -eq 0 || ${#SOLVER_ARR[@]} -eq 0 || ${#NP_ARR[@]} -eq 0 ]]; then + echo "DRUGS, SOLVERS and NP_LIST must each resolve to at least one value." >&2 + exit 2 +fi + +mkdir -p "$OUT_DIR/logs" +CSV_FILE="$OUT_DIR/results.csv" +echo "timestamp_utc,variant_flag,drug,solver,np,repeat,pacing,pacing_write,time_step_min,concentrations,hill_file,herg_file,elapsed_seconds,exit_code,log_file" > "$CSV_FILE" + +echo "Output directory: $OUT_DIR" +echo "Drugs: ${DRUG_ARR[*]}" +echo "Solvers: ${SOLVER_ARR[*]}" +echo "NP: ${NP_ARR[*]}" +echo "Repeats: $REPEATS" +echo + +if [[ "$DRY_RUN" != "1" ]]; then + if ! docker compose ps --status running --services 2>/dev/null | grep -qx "drugsim-linux"; then + echo "drugsim-linux container is not running, starting it (docker compose up -d)..." + docker compose up -d + fi + + if [[ "$SKIP_BUILD" != "1" ]]; then + echo "Building binary for VARIANT_FLAG=$VARIANT_FLAG once before the sweep (RUN_SIM=0)..." + docker compose exec -T drugsim-linux env \ + "VARIANT_FLAG=${VARIANT_FLAG}" "RUN_SIM=0" \ + bash docker/linux/build_and_run.sh + build_status=$? + if [[ $build_status -ne 0 ]]; then + echo "Build failed (exit $build_status); aborting sweep." >&2 + exit $build_status + fi + echo + fi +fi + +total=$(( ${#DRUG_ARR[@]} * ${#SOLVER_ARR[@]} * ${#NP_ARR[@]} * REPEATS )) +count=0 +failures=0 + +for drug in "${DRUG_ARR[@]}"; do + hill_file="./chantest_hill/${drug}/IC50_samples${HILL_SAMPLES}.csv" + herg_file="./chantest_herg/${drug}/boot_pars${HERG_SAMPLES}.csv" + + for solver in "${SOLVER_ARR[@]}"; do + for np in "${NP_ARR[@]}"; do + for ((rep = 1; rep <= REPEATS; rep++)); do + count=$((count + 1)) + label="drug=${drug} solver=${solver} np=${np} rep=${rep}" + printf '[%d/%d] %s\n' "$count" "$total" "$label" + + run_id="${drug}_${solver}_np${np}_rep${rep}" + log_file="$OUT_DIR/logs/${run_id}.log" + + cmd=(docker compose exec -T drugsim-linux env + "VARIANT_FLAG=${VARIANT_FLAG}" + "NP=${np}" + "PACING_OVERRIDE=${PACING}" + "PACING_WRITE_OVERRIDE=${PACING_WRITE}" + "TIME_STEP_MIN_OVERRIDE=${TIME_STEP_MIN}" + "SOLVER_OVERRIDE=${solver}" + "DRUG_NAME_OVERRIDE=${drug}" + "HILL_FILE_OVERRIDE=${hill_file}" + "HERG_FILE_OVERRIDE=${herg_file}") + [[ -n "$CONC" ]] && cmd+=("CONC_OVERRIDE=${CONC}") + cmd+=(bash docker/linux/run_only.sh) + + if [[ "$DRY_RUN" == "1" ]]; then + printf ' DRY RUN: %s\n' "${cmd[*]}" + continue + fi + + timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + TIMEFORMAT='%R' + time_file="$(mktemp)" + { time "${cmd[@]}" >"$log_file" 2>&1; } 2>"$time_file" + exit_code=$? + elapsed="$(cat "$time_file")" + rm -f "$time_file" + + conc_used="${CONC:-}" + echo "${timestamp},${VARIANT_FLAG},${drug},${solver},${np},${rep},${PACING},${PACING_WRITE},${TIME_STEP_MIN},${conc_used},${hill_file},${herg_file},${elapsed},${exit_code},${log_file}" >> "$CSV_FILE" + + if [[ $exit_code -ne 0 ]]; then + failures=$((failures + 1)) + echo " FAILED (exit $exit_code) after ${elapsed}s -- see $log_file" + if [[ "$FAIL_FAST" == "1" ]]; then + echo "FAIL_FAST=1, stopping sweep." >&2 + exit $exit_code + fi + else + echo " OK in ${elapsed}s" + fi + done + done + done +done + +if [[ "$DRY_RUN" == "1" ]]; then + echo "Dry run complete: $total combinations would run." + exit 0 +fi + +echo +echo "Sweep complete: $((count - failures))/$total runs succeeded ($failures failed)." +echo "Raw results: $CSV_FILE" + +summary_file="$OUT_DIR/summary.txt" +awk -F',' ' + NR == 1 { next } + { + key = $3 "|" $4 "|" $5 + if ($14 == "0") { + n[key]++ + sum[key] += $13 + sumsq[key] += $13 * $13 + } else { + fail[key]++ + } + seen[key] = 1 + } + END { + printf "%-22s %-8s %-4s %-6s %-12s %-12s %-6s\n", "drug", "solver", "np", "n_ok", "mean_s", "stdev_s", "fail" + for (k in seen) { + split(k, a, "|") + cnt = n[k] + 0 + mean = (cnt > 0) ? sum[k] / cnt : 0 + var = (cnt > 1) ? (sumsq[k] - cnt * mean * mean) / (cnt - 1) : 0 + sd = (var > 0) ? sqrt(var) : 0 + printf "%-22s %-8s %-4s %-6d %-12.3f %-12.3f %-6d\n", a[1], a[2], a[3], cnt, mean, sd, (fail[k] + 0) + } + } +' "$CSV_FILE" | sort | tee "$summary_file" + +echo +echo "Summary: $summary_file" diff --git a/docker/linux/build_and_run.sh b/docker/linux/build_and_run.sh index fb345a2..cce5fd1 100755 --- a/docker/linux/build_and_run.sh +++ b/docker/linux/build_and_run.sh @@ -12,6 +12,8 @@ PACING_WRITE_OVERRIDE="${PACING_WRITE_OVERRIDE:-}" SOLVER_OVERRIDE="${SOLVER_OVERRIDE:-}" TIME_STEP_MIN_OVERRIDE="${TIME_STEP_MIN_OVERRIDE:-}" HILL_FILE_OVERRIDE="${HILL_FILE_OVERRIDE:-}" +HERG_FILE_OVERRIDE="${HERG_FILE_OVERRIDE:-}" +DRUG_NAME_OVERRIDE="${DRUG_NAME_OVERRIDE:-}" CONC_OVERRIDE="${CONC_OVERRIDE:-}" variant_suffix_from_flag() { @@ -74,7 +76,7 @@ cd "/workspace/DrugSimulation/$SIM_DIR" PARAM_TO_USE="$PARAM_FILE" TEMP_PARAM_FILE="" -if [[ -n "$PACING_OVERRIDE" || -n "$PACING_WRITE_OVERRIDE" || -n "$SOLVER_OVERRIDE" || -n "$TIME_STEP_MIN_OVERRIDE" || -n "$HILL_FILE_OVERRIDE" || -n "$CONC_OVERRIDE" ]]; then +if [[ -n "$PACING_OVERRIDE" || -n "$PACING_WRITE_OVERRIDE" || -n "$SOLVER_OVERRIDE" || -n "$TIME_STEP_MIN_OVERRIDE" || -n "$HILL_FILE_OVERRIDE" || -n "$HERG_FILE_OVERRIDE" || -n "$DRUG_NAME_OVERRIDE" || -n "$CONC_OVERRIDE" ]]; then TEMP_PARAM_FILE=".docker-test-param.txt" cp "$PARAM_FILE" "$TEMP_PARAM_FILE" @@ -102,6 +104,14 @@ if [[ -n "$PACING_OVERRIDE" || -n "$PACING_WRITE_OVERRIDE" || -n "$SOLVER_OVERRI sed -E -i "s#^(hill_file[[:space:]]*=[[:space:]]*)\S+#\1${HILL_FILE_OVERRIDE}#" "$TEMP_PARAM_FILE" fi + if [[ -n "$HERG_FILE_OVERRIDE" ]]; then + sed -E -i "s#^(herg_file[[:space:]]*=[[:space:]]*)\S+#\1${HERG_FILE_OVERRIDE}#" "$TEMP_PARAM_FILE" + fi + + if [[ -n "$DRUG_NAME_OVERRIDE" ]]; then + sed -E -i "s#^(drug_name[[:space:]]*=[[:space:]]*)\S+#\1${DRUG_NAME_OVERRIDE}#" "$TEMP_PARAM_FILE" + fi + if [[ -n "$CONC_OVERRIDE" ]]; then sed -E -i "s#^(drug_concentrations[[:space:]]*=[[:space:]]*)\S+#\1${CONC_OVERRIDE}#" "$TEMP_PARAM_FILE" fi diff --git a/docker/linux/run_only.sh b/docker/linux/run_only.sh index 1a3a92e..60bc248 100755 --- a/docker/linux/run_only.sh +++ b/docker/linux/run_only.sh @@ -9,6 +9,11 @@ PARAM_FILE="${PARAM_FILE:-param.txt}" PACING_OVERRIDE="${PACING_OVERRIDE:-}" PACING_WRITE_OVERRIDE="${PACING_WRITE_OVERRIDE:-}" SOLVER_OVERRIDE="${SOLVER_OVERRIDE:-}" +TIME_STEP_MIN_OVERRIDE="${TIME_STEP_MIN_OVERRIDE:-}" +HILL_FILE_OVERRIDE="${HILL_FILE_OVERRIDE:-}" +HERG_FILE_OVERRIDE="${HERG_FILE_OVERRIDE:-}" +DRUG_NAME_OVERRIDE="${DRUG_NAME_OVERRIDE:-}" +CONC_OVERRIDE="${CONC_OVERRIDE:-}" variant_suffix_from_flag() { case "$1" in @@ -60,7 +65,7 @@ fi PARAM_TO_USE="$PARAM_FILE" TEMP_PARAM_FILE="" -if [[ -n "$PACING_OVERRIDE" || -n "$PACING_WRITE_OVERRIDE" || -n "$SOLVER_OVERRIDE" ]]; then +if [[ -n "$PACING_OVERRIDE" || -n "$PACING_WRITE_OVERRIDE" || -n "$SOLVER_OVERRIDE" || -n "$TIME_STEP_MIN_OVERRIDE" || -n "$HILL_FILE_OVERRIDE" || -n "$HERG_FILE_OVERRIDE" || -n "$DRUG_NAME_OVERRIDE" || -n "$CONC_OVERRIDE" ]]; then TEMP_PARAM_FILE=".docker-test-param.txt" cp "$PARAM_FILE" "$TEMP_PARAM_FILE" @@ -75,7 +80,29 @@ if [[ -n "$PACING_OVERRIDE" || -n "$PACING_WRITE_OVERRIDE" || -n "$SOLVER_OVERRI fi if [[ -n "$SOLVER_OVERRIDE" ]]; then - sed -E -i "s#^(solver_type[[:space:]]*=[[:space:]]*).*#\1${SOLVER_OVERRIDE}#" "$TEMP_PARAM_FILE" + sed -E -i "s#^(solver_type[[:space:]]*=[[:space:]]*).* *//?#\1${SOLVER_OVERRIDE} // #" "$TEMP_PARAM_FILE" + fi + + if [[ -n "$TIME_STEP_MIN_OVERRIDE" ]]; then + # Euler uses this as a fixed integration step for the whole run (time_step_max is ignored + # by the Euler branch), so this needs to be small (e.g. 0.005) to stay numerically stable. + sed -E -i "s#^(time_step_min[[:space:]]*=[[:space:]]*)[0-9.]+#\1${TIME_STEP_MIN_OVERRIDE}#" "$TEMP_PARAM_FILE" + fi + + if [[ -n "$HILL_FILE_OVERRIDE" ]]; then + sed -E -i "s#^(hill_file[[:space:]]*=[[:space:]]*)\S+#\1${HILL_FILE_OVERRIDE}#" "$TEMP_PARAM_FILE" + fi + + if [[ -n "$HERG_FILE_OVERRIDE" ]]; then + sed -E -i "s#^(herg_file[[:space:]]*=[[:space:]]*)\S+#\1${HERG_FILE_OVERRIDE}#" "$TEMP_PARAM_FILE" + fi + + if [[ -n "$DRUG_NAME_OVERRIDE" ]]; then + sed -E -i "s#^(drug_name[[:space:]]*=[[:space:]]*)\S+#\1${DRUG_NAME_OVERRIDE}#" "$TEMP_PARAM_FILE" + fi + + if [[ -n "$CONC_OVERRIDE" ]]; then + sed -E -i "s#^(drug_concentrations[[:space:]]*=[[:space:]]*)\S+#\1${CONC_OVERRIDE}#" "$TEMP_PARAM_FILE" fi PARAM_TO_USE="$TEMP_PARAM_FILE" From 16bd25e1618b3629c1d5bd33f84a33feefaf24c2 Mon Sep 17 00:00:00 2001 From: Iga Narendra Date: Mon, 20 Jul 2026 23:21:03 +0700 Subject: [PATCH 09/13] add benchmarking automator --- README.md | 110 +++++++++++++++++++++++++++++++++++ docker/linux/benchmark.sh | 117 +++++++++++++++++++++++++++++++++++++- 2 files changed, 224 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 76bd1c1..2c64242 100644 --- a/README.md +++ b/README.md @@ -390,6 +390,116 @@ git restore --source=HEAD --staged --worktree -- Makefile README.md docker-compo --- +## NEW: Run a Hardware Performance Benchmark + +Use `docker/linux/benchmark.sh` to run a reproducible timing sweep inside the Linux container. + +The benchmark auto-discovers drugs from `bin/simulation_cpu/chantest_hill/`, then sweeps: + +- drug +- solver (`CVode` and `Euler` by default) +- MPI process count (`NP_LIST`) +- repeat index (`REPEATS`) + +Each run calls `docker/linux/run_only.sh`, times the full simulation wall-clock duration, and appends one row to a benchmark CSV. + +### Default benchmark profile + +To keep the benchmark fast and comparable, the script uses short simulation overrides unless you change them: + +- `PACING=5` +- `PACING_WRITE=5` +- `TIME_STEP_MIN=0.001` +- `SOLVERS="CVode,Euler"` +- `NP_LIST="1"` +- `REPEATS=1` +- `VARIANT_FLAG=-DCIPAORDV1` + +It also builds the selected variant once before the sweep unless `SKIP_BUILD=1` is set. + +### Basic usage + +From the repository root, ready to run, just copy and paste to your terminal: + +```bash +bash docker/linux/benchmark.sh +``` + +**The script will start `drugsim-linux` automatically if needed!** + +### Common examples + +Single-process baseline: + +```bash +bash docker/linux/benchmark.sh +``` + +Compare 1, 2, 4, and 8 MPI ranks: + +```bash +NP_LIST="1,2,4,8" REPEATS=3 bash docker/linux/benchmark.sh +``` + +Benchmark only selected drugs: + +```bash +DRUGS="quinidine,bepridil" NP_LIST="1,4" REPEATS=2 bash docker/linux/benchmark.sh +``` + +Benchmark only one solver: + +```bash +SOLVERS="Euler" NP_LIST="1,4" bash docker/linux/benchmark.sh +``` + +Reuse an already-built binary: + +```bash +SKIP_BUILD=1 NP_LIST="1,4" bash docker/linux/benchmark.sh +``` + +Preview the sweep without running it: + +```bash +DRY_RUN=1 NP_LIST="1,4" bash docker/linux/benchmark.sh +``` + +### Useful environment variables + +| Variable | Default | Meaning | +|---|---|---| +| `DRUGS` | auto-discovered | Comma- or space-separated drug list | +| `SOLVERS` | `CVode,Euler` | Solvers to benchmark | +| `NP_LIST` | `1` | MPI process counts to test | +| `REPEATS` | `1` | Repeats per combination | +| `VARIANT_FLAG` | `-DCIPAORDV1` | Cell-model variant built once for the sweep | +| `PACING` | `5` | Override for `number_pacing` | +| `PACING_WRITE` | `5` | Override for `number_pacing_write` | +| `TIME_STEP_MIN` | `0.001` | Override for `time_step_min` | +| `CONC` | unset | Override `drug_concentrations` for every run | +| `HILL_SAMPLES` | `30` | Uses `chantest_hill//IC50_samples.csv` | +| `HERG_SAMPLES` | `10` | Uses `chantest_herg//boot_pars.csv` | +| `SKIP_BUILD` | `0` | Set to `1` to skip the one-time build step | +| `FAIL_FAST` | `0` | Set to `1` to stop on the first failed run | +| `OUT_DIR` | `benchmark_results/` | Output directory for this sweep | +| `DRY_RUN` | `0` | Set to `1` to print commands only | + +Run `bash docker/linux/benchmark.sh --help` to print the full built-in usage text. + +### Output files + +Each sweep writes a new directory under `benchmark_results//` containing: + +- `results.csv` — one row per run with elapsed time, exit code, selected parameters, and hardware metadata +- `summary.txt` — mean and standard deviation of elapsed time grouped by `drug`, `solver`, and `np` +- `environment.txt` — host and container details captured once per sweep +- `logs/__np_rep.log` — stdout/stderr for each individual run + +The benchmark stamps every CSV row with host OS, CPU model, visible RAM, container architecture, and whether Docker is running under CPU emulation. This is especially important on Apple Silicon hosts because the Docker service is intentionally pinned to `linux/amd64`; emulated timings should not be compared against native Linux runs. + +--- + ## Running a Simulation on an HPC Cluster (PBS/Torque) 1. Navigate to the working directory: diff --git a/docker/linux/benchmark.sh b/docker/linux/benchmark.sh index b9334a2..9737d05 100755 --- a/docker/linux/benchmark.sh +++ b/docker/linux/benchmark.sh @@ -3,7 +3,10 @@ # # Sweeps the drugsim container across drugs x solvers x MPI process counts, # holding every other hyperparameter fixed, and records wall-clock elapsed -# time for each run to a CSV so runs are comparable across hardware. +# time for each run to a CSV so runs are comparable across hardware. Every +# row is tagged with host + container hardware info (CPU, RAM, arch, whether +# Docker is emulating a foreign architecture) so CSVs from different machines +# can be compared or concatenated without losing track of what ran where. # # Run from the repository root (or anywhere; it cd's to the repo root itself): # bash docker/linux/benchmark.sh @@ -39,6 +42,12 @@ Environment variables (defaults shown): OUT_DIR benchmark_results/ where results.csv/summary.txt/logs/ are written DRY_RUN 0 set 1 to print the commands without running them +Hardware/environment info (host OS/arch/CPU/RAM, container OS/arch/CPU/RAM, +whether Docker is running inside a VM, whether the container is CPU-emulated) +is auto-detected once per sweep, printed up front, written to +OUT_DIR/environment.txt, and appended as extra columns on every results.csv +row -- no configuration needed. + Example - sweep all drugs across both solvers at 1 and 4 MPI ranks, 3 repeats: NP_LIST="1,4" REPEATS=3 bash docker/linux/benchmark.sh @@ -71,6 +80,83 @@ FAIL_FAST="${FAIL_FAST:-0}" DRY_RUN="${DRY_RUN:-0}" OUT_DIR="${OUT_DIR:-benchmark_results/$(date +%Y%m%d_%H%M%S)}" +# --- Hardware / environment detection -------------------------------------- +# Captured once per sweep (hardware doesn't change mid-run) and stamped onto +# every CSV row, so results from different machines stay self-describing. +norm_arch() { + case "$1" in + arm64 | aarch64) echo "arm64" ;; + x86_64 | amd64) echo "x86_64" ;; + *) echo "$1" ;; + esac +} + +gather_host_info() { + HOST_OS="$(uname -s)" + HOST_ARCH="$(uname -m)" + case "$HOST_OS" in + Darwin) + HOST_CPU_MODEL="$(sysctl -n machdep.cpu.brand_string 2>/dev/null)" + HOST_CPU_CORES="$(sysctl -n hw.logicalcpu 2>/dev/null)" + local ram_bytes + ram_bytes="$(sysctl -n hw.memsize 2>/dev/null)" + HOST_RAM_GB="$(awk -v b="${ram_bytes:-0}" 'BEGIN { printf "%.1f", b / 1024 / 1024 / 1024 }')" + ;; + Linux) + HOST_CPU_MODEL="$(awk -F: '/model name/ { gsub(/^ /, "", $2); print $2; exit }' /proc/cpuinfo 2>/dev/null)" + HOST_CPU_CORES="$(nproc 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null)" + local ram_kb + ram_kb="$(awk '/MemTotal/ { print $2 }' /proc/meminfo 2>/dev/null)" + HOST_RAM_GB="$(awk -v kb="${ram_kb:-0}" 'BEGIN { printf "%.1f", kb / 1024 / 1024 }')" + ;; + *) + HOST_CPU_MODEL="unknown" + HOST_CPU_CORES="unknown" + HOST_RAM_GB="unknown" + ;; + esac + HOST_CPU_MODEL="${HOST_CPU_MODEL:-unknown}" + HOST_CPU_MODEL="${HOST_CPU_MODEL//,/}" # keep CSV columns intact + HOST_CPU_CORES="${HOST_CPU_CORES:-unknown}" + HOST_RAM_GB="${HOST_RAM_GB:-unknown}" +} + +# Requires the drugsim-linux container to already be running. +gather_container_info() { + DOCKER_OS="$(docker info --format '{{.OperatingSystem}}' 2>/dev/null)" + if [[ "$DOCKER_OS" == *"Docker Desktop"* ]]; then + DOCKER_RUNTIME="docker-desktop-vm" + else + DOCKER_RUNTIME="linux-native" + fi + + CONTAINER_ARCH="$(docker compose exec -T drugsim-linux uname -m 2>/dev/null | tr -d '\r\n')" + CONTAINER_CPU_CORES="$(docker compose exec -T drugsim-linux nproc 2>/dev/null | tr -d '\r\n')" + local mem_kb + mem_kb="$(docker compose exec -T drugsim-linux awk '/MemTotal/ { print $2 }' /proc/meminfo 2>/dev/null | tr -d '\r\n')" + CONTAINER_RAM_GB="$(awk -v kb="${mem_kb:-0}" 'BEGIN { printf "%.1f", kb / 1024 / 1024 }')" + + if [[ -n "$CONTAINER_ARCH" && "$(norm_arch "$HOST_ARCH")" != "$(norm_arch "$CONTAINER_ARCH")" ]]; then + EMULATED="yes" + else + EMULATED="no" + fi + + CONTAINER_ARCH="${CONTAINER_ARCH:-unknown}" + CONTAINER_CPU_CORES="${CONTAINER_CPU_CORES:-unknown}" + CONTAINER_RAM_GB="${CONTAINER_RAM_GB:-unknown}" +} + +# Placeholders so results.csv columns line up even in DRY_RUN mode, where the +# container is never contacted. +DOCKER_RUNTIME="n/a (dry run)" +CONTAINER_ARCH="n/a" +CONTAINER_CPU_CORES="n/a" +CONTAINER_RAM_GB="n/a" +EMULATED="n/a" + +gather_host_info + if [[ -z "${DRUGS:-}" ]]; then DRUGS="$(find "$SIM_DIR/chantest_hill" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort | tr '\n' ',')" fi @@ -88,7 +174,7 @@ fi mkdir -p "$OUT_DIR/logs" CSV_FILE="$OUT_DIR/results.csv" -echo "timestamp_utc,variant_flag,drug,solver,np,repeat,pacing,pacing_write,time_step_min,concentrations,hill_file,herg_file,elapsed_seconds,exit_code,log_file" > "$CSV_FILE" +echo "timestamp_utc,variant_flag,drug,solver,np,repeat,pacing,pacing_write,time_step_min,concentrations,hill_file,herg_file,elapsed_seconds,exit_code,log_file,host_os,host_arch,host_cpu_model,host_cpu_cores,host_ram_gb,docker_runtime,container_arch,container_cpu_cores,container_ram_gb,emulated" > "$CSV_FILE" echo "Output directory: $OUT_DIR" echo "Drugs: ${DRUG_ARR[*]}" @@ -103,6 +189,8 @@ if [[ "$DRY_RUN" != "1" ]]; then docker compose up -d fi + gather_container_info + if [[ "$SKIP_BUILD" != "1" ]]; then echo "Building binary for VARIANT_FLAG=$VARIANT_FLAG once before the sweep (RUN_SIM=0)..." docker compose exec -T drugsim-linux env \ @@ -117,6 +205,28 @@ if [[ "$DRY_RUN" != "1" ]]; then fi fi +echo "Hardware / environment:" +echo " Host: ${HOST_OS} ${HOST_ARCH} | ${HOST_CPU_MODEL} | ${HOST_CPU_CORES} logical cores | ${HOST_RAM_GB} GB RAM" +echo " Container: ${DOCKER_RUNTIME} | arch=${CONTAINER_ARCH} (emulated=${EMULATED}) | ${CONTAINER_CPU_CORES} cores visible | ${CONTAINER_RAM_GB} GB RAM visible" +if [[ "$EMULATED" == "yes" ]]; then + echo " WARNING: container arch differs from host arch -- this run is CPU-emulated (e.g. Rosetta/QEMU)." + echo " Timings will NOT reflect native hardware performance; do not compare them against native runs." +fi +echo + +{ + echo "host_os=${HOST_OS}" + echo "host_arch=${HOST_ARCH}" + echo "host_cpu_model=${HOST_CPU_MODEL}" + echo "host_cpu_cores=${HOST_CPU_CORES}" + echo "host_ram_gb=${HOST_RAM_GB}" + echo "docker_runtime=${DOCKER_RUNTIME}" + echo "container_arch=${CONTAINER_ARCH}" + echo "container_cpu_cores=${CONTAINER_CPU_CORES}" + echo "container_ram_gb=${CONTAINER_RAM_GB}" + echo "emulated=${EMULATED}" +} > "$OUT_DIR/environment.txt" + total=$(( ${#DRUG_ARR[@]} * ${#SOLVER_ARR[@]} * ${#NP_ARR[@]} * REPEATS )) count=0 failures=0 @@ -162,7 +272,7 @@ for drug in "${DRUG_ARR[@]}"; do rm -f "$time_file" conc_used="${CONC:-}" - echo "${timestamp},${VARIANT_FLAG},${drug},${solver},${np},${rep},${PACING},${PACING_WRITE},${TIME_STEP_MIN},${conc_used},${hill_file},${herg_file},${elapsed},${exit_code},${log_file}" >> "$CSV_FILE" + echo "${timestamp},${VARIANT_FLAG},${drug},${solver},${np},${rep},${PACING},${PACING_WRITE},${TIME_STEP_MIN},${conc_used},${hill_file},${herg_file},${elapsed},${exit_code},${log_file},${HOST_OS},${HOST_ARCH},${HOST_CPU_MODEL},${HOST_CPU_CORES},${HOST_RAM_GB},${DOCKER_RUNTIME},${CONTAINER_ARCH},${CONTAINER_CPU_CORES},${CONTAINER_RAM_GB},${EMULATED}" >> "$CSV_FILE" if [[ $exit_code -ne 0 ]]; then failures=$((failures + 1)) @@ -187,6 +297,7 @@ fi echo echo "Sweep complete: $((count - failures))/$total runs succeeded ($failures failed)." echo "Raw results: $CSV_FILE" +echo "Environment: $OUT_DIR/environment.txt" summary_file="$OUT_DIR/summary.txt" awk -F',' ' From abe3a6f4695adc9c7b84ee2b6c7b0c2f9dafb079 Mon Sep 17 00:00:00 2001 From: Iga Narendra Date: Mon, 20 Jul 2026 23:21:51 +0700 Subject: [PATCH 10/13] update gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 6d2db6f..c7fb278 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ bin/simulation_cpu/responses/*.json bin/simulation_cpu_postprocessing/initial_values_zip bin/simulation_cpu_postprocessing/requests/*filled* bin/simulation_cpu_postprocessing/responses/*.json +.DS_Store *.tex *.pdf *.aux @@ -81,3 +82,4 @@ bin/simulation_cpu_postprocessing/chantest_herg/ranolazine/ bin/simulation_cpu_postprocessing/chantest_herg/sotalol/ bin/simulation_cpu_postprocessing/chantest_herg/terfenadine/ bin/simulation_cpu_postprocessing/chantest_herg/verapamil/ + From bbaf5adf26b5b32c89dbf5ea5227f37ba8bd1e61 Mon Sep 17 00:00:00 2001 From: Iga Narendra Date: Mon, 20 Jul 2026 23:22:29 +0700 Subject: [PATCH 11/13] add temp to gitignore --- .gitignore | 4 ++-- bin/simulation_cpu/.docker-test-param.txt | 18 ------------------ 2 files changed, 2 insertions(+), 20 deletions(-) delete mode 100644 bin/simulation_cpu/.docker-test-param.txt diff --git a/.gitignore b/.gitignore index c7fb278..46ea4e1 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ bin/simulation_cpu_postprocessing/initial_values_zip bin/simulation_cpu_postprocessing/requests/*filled* bin/simulation_cpu_postprocessing/responses/*.json .DS_Store +bin/simulation_cpu/.docker-test-param.txt *.tex *.pdf *.aux @@ -81,5 +82,4 @@ bin/simulation_cpu_postprocessing/chantest_herg/ondansetron/ bin/simulation_cpu_postprocessing/chantest_herg/ranolazine/ bin/simulation_cpu_postprocessing/chantest_herg/sotalol/ bin/simulation_cpu_postprocessing/chantest_herg/terfenadine/ -bin/simulation_cpu_postprocessing/chantest_herg/verapamil/ - +bin/simulation_cpu_postprocessing/chantest_herg/verapamil/ \ No newline at end of file diff --git a/bin/simulation_cpu/.docker-test-param.txt b/bin/simulation_cpu/.docker-test-param.txt deleted file mode 100644 index 252f61e..0000000 --- a/bin/simulation_cpu/.docker-test-param.txt +++ /dev/null @@ -1,18 +0,0 @@ -user_name = marcell // User Name that will be used in the file name. -number_pacing = 5 // Number of paces. -number_pacing_write = 5 // Last X paces to be written in the file. -cycle_length = 2000 // Time period of one cycle (msec). -cell_model = CiPAORdv1.0_endo // Name of the cell model. Refer to the guide to fill it -stimulus_duration = 0.5 // Duration of stimulus current applied (msec). -stimulus_amplitude_scale = 1.0 // Scaling factor of stimulus current amplitude. -solver_type = CVode // Type of ODE solver used. Only CVode or Euler are available. -time_step_min = 0.5 // The minimum time step of the computation (msec). Only for CVode solver. -time_step_max = 2.0 // The maximum time step of the computation (msec). Only for CVode solver. -writing_step = 1.0 // The interval time to write the result into the file (msec). -drug_name = quinidine // Name of the drug -drug_concentrations = 3237,6474,9711,12948 // Concentration of the drug, Cmax1 to Cmax4 values (uMolar) -hill_file = ./chantest_hill/quinidine/IC50_samples30.csv // File that contains the IC50 information for each ionic channel. -herg_file = ./chantest_herg/quinidine/boot_pars10.csv // File that contains the hERG parameters. -is_cvar = 0 // Conductance variability. Set true to activate and assign the file to Cvar_File -cvar_file = ./population/indi_1sample.csv // the file for conductance variability. -number_of_cpu = 10 // Number of CPU used for mpiexec call From 21beffc704e0fe18e906e159393cd38509d8130b Mon Sep 17 00:00:00 2001 From: Iga Narendra Date: Wed, 22 Jul 2026 11:07:05 +0700 Subject: [PATCH 12/13] add conc for benchmark --- .gitignore | 2 ++ docker/linux/benchmark.sh | 63 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 46ea4e1..2831466 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ bin/simulation_cpu/chantest_hill/sotalol/ bin/simulation_cpu/chantest_hill/terfenadine/ bin/simulation_cpu/chantest_hill/verapamil/ bin/simulation_cpu/chantest_hill/CiPA_training_drugs_Cmax.csv +bin/simulation_cpu/chantest_hill/CiPA_drug_concentrations.csv bin/simulation_cpu/chantest_herg/bepridil/ bin/simulation_cpu/chantest_herg/chlorpromazine/ bin/simulation_cpu/chantest_herg/cisapride/ @@ -72,6 +73,7 @@ bin/simulation_cpu_postprocessing/chantest_hill/sotalol/ bin/simulation_cpu_postprocessing/chantest_hill/terfenadine/ bin/simulation_cpu_postprocessing/chantest_hill/verapamil/ bin/simulation_cpu_postprocessing/chantest_hill/CiPA_training_drugs_Cmax.csv +bin/simulation_cpu_postprocessing/chantest_hill/CiPA_drug_concentrations.csv bin/simulation_cpu_postprocessing/chantest_herg/bepridil/ bin/simulation_cpu_postprocessing/chantest_herg/chlorpromazine/ bin/simulation_cpu_postprocessing/chantest_herg/cisapride/ diff --git a/docker/linux/benchmark.sh b/docker/linux/benchmark.sh index 9737d05..afc2564 100755 --- a/docker/linux/benchmark.sh +++ b/docker/linux/benchmark.sh @@ -33,7 +33,9 @@ Environment variables (defaults shown): PACING 5 number_pacing override PACING_WRITE 5 number_pacing_write override TIME_STEP_MIN 0.001 time_step_min override - CONC (unset -> use param.txt's own concentrations for every drug) + CONC_LEVELS 1x,4x which columns of CMAX_TABLE to use as drug_concentrations (or "all" for 1x,2x,3x,4x) + CMAX_TABLE /chantest_hill/CiPA_drug_concentrations.csv per-drug Cmax table (drug,therapeutic_Cmax_nM,conc_1x..conc_4x,CiPA_risk_category) + CONC (unset -> look up each drug's own concentrations in CMAX_TABLE; set to force the SAME concentrations for every drug instead) HILL_SAMPLES 30 -> chantest_hill//IC50_samples.csv HERG_SAMPLES 10 -> chantest_herg//boot_pars.csv SIM_DIR bin/simulation_cpu @@ -72,6 +74,8 @@ REPEATS="${REPEATS:-1}" PACING="${PACING:-5}" PACING_WRITE="${PACING_WRITE:-5}" TIME_STEP_MIN="${TIME_STEP_MIN:-0.001}" +CONC_LEVELS="${CONC_LEVELS:-1x,4x}" +CMAX_TABLE="${CMAX_TABLE:-$SIM_DIR/chantest_hill/CiPA_drug_concentrations.csv}" CONC="${CONC:-}" HILL_SAMPLES="${HILL_SAMPLES:-30}" HERG_SAMPLES="${HERG_SAMPLES:-10}" @@ -157,6 +161,45 @@ EMULATED="n/a" gather_host_info +# --- Per-drug concentration lookup ------------------------------------------ +# Each drug has its own Cmax, so reusing one drug's concentrations for every +# drug in the sweep understates/overstates the real workload. CMAX_TABLE maps +# drug -> conc_1x..conc_4x (uM); CONC_LEVELS picks which of those columns to +# combine into drug_concentrations for each run. +resolve_conc_levels() { + if [[ "$CONC_LEVELS" == "all" ]]; then + echo "1x 2x 3x 4x" + else + split_list "$CONC_LEVELS" + fi +} + +lookup_drug_conc() { + local drug="$1" + local levels + levels="$(resolve_conc_levels)" + [[ -f "$CMAX_TABLE" ]] || return 1 + awk -F',' -v drug="$drug" -v levels="$levels" ' + NR == 1 { + for (i = 1; i <= NF; i++) col[$i] = i + next + } + $1 == drug { + n = split(levels, lv, " ") + out = "" + ok = 1 + for (j = 1; j <= n; j++) { + idx = col["conc_" lv[j]] + val = (idx != "") ? $(idx) : "" + if (val == "") { ok = 0; break } + out = (out == "") ? val : out "," val + } + if (ok) { print out; found = 1 } + } + END { exit (found ? 0 : 1) } + ' "$CMAX_TABLE" +} + if [[ -z "${DRUGS:-}" ]]; then DRUGS="$(find "$SIM_DIR/chantest_hill" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort | tr '\n' ',')" fi @@ -181,6 +224,11 @@ echo "Drugs: ${DRUG_ARR[*]}" echo "Solvers: ${SOLVER_ARR[*]}" echo "NP: ${NP_ARR[*]}" echo "Repeats: $REPEATS" +if [[ -n "$CONC" ]]; then + echo "Conc: fixed for all drugs: $CONC" +else + echo "Conc: per-drug, levels=${CONC_LEVELS} from ${CMAX_TABLE}" +fi echo if [[ "$DRY_RUN" != "1" ]]; then @@ -235,6 +283,15 @@ for drug in "${DRUG_ARR[@]}"; do hill_file="./chantest_hill/${drug}/IC50_samples${HILL_SAMPLES}.csv" herg_file="./chantest_herg/${drug}/boot_pars${HERG_SAMPLES}.csv" + if [[ -n "$CONC" ]]; then + conc_override="$CONC" + elif conc_override="$(lookup_drug_conc "$drug")"; then + : + else + conc_override="" + echo "WARNING: no concentrations for drug=${drug} (levels=${CONC_LEVELS}) in ${CMAX_TABLE}; falling back to param.txt's own concentrations for this drug." >&2 + fi + for solver in "${SOLVER_ARR[@]}"; do for np in "${NP_ARR[@]}"; do for ((rep = 1; rep <= REPEATS; rep++)); do @@ -255,7 +312,7 @@ for drug in "${DRUG_ARR[@]}"; do "DRUG_NAME_OVERRIDE=${drug}" "HILL_FILE_OVERRIDE=${hill_file}" "HERG_FILE_OVERRIDE=${herg_file}") - [[ -n "$CONC" ]] && cmd+=("CONC_OVERRIDE=${CONC}") + [[ -n "$conc_override" ]] && cmd+=("CONC_OVERRIDE=${conc_override}") cmd+=(bash docker/linux/run_only.sh) if [[ "$DRY_RUN" == "1" ]]; then @@ -271,7 +328,7 @@ for drug in "${DRUG_ARR[@]}"; do elapsed="$(cat "$time_file")" rm -f "$time_file" - conc_used="${CONC:-}" + conc_used="${conc_override:-}" echo "${timestamp},${VARIANT_FLAG},${drug},${solver},${np},${rep},${PACING},${PACING_WRITE},${TIME_STEP_MIN},${conc_used},${hill_file},${herg_file},${elapsed},${exit_code},${log_file},${HOST_OS},${HOST_ARCH},${HOST_CPU_MODEL},${HOST_CPU_CORES},${HOST_RAM_GB},${DOCKER_RUNTIME},${CONTAINER_ARCH},${CONTAINER_CPU_CORES},${CONTAINER_RAM_GB},${EMULATED}" >> "$CSV_FILE" if [[ $exit_code -ne 0 ]]; then From 3b011f3ce5cc80ce3617d5f600e10b9c5300ec9c Mon Sep 17 00:00:00 2001 From: Iga Narendra Date: Wed, 22 Jul 2026 11:23:11 +0700 Subject: [PATCH 13/13] add sample limit for benchmarking --- .gitignore | 1 + docker/linux/benchmark.sh | 48 +++++++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 2831466..d35ab9e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ bin/simulation_cpu_postprocessing/requests/*filled* bin/simulation_cpu_postprocessing/responses/*.json .DS_Store bin/simulation_cpu/.docker-test-param.txt +.bench_samples/ *.tex *.pdf *.aux diff --git a/docker/linux/benchmark.sh b/docker/linux/benchmark.sh index afc2564..795a911 100755 --- a/docker/linux/benchmark.sh +++ b/docker/linux/benchmark.sh @@ -38,6 +38,13 @@ Environment variables (defaults shown): CONC (unset -> look up each drug's own concentrations in CMAX_TABLE; set to force the SAME concentrations for every drug instead) HILL_SAMPLES 30 -> chantest_hill//IC50_samples.csv HERG_SAMPLES 10 -> chantest_herg//boot_pars.csv + SAMPLES 1 rows of hill/herg to actually use per run (truncated from the + HILL_SAMPLES/HERG_SAMPLES files above); "all" disables truncation. + MPI distributes samples round-robin across NP ranks, so with the + default SAMPLES=1 only rank 0 does simulation work regardless of + NP -- that isolates a single-core, hardware-only timing free of + virtual-population noise. To measure multi-core scaling instead, + set SAMPLES to match each NP value (one sample per core). SIM_DIR bin/simulation_cpu SKIP_BUILD 0 set 1 to reuse an already-built binary FAIL_FAST 0 set 1 to stop the sweep on first failure @@ -79,6 +86,7 @@ CMAX_TABLE="${CMAX_TABLE:-$SIM_DIR/chantest_hill/CiPA_drug_concentrations.csv}" CONC="${CONC:-}" HILL_SAMPLES="${HILL_SAMPLES:-30}" HERG_SAMPLES="${HERG_SAMPLES:-10}" +SAMPLES="${SAMPLES:-1}" SKIP_BUILD="${SKIP_BUILD:-0}" FAIL_FAST="${FAIL_FAST:-0}" DRY_RUN="${DRY_RUN:-0}" @@ -217,13 +225,23 @@ fi mkdir -p "$OUT_DIR/logs" CSV_FILE="$OUT_DIR/results.csv" -echo "timestamp_utc,variant_flag,drug,solver,np,repeat,pacing,pacing_write,time_step_min,concentrations,hill_file,herg_file,elapsed_seconds,exit_code,log_file,host_os,host_arch,host_cpu_model,host_cpu_cores,host_ram_gb,docker_runtime,container_arch,container_cpu_cores,container_ram_gb,emulated" > "$CSV_FILE" +echo "timestamp_utc,variant_flag,drug,solver,np,repeat,pacing,pacing_write,time_step_min,concentrations,hill_file,herg_file,samples,elapsed_seconds,exit_code,log_file,host_os,host_arch,host_cpu_model,host_cpu_cores,host_ram_gb,docker_runtime,container_arch,container_cpu_cores,container_ram_gb,emulated" > "$CSV_FILE" + +# Truncated hill/herg files live here when SAMPLES limits the population size +# (see the per-drug loop below); gitignored, regenerated fresh each run. +SAMPLES_TMP_DIR="$SIM_DIR/.bench_samples" +mkdir -p "$SAMPLES_TMP_DIR" echo "Output directory: $OUT_DIR" echo "Drugs: ${DRUG_ARR[*]}" echo "Solvers: ${SOLVER_ARR[*]}" echo "NP: ${NP_ARR[*]}" echo "Repeats: $REPEATS" +if [[ "$SAMPLES" == "all" ]]; then + echo "Samples: all (untruncated, HILL_SAMPLES=${HILL_SAMPLES}/HERG_SAMPLES=${HERG_SAMPLES})" +else + echo "Samples: ${SAMPLES} row(s) per drug (truncated from HILL_SAMPLES=${HILL_SAMPLES}/HERG_SAMPLES=${HERG_SAMPLES})" +fi if [[ -n "$CONC" ]]; then echo "Conc: fixed for all drugs: $CONC" else @@ -282,6 +300,25 @@ failures=0 for drug in "${DRUG_ARR[@]}"; do hill_file="./chantest_hill/${drug}/IC50_samples${HILL_SAMPLES}.csv" herg_file="./chantest_herg/${drug}/boot_pars${HERG_SAMPLES}.csv" + samples_used="all" + + if [[ "$SAMPLES" != "all" && "$SAMPLES" != "0" ]]; then + src_hill="$SIM_DIR/chantest_hill/${drug}/IC50_samples${HILL_SAMPLES}.csv" + src_herg="$SIM_DIR/chantest_herg/${drug}/boot_pars${HERG_SAMPLES}.csv" + avail_hill=$(($(wc -l < "$src_hill") - 1)) + avail_herg=$(($(wc -l < "$src_herg") - 1)) + samples_used=$((avail_hill < avail_herg ? avail_hill : avail_herg)) + if ((SAMPLES < samples_used)); then + samples_used=$SAMPLES + elif ((SAMPLES > samples_used)); then + echo "WARNING: SAMPLES=${SAMPLES} exceeds available rows for ${drug} (hill=${avail_hill}, herg=${avail_herg}); using ${samples_used} instead. Bump HILL_SAMPLES/HERG_SAMPLES to unlock more." >&2 + fi + + hill_file="./.bench_samples/${drug}_hill_n${samples_used}.csv" + herg_file="./.bench_samples/${drug}_herg_n${samples_used}.csv" + head -n "$((samples_used + 1))" "$src_hill" > "$SAMPLES_TMP_DIR/${drug}_hill_n${samples_used}.csv" + head -n "$((samples_used + 1))" "$src_herg" > "$SAMPLES_TMP_DIR/${drug}_herg_n${samples_used}.csv" + fi if [[ -n "$CONC" ]]; then conc_override="$CONC" @@ -329,7 +366,8 @@ for drug in "${DRUG_ARR[@]}"; do rm -f "$time_file" conc_used="${conc_override:-}" - echo "${timestamp},${VARIANT_FLAG},${drug},${solver},${np},${rep},${PACING},${PACING_WRITE},${TIME_STEP_MIN},${conc_used},${hill_file},${herg_file},${elapsed},${exit_code},${log_file},${HOST_OS},${HOST_ARCH},${HOST_CPU_MODEL},${HOST_CPU_CORES},${HOST_RAM_GB},${DOCKER_RUNTIME},${CONTAINER_ARCH},${CONTAINER_CPU_CORES},${CONTAINER_RAM_GB},${EMULATED}" >> "$CSV_FILE" + conc_used_csv="${conc_used//,/;}" # concentrations are comma-joined; swap to ';' so they don't split into extra CSV fields + echo "${timestamp},${VARIANT_FLAG},${drug},${solver},${np},${rep},${PACING},${PACING_WRITE},${TIME_STEP_MIN},${conc_used_csv},${hill_file},${herg_file},${samples_used},${elapsed},${exit_code},${log_file},${HOST_OS},${HOST_ARCH},${HOST_CPU_MODEL},${HOST_CPU_CORES},${HOST_RAM_GB},${DOCKER_RUNTIME},${CONTAINER_ARCH},${CONTAINER_CPU_CORES},${CONTAINER_RAM_GB},${EMULATED}" >> "$CSV_FILE" if [[ $exit_code -ne 0 ]]; then failures=$((failures + 1)) @@ -361,10 +399,10 @@ awk -F',' ' NR == 1 { next } { key = $3 "|" $4 "|" $5 - if ($14 == "0") { + if ($15 == "0") { n[key]++ - sum[key] += $13 - sumsq[key] += $13 * $13 + sum[key] += $14 + sumsq[key] += $14 * $14 } else { fail[key]++ }