Skip to content

Grid fitting#18

Closed
IVinterbladh wants to merge 9 commits into
grid-fittingfrom
master
Closed

Grid fitting#18
IVinterbladh wants to merge 9 commits into
grid-fittingfrom
master

Conversation

@IVinterbladh

Copy link
Copy Markdown
Collaborator

A new method for fitting the parameters c1 and c2 is added - an exhaustive grid search method.
The possibility to set specific boundaries for all fit methods is added.
For grid fit number of grid points can be chosen when running Pripps.

New updated atomic form factors for amino acids and martini beads are added

Python code updated and scripts changed to fit with latest changes

mlund and others added 9 commits June 18, 2026 13:54
Add an optional `gpu` cargo feature providing a wgpu/WGSL implementation
of the Direct Fourier transform (vacuum path) for fast trajectory
averaging. The kernel evaluates F(q) = Σ f_i·exp(i q·r_i), |F(q)|² in f32
with one workgroup per q-vector and a shared-memory reduction over atoms;
static q-vectors and atom ids stay device-resident across frames.

When built with the feature, `direct` runs on the GPU by default and
`--cpu` forces the CPU path; it falls back to the CPU on an active solvent
model or when no adapter is available. Degenerate sizes and device-side
failures return errors rather than panicking.

Measured ~15x over the rayon CPU path on an Apple M4 (88,400 atoms,
1300 q-vectors), matching the CPU f64 reference to <5e-6 relative.

Also add offset-scan and per-frame timing logs to trajectory averaging.
Time trajectory averaging against the number of frames actually consumed
rather than the offset-table length, which overstates frames (and skews
ms/frame) if XtcIterator stops early on a frame read error.

Document why the GPU q-magnitudes stay f64 (form factors and q-grid match
the CPU path; only the in-shader phase is f32).
Note the optional `gpu` build feature: GPU-accelerated `direct` scheme
for trajectory averaging, automatic CPU fallback, and the `--cpu` opt-out.
Add GPU (wgpu) Direct Fourier transform and trajectory timing
Form-factor definitions can now be composed from several YAML files.
Repeat --atomfile (or pass several paths), or give the Python Pripps
constructor a list. Files are merged in order; on a duplicate species the
last file wins and a warning is logged. Merging happens before symbolic
resolution, so an !Alias/!United/!Linear in one file may reference a
species defined in another.

- formfactor.rs: split read_formfactors into parse_specs + finalize_specs;
  add read_formfactors_merged.
- lib.rs: add Pripps::from_yaml_files; from_yaml delegates via from_formfactors.
- cli.rs: --atomfile becomes Vec<PathBuf> (repeatable, order-preserving).
- pripps-py: Pripps(...) accepts a str or list[str].

Split gaussian_atoms.yaml into an atomic/united-atom-only table: drop the
20 coarse-grained residue beads (byte-identical duplicates of
poly_amino_acid.yaml) and 8 unused residue-level aliases, and rewrite the
header. Residue-bead consumers load poly_amino_acid.yaml (or merge it with
the atomic file). This resolves the mixed-representation file and
supersedes the rename-only approach in #11.

Closes #7
- Rename gaussian_atoms.yaml -> pepsi_atoms.yaml and document provenance:
  atomic five-Gaussians are Waasmaier & Kirfel (1995); united-atom groups
  and K=4π solvent convention are PepsiSAXS (Grudinin et al. 2017). Update
  all references (tests, script, README) and link DOIs inline.
- Trim the README "Merging multiple files" section per review.
- Use tempfile::tempdir() in the merge test to avoid fixed temp-file names
  colliding under parallel test runs.
Merge multiple --atomfile files; split gaussian_atoms.yaml
* Add --num-molecules: effective structure factor S_eff(q)

For a periodic box of N identical molecules (the `direct` scheme,
optionally XTC-averaged), `--num-molecules N` outputs the apparent
structure factor and the at-concentration single-molecule form factor:

  S_eff(q) = |Σ_m F_m(q)|² / Σ_m |F_m(q)|²    (exact, no approximation)
  P(q)     = (1/N) · Σ_m |F_m(q)|²

The box is split into N contiguous equal-size molecules and each
molecule's amplitude F_m is accumulated alongside the self term
Σ_m|F_m|² in DirectTransform::vacuum_intensities. Both use the
box-commensurate q-vectors, so they are PBC-invariant (no unwrapping).
S_eff and P are derived from the self term in Pripps::intensity and
emitted as the CSV columns `p,s_eff` (and the Python .p/.s_eff getters).

P(q) is the at-concentration form factor — for a flexible molecule it
shifts with crowding, unlike the dilute P an experiment divides by.

Guards (fail up front, before any trajectory pass): direct scheme only,
vacuum only, box atom count a multiple of N, and every M-atom block must
share the first block's atom-kind sequence (rejects a count that splits
molecules or doesn't match the box composition). --num-molecules
conflicts with --fit and runs on the CPU even with the gpu feature (the
GPU kernel emits only the total |Σf|²); both are noted. XTC averaging
assumes a fixed box.

* Run --num-molecules on the GPU; tidy and test

Extend the WGSL direct kernel to emit the per-molecule self term
Σ_m|F_m|² alongside the box intensity (one workgroup per q-vector,
threads stride over whole molecules), so the effective structure factor
runs on the GPU instead of falling back to the CPU. Output is two f32
per q-vector; the whole-box path (no --num-molecules) is unchanged.
Route DirectGpu + --num-molecules to the GPU, with the existing CPU
fallback when no adapter is available.

Cleanups: route average_duplicates and the 5-column averager through the
shared average_by_qnorm (removing the triplicated q-binning/sort), and
add an OUT_BYTES_PER_Q constant for the GPU output stride. Rename the
internal Intensity carrier monomer_self -> self_term to match the local
accumulators and drop the stale "monomer" naming.

Tests: GPU-vs-CPU parity for the self term, and a trajectory test pinning
S_eff as the ratio of frame-averaged box intensity to frame-averaged self
term (ratio of averages, not the average of per-frame S_eff).

* Address PR #15 review: guard zero consumed-weight-sum

trajectory_average divides accumulated curves by the consumed weight
sum. read_weights validates the full set, but striding can select a
zero-sum subset of signed weights, which would turn every average into
NaN; reject it explicitly instead.

The companion review note about an empty box (0 atoms) yielding NaN
S_eff is already covered upstream — Structure::from_file rejects
atomless inputs before the --num-molecules guard runs — so no extra
check is needed there; add a regression test and a clarifying comment.

* README: derive the effective structure factor theory

Replace the terse --num-molecules block with a proper derivation
(GitHub LaTeX): per-molecule amplitude, the self/cross split of the box
intensity, the exact P(q) and S_eff(q) definitions, and the decoupling
relation S_eff = 1 + beta(S-1) with the beta-node inversion caveat.
Reference the primary source for the decoupling approximation
(Kotlarchyk & Chen, J. Chem. Phys. 79, 2461 (1983)).

* README: fix inline math rendering on GitHub

GitHub's markdown pairs the underscores in adjacent inline $...$ spans
as emphasis, stripping them and breaking the math (e.g. R_m, S_eff).
Switch inline math to the backtick-protected $`...`$ form, whose
contents are immune to markdown; display blocks are unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants