Skip to main content

SANS Model Fitter

Tests Docs PyPI badge Docs badge Codacy Badge

A flexible, model-agnostic Python template for fitting Small-Angle Neutron Scattering (SANS) data using the SasModels library.

Features

  • Model-Agnostic Design: Works with any model from the SasModels library (cylinder, sphere, core_shell, etc.)
  • Multiple Fitting Engines: Supports both BUMPS (default) and LMFit optimization engines
  • Flexible Data Loading: Reads CSV, XML, and HDF5 formats via sasdata
  • Example Data Built In: A curated set of ready-to-fit datasets plus a simulator that generates data — examples.load_fitter('silica_spheres') or examples.simulate('sphere', radius=50)
  • Q-Range Restriction: Fit only a chosen [qmin, qmax] window (e.g. trim beam-stop or background-dominated points)
  • Dataset Arithmetic: Add, subtract, multiply, and divide datasets (or scale by constants) with propagated uncertainties via data_ops — e.g. background subtraction and transmission correction before fitting
  • User-Friendly Parameter Management: Easy-to-use interface for setting parameter values, bounds, and fitting flags
  • Interactive Visualization: Automatic plotting of data, fitted model, and residuals with Plotly
  • Bayesian Analysis: Posterior sampling with BUMPS DREAM (MCMC) plus corner, marginal, predictive-band, correlation, and trace plots
  • P(r) Inversion: Model-free pair distance distribution analysis (indirect Fourier transform) via pr_inversion — D_max exploration, automatic regularization/term selection, and Rg/I(0)/positivity diagnostics
  • Result Export: Save fitted parameters and curves to CSV files

Installation

Option 1: Using pip (recommended for users)

# Clone the repository
git clone https://github.com/ai4se1dk/SANS-fitter.git
cd SANS-fitter

# Install the package
pip install -e .

# Or install with development dependencies
pip install -e ".[dev]"

Option 2: Using Pixi (recommended for development)

# Clone the repository
git clone https://github.com/ai4se1dk/SANS-fitter.git
cd SANS-fitter

# Install dependencies with Pixi
pixi install

# Run tests
pixi run test

# Run demo notebook
pixi run run-demo

Quick Start

No data of your own yet? Start from a bundled example:

from sans_fitter import examples

examples.describe()                              # see what's available
fitter = examples.load_fitter('silica_spheres')  # data + model + parameters
result = fitter.fit()
fitter.plot_results()

Or simulate data, so you can check the fit recovers it:

data = examples.simulate('sphere', radius=50, noise=0.02, seed=0)
data.truth['radius']    # 50.0

See the Example Data guide for the full collection.

With your own data:

from sans_fitter import SANSFitter

# Create fitter instance
fitter = SANSFitter()

# Load your data
fitter.load_data('my_sans_data.csv')

# Set the model (any model from SasModels!)
fitter.set_model('cylinder')

# Optionally restrict the Q range used for fitting
fitter.set_q_range(qmin=0.01, qmax=0.3)

# View initial parameter values
fitter.get_params()

# Configure parameters for fitting
fitter.set_param('radius', value=20, min=1, max=100, vary=True)
fitter.set_param('length', value=400, min=10, max=1000, vary=True)
fitter.set_param('scale', value=0.1, min=0, max=1, vary=True)
fitter.set_param('background', value=0.01, min=0, max=1, vary=True)

# View current parameters
fitter.get_params()

# Perform the fit (using BUMPS by default)
result = fitter.fit(engine='bumps', method='amoeba')

# Visualize results
fitter.plot_results(show_residuals=True)

# Save results
fitter.save_results('fit_results.csv')

Switching Models

The fitter is completely model-agnostic. Simply load a different model:

# Try with a sphere model instead
fitter.set_model('sphere')
fitter.get_params()  # See different parameters!

fitter.set_param('radius', value=25, min=5, max=100, vary=True)
result = fitter.fit()

Switching Fitting Engines

# Use BUMPS (default)
result = fitter.fit(engine='bumps', method='amoeba')

# Or use LMFit
result = fitter.fit(engine='lmfit', method='leastsq')

Working with Structure Factors

Combine any SasModels form factor with an interaction model to capture correlated systems.

fitter.set_model('sphere')

# Apply a structure factor (creates sphere@hardsphere product model)
fitter.set_structure_factor('hardsphere', radius_effective_mode='link_radius')

# Inspect linked parameters and run the fit as usual
fitter.get_params()
result = fitter.fit()

# Remove the structure factor to go back to the pure form factor
fitter.remove_structure_factor()
  • Supported structure factors: hardsphere, hayter_msa, squarewell, stickyhardsphere.
  • Radius handling: use radius_effective_mode='link_radius' to keep radius_effective equal to the form-factor radius, or leave the default unconstrained to fit it independently.
  • State helpers: get_structure_factor() returns the active structure factor so notebooks/scripts can branch as needed.

Combining Models

Describe a dataset with several models at once — for example a low-Q diffuse feature plus a high-Q peak — fitted simultaneously against one dataset:

fitter = SANSFitter()
fitter.load_data('data.csv')

# Combine models; parameters get friendly per-model prefixes
fitter.set_models('dab', 'peak_lorentz')
fitter.set_param('dab_cor_length', value=50, min=1, max=500, vary=True)
fitter.set_param('peak_lorentz_peak_pos', value=0.1, min=0.01, max=0.5, vary=True)
fitter.set_param('background', value=0.001, min=0, max=0.1, vary=True)

result = fitter.fit(engine='bumps')

# See which feature each model accounts for
fitter.plot_results(show_components=True)

The combined intensity is

$I(q) = \text{scale} \cdot \sum_i \text{scale}_i \cdot I_i(q) + \text{background}$

so the global scale and background are shared by all components natively, while each component has its own <model>_scale.

Custom names (monikers) — for long model names, duplicates, or physics labels:

fitter.set_models(small='sphere', large='sphere', shared=['sld', 'sld_solvent'])
fitter.set_param('small_radius', value=20, vary=True)
fitter.set_param('large_radius', value=200, vary=True)

Sharing parameters — any name in shared=[...] that exists in ≥ 2 components becomes a single unprefixed parameter driving all of them (sld above). Polydispersity configuration stays per-component under the prefixed names.

Component curves — after fitting a '+' mixture, plot_results(show_components=True) overlays one dashed curve per component, each drawn as scale · part_scale · I_part(q) (background shown implicitly in the total).

Advanced: the raw sasmodels expression syntax is also available and keeps sasmodels' native A_/B_ names: fitter.set_model('dab+peak_lorentz'). For after-the-fact or asymmetric sharing, use equality links: fitter.link_params('large_sld', to='small_sld') and fitter.unlink_params('large_sld'). Composite models and parameter links currently require the bumps engine.

Bayesian Analysis

Sample the full posterior distribution of the varying parameters with the DREAM MCMC sampler (built into BUMPS — no extra dependencies):

fitter.set_model('sphere')
fitter.set_param('radius', value=50, min=10, max=200, vary=True)
fitter.set_param('scale', value=0.1, min=0.01, max=1.0, vary=True)

# Run the Bayesian fit (prints point estimates + credible intervals + diagnostics)
result = fitter.fit_bayesian(samples=10000, burn=200)

# Corner plot of the posterior
fitter.plot_posterior_pairs()

# More displays
fitter.plot_param_distribution('radius')      # marginal posterior
fitter.plot_posterior_predictive()            # 95% credible band over the data
fitter.plot_param_correlations()              # correlation heatmap
fitter.plot_trace()                           # MCMC chain traces

# Raw chain access / export
posterior = fitter.get_posterior()
posterior.save_posterior_csv('posterior_chain.csv')

See the User Guide for details.

Available Methods

BUMPS methods:

  • 'amoeba' - Nelder-Mead simplex (default, robust)
  • 'lm' - Levenberg-Marquardt
  • 'newton' - Newton's method
  • 'de' - Differential evolution

LMFit methods:

  • 'leastsq' - Levenberg-Marquardt (default)
  • 'least_squares' - Trust Region Reflective
  • 'differential_evolution' - Global optimizer
  • 'powell', 'nelder', etc.

Demo Notebooks

Design Philosophy

This implementation follows a template pattern where:

  1. The core fitting logic is abstracted into a reusable class
  2. Models are loaded dynamically from SasModels - no hardcoded model assumptions
  3. Parameters are discovered automatically from the model definition
  4. Multiple optimization engines are supported through a unified interface
  5. The user maintains full control over parameter initialization and bounds

Implementation Details

Engine Adapters

The fitter implements adapter patterns for both BUMPS and LMFit:

  • BUMPS: Uses native sasmodels.bumps_model integration
  • LMFit: Uses sasmodels.direct_model.DirectModel with a custom residual function

Parameter Management

Parameters are stored internally with:

  • value: Current/initial value
  • min, max: Bounds
  • vary: Fitting flag
  • description: From model metadata

This allows the fitter to work with any model without prior knowledge of its parameters.

License

BSD 3-Clause License. See LICENSE for the full text.

References

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sans_fitter-0.3.0.tar.gz (119.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sans_fitter-0.3.0-py3-none-any.whl (89.0 kB view details)

Uploaded Python 3

File details

Details for the file sans_fitter-0.3.0.tar.gz.

File metadata

  • Download URL: sans_fitter-0.3.0.tar.gz
  • Upload date:
  • Size: 119.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for sans_fitter-0.3.0.tar.gz
Algorithm Hash digest
SHA256 f16cbdd9ab42a7cc53eb23cf6746157305365dc51554eb1e473975c40399d787
MD5 2369920a16d3c51cbc4e22b8d9538f6c
BLAKE2b-256 09a84beec5348db64b89c3257049800050c7f736bac18d9cea8ad08cb140797d

See more details on using hashes here.

Provenance

The following attestation bundles were made for sans_fitter-0.3.0.tar.gz:

Publisher: ci.yml on ai4se1dk/SANS-fitter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sans_fitter-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: sans_fitter-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 89.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for sans_fitter-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 38146c5acb271db5e0da29d965f800fab1c3926e4bda2834c7f0cc5cd46e3b33
MD5 daa43d1fcb0534af155815ea079d17c2
BLAKE2b-256 9e8d570b0c6b82a098508295c3dd9dc1a8826ef2df955b4f6cb181fb3f5d4b3f

See more details on using hashes here.

Provenance

The following attestation bundles were made for sans_fitter-0.3.0-py3-none-any.whl:

Publisher: ci.yml on ai4se1dk/SANS-fitter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

0.2.2

2 files

0.2.0

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page