Skip to content

Support for SAXS fitting (v2)#3786

Merged
krzywon merged 28 commits intomainfrom
saxs_fitting_v2
Mar 20, 2026
Merged

Support for SAXS fitting (v2)#3786
krzywon merged 28 commits intomainfrom
saxs_fitting_v2

Conversation

@klytje
Copy link
Copy Markdown
Contributor

@klytje klytje commented Nov 22, 2025

Description

After long discussions on how to support SAXS fitting in SasView in the short term, it was decided to extend the generic scattering calculator with a 'SAXS fitting' option.

When this option is selected, the Compute button text is replaced with Generate plugin model. Loading a Nuclear data file with this option enabled circumvents the usual loading logic, and instead forwards the selected file path to AUSAXS. This serves two purposes:

  1. AUSAXS also supports mmCIF files, which is the new standard in biological scattering, and
  2. we avoid having to extend/modify the PDB loading logic with the additional information required for SAXS calculations.

Clicking the Generate plugin model will then create a new SAXS fit (<filename>) model, which is automatically selected & opened for the user in the fit panel. The SAXS data may then be loaded in & fitted.

Though a little awkward, I found this implementation to work decently well, at least for a short-term solution. In the long run, I'd like to have the following:

  1. The ability to load both the SAXS data & structure file directly into the fitting panel (see my discussion here).
  2. An optional initialization function for plugin models, which is invoked once before the actual Iq calculations start. I need this to avoid attaching the loaded structure file as a property to the Iq function. Having it float in the module namespace would make it load on SasView startup, which is also not ideal.
  3. Custom plugin model interfaces in the fit panel. There's a lot of additional options available in AUSAXS which are currently difficult/impossible to expose to the user. Though this could also be implemented through a dedicated SAXS fitting window, for (1) to work, this is required.
  4. We could potentially extend the SasView file loaders to support mmCIF & full PDB data loading, if we want to avoid relying on AUSAXS for this. Alternatively, we could continue to rely on AUSAXS to parse the files, but store the data on the SasView side. Or just keep it as is. I'm indifferent on this issue.

This PR replaces #3194.

Review Checklist:

[if using the editor, use [x] in place of [ ] to check a box]

Documentation
Do we need documentation for this? There's links to the AUSAXS paper in the generated plugin model file itself, if people are interested.

Installers

  • There is a chance this will affect the installers, if so
    • Windows installer (GH artifact) has been tested (installed and worked)
    • MacOSX installer (GH artifact) has been tested (installed and worked)
    • Wheels installer (GH artifact) has been tested (installed and worked)

Licensing (untick if necessary)

  • The introduced changes comply with SasView license (BSD 3-Clause)

@klytje klytje mentioned this pull request Nov 22, 2025
14 tasks
@klytje klytje marked this pull request as ready for review November 23, 2025 07:52
@wpotrzebowski wpotrzebowski self-requested a review December 2, 2025 14:30
Comment thread src/sas/qtgui/Calculators/GenericScatteringCalculator.py Outdated
Comment thread src/sas/qtgui/Calculators/SAXSPluginModelGenerator.py Outdated
Copy link
Copy Markdown
Contributor

@krzywon krzywon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested, but see my code comments.

Comment thread build_tools/requirements.txt
Comment thread src/sas/qtgui/Calculators/GenericScatteringCalculator.py
Comment thread src/sas/qtgui/Calculators/GenericScatteringCalculator.py
Comment thread src/sas/qtgui/Calculators/SAXSPluginModelGenerator.py Outdated
@krzywon
Copy link
Copy Markdown
Contributor

krzywon commented Dec 17, 2025

Do not merge: This PR needs to make a minor change to what is introduced by #3544 (due to an API-breaking pyAUSAXS version update).

Is this comment in your PR description still valid, @klytje?

@klytje
Copy link
Copy Markdown
Contributor Author

klytje commented Dec 18, 2025

@krzywon No, it is ready for merging. The shape2sas calculations were different from what I remembered and will not need manual updating (I just checked).

Copy link
Copy Markdown
Contributor

@krzywon krzywon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One final code review. I'll do a functionality test later.

Comment thread src/sas/qtgui/Calculators/GenericScatteringCalculator.py
Comment thread src/sas/qtgui/Calculators/GenericScatteringCalculator.py
Comment thread src/sas/qtgui/Calculators/GenericScatteringCalculator.py Outdated
Comment thread src/sas/qtgui/Calculators/GenericScatteringCalculator.py
Comment thread src/sas/sascalc/calculator/sas_gen.py Outdated
Comment thread src/sas/qtgui/Calculators/SAXSPluginModelGenerator.py
@krzywon
Copy link
Copy Markdown
Contributor

krzywon commented Jan 13, 2026

I tested today and am getting a warning when trying to create the plugin model. I think this is a Win-specific issue. The path saved in the plugin model uses backslashes causing a unicode error.

When I open the file generated in the model editor, I get the following error:

2026-01-13 14:56:52,113 : WARNING : sas.sascalc.fit.models (models.py:166) :: Failed to load plugin '\sasview\SasView\plugin_models\ausaxs_saxs_plugin.py'. See \sasview\SasView\plugin_models\plugins.log for details
2026-01-13 14:57:07,276 : ERROR : sas.qtgui.Utilities.ModelEditors.TabbedEditor.TabbedModelEditor (TabbedModelEditor.py:448) :: Error building model: (unicode error) 'unicodeescape' codec can't decode bytes in position 2-3: truncated \UXXXXXXXX escape (, line 21)
2026-01-13 14:57:07,278 : ERROR : sas.qtgui.Utilities.ModelEditors.TabbedEditor.TabbedModelEditor (TabbedModelEditor.py:454) :: structure_path = "\sasview\src\sas\example_data\coordinate_data\diamond.pdb"

@klytje
Copy link
Copy Markdown
Contributor Author

klytje commented Jan 15, 2026

@krzywon Windows issue should be fixed now. I changed the structure path to always be stored as a posix path instead. Verified it works on my own Windows system.

Can you can take a look at the open review conversations?

@klytje
Copy link
Copy Markdown
Contributor Author

klytje commented Jan 20, 2026

pyausaxs is no longer pinned as it was causing issues with the debian sasview packaging.

@krzywon
Copy link
Copy Markdown
Contributor

krzywon commented Jan 23, 2026

This is getting closer! The code looks fine at this point, but running locally, I am seeing an error. Also, closing the GSC and immediately switching to the fitting perspective is a bit jarring and seems like the GSC crashed. Auto-loading the model is useful, but I don't think closing the tool is ideal.

Steps to repeat the error below:

  1. Open the GSC
  2. Load sas.example_data.coordinate_data.diamond.pdb
  3. Generate a SAXS fit
  4. In the fitting window, click the Calculate button.
  5. Error

ERROR: Traceback (most recent call last): File "sasview\src\sas\sascalc\data_util\calcthread.py", line 269, in _run self.compute(*args, **kwargs) ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ File "sasview\src\sas\qtgui\Perspectives\Fitting\ModelThread.py", line 216, in compute return_data = self.model.calculate_Iq(self.data.x[index]) File "sasmodels\sasmodels\sasview_model.py", line 710, in calculate_Iq return self._calculate_Iq(qx, qy) ~~~~~~~~~~~~~~~~~~^^^^^^^^ File "sasmodels\sasmodels\sasview_model.py", line 736, in _calculate_Iq result = calculator(call_details, values, cutoff=self.cutoff, magnetic=is_magnetic) File "sasmodels\sasmodels\kernel.py", line 94, in Iq _, F2, _, shell_volume, _ = self.Fq(call_details, values, cutoff, ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ magnetic, radius_effective_mode=0) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "sasmodels\sasmodels\kernel.py", line 169, in Fq self._call_kernel(call_details, values, cutoff, magnetic, ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ radius_effective_mode) ^^^^^^^^^^^^^^^^^^^^^^ File "sasmodels\sasmodels\kernelpy.py", line 190, in _call_kernel self.result = _loops( ~~~~~~^ self._parameter_vector, self._form, self._volume, radius, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ self.q_input.nq, call_details, values, cutoff) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "sasmodels\sasmodels\kernelpy.py", line 226, in _loops total = form() File "sasmodels\sasmodels\kernelpy.py", line 168, in self._form = lambda: form(q, *kernel_args) ~~~~^^^^^^^^^^^^^^^^^ File "~\AppData\Local\sasview\SasView\plugin_models\ausaxs_saxs_plugin.py", line 26, in Iq Iq._mol = ausaxs.create_molecule(structure_path) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^ File "sasview\venv\Lib\site-packages\pyausaxs\wrapper\Molecule.py", line 336, in create_molecule return Molecule(*args) File "sasview\venv\Lib\site-packages\pyausaxs\wrapper\Molecule.py", line 17, in init self._create_molecule(*args) ~~~~~~~~~~~~~~~~~~~~~^^^^^^^ File "sasview\venv\Lib\site-packages\pyausaxs\wrapper\Molecule.py", line 63, in _create_molecule self._create_molecule_from_file(args[0]) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^ File "sasview\venv\Lib\site-packages\pyausaxs\wrapper\Molecule.py", line 27, in _create_molecule_from_file _check_error_code(status, "_create_molecule_from_file") ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "sasview\venv\Lib\site-packages\pyausaxs\wrapper\AUSAXS.py", line 46, in _check_error_code raise RuntimeError(f"AUSAXS: "{function_name}" failed with error code {status.value}: \n"{error_message}"") RuntimeError: AUSAXS: "_create_molecule_from_file" failed with error code 1: "PDBAtom::add_implicit_hydrogens: Could not identify group of atom 1. Unknown element, residual or atom: (O, ???, O)"

@klytje
Copy link
Copy Markdown
Contributor Author

klytje commented Feb 1, 2026

@krzywon what is the expected outcome when trying to calculate the solution scattering by a crystal? My library is complaining that the PDB file is invalid (and indeed it is: there's no element or residue information, only an atom name which can be misinterpreted).

@klytje
Copy link
Copy Markdown
Contributor Author

klytje commented Feb 1, 2026

I have now added support for processing files with unknown residues, but I really do not think we should enable this by default. It needs an explicit toggle so users are aware of what is actually being done with their data, which is currently not possible.

Regarding automatically closing the GSC: I tried disabling this, and I found that to be an even worse experience, as there is no feedback that something actually happened. I think that, though it may not be ideal, closing the GSC & opening the fitting panel with the model already selected gives much clearer feedback. And it would be the next natural step to take for the user anyway.

@klytje
Copy link
Copy Markdown
Contributor Author

klytje commented Feb 17, 2026

@krzywon bump

@krzywon
Copy link
Copy Markdown
Contributor

krzywon commented Feb 19, 2026

Opening the same PDB file that I tested for the last error (diamond.pdb), I am now seeing this error. Through the scripting interface, I could understand seeing this error, but from the GUI, there is no obvious way to implement this in the plugin model. Perhaps adding a couple of flags to the model file that are checked when running ausaxs, and updating the error message to reference those?

Otherwise, I would say this is ready.

ERROR: raise RuntimeError(f"AUSAXS: "{function_name}" failed with error code {status.value}: \n"{error_message}"") RuntimeError: AUSAXS: "_create_molecule_from_file" failed with error code 1: "Molecule::add_implicit_hydrogens: Molecule contains 621 atoms with unknown residues. Disable implicit hydrogens with --no-implicit-hydrogens flag, or use --allow-unknown-residues to continue anyway."

@krzywon
Copy link
Copy Markdown
Contributor

krzywon commented Feb 27, 2026

@klytje, the tasks left for this PR:

  • Document how to access the --no-implicit-hydrogens and --allow-unknown-residues flags within SasView (if possible)
  • Find a way to include the flags in the SAXS fitting model, or, alternately, include comments within the model on how to implement them

These can either be done within this PR, or I can approve and merge this and we create separate issues to track them.

@klytje
Copy link
Copy Markdown
Contributor Author

klytje commented Feb 27, 2026

@krzywon Sorry for the delay, I was considering my options for changing the error messages when used through Python, but I don't see any good long-term solution for this. So I think it is best to leave them as is, and guide users towards the solution instead: changing the two flags which are now added to all generated plugins.

The downside with this approach is that the changes are overwritten whenever a new plugin is generated. On the other hand, maybe this is fine since it is really not a good idea to have these flags enabled by default.

I think it would be good to create an issue to document the changes we'd like to see in a proper GUI interface. I can do this once this is merged.

Copy link
Copy Markdown
Contributor

@krzywon krzywon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is ready. I think the two flags added to the SAXS model file need more (any?) documentation, but that shouldn't hold this PR up.

Let's wait to merge this until after the biweekly call next week.

@wpotrzebowski
Copy link
Copy Markdown
Contributor

It generally seems to work as expected. The error occurs when when one tries to regenerate plugin. It throws error about magnetic model, which is quite confusing. Particularly because a new plugin model is generated.

ERROR: Traceback (most recent call last): File "sas/sascalc/data_util/calcthread.py", line 269, in _run File "sas/qtgui/Perspectives/Fitting/ModelThread.py", line 216, in compute File "/private/var/folders/1h/nkglbdgx00nf690grw9xl0j00000gn/T/AppTranslocation/A6E5CE06-FE20-4046-A3D3-19A1DA7F8A80/d/SasView6.app/Contents/Frameworks/sasmodels/sasview_model.py", line 710, in calculate_Iq return self._calculate_Iq(qx, qy) ~~~~~~~~~~~~~~~~~~^^^^^^^^ File "/private/var/folders/1h/nkglbdgx00nf690grw9xl0j00000gn/T/AppTranslocation/A6E5CE06-FE20-4046-A3D3-19A1DA7F8A80/d/SasView6.app/Contents/Frameworks/sasmodels/sasview_model.py", line 736, in _calculate_Iq result = calculator(call_details, values, cutoff=self.cutoff, magnetic=is_magnetic) File "/private/var/folders/1h/nkglbdgx00nf690grw9xl0j00000gn/T/AppTranslocation/A6E5CE06-FE20-4046-A3D3-19A1DA7F8A80/d/SasView6.app/Contents/Frameworks/sasmodels/kernel.py", line 94, in Iq _, F2, _, shell_volume, _ = self.Fq(call_details, values, cutoff, ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ magnetic, radius_effective_mode=0) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/private/var/folders/1h/nkglbdgx00nf690grw9xl0j00000gn/T/AppTranslocation/A6E5CE06-FE20-4046-A3D3-19A1DA7F8A80/d/SasView6.app/Contents/Frameworks/sasmodels/kernel.py", line 169, in Fq self._call_kernel(call_details, values, cutoff, magnetic, ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ radius_effective_mode) ^^^^^^^^^^^^^^^^^^^^^^ File "/private/var/folders/1h/nkglbdgx00nf690grw9xl0j00000gn/T/AppTranslocation/A6E5CE06-FE20-4046-A3D3-19A1DA7F8A80/d/SasView6.app/Contents/Frameworks/sasmodels/kernelpy.py", line 185, in _call_kernel raise NotImplementedError("Magnetism not implemented for pure python models") NotImplementedError: Magnetism not implemented for pure python models

@wpotrzebowski
Copy link
Copy Markdown
Contributor

It generally seems to work as expected. The error occurs when when one tries to regenerate plugin. It throws error about magnetic model, which is quite confusing. Particularly because a new plugin model is generated.

ERROR: Traceback (most recent call last): File "sas/sascalc/data_util/calcthread.py", line 269, in _run File "sas/qtgui/Perspectives/Fitting/ModelThread.py", line 216, in compute File "/private/var/folders/1h/nkglbdgx00nf690grw9xl0j00000gn/T/AppTranslocation/A6E5CE06-FE20-4046-A3D3-19A1DA7F8A80/d/SasView6.app/Contents/Frameworks/sasmodels/sasview_model.py", line 710, in calculate_Iq return self._calculate_Iq(qx, qy) ~~~~~~~~~~~~~~~~~~^^^^^^^^ File "/private/var/folders/1h/nkglbdgx00nf690grw9xl0j00000gn/T/AppTranslocation/A6E5CE06-FE20-4046-A3D3-19A1DA7F8A80/d/SasView6.app/Contents/Frameworks/sasmodels/sasview_model.py", line 736, in _calculate_Iq result = calculator(call_details, values, cutoff=self.cutoff, magnetic=is_magnetic) File "/private/var/folders/1h/nkglbdgx00nf690grw9xl0j00000gn/T/AppTranslocation/A6E5CE06-FE20-4046-A3D3-19A1DA7F8A80/d/SasView6.app/Contents/Frameworks/sasmodels/kernel.py", line 94, in Iq _, F2, _, shell_volume, _ = self.Fq(call_details, values, cutoff, ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ magnetic, radius_effective_mode=0) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/private/var/folders/1h/nkglbdgx00nf690grw9xl0j00000gn/T/AppTranslocation/A6E5CE06-FE20-4046-A3D3-19A1DA7F8A80/d/SasView6.app/Contents/Frameworks/sasmodels/kernel.py", line 169, in Fq self._call_kernel(call_details, values, cutoff, magnetic, ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ radius_effective_mode) ^^^^^^^^^^^^^^^^^^^^^^ File "/private/var/folders/1h/nkglbdgx00nf690grw9xl0j00000gn/T/AppTranslocation/A6E5CE06-FE20-4046-A3D3-19A1DA7F8A80/d/SasView6.app/Contents/Frameworks/sasmodels/kernelpy.py", line 185, in _call_kernel raise NotImplementedError("Magnetism not implemented for pure python models") NotImplementedError: Magnetism not implemented for pure python models

I can see that @krzywon also faced the similiar issue

@klytje
Copy link
Copy Markdown
Contributor Author

klytje commented Mar 11, 2026

@wpotrzebowski This is a new error: the previous one was thrown from inside my own library, while this new one is thrown by sasview itself.

I cannot replicate this error, regenerating the plugin model works just fine for me. What are the exact steps required to trigger this? It seems to be attempting to do some intensity calculations, which makes no sense to me.

@wpotrzebowski
Copy link
Copy Markdown
Contributor

@klytje it involves only a few steps:

  1. Open Generic Scattering Calculator from Tools menu
  2. Load PDB File
  3. Change to SAXS Fitting and click on Generate plugin model
  4. In Fit window (for plugin) click on Compute
  5. Reopen Generic Scattering Calculator
  6. Click on Generate plugin model
  7. See error

@klytje
Copy link
Copy Markdown
Contributor Author

klytje commented Mar 11, 2026

@wpotrzebowski I still cannot replicate it. Which files are you using? And which platform? Did you use the installer or just check out this branch?

@wpotrzebowski
Copy link
Copy Markdown
Contributor

@klytje I use installer on macOS and a large PDB file for the virus capsid https://github.com/Andre-lab/hbv_trSAXS/blob/master/HBVCP_empty_assembly/bayesian_models/capsid_T4.pdb

@klytje
Copy link
Copy Markdown
Contributor Author

klytje commented Mar 11, 2026

@wpotrzebowski I cannot replicate the error on ubuntu. Can someone else give it a try on mac? I don't have one to test.

@krzywon
Copy link
Copy Markdown
Contributor

krzywon commented Mar 20, 2026

After some discussion during todays technical meeting, the plan is to merge this as-is, and to document the issues noted.

@krzywon krzywon merged commit f1dd5bb into main Mar 20, 2026
49 checks passed
@krzywon krzywon deleted the saxs_fitting_v2 branch March 20, 2026 15:45
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.

3 participants