Skip to content

Commit 9ad4e1b

Browse files
committed
Release: Milestone A FreeSolv gate ready for M5 Max handoff
Wires OpenMM Metal / OpenCL platform preference into sampling.py (falls back CPU if neither available). Rewrites scripts/run_freesolv_m5max.sh as a self-contained production- parameter runner: doctor check → 11 windows × 50 ps production × 2 legs × 12 compounds → tarball + macOS notification. Adds HANDOFF.md as the definitive friend-facing instructions (prerequisites, run, what to send back, what Henry wants in the CSV, troubleshooting). This is the first tagged release intended for external compute.
1 parent a61105b commit 9ad4e1b

3 files changed

Lines changed: 379 additions & 1 deletion

File tree

HANDOFF.md

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# CellSim FEP Handoff — Apple-silicon Mac runner
2+
3+
You are running CellSim's Milestone A accuracy gate on behalf of
4+
a friend. This is one command, ~4–6 hours wall time on an M5 Max,
5+
and produces one tarball to send back.
6+
7+
This README is written for you **and** your Claude Code. Either
8+
can follow it; Claude will probably run it end-to-end with minimal
9+
human intervention.
10+
11+
## What you're running
12+
13+
Absolute hydration free energy (ΔG_hyd) of 12 small molecules
14+
using alchemical free-energy perturbation (FEP). The 12 molecules
15+
span +2.0 kcal/mol (methane, hydrophobic) to −9.7 kcal/mol
16+
(acetamide, hydrophilic).
17+
18+
**Target**: predicted ΔG_hyd within 1.5 kcal/mol MAE of the
19+
published FreeSolv experimental values.
20+
21+
The pipeline is:
22+
`SMILES → OpenFF Sage 2.1.0 + AM1-BCC charges → OpenMM TIP3P
23+
solvated System → openmmtools alchemical decoupling →
24+
MCMC sampling with GHMC integrator + MBAR free-energy estimate`
25+
26+
## Hardware requirements
27+
28+
- **Apple silicon Mac** (M1/M2/M3/M4/M5, any Pro/Max/Ultra
29+
variant). Intel Mac also works but slower.
30+
- **32 GB RAM recommended**, 16 GB will work but may swap.
31+
- **~5 GB free disk** in the CellSim repo (data + output).
32+
- **Plugged in**, display-sleep OK but full-sleep NOT OK.
33+
Set before running:
34+
```bash
35+
sudo pmset -c disablesleep 1
36+
```
37+
Re-enable after:
38+
```bash
39+
sudo pmset -c disablesleep 0
40+
```
41+
42+
## One-time setup (first run only)
43+
44+
1. Install mamba/miniforge if you don't have it:
45+
```bash
46+
brew install miniforge
47+
```
48+
49+
2. Clone the release:
50+
```bash
51+
git clone https://github.com/Rockman6/CellSim-3D-Multicellular-Biology-Drug-Testing-Simulator.git CellSim
52+
cd CellSim
53+
git checkout <release-tag> # Henry will tell you which tag
54+
```
55+
56+
3. Create the conda environment:
57+
```bash
58+
mamba env create -f environment.yml
59+
conda activate cellsim
60+
```
61+
This installs ~60 packages and takes 10–20 minutes.
62+
63+
4. Install `terminal-notifier` so the script can ping you when
64+
it finishes:
65+
```bash
66+
brew install terminal-notifier
67+
```
68+
69+
5. Verify the install:
70+
```bash
71+
./scripts/cellsim doctor
72+
```
73+
You should see "**39/39 checks passed**". If fewer pass, send
74+
Henry the output — do **not** proceed.
75+
76+
## The run
77+
78+
One command:
79+
80+
```bash
81+
./scripts/run_freesolv_m5max.sh
82+
```
83+
84+
The script will:
85+
1. Activate the `cellsim` conda env.
86+
2. Print the OpenMM platform list (Metal / OpenCL / CPU) so you
87+
can see which backend is used.
88+
3. Run `cellsim doctor` and abort if it fails.
89+
4. Execute the 12-compound FreeSolv gate at production parameters:
90+
- 11 alchemical windows per leg
91+
- 50 ps equilibration + 50 ps production per window
92+
- 100 samples per window (2 ps stride)
93+
- 2 legs per compound (vacuum + TIP3P-solvated)
94+
- = ~2.2 ns simulated MD per compound × 12 = 26 ns total
95+
5. Write everything to `run/fep/<timestamp>/` and bundle into
96+
`freesolv_m5max_<timestamp>.tar.gz`.
97+
6. Fire a macOS notification ("CellSim FreeSolv FEP: PASS/FAIL")
98+
when complete.
99+
100+
Expected wall time:
101+
- **M5 Max (40-core GPU)**: 4–6 hours
102+
- **M2/M3 Max**: 6–9 hours
103+
- **M1 Pro/Max**: 8–12 hours
104+
- **Intel Mac (CPU only)**: 24–48 hours — not recommended
105+
106+
You can close the Terminal window during the run — the process
107+
continues. Use `tail -f run/fep/*/run.log` in another Terminal to
108+
watch progress.
109+
110+
## What to send back
111+
112+
When the script finishes, send **one file**:
113+
114+
```
115+
freesolv_m5max_<timestamp>.tar.gz
116+
```
117+
118+
(in the CellSim repo root). Send via:
119+
- **WeChat file transfer** (works for files up to ~100 MB; tarball
120+
will be ~1–5 MB)
121+
- **AirDrop** (if you're close geographically)
122+
- **Email / Google Drive / Dropbox**
123+
- **GitHub Gist** (if public data is OK — it is; FreeSolv
124+
numbers are public benchmark data)
125+
126+
The tarball contains:
127+
128+
| File | What it is | Why Henry wants it |
129+
|---|---|---|
130+
| `env.log` | OpenMM/Python/platform versions | Reproducibility — must match exactly |
131+
| `doctor.log` | cellsim doctor output | Catches env drift between machines |
132+
| `run.log` | Full pipeline stdout | Contains per-compound ΔG, per-window GHMC acceptance, wall times, any NaN warnings |
133+
| `freesolv_results.csv` | 12-row CSV of predictions | The numbers Henry analyses |
134+
135+
## What Henry specifically wants in the CSV
136+
137+
Each row of `freesolv_results.csv` has:
138+
139+
```
140+
name, smiles, dG_expt_kcalmol, dG_pred_kcalmol, uncertainty_kcalmol,
141+
residual_kcalmol, wall_seconds, ok, reason
142+
```
143+
144+
Specifically he'll compute:
145+
- **MAE** = mean |residual| across all 12 → gate is ≤ 1.5 kcal/mol.
146+
- **Per-compound sign correctness** — methane should give ≈ +2 kcal/mol
147+
(positive), not −2 (negative); the CPU pilot gave −2 due to
148+
under-sampling, so seeing it flip to positive on M5 Max is the
149+
load-bearing finding.
150+
- **GHMC acceptance rate** — must be ≥ 70% per window (≥ 75%
151+
preferred). Low acceptance means the ΔG is unreliable.
152+
- **Wall time per compound** — tells Henry the M5 Max throughput
153+
so he can plan the next round (EGFR kinase relative FEP).
154+
155+
## Troubleshooting
156+
157+
### `cellsim doctor` fails
158+
159+
Copy the terminal output, send it to Henry. Do NOT try to fix it.
160+
161+
### Script errors immediately with "python: command not found"
162+
163+
You didn't activate the env. Run:
164+
```bash
165+
conda activate cellsim
166+
```
167+
168+
### Script errors with "Particle coordinate is NaN"
169+
170+
**Send Henry the log anyway.** That failure mode is itself
171+
scientifically valuable. The CPU version sometimes hits this at
172+
shorter MD lengths; Henry needs to know if it shows up on
173+
M5 Max too or if longer sampling dissolves it.
174+
175+
### Run times out after 6 hours and hasn't finished
176+
177+
That's fine. Send the partial CSV — 6 of 12 compounds is better
178+
than nothing. Just append a line to the tarball note saying
179+
"killed at <time>, <N> of 12 compounds completed".
180+
181+
### Machine overheats / fan roars / battery drains
182+
183+
Normal for sustained GPU work. Machine is plugged in and
184+
physically safe. Continue.
185+
186+
### You want to reduce the run to ~2 hours
187+
188+
Edit `scripts/run_freesolv_m5max.sh`, change:
189+
```
190+
--production-steps 25000
191+
```
192+
to:
193+
```
194+
--production-steps 10000
195+
```
196+
197+
This gives a less-converged result (still useful as a first pass).
198+
Tell Henry you used shorter production when you send the tarball.
199+
200+
## What this actually tests (Henry's framing)
201+
202+
The CellSim project got a long critique from Henry's professor
203+
about whether the simulator can actually replace wet-lab
204+
experiments — specifically whether "physics-derived priors" from
205+
FEP are good enough to build cell biology on top of. Methane
206+
hydration is the simplest FEP test: if we can't get methane right,
207+
nothing else matters.
208+
209+
Your run is **the M5 Max-accessible test** of whether longer MD
210+
sampling dissolves the entropy-capture issue Henry saw on CPU.
211+
Professor threshold:
212+
- Acceptance ≥ 75%
213+
- ΔG_hyd(methane) = +2.0 ± 0.5 kcal/mol
214+
- Uncertainty < 0.6 kcal/mol
215+
216+
Hit those and Milestone A clears, Campaign 2 (real cell biology)
217+
opens.
218+
219+
## Questions
220+
221+
Ask Henry directly. He will give Claude explicit instructions
222+
too — if your Claude is unsure about something, let it paste
223+
the question into WeChat to Henry and wait.
224+
225+
Thanks for lending the machine. Good luck.

scripts/run_freesolv_m5max.sh

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env bash
2+
# CellSim — Milestone A FreeSolv FEP gate runner for Apple-silicon Mac.
3+
# One command, walk away, come back to a tarball to send back to Henry.
4+
#
5+
# Target hardware: M1 Pro / Max / Ultra, M2, M3, M4, M5 Max. ≥ 32 GB
6+
# unified memory recommended; 16 GB works but may swap.
7+
#
8+
# Expected wall time (from CPU grid extrapolation + OpenCL 5× speedup):
9+
# - 4-6 hours on M5 Max
10+
# - 6-10 hours on M1/M2 base
11+
# Plugged in + display-sleep-only (not full sleep) required.
12+
#
13+
# The script runs the Milestone A gate per the professor's acceptance
14+
# criteria: all 12 FreeSolv compounds at production parameters
15+
# (50 ps equilibration + 50 ps production per window × 11 windows ×
16+
# 2 legs = ~2.2 ns simulated MD per compound). Emits:
17+
# - per-compound predicted ΔG_hyd with MBAR uncertainty
18+
# - per-compound per-window GHMC acceptance rates
19+
# - aggregate MAE vs experimental (FreeSolv gate: MAE ≤ 1.5 kcal/mol)
20+
21+
set -euo pipefail
22+
23+
if [ ! -f "benchmarks/fep/freesolv_12.yaml" ]; then
24+
echo "error: run from CellSim repo root (not $(pwd))" >&2
25+
exit 2
26+
fi
27+
28+
# Activate env
29+
if ! command -v conda >/dev/null 2>&1; then
30+
echo "error: conda/mamba not found. Install miniforge first:" >&2
31+
echo " brew install miniforge" >&2
32+
exit 2
33+
fi
34+
source "$(conda info --base)/etc/profile.d/conda.sh"
35+
conda activate cellsim
36+
37+
# Output dir, timestamped so repeat runs don't collide
38+
STAMP=$(date +%Y%m%d_%H%M%S)
39+
OUT_DIR="run/fep/${STAMP}"
40+
mkdir -p "${OUT_DIR}"
41+
42+
echo "============================================================"
43+
echo "CellSim — FreeSolv FEP gate (Milestone A)"
44+
echo "============================================================"
45+
echo " started: $(date)"
46+
echo " machine: $(uname -a)"
47+
echo " ram: $(sysctl -n hw.memsize 2>/dev/null | awk '{print $1/1024/1024/1024 " GB"}' || echo '?')"
48+
echo " git commit: $(git rev-parse HEAD)"
49+
echo " git ref: $(git describe --tags --always 2>/dev/null || echo '?')"
50+
echo " output: ${OUT_DIR}/"
51+
echo ""
52+
53+
# Env + platform report — critical for reproducibility
54+
python -c "
55+
import openmm, openmmtools, openff.toolkit, pymbar, sys
56+
from openmm import Platform
57+
print('python ', sys.version.split()[0])
58+
print('openmm ', openmm.__version__)
59+
print('openmmtools ', openmmtools.__version__)
60+
print('openff-toolkit', openff.toolkit.__version__)
61+
print('pymbar ', pymbar.__version__)
62+
print()
63+
print('Available OpenMM platforms (fastest first):')
64+
for i in range(Platform.getNumPlatforms()):
65+
p = Platform.getPlatform(i)
66+
print(f' {p.getName()} (speed {p.getSpeed()})')
67+
print()
68+
print('Pipeline will prefer Metal → OpenCL → CUDA → CPU.')
69+
" 2>&1 | tee "${OUT_DIR}/env.log"
70+
echo ""
71+
72+
# Cellsim doctor — fail fast if env is broken
73+
echo "=== cellsim doctor ==="
74+
./scripts/cellsim doctor 2>&1 | tee "${OUT_DIR}/doctor.log" | tail -5
75+
DOCTOR_EXIT=${PIPESTATUS[0]}
76+
if [ $DOCTOR_EXIT -ne 0 ]; then
77+
echo "" >&2
78+
echo "error: cellsim doctor failed. Check ${OUT_DIR}/doctor.log" >&2
79+
echo "Do NOT proceed with the gate run until doctor passes." >&2
80+
exit 3
81+
fi
82+
echo ""
83+
84+
# Run the full FreeSolv gate
85+
echo "=== FreeSolv FEP gate starting at $(date) ==="
86+
echo "Parameters: 11 windows × 25 000 production steps (50 ps at 2 fs)"
87+
echo " × 5 000 equilibration steps (10 ps)"
88+
echo " × 2 legs (vacuum + TIP3P-solvated)"
89+
echo " × 12 FreeSolv compounds (methane → acetamide)"
90+
echo ""
91+
92+
time ./scripts/cellsim fep-freesolv \
93+
benchmarks/fep/freesolv_12.yaml \
94+
--n-windows 11 \
95+
--production-steps 25000 \
96+
--equilibration-steps 5000 \
97+
--sample-stride 250 \
98+
--out-csv "${OUT_DIR}/freesolv_results.csv" \
99+
2>&1 | tee "${OUT_DIR}/run.log"
100+
101+
EXIT_CODE=${PIPESTATUS[0]}
102+
103+
echo ""
104+
echo "============================================================"
105+
echo "Finished at $(date) (exit ${EXIT_CODE})"
106+
echo " 0 = MAE ≤ 1.5 kcal/mol (gate passed)"
107+
echo " non-0 = gate failed or some compounds NaN'd"
108+
echo "============================================================"
109+
echo ""
110+
ls -la "${OUT_DIR}/"
111+
echo ""
112+
113+
# Bundle for transfer
114+
TARBALL="freesolv_m5max_${STAMP}.tar.gz"
115+
tar czf "${TARBALL}" -C run/fep "${STAMP}/"
116+
echo "Created: ${TARBALL} ($(du -h ${TARBALL} | cut -f1))"
117+
echo ""
118+
echo "Send this tarball back to Henry (WeChat / email / Google Drive /"
119+
echo "AirDrop). It contains the CSV, the env report, the doctor log, and"
120+
echo "the full stdout log — Henry will analyse locally."
121+
echo ""
122+
123+
# Notify on completion (silent if terminal-notifier isn't installed)
124+
if command -v terminal-notifier >/dev/null 2>&1; then
125+
STATUS=$([ $EXIT_CODE -eq 0 ] && echo "PASS" || echo "FAIL")
126+
terminal-notifier \
127+
-title "CellSim FreeSolv FEP" \
128+
-subtitle "${STATUS} (exit ${EXIT_CODE})" \
129+
-message "Tarball ready: ${TARBALL}. Send it to Henry." \
130+
-sound Glass \
131+
-activate com.apple.Terminal 2>/dev/null || true
132+
fi
133+
134+
exit ${EXIT_CODE}

src/fep/sampling.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,29 @@ def sample_alchemical_windows(
9494
Context as _Context,
9595
VerletIntegrator as Verlet,
9696
LocalEnergyMinimizer as _Min,
97+
Platform,
9798
)
98-
from openmmtools import mcmc, states
99+
from openmmtools import mcmc, states, cache
99100
from openmmtools.alchemy import AlchemicalState
100101

102+
# Prefer the fastest available platform: Metal (Apple silicon
103+
# OpenMM 8.2+) → OpenCL (works on Apple silicon via Metal shim,
104+
# Intel, AMD, NVIDIA) → CUDA → CPU. On an M5 Max this gives a
105+
# ~5-10× speedup over CPU. openmmtools' context cache uses the
106+
# first platform available in its list unless we pin one.
107+
_preferred = ("Metal", "OpenCL", "CUDA", "CPU")
108+
_chosen_platform = None
109+
for _name in _preferred:
110+
try:
111+
_p = Platform.getPlatformByName(_name)
112+
cache.global_context_cache.platform = _p
113+
_chosen_platform = _name
114+
break
115+
except Exception:
116+
continue
117+
logger.info(
118+
"FEP sampling platform: %s", _chosen_platform or "default")
119+
101120
t0 = time.time()
102121
schedule = _default_lambda_schedule(n_windows)
103122
result = AlchemicalSamplingResult(

0 commit comments

Comments
 (0)