A library, not a web application, for LigandMPNN sequence design and learned side-chain packing. TypeScript handles structure preparation, constraints, autoregressive sampling, and atom reconstruction; ONNX Runtime Web executes the neural networks with WASM or WebGPU.
The architecture follows webProteinMPNN: load model assets once, prepare a structure, reuse its encoding, and sample in JavaScript. The model implementation and checkpoint conversion target dauparas/LigandMPNN.
Release 0.1.1 supports all four official LigandMPNN sequence models and the learned packer. The converted ONNX bundles are hosted in MurrellLab/webports, while ONNX Runtime Web is installed through the pinned npm dependency. Original .pt checkpoints are not committed to GitHub or uploaded to the web-model collection.
All five bundles passed strict official-weight loading, ONNX checking, comparison to upstream PyTorch, and actual ONNX Runtime Web/WASM inference. Library integration checks cover design, scoring, constraints, learned packing, and all-20-amino-acid OpenFold geometry. A separate run on upstream's 93-residue 1BC8 input passed independent PyTorch comparison, including fixed-side-chain context, and produced downloadable FASTA/PDB examples.
Browser WASM and WebGPU remain unvalidated here: the available browser blocked access to the local test server. Native Python ONNX Runtime also crashes during import in this restricted environment. The exporter now supports direct Node/WASM validation with the same numerical tolerances; no gate was replaced by mock inference. See the validation record for results and remaining limits.
No Python conversion is needed to use this release:
npm ci
node examples/node.mjs validation-inputs/1BC8.pdb design --packmodelManifestURL() points to an immutable Hugging Face revision by default, so the example downloads and integrity-checks the converted sequence and packing bundles automatically. validation-results/ contains a tested design, packed PDB, and numerical reports. See DOWNLOADS.md for model-hosting details.
There is no default mock model and no heuristic substitute for the learned packer. Missing or incompatible model assets produce errors. Synthetic inference backends appear only in tests, through explicit dependency injection.
| Area | Implementation |
|---|---|
| Input | PDB parser; flat-coordinate and residue-object adapters; multiple chains, residue numbers, insertion codes, alternate conformers, ligand elements and masks |
| Design | Ligand context, optional fixed-residue side-chain context, chain/position selection, fixed residues, global/local amino-acid biases and omissions |
| Sampling | Incremental autoregressive decoding, weighted tied positions, homooligomer ties, seeded sampling, explicit decoding orders, multiple temperatures, streaming results |
| Scoring | Autoregressive sequence scores, per-residue distributions, unconditional and target-last conditional probabilities, ligand-interface scores |
| Packing | Separate learned side-chain model, iterative denoising, von Mises mixture sampling, optional fixed chi angles, all-atom heavy-atom reconstruction |
| Output | Typed arrays, per-chain sequences, FASTA and PDB writers, packed atom14 coordinates and masks |
| Deployment | ESM and TypeScript declarations, WASM/WebGPU backend, integrity-checked assets, cancellation, disposal, browser/worker/Node examples |
Side-chain packing is optional for sequence design. Fixed side-chain context is also distinct from packing: it uses native atoms supplied in the input structure. Mutable residues' native side chains are excluded from that context. Neither operation requires Python, ProDy, an installed OpenFold package, a side-chain service, or a backend inference server at runtime.
Use Node 20 or later and Python 3.11 or 3.12 for conversion; this release was validated with Python 3.12.14 and Node 24.19.0. Python is needed only for conversion and reference validation.
npm ci
npm run build
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
python -m pip install -r requirements-export.txt
python tools/export.py --defaults
npm run assets--defaults builds the default ligandmpnn_v_32_010_25 sequence model and ligandmpnn_sc_v_32_002_16 learned packer. --all-ligand builds all four official LigandMPNN sequence checkpoints plus the packer. To convert only the sequence model:
python tools/export.py --model ligandmpnn_v_32_010_25The included upstream.lock.json pins revision 26ec57ac976ade5379920dbd43c7f97a91cf82de and the SHA-256 hashes of the upstream sources and all five checkpoints. package-lock.json pins the installed JavaScript dependencies. Conversion reuses cached files and enforces the lock; a deliberate upstream upgrade requires a new lock path. The original selection was resolved from upstream, then tested; it is not an independent security audit.
If native Python ONNX Runtime is unavailable, validate against the deployment runtime directly:
python tools/export.py --all-ligand --validation-backend wasmThis is the conversion route executed for this release. It uses the installed, pinned onnxruntime-web in Node, compares every graph against PyTorch with unchanged tolerances, and records the actual validation backend in each bundle.
For an existing trusted checkout/checkpoint, no source or checkpoint download is needed:
python tools/export.py --model my_ligand_model --family ligand_mpnn \
--upstream /path/to/LigandMPNN --checkpoint /path/to/checkpoint.pt \
--lock my-model.lock.jsonStrict checkpoint loading is the default. --trusted-pickle is an explicit escape hatch for independently trusted legacy checkpoints only. Downloaded upstream Python is executable code; review its provenance before running it.
The exporter compares the rewritten graph against upstream PyTorch, checks the ONNX model, and compares the selected ONNX runtime's outputs at multiple lengths before writing the final manifest. A failed comparison leaves no usable manifest. It does not silently weaken tolerances or switch to random weights.
By default, modelManifestURL() resolves one of the five converted bundles from an immutable revision of MurrellLab/webports. The runtime fetches manifest.json and only the files listed there, then verifies byte lengths and SHA-256 hashes before creating graph sessions. Reference files such as parity.json, geometry-reference.json, and validation.json are hosted for validation but are not fetched during normal inference.
To self-host, serve generated model directories as /models/<checkpoint>/... and call modelManifestURL(checkpoint, '/models/'). Serve the contents of public/ort/ as /ort/; npm run assets copies WASM and loader files from the installed, pinned ONNX Runtime version. Keep the ONNX Runtime JavaScript and these files together when upgrading.
For a consumer project, install this repository locally (npm install /path/to/webLigandMPNN) or build an npm tarball with npm pack. The package name is ligandmpnn-web; it is not claimed to be published to npm. Repository-only conversion and validation tools are not part of the smaller npm package.
The following is application/worker library code; use an existing ESM-compatible bundler.
import {
LigandMPNN, parsePDB, modelManifestURL, writeFASTA, writePDB,
} from 'ligandmpnn-web';
export async function design(pdbText: string, signal?: AbortSignal) {
const model = await LigandMPNN.load(modelManifestURL(), {
backend: 'auto', // 'wasm', 'webgpu', or 'auto'
wasmPaths: '/ort/',
numThreads: 1,
onFallback: reason => console.warn(reason),
signal,
});
try {
const structure = parsePDB(pdbText);
const prepared = await model.prepare(structure, { signal });
const result = await prepared.sample({
seed: 42,
temperature: 0.1,
omitAminoAcids: 'C',
signal,
});
return {
result,
fasta: writeFASTA(result.sequence, 'ligand-conditioned design'),
backbonePDB: writePDB(structure, { sequence: result.sequence }),
};
} finally {
await model.dispose();
}
}Keep a loaded model alive across requests in a production integration instead of reloading it for every sequence. PreparedProtein.dispose() releases its cached encodings; model.dispose() disposes all prepared structures and graph sessions. examples/worker.ts shows reuse, progress, cancellation, and error messages without introducing an application framework.
Numeric selectors are zero-based indices into the parsed structure. String selectors such as A23 and B42D are PDB chain/residue/insertion identities. Objects such as { chain: 'A', number: 23 } avoid ambiguity. Unknown or ambiguous selectors throw.
const prepared = await model.prepare(structure, {
useAtomContext: true,
useSideChainContext: true,
});
// These example identities must exist in the input structure.
const result = await prepared.sample({
seed: 0, // Zero is a valid deterministic seed.
designChains: ['A', 'B'],
fixedPositions: ['A23', 'B23'],
tiedPositions: [{ positions: ['A42', 'B42'], weights: [0.5, 0.5] }],
biasAminoAcids: { W: -1, Y: 0.3 },
biasByResidue: [{ position: 'A50', bias: { H: 1.5 } }],
omitByResidue: [{ position: 'B50', aminoAcids: 'CP' }],
tiedConstraintMode: 'intersection',
temperature: 0.2,
});Fixed residues retain native sequence identities. Tied groups containing a fixed residue are pinned to its identity; conflicting fixed identities throw. Group weights are summed as supplied, not normalized. tiedConstraintMode: 'upstream' is the default and uses the last member's biases/omissions; 'intersection' intersects allowed sets and averages member biases. See compatibility details, especially fixed/tied behavior.
With useSideChainContext, the encoding is recomputed when the design mask changes. Preparing with this option alone does not expose every native side chain: only residues fixed by the subsequent sampling/scoring selection supply side-chain context.
const results = await prepared.sampleMany({
seed: 7,
numSequences: 4, // Four sequences per temperature.
temperatures: [0.1, 0.2], // Eight results in total.
});
for await (const result of prepared.sampleStream({ numSequences: 8, seed: 7 })) {
consume(result);
}
const score = await prepared.score(results[0].sequence, {
decodingOrder: results[0].decodingOrder,
});
const unconditional = await prepared.unconditionalProbabilities(); // [N,21]
const conditional = await prepared.conditionalProbabilities(results[0].sequence, {
positions: [0, 1], // Unrequested rows are NaN.
});Pass the same fixed/design selection when rescoring a sample that used fixed-side-chain context. Conditional scoring decodes each requested target last, conditioning on the other sequence identities. It requires a full decoder pass per requested position; unconditional scoring requires one pass. Multiple-sequence sampling reuses encodings but currently processes samples serially, not as a GPU batch.
score is mean negative log likelihood over selected valid residues; lower is better. globalScore includes all valid residues. ligandScore uses selected residues whose inferred Cβ is within the configured cutoff (default 8 Å) of a ligand heavy atom. confidence = exp(-score) is a model sequence-likelihood summary, not a folding, binding-affinity, or experimental-confidence prediction. An empty selection/interface returns null, not a misleading zero score.
import { LigandMPNNPacker, modelManifestURL } from 'ligandmpnn-web';
const packer = await LigandMPNNPacker.load(
modelManifestURL('ligandmpnn_sc_v_32_002_16'),
{ backend: 'auto', wasmPaths: '/ort/', numThreads: 1 },
);
try {
const packed = await packer.pack(structure, {
sequence: result.sequence,
seed: 43,
numDenoisingSteps: 3,
numSamples: 10,
encoderContextAtoms: 25, // Match the default LigandMPNN design+pack flow.
});
consumeAtom14(packed.coordinates, packed.atomMask, packed.atomNames);
if (packed.pdb !== null) consumePDB(packed.pdb);
} finally {
await packer.dispose();
}Standalone packing defaults to the packer checkpoint's context count (16); its decoder always uses that learned fixed context width. A larger encoderContextAtoms, such as 25, is supported for the encoder. There is no additional side-chain package to install in the browser: geometry tables are produced from upstream OpenFold constants during conversion, and rigid-frame/chi reconstruction is implemented in TypeScript.
By default every valid residue is repacked, even when a design selection is supplied. To retain fixed native chi angles, explicitly use repackEverything: false with fixedPositions, designPositions, or designChains. Fixed residues must keep their native amino-acid identities and supply the atoms needed to measure their chi angles; missing fixed atoms throw instead of becoming invented angles. The reconstruction uses idealized heavy-atom geometry, so preserving chi angles does not promise byte-identical Cartesian coordinates.
The returned PDB contains packed protein heavy atoms and retained ligand atom records. Multi-character chain IDs or out-of-range PDB identities produce pdb: null; typed arrays remain available. Arbitrary malformed/overflowing numeric PDB fields still throw. Packer B-factor fields encode per-chi log density, with the upstream fixed-side-chain sentinel, not experimental B factors. This is learned packing, not force-field relaxation, clash minimization, hydrogen addition, or ligand optimization.
node examples/node.mjs input.pdb design --packThis writes design.fasta, a backbone-only design.pdb, and design.packed.pdb. Omit --pack to use only the sequence model. Local-file loading uses tools/read-bundle.mjs to create an InMemoryBundle; browser/HTTP loading accepts a manifest URL. Node file: URLs are deliberately not treated as HTTP fetches.
WASM defaults to one thread. WebGPU requires a supporting browser and secure context; integrity verification also requires Web Crypto. More than one WASM thread in a browser requires cross-origin isolation. Set the corresponding server headers and make all relevant assets compatible with those policies; see deployment notes.
backend: 'auto' falls back to WASM if WebGPU is unavailable or graph-session creation fails. Explicit 'webgpu' fails in that situation. ONNX Runtime can still execute unsupported individual operators on WASM within a WebGPU session; the API does not claim every operator runs on the GPU. Tensors are currently read back to CPU between graph calls. No end-to-end WebGPU speedup or device-size limit has been measured here.
npm test
npm run test:python
node tools/structure-validate.mjs
python tools/structure_reference.py
# After successful checkpoint conversion:
npm run test:parity
python -m pip install -r requirements-browser.txt
python -m playwright install chromium
python tools/browser_validate.py --backend wasm --report wasm-report.json
python tools/browser_validate.py --backend webgpu --headed --report webgpu-report.jsonThe WebGPU test fails when an adapter is unavailable; it does not report a skipped GPU test as a pass. A hardware-backed GPU test is a separate release gate. The browser harness is a minimal test page, not a shipped web app. See VALIDATION.md for what each gate proves and does not prove.
This is an inference library, not a byte-for-byte replacement for upstream command-line file naming or NPZ outputs. It has no training loop, mmCIF parser, structure viewer, or force-field code. Another parser can feed fromResidues/fromCoordinates. GPU-resident decoder state, batched sampling, FP16/quantized models, and benchmarking are not implemented. Stable neighbor tie-breaking, RNG streams, and strict input/error handling differ from PyTorch; equal numeric seeds do not promise the same sampled sequences across implementations.
Treat returned structure/geometry/manifest arrays as read-only. Input adapters copy caller arrays, but JavaScript typed arrays exposed for interoperability are not deeply frozen. The default 10,000-residue validation cap is not a tested capacity guarantee. Packing in particular has substantial neighbor-pair feature memory requirements.
src/ contains the library; dist/ contains compiled ESM and declarations. tools/ contains model conversion and real-runtime validation. tests/ contains source/unit/numerical tests. examples/ contains integration modules, not an application. docs/ documents the graph contract, API, compatibility choices, and validation record.
The new library code is MIT-licensed. Upstream LigandMPNN notices are retained, and exported OpenFold-derived geometry includes the applicable Apache-2.0 notices. See THIRD_PARTY_NOTICES.md, LICENSE, and vendor/.