Skip to main content

IQuana — Small-Angle Neutron Scattering model fitting

IQuana - 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, including files holding several datasets — pick one with load_data(path, dataset=...) by index or name
  • 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)
  • Polydispersity and Custom Models: Size distributions on any polydisperse parameter via set_pd_param(), and your own SasModels plugin models loaded by file path
  • Resolution Control: Choose how instrument smearing is applied — dQ from the data, none, custom pinhole (dq_over_q) or slit — with set_resolution()
  • Data Weighting: Choose which σ divides each residual — the data's dI, √|I|, |I| or none — with set_weighting(), matching SasView's Fit Page; this is also what lets the bumps engine and fit_bayesian() fit data without uncertainties
  • 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
  • Theory Preview: See the model at the current parameters before fitting — plot_model() for data, curve and residuals, calculate() for the intensities on any Q grid, and compare() to overlay several parameter sets
  • Interactive Visualization: Automatic plotting of data, fitted model, and residuals with Plotly
  • Simultaneous Fitting: Fit several datasets together with shared or related parameters via MultiFitter — contrast variation, temperature series and multi-configuration data, with per-dataset models, Q ranges and resolution, plus constraints such as '0.8 * h2o.scale'
  • 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
  • Fit-Quality Reporting: Reduced χ² alongside raw χ², point and parameter counts, covariance/correlation matrices, a convergence verdict and a warning when a fitted parameter rests on a bound — all via fitter.get_fit_report(), which renders as a table in a notebook, as text, or as Markdown
  • Result Export: Save fitted parameters and curves to CSV files
  • Save, Reload and Share: Store the whole analysis (data source, model, parameters, links, resolution, result) as JSON with save_analysis() / IQuana.load_analysis(), or write a single shareable HTML report with report()

Installation

# Clone the repository
git clone https://github.com/ai4se1dk/IQuana.git
cd IQuana

# Install the package
pip install -e .

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

# The Jupyter stack is optional - only needed to run notebooks/
pip install -e ".[notebooks]"
# Clone the repository
git clone https://github.com/ai4se1dk/IQuana.git
cd IQuana

# 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 iquana 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 iquana import IQuana

# Create fitter instance
fitter = IQuana()

# 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()

# Check the starting values against the data before fitting
fitter.plot_model()

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

# Visualize results
fitter.plot_results(show_residuals=True)

# Judge the fit: reduced chi-squared, correlations, bound checks
report = fitter.get_fit_report()
print(report)

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

# Save the whole analysis, and reopen it later or elsewhere
fitter.save_analysis('my_analysis.json')
fitter = IQuana.load_analysis('my_analysis.json')

# One shareable document: settings, tables and the plot
fitter.report('my_analysis.html')

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()
  • Structure factors: queried from sasmodels at runtime — call get_structure_factors() for the full list (currently includes hardsphere, hayter_msa, squarewell, stickyhardsphere, two_yukawa).
  • 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 = IQuana()
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.

Simultaneous Fitting

Fit several datasets at once, sharing the parameters they have in common:

from iquana import MultiFitter

fit = MultiFitter()
fit.add('h2o', 'contrast_h2o.xml', model='sphere')
fit.add('d2o', 'contrast_d2o.xml', model='sphere')

for name in ('h2o', 'd2o'):
    fit[name].set_param('radius', value=45, min=10, max=100, vary=True)
    fit[name].set_param('scale', value=0.02, min=0.001, max=0.1, vary=True)
    fit[name].set_param('background', value=0.01, min=0, max=0.1, vary=True)

fit.share('radius', 'scale')             # one radius and scale across both curves
fit.constrain('h2o.sld_solvent', -0.56)  # known solvent contrasts
fit.constrain('d2o.sld_solvent', 6.34)

fit.describe()                 # datasets, relationships, free-parameter count
result = fit.fit()
print(fit.get_fit_report())
fit.plot_results()             # one data/fit + residual panel per dataset
fit.save_results('contrast_results')

Parameters are addressed as dataset.parameter. Beyond share(), use link_params('cyl.radius', to='sph.radius') to make one parameter follow another, and constrain('d2o.scale', '0.8 * h2o.scale') for a fixed value or an arithmetic relationship. Each dataset keeps its own model, Q range, mask and resolution — nothing is merged or interpolated — and a shared parameter costs exactly one degree of freedom.

See the Simultaneous Fitting guide for choosing what to share, how constraint bounds are enforced, and what dataset weights mean for the reported uncertainties.

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

Metadata

Release files for iquana 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for iquana 0.4.0
File Size Uploaded Provenance
iquana-0.4.0.tar.gz 306.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for iquana 0.4.0
File Interpreter ABI Platform
iquana-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 528.7 kB

Release files / iquana-0.4.0.tar.gz

Download URL iquana-0.4.0.tar.gz
Size 306.1 kB
Tags Source
SHA-256 checksum
How to use checksums
8f7c4969a76701176644ecd60a5aedf1759a37c1fbe2815ffe6edac3e55dc0e1
BLAKE2b-256 checksum
How to use checksums
3c484dddf3cf159848f287da28a95c8024328b29749e2bf8add6f6120652ae6e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 23, 2026.

Transparency log

Release files / iquana-0.4.0-py3-none-any.whl

Download URL iquana-0.4.0-py3-none-any.whl
Size 222.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3cb6c56f0f294815ff7b35d9b3ae06f86988e33e4454d90d743eef050409f18f
BLAKE2b-256 checksum
How to use checksums
4c934d0c47ed51da8373c74ce59acb95658ee7ad39095be1d8ff1392b670ec38
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 23, 2026.

Transparency log

Security for iquana 0.4.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for iquana-0.4.0.tar.gz

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 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files Full provenance

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