A PyTorch-based scaling and merging library for serial crystallography (SX)
In serial crystallography every reflection is seen once per crystal, with an unknown partiality, incident intensity and crystal volume. Established programs scale each crystal with a few parameters and average. TorchSX instead trains one neural network per dataset that predicts, for every observation, a scale factor and a scatter distribution from that observation's crystal- and image-level context. Merging is a posterior integral rather than an average: each merged amplitude and its uncertainty are moments of the posterior of the reflection's intensity. The network is trained on the marginal likelihood of the data.
TorchSX starts from integrated CrystFEL .stream files and writes merged MTZs.
We are working on integration with dials and deep integration of Probixi.
TorchSX is under active development, if you find something behaving unexpectedly please create an issue on GitHub.
Refinement and difference maps are done with TorchRef, or any other program that reads amplitudes.
Install
pip install torchsx
Python ≥ 3.10. This brings TorchRef 0.7, torch, triton, gemmi and
torchsx-native, the compiled .stream reader for your platform. TorchSX is intended to be run on a GPU, while performance and memory requirements should be quite low, it relies on triton kernels for core algorithms. We have not investigated CPU runtime or even functionality.
From a checkout, build the native package first (needs Rust, see
native/README.md):
pip install ./native, then pip install -e ..
Usage
torchsx.train indexed.stream --spacegroup 'P 63' --dmin 2.0 -o run/
This trains the network (400 updates by default) and merges the data with it.
run/ then contains:
run/
├── merged.mtz merged amplitudes from all crystals
├── merged_half1.mtz the same from one half of the crystals
├── merged_half2.mtz ... and from the other half
├── merged.metrics.json CC1/2 and R_split between the two halves
├── best.pt the network at the lowest validation loss; it made the merges
├── last.pt the network after the last update
├── prepared.pt the parsed observations, reused by later runs on the same input
├── run.json the resolved settings
├── training_summary.json updates, best step and run time
└── metrics.jsonl training log: one line per update and per validation
Each MTZ holds:
| Column | MTZ type | Content |
|---|---|---|
H, K, L |
H | Miller index; Friedel mates merged |
F-obs |
F | posterior mean of the amplitude |F| |
SIGF-obs |
Q | posterior standard deviation of the amplitude |
R-free-flags |
I | refinement free set: 0 = free (5 %), 1 = work |
There are no intensity columns. F-obs is already the posterior estimate of
the amplitude, so it must not go through French–Wilson again; refinement
programs use it as it is. With --no-merge-friedel the mates stay apart and the
files carry F-obs(+), SIGF-obs(+), F-obs(-) and SIGF-obs(-).
--free-from earlier.mtz takes the free set from an existing file.
The two halves split the crystals, not the training: both are merged with the same network. For an independent comparison, train two runs on disjoint sets of patterns.
Light and dark
torchsx.train --split dark=dark.stream --split light=light.stream \
--spacegroup 'P 63' --dmin 2.0 -o run/
One network is trained on both states, and each state is merged on its own:
merged_dark.mtz and merged_light.mtz, each with its two halves. A single
stream with a CrystFEL custom-split file works too (--split-file).
Compared with Partialator
On the bacteriorhodopsin (bR) data of Weinert et al.1:
Rfree of models refined from one starting model against dark merges of increasing size. Each size was merged as two independent halves: points show their mean, bars their range. Rfree is computed on the same reflections for both programs at each size; refinement used TorchRef with one protocol for all merges.
Light-minus-dark difference maps from two independent halves of the patterns (7,800 per state and half), each merged on its own and phased with the dark model. Shown: the end of helix E, the EF loop and helix F of the dark model (grey) and of the open state 6RPH1 (orange). The number under each program is the correlation of its two maps over helix F.
Other commands
torchsx.merge run/best.pt more.stream -o merged.mtzmerges new or additional data with a trained network.torchsx.merge-stats A/merged.mtz B/merged.mtz -o stats/compares merges: completeness, CC1/2, R_split and, for light/dark pairs, the information in ΔF (docs/merge_stats.md).torchsx.observation-stats indexed.stream --spacegroup 'P 63' -o figures/plots the raw observations: intensity distributions, skewness, examples (docs/stats.md).
All options, gain and measurement settings, caching and reproducibility are in the command-line reference.
In Python, torchsx.SFXStream.read('indexed.stream', spacegroup='P 63') reads a
stream with its geometry and measurement settings.
References
- Weinert, T. et al. Proton uptake mechanism in bacteriorhodopsin captured by serial synchrotron crystallography. Science 365, 61–65 (2019). doi:10.1126/science.aaw8634
- Seidel, H.-P., Standfuss, J. & Weinert, T. TorchRef: an open-source PyTorch framework for crystallographic refinement. J. Appl. Cryst., accepted (2026).
License
MIT. See LICENSE.
Metadata
Release files for torchsx 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| torchsx-0.1.0.tar.gz | 392.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| torchsx-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 703.3 kB
Release files / torchsx-0.1.0.tar.gz
| Download URL | torchsx-0.1.0.tar.gz |
|---|---|
| Size | 392.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9fda7dfbac1d55231cd856061a3d2d30719ba7b2cec856d112bc60de5175829c
|
|
BLAKE2b-256 checksum How to use checksums |
83ea5779386d1fb601a4b661d2c3649fd0d146742e24262f7151bfa58e0afa0e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / torchsx-0.1.0-py3-none-any.whl
| Download URL | torchsx-0.1.0-py3-none-any.whl |
|---|---|
| Size | 311.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0317d2c0c5b8e396217db657a5f79e8c91742d57414d51b6f9760d3c3edc8e6b
|
|
BLAKE2b-256 checksum How to use checksums |
e92c5aaf2ef114977e92fdf19dfb03be192bb228f501ba35274ac91d57846694
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency log