sassafras 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sassafras-0.5.0/LICENSE +28 -0
- sassafras-0.5.0/PKG-INFO +522 -0
- sassafras-0.5.0/README.md +472 -0
- sassafras-0.5.0/pyproject.toml +54 -0
- sassafras-0.5.0/sassafras/__init__.py +5 -0
- sassafras-0.5.0/sassafras/__main__.py +3 -0
- sassafras-0.5.0/sassafras/_guess_worker.py +39 -0
- sassafras-0.5.0/sassafras/backend.py +607 -0
- sassafras-0.5.0/sassafras/fit.py +303 -0
- sassafras-0.5.0/sassafras/guess.py +475 -0
- sassafras-0.5.0/sassafras/library.py +544 -0
- sassafras-0.5.0/sassafras/ml/SAS-55M-20k_LICENSE.txt +21 -0
- sassafras-0.5.0/sassafras/ml/model.pt +0 -0
- sassafras-0.5.0/sassafras/ml/sans_ml/__init__.py +0 -0
- sassafras-0.5.0/sassafras/ml/sans_ml/catalogue.py +107 -0
- sassafras-0.5.0/sassafras/ml/sans_ml/encode.py +51 -0
- sassafras-0.5.0/sassafras/ml/sans_ml/net.py +59 -0
- sassafras-0.5.0/sassafras/ml/sans_ml/predict.py +89 -0
- sassafras-0.5.0/sassafras/ml/sans_ml/priors.py +514 -0
- sassafras-0.5.0/sassafras/ml/sans_ml/simulate.py +270 -0
- sassafras-0.5.0/sassafras/multi.py +147 -0
- sassafras-0.5.0/sassafras/notebook.py +502 -0
- sassafras-0.5.0/sassafras/server.py +244 -0
- sassafras-0.5.0/sassafras/static/app.css +563 -0
- sassafras-0.5.0/sassafras/static/app.js +1095 -0
- sassafras-0.5.0/sassafras/static/copy.js +213 -0
- sassafras-0.5.0/sassafras/static/favicon-chalked.png +0 -0
- sassafras-0.5.0/sassafras/static/favicon-cooked.png +0 -0
- sassafras-0.5.0/sassafras/static/favicon-girlypop.png +0 -0
- sassafras-0.5.0/sassafras/static/favicon-washed.png +0 -0
- sassafras-0.5.0/sassafras/static/favicon.png +0 -0
- sassafras-0.5.0/sassafras/static/fitting.js +224 -0
- sassafras-0.5.0/sassafras/static/guess.js +100 -0
- sassafras-0.5.0/sassafras/static/index.html +152 -0
- sassafras-0.5.0/sassafras/static/logo-chalked-dark.png +0 -0
- sassafras-0.5.0/sassafras/static/logo-chalked-light.png +0 -0
- sassafras-0.5.0/sassafras/static/logo-cooked-dark.png +0 -0
- sassafras-0.5.0/sassafras/static/logo-cooked-light.png +0 -0
- sassafras-0.5.0/sassafras/static/logo-dark.png +0 -0
- sassafras-0.5.0/sassafras/static/logo-girlypop-dark.png +0 -0
- sassafras-0.5.0/sassafras/static/logo-girlypop-light.png +0 -0
- sassafras-0.5.0/sassafras/static/logo-light.png +0 -0
- sassafras-0.5.0/sassafras/static/logo-washed-dark.png +0 -0
- sassafras-0.5.0/sassafras/static/logo-washed-light.png +0 -0
- sassafras-0.5.0/sassafras/static/multi.js +571 -0
- sassafras-0.5.0/sassafras/static/notebook.js +53 -0
- sassafras-0.5.0/sassafras.egg-info/PKG-INFO +522 -0
- sassafras-0.5.0/sassafras.egg-info/SOURCES.txt +59 -0
- sassafras-0.5.0/sassafras.egg-info/dependency_links.txt +1 -0
- sassafras-0.5.0/sassafras.egg-info/entry_points.txt +2 -0
- sassafras-0.5.0/sassafras.egg-info/requires.txt +25 -0
- sassafras-0.5.0/sassafras.egg-info/top_level.txt +1 -0
- sassafras-0.5.0/setup.cfg +4 -0
- sassafras-0.5.0/tests/test_backend.py +116 -0
- sassafras-0.5.0/tests/test_fit.py +84 -0
- sassafras-0.5.0/tests/test_guess.py +59 -0
- sassafras-0.5.0/tests/test_library.py +123 -0
- sassafras-0.5.0/tests/test_mixture.py +88 -0
- sassafras-0.5.0/tests/test_multi.py +110 -0
- sassafras-0.5.0/tests/test_notebook.py +93 -0
- sassafras-0.5.0/tests/test_server.py +87 -0
sassafras-0.5.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Oliver Hammond
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
sassafras-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sassafras
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Sassafras: Small Angle Scattering Structure- And Form-factor Rapid ASsessment. A SANS/SAXS data viewer with live sasmodels overlays, fitting and IQuana export
|
|
5
|
+
Author: Oliver Hammond
|
|
6
|
+
Maintainer: SasView developers
|
|
7
|
+
License-Expression: BSD-3-Clause
|
|
8
|
+
Project-URL: Homepage, https://github.com/SasView/sassafras
|
|
9
|
+
Project-URL: Source, https://github.com/SasView/sassafras
|
|
10
|
+
Project-URL: Issues, https://github.com/SasView/sassafras/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/SasView/sassafras/blob/main/CHANGELOG.md
|
|
12
|
+
Project-URL: DOI, https://doi.org/10.5281/zenodo.24041504
|
|
13
|
+
Keywords: small-angle scattering,SANS,SAXS,sasmodels,sasview,fitting,visualisation
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Framework :: Jupyter
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
License-File: sassafras/ml/SAS-55M-20k_LICENSE.txt
|
|
30
|
+
Requires-Dist: sasmodels>=1.0.7
|
|
31
|
+
Requires-Dist: sasdata>=0.9
|
|
32
|
+
Requires-Dist: numpy
|
|
33
|
+
Provides-Extra: fit
|
|
34
|
+
Requires-Dist: bumps>=0.9; extra == "fit"
|
|
35
|
+
Provides-Extra: notebook
|
|
36
|
+
Requires-Dist: iquana>=0.5; extra == "notebook"
|
|
37
|
+
Requires-Dist: jupyterlab; extra == "notebook"
|
|
38
|
+
Provides-Extra: guess
|
|
39
|
+
Requires-Dist: torch>=2.5; extra == "guess"
|
|
40
|
+
Requires-Dist: scipy; extra == "guess"
|
|
41
|
+
Requires-Dist: iquana>=0.5; extra == "guess"
|
|
42
|
+
Provides-Extra: library
|
|
43
|
+
Requires-Dist: pyarrow; extra == "library"
|
|
44
|
+
Provides-Extra: test
|
|
45
|
+
Requires-Dist: pytest; extra == "test"
|
|
46
|
+
Requires-Dist: bumps>=0.9; extra == "test"
|
|
47
|
+
Provides-Extra: all
|
|
48
|
+
Requires-Dist: sassafras[fit,guess,notebook]; extra == "all"
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# Sassafras
|
|
52
|
+
|
|
53
|
+
[](https://doi.org/10.5281/zenodo.24041504)
|
|
54
|
+
|
|
55
|
+
**Small Angle Scattering Structure- And Form-factor Rapid ASsessment**
|
|
56
|
+
|
|
57
|
+
A lightweight SANS/SAXS analysis tool. Load reduced 1D data
|
|
58
|
+
in any format sasdata reads, pick a sasmodels form factor (optionally × a
|
|
59
|
+
structure factor), and drag sliders to overlay the model on the data —
|
|
60
|
+
chi-by-eye, with live χ² and residuals. With the **Fit** toggle off there is
|
|
61
|
+
no optimiser; the only automatic step is a one-click linear solve for scale
|
|
62
|
+
and background. Switch **Fit** on to refine the chi-by-eye starting point
|
|
63
|
+
with BUMPS (needs the optional `bumps` package).
|
|
64
|
+
|
|
65
|
+
It is a generalised local browser GUI with no external JavaScript, working with
|
|
66
|
+
any model and any (including no) data file.
|
|
67
|
+
|
|
68
|
+
Sassafras is part of the [SasView](https://www.sasview.org) ecosystem,
|
|
69
|
+
alongside sasmodels, sasdata and [IQuana](https://github.com/SasView/IQuana).
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install sassafras # viewer: sasmodels, sasdata, numpy
|
|
75
|
+
pip install "sassafras[all]" # + fitting (bumps), IQuana notebooks, Guess (torch)
|
|
76
|
+
python -m sassafras.library fetch # optional: the ~290 MB curve library for Guess
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Extras on their own: `fit` (bumps), `notebook` (IQuana, JupyterLab), `guess`
|
|
80
|
+
(torch, scipy, IQuana). From a clone, `pip install -e ".[all,test]"`.
|
|
81
|
+
|
|
82
|
+
## Run
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
sassafras # or: python -m sassafras
|
|
86
|
+
sassafras run_1234.xml run_1235.h5 -m core_shell_sphere -s hardsphere
|
|
87
|
+
sassafras /data/reduced/ # load every readable file in a folder (≤ 50)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A browser tab opens automatically. If it doesn't, open the printed
|
|
91
|
+
`http://127.0.0.1:…` URL. Keep the terminal running, and stop with Ctrl+C or the
|
|
92
|
+
**Stop server** button. Everything runs on your machine, and the server only
|
|
93
|
+
listens on 127.0.0.1.
|
|
94
|
+
|
|
95
|
+
Options: `--port 8765` · `--no-browser` · `-m MODEL` · `-s STRUCTURE_FACTOR` ·
|
|
96
|
+
`--check` (load, compile, evaluate once, print a JSON summary, exit).
|
|
97
|
+
|
|
98
|
+
sasmodels needs a C compiler to build model kernels (Xcode command-line tools
|
|
99
|
+
on macOS). The first use of each model compiles it, which takes about 1–2 s,
|
|
100
|
+
and sasmodels caches the result after that. OpenCL is off by default
|
|
101
|
+
(`SAS_OPENCL=none`); set that variable yourself to change it.
|
|
102
|
+
|
|
103
|
+
## Data
|
|
104
|
+
|
|
105
|
+
* **Drop files** on the page, click to browse, or type a **file or folder
|
|
106
|
+
path** (path loads can later be re-read with ↻, which is useful while
|
|
107
|
+
reduction is still rewriting a file).
|
|
108
|
+
* Formats: whatever sasdata's `Loader` supports — canSAS XML, NXcanSAS
|
|
109
|
+
(`.h5 .hdf5 .nxs`), ASCII/CSV (`.txt .dat .csv`), IGOR `.abs/.cor`, Anton
|
|
110
|
+
Paar `.pdh`, and others. Multi-entry files give one dataset per entry. 2D data
|
|
111
|
+
are skipped with a message.
|
|
112
|
+
* Q is shown in Å⁻¹. sasdata converts nm⁻¹ files on load.
|
|
113
|
+
* Points with Q ≤ 0, non-finite values or dI ≤ 0 are dropped, and the dataset's
|
|
114
|
+
tooltip says so.
|
|
115
|
+
* **Active** dataset (radio button): its Q points and resolution are used for
|
|
116
|
+
the model, and it is the one scored. Pinhole dQ or slit (dxl/dxw) smearing
|
|
117
|
+
is applied by sasmodels exactly as in SasView.
|
|
118
|
+
* **Shown** datasets (colour swatch): drawn for comparison, e.g. sample vs
|
|
119
|
+
buffer, or successive runs.
|
|
120
|
+
* **Model only**: evaluates on a log-spaced grid (Q min, Q max, points in the
|
|
121
|
+
toolbar). You can still show datasets underneath.
|
|
122
|
+
|
|
123
|
+
## Model and parameters
|
|
124
|
+
|
|
125
|
+
* Form factor list = all sasmodels 1D models, grouped by category, with a
|
|
126
|
+
filter box (type, then Enter).
|
|
127
|
+
* S(Q) = hardsphere, hayter_msa, squarewell, stickyhardsphere, …, combined as
|
|
128
|
+
`model@sf` by sasmodels, so you get `radius_effective`, `volfraction`,
|
|
129
|
+
`radius_effective_mode` and, where supported, `structure_factor_mode` (β
|
|
130
|
+
approximation). `radius_effective` is greyed out unless its mode is
|
|
131
|
+
"unconstrained".
|
|
132
|
+
* Every float parameter has an exact-value box, a slider, editable slider
|
|
133
|
+
min/max boxes and a **log** toggle. Initial ranges come from the sasmodels
|
|
134
|
+
defaults and limits. If you type a value outside the slider range, the range
|
|
135
|
+
widens to include it. Values outside sasmodels' hard limits are refused.
|
|
136
|
+
* Choice parameters get a dropdown. Shell counts such as `n` in
|
|
137
|
+
`core_multi_shell` rebuild the parameter list.
|
|
138
|
+
* **PD** opens polydispersity (σ/mean, distribution type; 35 points, 3σ) for
|
|
139
|
+
any polydisperse parameter.
|
|
140
|
+
* Switching model keeps values for parameters with the same name (scale,
|
|
141
|
+
background, sld_solvent, radius, …) but not polydispersity. Each model also
|
|
142
|
+
remembers its own last settings for the session, and the last model and
|
|
143
|
+
values are restored on the next launch in the same browser.
|
|
144
|
+
* **Background guess on load:** when a newly loaded dataset becomes active,
|
|
145
|
+
the background box is set to the mean I of its 3 highest-Q points
|
|
146
|
+
(once per file, so reloading the page keeps any value you've typed since).
|
|
147
|
+
**bg** (Match) re-applies it.
|
|
148
|
+
* **Scale / Scale+bg** (Match): a weighted linear least-squares solve
|
|
149
|
+
over the χ² window, with the model shape fixed. It's for getting the curve
|
|
150
|
+
onto the data before you start adjusting by eye.
|
|
151
|
+
* **Pin** (Curve) keeps the current curve, residuals and χ² as a dashed
|
|
152
|
+
reference to compare against.
|
|
153
|
+
* **Save / Load** (Parameters; JSON: model, values, PD, slider ranges, Q window)
|
|
154
|
+
and **CSV ↓** (Curve; Q, I, dI, model, residual, in-window flag).
|
|
155
|
+
* These buttons sit in a compact bar at the top right of the plot panel, next
|
|
156
|
+
to the χ²/N readout; hover any of them for the full description.
|
|
157
|
+
|
|
158
|
+
## Fitting (optional)
|
|
159
|
+
|
|
160
|
+
Install `bumps` (`pip install "sassafras[fit]"`) and restart
|
|
161
|
+
sassafras. A **Fit** toggle next to **Defaults** in the Parameters panel then shows the fitting controls. With
|
|
162
|
+
it off, or without bumps installed, the page is the plain chi-by-eye viewer.
|
|
163
|
+
|
|
164
|
+
1. Get close by eye: sliders, **Bkg from high Q**, **Match scale + bkg**.
|
|
165
|
+
2. Tick the parameters to vary. A **fit** tick also appears in each open PD
|
|
166
|
+
panel. Ticks are remembered per model.
|
|
167
|
+
3. **The slider's min/max boxes are the fit bounds** (0–1 for PD widths), so
|
|
168
|
+
narrow them to keep the fit physical.
|
|
169
|
+
4. Pick a method and press **Fit**:
|
|
170
|
+
* **Levenberg–Marquardt**: fast, local, with ± from the covariance. The
|
|
171
|
+
default.
|
|
172
|
+
* **Nelder–Mead simplex**: local and robust to rough χ² surfaces.
|
|
173
|
+
* **Differential evolution**: global search within the bounds. Slower.
|
|
174
|
+
* **DREAM**: MCMC, so the ± values come from the posterior. The slowest
|
|
175
|
+
(seconds to minutes).
|
|
176
|
+
5. The fit runs in the background with live step and χ²ᵣ readouts.
|
|
177
|
+
**Cancel** keeps the best point found so far.
|
|
178
|
+
|
|
179
|
+
When it finishes:
|
|
180
|
+
|
|
181
|
+
* the sliders move to the fitted values;
|
|
182
|
+
* each fitted parameter shows its ± 1σ uncertainty (flagged ⚑ if it ended on
|
|
183
|
+
a bound: widen that range and refit);
|
|
184
|
+
* the starting curve is pinned as "before fit";
|
|
185
|
+
* **Undo fit** restores the previous values.
|
|
186
|
+
|
|
187
|
+
The fit uses the same model, resolution (dQ or slit) and χ² window as the
|
|
188
|
+
overlay, through `sasmodels.bumps_model`.
|
|
189
|
+
|
|
190
|
+
Two χ² values are shown side by side. **Fit χ²ᵣ (BUMPS)** is
|
|
191
|
+
Σ((I − model)/dI)² / (N − p), the usual reduced χ². **χ²/N** is the
|
|
192
|
+
fit-agnostic number used everywhere else. Change any value by hand and the
|
|
193
|
+
± values disappear and the fit χ²ᵣ is struck through, because they no longer
|
|
194
|
+
describe the parameters on screen.
|
|
195
|
+
|
|
196
|
+
**Copy** and **Save parameters** include the method, χ²ᵣ, degrees of freedom,
|
|
197
|
+
uncertainties and any parameters at a bound while the fit is current.
|
|
198
|
+
|
|
199
|
+
## Mix (several form factors)
|
|
200
|
+
|
|
201
|
+
Tick **Mix** (Model panel) to sum up to four form factors, e.g. particles plus
|
|
202
|
+
a low-Q power law, or two populations. The form factor and S(Q) selected above
|
|
203
|
+
stay as form factor 1 (the only one that can carry a structure factor); **+
|
|
204
|
+
form factor** adds the next, × removes one, and unticking Mix goes back to a
|
|
205
|
+
single model, keeping form factor 1's values and polydispersity.
|
|
206
|
+
|
|
207
|
+
* This is sasmodels' mixture (`sphere@hardsphere+power_law+cylinder`): one
|
|
208
|
+
global **scale** and **background**, and per-form parameters prefixed `A_`,
|
|
209
|
+
`B_`, `C_`, `D_`. The panel shows form factor 1's parameters by their plain
|
|
210
|
+
names (its own scale as **scale ₁**) and the others with their index
|
|
211
|
+
(**radius ₂**, **power ₂**, …), listed after form factor 1; hover a label
|
|
212
|
+
for the sasmodels name, which is also what fits, reports and JSON use.
|
|
213
|
+
* A newly added form factor's scale is set to about 10% of the rest of the
|
|
214
|
+
model at every Q, so it doesn't swamp the curve; adjust from there.
|
|
215
|
+
* Fitting, Match, Multi (shared/per-dataset/expressions on any of these
|
|
216
|
+
parameters) and the IQuana notebook all work with mixtures. The notebook
|
|
217
|
+
uses IQuana's raw `set_model('sphere@hardsphere+power_law')`, which keeps
|
|
218
|
+
the same `A_`/`B_` names, and `MultiFitter.add(..., model=...)` with the same
|
|
219
|
+
string; IQuana's Bayesian sampler doesn't take mixtures, so a DREAM setting
|
|
220
|
+
exports as simplex.
|
|
221
|
+
* **Guess** suggests single models: applying a suggestion turns Mix off.
|
|
222
|
+
|
|
223
|
+
## Multi (several datasets at once)
|
|
224
|
+
|
|
225
|
+
Tick **Multi** (next to **Fit** in the Parameters panel) to work on every
|
|
226
|
+
ticked dataset together — a contrast series, a concentration series, SANS +
|
|
227
|
+
SAXS — on the same page:
|
|
228
|
+
|
|
229
|
+
* **Members** are the ticked (drawn) datasets. The active (radio) one is the
|
|
230
|
+
one the parameter panel edits; click another radio to edit that dataset.
|
|
231
|
+
Every member's model curve is drawn in its dataset colour, the residual plot
|
|
232
|
+
shows them all, and the χ²/N readout is the combined value (hover it for
|
|
233
|
+
each dataset's).
|
|
234
|
+
* **all / each** next to each parameter: *all* means one shared value, slider
|
|
235
|
+
range and polydispersity for every member; *each* means every dataset has its
|
|
236
|
+
own (scale, background and solvent SLDs start as *each*, the rest as *all*).
|
|
237
|
+
* **ƒ** (on an *each* parameter) sets this dataset's value by an expression of
|
|
238
|
+
other parameters, e.g. `0.8 * h2o.scale` or
|
|
239
|
+
`(h2o.background + d2o.background) / 2`. Datasets are referred to by the
|
|
240
|
+
short names shown in the Multi line (from the file names); a bare name such
|
|
241
|
+
as `scale` means the same dataset. Numbers, `+ - * /`, parentheses and
|
|
242
|
+
integer powers are allowed, as in IQuana; expressions are parsed, never
|
|
243
|
+
executed, and cycles are refused.
|
|
244
|
+
* **Fit** (with the Fit toggle on) runs one joint BUMPS fit: shared parameters
|
|
245
|
+
are one fitted quantity, expression parameters are derived, and the fit
|
|
246
|
+
ticks apply to every dataset (bounds: the slider range — per dataset for
|
|
247
|
+
*each* parameters). **Undo fit** restores every dataset.
|
|
248
|
+
* **IQuana notebook ↗** writes a `MultiFitter` notebook: one `fit.add(...)`
|
|
249
|
+
per dataset with its own values, bounds, PD, Q range and resolution, then
|
|
250
|
+
`fit.share(...)` and `fit.constrain(...)`, a check that prints each
|
|
251
|
+
dataset's χ²/N next to sassafras's, the joint fit, `fit.get_fit_report()`
|
|
252
|
+
and `fit.save_results(...)` (parameters, datasets, covariance and curves as
|
|
253
|
+
CSV). IQuana has no joint DREAM yet, so a DREAM setting exports as simplex.
|
|
254
|
+
* **Copy to clipboard → Params**, **Save** and **Load** cover all members (a table with one
|
|
255
|
+
column per dataset; the JSON restores the datasets it can find by path).
|
|
256
|
+
* All members share the model and the χ² window. **Guess** still looks at the
|
|
257
|
+
active dataset only and says so when Multi is on.
|
|
258
|
+
|
|
259
|
+
## Guess (ML model suggestion)
|
|
260
|
+
|
|
261
|
+
**Guess** (Model panel) passes the active dataset, inside the χ² window, to the
|
|
262
|
+
trained network from the `ml-sans` project. It shows the ranked sasmodels
|
|
263
|
+
suggestions with their probabilities, and loads the top one sassafras can
|
|
264
|
+
handle. Click any other row to load that model instead.
|
|
265
|
+
|
|
266
|
+
The network ranks models only; it doesn't predict parameters. Starting values
|
|
267
|
+
come from a quick prior search:
|
|
268
|
+
|
|
269
|
+
1. Up to 300 parameter sets for the suggested model (about 4 s) are drawn from
|
|
270
|
+
the same physical priors the network was trained on (`sans_ml.priors`,
|
|
271
|
+
including PD and the S(Q) effective-radius handling).
|
|
272
|
+
2. Each set is scored against the data with sassafras's resolution-smeared
|
|
273
|
+
evaluation and the closed-form scale/background solve.
|
|
274
|
+
3. If `bumps` is installed, the best three draws get a short, time-limited
|
|
275
|
+
Levenberg–Marquardt polish of the parameters the prior varies.
|
|
276
|
+
|
|
277
|
+
The result is a starting point, not a final fit; refine it with the sliders or
|
|
278
|
+
the Fit toggle.
|
|
279
|
+
|
|
280
|
+
* Two-component suggestions (`A+B`) are listed but can't be loaded yet.
|
|
281
|
+
* As the ml-sans README notes, the network does well on simulated-like curves
|
|
282
|
+
and less well on measured data, so treat the top three as the answer.
|
|
283
|
+
* Setup: the trained model is bundled, so only the Python packages are
|
|
284
|
+
needed: `pip install "sassafras[guess]"` (`torch`, `scipy`, `iquana`) in the same
|
|
285
|
+
environment as sassafras. Without them the Guess button is dimmed; clicking it shows what is missing,
|
|
286
|
+
and the terminal prints a `Guess: ready` or `Guess: unavailable` line at
|
|
287
|
+
start-up.
|
|
288
|
+
* The network runs in a short-lived child process, so PyTorch is never loaded
|
|
289
|
+
into the sassafras server. (On macOS, PyTorch and numpy/sasmodels can bring
|
|
290
|
+
conflicting OpenMP runtimes, which aborts the process that loads both.)
|
|
291
|
+
* `sassafras/ml` holds `model.pt` and the inference subset of ml-sans's
|
|
292
|
+
`sans_ml` package (`__init__`, `catalogue`, `encode`, `net`, `predict`,
|
|
293
|
+
`priors`, `simulate`); training and data-generation code stays in ml-sans.
|
|
294
|
+
After retraining, refresh the copy from the ml-sans folder:
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
cp ../ml-sans/model.pt sassafras/ml/
|
|
298
|
+
for f in __init__ catalogue encode net predict priors simulate; do
|
|
299
|
+
cp ../ml-sans/sans_ml/$f.py sassafras/ml/sans_ml/; done
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
To try a new model without copying, start with `--ml-sans /path/to/ml-sans`
|
|
303
|
+
(or set `SASSAFRAS_ML_SANS`).
|
|
304
|
+
* S(Q) can now be added to any form factor except those that already include
|
|
305
|
+
inter-particle structure or describe no finite particle. This is the same
|
|
306
|
+
rule ml-sans uses, so its P@S suggestions load.
|
|
307
|
+
|
|
308
|
+
### Curve library (SAS-55M-20k)
|
|
309
|
+
|
|
310
|
+
When a curve-library index is installed, **Guess** uses it instead of the
|
|
311
|
+
network. SAS-55M-20k (the Sasformer training set) holds 1.1 million noise-free
|
|
312
|
+
sasmodels curves: 55 form factors × 20,000 random parameter sets on 512 Q
|
|
313
|
+
points from 0.001 to 1 Å⁻¹, each stored with its structural parameters.
|
|
314
|
+
|
|
315
|
+
* **Ranking:** every library curve is interpolated onto the active dataset's Q
|
|
316
|
+
points inside the χ² window and given its own weighted least-squares scale
|
|
317
|
+
and background, so only the shape counts. The list shows each model's best
|
|
318
|
+
χ²/N (unsmeared, so it is a ranking rather than a fit quality). The match
|
|
319
|
+
takes 1–3 s.
|
|
320
|
+
* **Starting values:** the three closest curves of the chosen model supply
|
|
321
|
+
their stored parameters. SLDs and polydispersity are not stored, so
|
|
322
|
+
sassafras keeps the default SLDs (scale absorbs the contrast), scans Schulz
|
|
323
|
+
polydispersity widths 0–0.3 on the parameters the generator made
|
|
324
|
+
polydisperse (fewer of them for slow kernels), then runs the short LM
|
|
325
|
+
polish. For models whose shape depends on contrast ratios (two or more
|
|
326
|
+
particle SLDs, e.g. core–shell), the polish also moves those SLDs.
|
|
327
|
+
* **S(Q) sub-library:** SAS-55M-20k itself has no structure factors (apart
|
|
328
|
+
from `binary_hard_sphere`), so a second index adds P×S curves. It takes 3,000
|
|
329
|
+
random curves of each of the 20 particle form factors that have a defined
|
|
330
|
+
effective radius (spheres, core–shells, vesicles, cylinders, ellipsoids,
|
|
331
|
+
prisms, …; not the shape-independent, lamellar or flexible-chain models),
|
|
332
|
+
and multiplies each by four structure factors:
|
|
333
|
+
|
|
334
|
+
| S(Q) | weak | strong |
|
|
335
|
+
|---|---|---|
|
|
336
|
+
| `hardsphere` | φ 0.02–0.10 | φ 0.20–0.40 |
|
|
337
|
+
| `hayter_msa` (water, 298 K) | φ 0.005–0.05, charge 5–20 e, salt 0.01–0.1 M | φ 0.02–0.15, charge 20–60 e, salt 0–0.005 M |
|
|
338
|
+
|
|
339
|
+
S(Q) is evaluated at the form's effective radius (equivalent-volume sphere
|
|
340
|
+
where defined) × 1.0–1.1, on the same Q points, as
|
|
341
|
+
I = (I_stored − 0.001)·S(Q) + 0.001, so each curve keeps the generator's
|
|
342
|
+
polydispersity and SLDs. Settings where Hayter–MSA has no physical solution
|
|
343
|
+
(S ≤ 0, mostly large particles at weak charge) are dropped, leaving about
|
|
344
|
+
205,000 curves (~90 MB). The lookup searches both indexes at once, and a
|
|
345
|
+
`form@sf` match loads with `radius_effective_mode` "unconstrained" at the
|
|
346
|
+
stored radius_effective; temperature and dielectric constant stay fixed in
|
|
347
|
+
the polish.
|
|
348
|
+
* **Limits:** P×S is only covered for the S(Q) settings above, and
|
|
349
|
+
two-population mixtures aren't covered at all.
|
|
350
|
+
Where the generator coupled parameters or depended on the unknown SLDs, the
|
|
351
|
+
stored values are only approximate starting points.
|
|
352
|
+
* **Setup (quick):** download the prebuilt indexes (~290 MB, ~480 MB
|
|
353
|
+
unpacked; [doi:10.5281/zenodo.24080759](https://doi.org/10.5281/zenodo.24080759)),
|
|
354
|
+
checksum-verified, into your per-user data folder:
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
python -m sassafras.library fetch
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
The folder is `~/Library/Application Support/sassafras` on macOS,
|
|
361
|
+
`~/.local/share/sassafras` on Linux and `%LOCALAPPDATA%\sassafras` on
|
|
362
|
+
Windows; set `SASSAFRAS_DATA` to use another. It survives upgrades and is
|
|
363
|
+
shared by every Python environment. Indexes built or fetched into
|
|
364
|
+
`sassafras/ml/` by versions before 0.5 are still found.
|
|
365
|
+
|
|
366
|
+
* **Setup (from the dataset):** or download SAS-55M-20k
|
|
367
|
+
([Figshare](https://doi.org/10.6084/m9.figshare.21716618), ~4.5 GB) and build
|
|
368
|
+
the index once (about 20 s; it needs `pyarrow`, `pip install -e ".[library]"`):
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
python -m sassafras.library build ~/Downloads/sas-55m-20k
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
This writes about 400 MB to `library` in the data folder. Then build the
|
|
375
|
+
S(Q) sub-library (about 30 s, ~90 MB in `library_sq`; `--per-model` sets
|
|
376
|
+
the size):
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
python -m sassafras.library build-sq ~/Downloads/sas-55m-20k
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Use `--out` to put either elsewhere and start sassafras with `--library DIR`
|
|
383
|
+
/ `--library-sq DIR` (or set `SASSAFRAS_LIBRARY` / `SASSAFRAS_LIBRARY_SQ`). Without an index, Guess uses the ml-sans network as
|
|
384
|
+
before.
|
|
385
|
+
|
|
386
|
+
* **Data credit and licence:** SAS-55M-20k is by B. Yildirim, J. Doutch and
|
|
387
|
+
J. M. Cole (*Digital Discovery* **3**, 694–704, 2024) and is MIT-licensed;
|
|
388
|
+
the licence is in `sassafras/ml/SAS-55M-20k_LICENSE.txt` and is shipped as
|
|
389
|
+
`DATA_LICENSE.txt` inside the release archive. To publish a rebuilt index:
|
|
390
|
+
`python -m sassafras.library pack sassafras-library-vN.tar.gz`, upload it as
|
|
391
|
+
a new version of the library's Zenodo record (and attach it to a
|
|
392
|
+
`library-vN` GitHub release as a mirror), then update `RELEASE_TAG`,
|
|
393
|
+
`RELEASE_FILE`, `RELEASE_SHA256` and `ZENODO_URL` in `sassafras/library.py`.
|
|
394
|
+
|
|
395
|
+
## IQuana notebook
|
|
396
|
+
|
|
397
|
+
**IQuana notebook ↗** (top right of the plot panel) writes a Jupyter notebook with
|
|
398
|
+
[IQuana](https://sasview.github.io/IQuana/) code that reproduces what is on
|
|
399
|
+
screen, then opens it in JupyterLab. It covers the data, model and structure
|
|
400
|
+
factor, every parameter value, polydispersity, the Q window and the
|
|
401
|
+
resolution. When the Fit toggle is on it also sets up the same fit: the ticked
|
|
402
|
+
parameters with the slider ranges as bounds, and the same method (LM, simplex
|
|
403
|
+
and DE → `fit(engine='bumps', method=…)`, DREAM → `fit_bayesian()`).
|
|
404
|
+
|
|
405
|
+
* Notebooks are saved in `~/sassafras_notebooks` (change it with
|
|
406
|
+
`--notebook-dir`). One JupyterLab server rooted there is started on first
|
|
407
|
+
use and reused afterwards; it keeps running after sassafras stops, so open
|
|
408
|
+
notebooks aren't lost.
|
|
409
|
+
* A check cell prints IQuana's χ²/N next to the sassafras value. IQuana builds
|
|
410
|
+
kernels in single precision, so they agree to about 1 part in 10⁴.
|
|
411
|
+
* Path-loaded files are read from their original location. Uploaded files,
|
|
412
|
+
and files that sassafras rescaled or trimmed on load, are saved next to the
|
|
413
|
+
notebook as a CSV snapshot (Q, I, dI, dQ).
|
|
414
|
+
* Effective radius: sasmodels' `radius_effective_mode` "unconstrained" maps
|
|
415
|
+
to IQuana's `'unconstrained'`, and "radius" to `'link_radius'`. Other
|
|
416
|
+
computed modes (equivalent volume sphere, excluded volume, …) have no IQuana
|
|
417
|
+
equivalent, so `radius_effective` is fixed at the value sasmodels computes
|
|
418
|
+
for the current geometry, and the notebook says so.
|
|
419
|
+
* Models with per-shell vector parameters (e.g. `core_multi_shell`) can't be
|
|
420
|
+
addressed shell by shell in IQuana 0.5, so they are refused with a message.
|
|
421
|
+
* Needs `iquana` and `jupyterlab` in the same Python environment:
|
|
422
|
+
`pip install "sassafras[notebook]"`.
|
|
423
|
+
|
|
424
|
+
## Copy to clipboard
|
|
425
|
+
|
|
426
|
+
Buttons above the plot put content on the clipboard, ready to paste into
|
|
427
|
+
OneNote, an ELN, Word, Excel or a plain-text cell:
|
|
428
|
+
|
|
429
|
+
* **All**: title line, figure and parameter table as one rich paste. Plain-text
|
|
430
|
+
editors get the text summary instead.
|
|
431
|
+
* **Figure**: PNG of the data, model, pinned curve, residuals and legend, with
|
|
432
|
+
the dataset, model and χ²/N as a title. It is always drawn on a white
|
|
433
|
+
background, whatever the screen theme.
|
|
434
|
+
* **Parameters**: model, χ²/N with N and the Q window, and every parameter with
|
|
435
|
+
units and polydispersity. It pastes as a table, or as aligned text where
|
|
436
|
+
tables aren't supported.
|
|
437
|
+
* **Data**: Q, I, dI, model, residual and in-window flag at full precision. It
|
|
438
|
+
pastes as columns into Excel and as a table into notebooks.
|
|
439
|
+
* **PNG ↓**: downloads the figure instead.
|
|
440
|
+
|
|
441
|
+
## Plot
|
|
442
|
+
|
|
443
|
+
**Plot type** (next to log Q / log I): **I(Q)**; **Kratky**, Q²·I(Q) vs Q
|
|
444
|
+
(linear axes by default), a quick check of folding, compactness or chain-like
|
|
445
|
+
scattering; or **Guinier**, ln I vs Q² (log axes off), where a straight
|
|
446
|
+
low-Q line gives Rg from its slope (−Rg²/3). Guinier opens on the low-Q region
|
|
447
|
+
where ln I has fallen by about 0.8, a little past the usual Q·Rg ≈ 1.3 limit;
|
|
448
|
+
drag to zoom as usual. Data, error bars, the model and any pinned curve are
|
|
449
|
+
all transformed; the residual panel keeps (I − model)/dI on the same Q axis.
|
|
450
|
+
|
|
451
|
+
Light and dark themes follow the system setting. The **Theme** button in the header switches between auto, light and dark, and the browser remembers your choice. The **Colour** button next to it cycles the colour scheme (accents, first data series and logo): chopped (green, default), cooked (orange), washed (blue), chalked (grey) and girlypop (pink); it is remembered too. Log/linear axes, drag a box to zoom (a horizontal drag zooms Q only),
|
|
452
|
+
double-click or **Reset zoom** to undo, and hover for point values. The χ² window
|
|
453
|
+
greys out points outside it and restricts both χ² and Match.
|
|
454
|
+
|
|
455
|
+
χ²/N = Σ((I − model)/dI)² ÷ (points in window). No parameters are fitted, so
|
|
456
|
+
this is a number for comparing settings, not a goodness-of-fit statistic. To
|
|
457
|
+
compare with a BUMPS/SasView fit's reduced χ², multiply by N/(N − p).
|
|
458
|
+
|
|
459
|
+
## Checks
|
|
460
|
+
|
|
461
|
+
`python -m pytest tests` covers loading every example format, nm⁻¹
|
|
462
|
+
conversion, product and polydisperse models, vector parameters, the
|
|
463
|
+
scale/background solve, error reporting and every HTTP endpoint. One
|
|
464
|
+
regression test reproduces the `elliptical_cylinder_gui` starting curve to
|
|
465
|
+
within 2 × 10⁻⁶ relative, and its BUMPS χ² (561.675) after the N/(N − p)
|
|
466
|
+
rescale. With bumps installed, `tests/test_fit.py` checks that LM and
|
|
467
|
+
simplex fits recover known parameters within their uncertainties, that the Q
|
|
468
|
+
window and PD widths are respected, that cancellation works and that bad
|
|
469
|
+
requests are rejected. Those tests are skipped when bumps is absent.
|
|
470
|
+
|
|
471
|
+
`examples/` contains measured flexible elliptical cylinder SANS data
|
|
472
|
+
(`flexible_cylinder_elliptical.dat`; four columns Q (Å⁻¹), I, dI (cm⁻¹), dQ (Å⁻¹), with
|
|
473
|
+
metadata removed), synthetic sphere × hard-sphere data (canSAS XML and
|
|
474
|
+
NXcanSAS), and a synthetic SAXS core–shell dataset written in nm⁻¹.
|
|
475
|
+
|
|
476
|
+
## Layout
|
|
477
|
+
|
|
478
|
+
```
|
|
479
|
+
sassafras/backend.py data loading (sasdata), model catalogue, evaluation (sasmodels)
|
|
480
|
+
sassafras/server.py stdlib HTTP server + CLI
|
|
481
|
+
sassafras/fit.py optional BUMPS fitting (background jobs, progress, cancel; joint multi-dataset fits)
|
|
482
|
+
sassafras/multi.py Multi: request checks and safe expressions shared by the joint fit and the export
|
|
483
|
+
sassafras/notebook.py IQuana notebook export and JupyterLab launcher
|
|
484
|
+
sassafras/guess.py Guess: ml-sans model suggestion + prior-search starting values
|
|
485
|
+
sassafras/_guess_worker.py child process that runs the ml-sans network (torch)
|
|
486
|
+
sassafras/library.py curve-library index (build) and shape matching for Guess
|
|
487
|
+
sassafras/ml/ bundled model.pt + sans_ml inference code (copied from ml-sans)
|
|
488
|
+
sassafras/static/ index.html, app.js (SVG plotting, controls), copy.js (clipboard),
|
|
489
|
+
fitting.js (Fit toggle and controls), multi.js (Multi), notebook.js, guess.js, app.css, logos + favicon
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
The backend can be used without the GUI:
|
|
493
|
+
|
|
494
|
+
```python
|
|
495
|
+
from sassafras.backend import Session
|
|
496
|
+
s = Session()
|
|
497
|
+
ds, _ = s.load_path("run.xml")
|
|
498
|
+
out = s.evaluate({"model": "sphere", "structure_factor": "hardsphere",
|
|
499
|
+
"params": {"radius": 60, "volfraction": 0.2}, "dataset": ds[0].id})
|
|
500
|
+
out["chisq"]
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Tested with Python 3.13, sasmodels 1.1.0, sasdata 0.12.2, bumps 1.0.3 and numpy 2.5.3, in
|
|
504
|
+
headless Chromium (light and dark themes, desktop and phone widths).
|
|
505
|
+
|
|
506
|
+
## Contributing
|
|
507
|
+
|
|
508
|
+
Issues and pull requests are welcome; see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
509
|
+
|
|
510
|
+
## Citing
|
|
511
|
+
|
|
512
|
+
If sassafras helps your work, please cite it:
|
|
513
|
+
[doi:10.5281/zenodo.24041504](https://doi.org/10.5281/zenodo.24041504). That DOI
|
|
514
|
+
always resolves to the latest version; each release also has its own DOI on
|
|
515
|
+
that page. GitHub's "Cite this repository" button gives the same reference
|
|
516
|
+
from `CITATION.cff`.
|
|
517
|
+
|
|
518
|
+
## Licence
|
|
519
|
+
|
|
520
|
+
BSD 3-Clause; see [LICENSE](LICENSE). Sassafras uses
|
|
521
|
+
[sasmodels](https://github.com/SasView/sasmodels) and
|
|
522
|
+
[sasdata](https://github.com/SasView/sasdata), which are also BSD-licensed.
|