Skip to content

Contributing

Development Setup

git clone https://github.com/DanaResearchGroup/AdsPro.git
cd AdsPro

python3 -m venv ~/adspro-venv
source ~/adspro-venv/bin/activate

pip install -r requirements.txt
pip install -e .

Running the Test Suite

# All 95 tests
python3 -m pytest tests/ -v

# With coverage report
python3 -m pytest tests/ --cov=adspro --cov-report=term-missing

# Single module
python3 -m pytest tests/test_pmf_calculator.py -v

Test Modules

File Module What it covers
test_config_manager.py config_manager.py Config parsing, validation, Debye length, n_runs
test_grainer.py grainer.py CG geometry, Henderson-Hasselbalch, Lysozyme graining
test_surface_builder.py surface_builder.py Fibonacci sphere, Grahame equation, ZetaSurface
test_energy_calculator.py energy_calculator.py Gō energy, SASA, internal energy
test_pmf_calculator.py pmf_calculator.py WHAM keys, flat potential, edge cases
test_bond_detector.py bond_detector.py Bond energy function, charge classification
test_reporter.py reporter.py Output file generation smoke tests
test_runner.py runner.py _select_best_run: median selection, NaN handling
test_validator.py validator.py Tilt angle, native contact retention, surface distances
test_physics_engine.py physics_engine.py Velocity-Verlet integration, Langevin forces
test_cache_manager.py cache_manager.py Caching logic

Linting with Ruff

AdsPro uses Ruff for linting and style checking.

pip install ruff

# Check
ruff check adspro/

# Fix automatically where possible
ruff check adspro/ --fix

Ruff is configured in pyproject.toml:

[tool.ruff]
line-length = 100
target-version = "py39"

[tool.ruff.lint]
select = ["E", "F", "W"]
ignore = ["E501"]

Validating Code Changes

Important

When modifying any module (physics_engine, energy_calculator, pmf_calculator, runner), always validate by running a short simulation before committing:

source ~/adspro-venv/bin/activate

# Quick validation run (reduce steps for speed)
python -m adspro --project example --log-level DEBUG

Check the output for: - WHAM converges - ΔG_ads is in the range −10 to −50 kJ/mol for Lysozyme on silica - No Python exceptions or warnings


CI Pipeline

The CI runs on every push and pull request to main:

  1. Ruff — linting on Python 3.11
  2. Tests — pytest on Python 3.9, 3.10, 3.11
  3. Coverage — uploaded to Codecov (Python 3.11 only)
  4. Docs — MkDocs deployed to GitHub Pages (main branch only)

Module Architecture

adspro/
├── __main__.py          # CLI entry point (argparse)
├── config_manager.py    # YAML parsing, validation, defaults
├── grainer.py           # PDB → CG beads (Henderson-Hasselbalch charges)
├── surface_builder.py   # Fibonacci sphere, Grahame equation, Debye length
├── physics_engine.py    # Velocity-Verlet Langevin MD, force evaluation
├── energy_calculator.py # Morse, Gō, SASA, internal energy computations
├── bond_detector.py     # Non-covalent bond inventory (H-bonds, salt bridges)
├── orientation_sampler.py # Phase 0: rigid-body SO(3) sampling + protected orientations
├── pmf_calculator.py    # WHAM self-consistent iteration
├── runner.py            # Pipeline orchestration: Phase 0 → 1 → 2, smart window
├── reporter.py          # Write summary_report.txt, summary.json, pmf_result.json
├── validator.py         # Tilt angle, contact retention, COM distance
└── visualizer.py        # Three.js HTML viewer generation

Data Flow

runner.py
  ├── grainer.py           → CG protein
  ├── surface_builder.py   → NP surface beads
  ├── bond_detector.py     → initial bond inventory
  ├── energy_calculator.py → initial energy baseline
  ├── orientation_sampler.py → Phase 0 screening
  ├── physics_engine.py    → Phase 1 Langevin MD
  ├── physics_engine.py    → Phase 2 steered MD
  ├── (smart window detection)
  ├── physics_engine.py    → Phase 2 umbrella windows
  ├── pmf_calculator.py    → WHAM → G(ξ) → ΔG_ads
  ├── validator.py         → observables
  ├── bond_detector.py     → final bond inventory
  ├── reporter.py          → output files
  └── visualizer.py        → HTML viewer

Extending AdsPro

Adding a New Surface

See Extending to New Surfaces for the full protocol.

Adding a New Observable

  1. Implement the computation in validator.py
  2. Add the result to the summary dict in runner.py
  3. Write it to summary.json in reporter.py
  4. Add a test in tests/test_validator.py

Adding a New Force Term

  1. Implement _f_<name> in physics_engine.py
  2. Add it to the _total_force call in run_phase1 and run_umbrella_window
  3. Add a test in tests/test_physics_engine.py
  4. Validate with a simulation run

Reporting Issues

Open an issue on GitHub with: - AdsPro version - Python version and OS - The config.yaml you used - The full terminal output (use --log-level DEBUG) - The summary_report.txt if the run completed