Skip to content

PowderN remove erroneous order parameter - #2622

Open
Lomholy wants to merge 7 commits into
mainfrom
PowderN_and_SingleCrystal_minorfixes
Open

PowderN remove erroneous order parameter#2622
Lomholy wants to merge 7 commits into
mainfrom
PowderN_and_SingleCrystal_minorfixes

Conversation

@Lomholy

@Lomholy Lomholy commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Free-form text area

Please describe what your PR is adding in terms of features or bugfixes:
This PR fixes two minor bugs on the Single_crystal component, and the PowderN component.

The bug in the Single_Crystal component is, that when the number of scatterings is exceeded due to the order parameter, the ray is weighted with exp(-absl). This is wrong, since it should be the total cross section of events i.e exp(-totl).

The bug in the PowderN component is that the attenuation of the ray due to sampling the position is wrong. Currently when order is not set, then my_s is set to my_inc, and it should always be set to the total cross section.
I (embarrasingly enough) implemented this erroneous parameter myself, due to a misunderstanding of the internal workings of this component. I believed that my_s was set to the total cross section in order to mitigate multiple scattering, when it in fact was only there to correct for sampling uniformly instead of exponentially.


Development OS / boundary conditions

Please describe what OS you developed and tested your additions on, and if any special dependencies are required:
MacOS Tahoe 26.6.2


PR Checklist for contributing to McStas/McXtrace

For a coherent and useful contribution to McStas/McXtrace, please fill in relevant parts of the checklist:

  • My contribution includes patches to an existing component file

    • I have used the mcdoc utility and rendered a reasonable documentation page for the component (please attach as screenshot in comments!)
    • I have ensured that basic use of the component is OK (e.g. an instrument using it compiles?)
    • I have used the mctest utility to test one or more instruments making use of the component (please attach mcviewtest report as screenshot in comments)
    • I have used the mccode-clangformat tool to apply the standard McCode component indentation scheme
    • I have used the mcrun --c-lint "linter" and followed advice to remove most / all warnings that are raised

…e with the total cross section, not only the absorption cross section
…erefore it should be the total scattering cross section of the powder, and not only the incoherent xsect, no matter the order
@Lomholy

Lomholy commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Screenshot of relevant part of PowderN mcdoc page:

billede

Single Crystals documentation was not changed.

@Lomholy

Lomholy commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

@willend I am trying to run mctest --testdir tmp --comp PowderN -n 1e6 but this throws the following error (that I don't understand):

File "mcstas-dev/share/mcstas/tools/Python/mctest/mctest.py", line 979, in
main(args)
~~~~^^^^^^
File "mcstas-dev/share/mcstas/tools/Python/mctest/mctest.py", line 946, in main
run_default_test(testdir, mccoderoot, limit, instrfilter, compfilter, suffix)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "mcstas-dev/share/mcstas/tools/Python/mctest/mctest.py", line 648, in run_default_test
(results, failed, num_compilefail, num_runfail, num_valfail, num_noexample) = mccode_test(mccoderoot, labeldir, limit, instrfilter, compfilter)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "mcstas-dev/share/mcstas/tools/Python/mctest/mctest.py", line 426, in mccode_test
formatstr = "%-" + "%ds: Display FAILED (%ds)" % (maxnamelen+1, test.displaytime)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
TypeError: %d format: a real number is required, not NoneType

Have you seen this error before / know the work around? (The same command works for Single_crystal)

@mads-bertelsen

Copy link
Copy Markdown
Contributor

The PowderN bug makes sense. Why was there an order parameter in PowderN, thought it was only single scattering? Was it done in preparation to adding multiple scattering to the component?

I don't think it's wrong to only apply the absorption cross section for the exit path in Single_crystal when order is reached. After a scattering (which is known to have happened since the order is reached), the new wavevector will match the bragg condition of the crystal plane it already scattered in, and have a very high probability to scatter again, reducing the weight to almost 0 if it was used for the remaining path. What would happen if order was larger is the ray reflecting back and forth on that same reflection (and maybe more if it happens that more can be reached), so it would still have a big probability to continue in the direction order 1 predicted, but it would of course be attenuated by the path length, which is what the current order=1 approximates (the real path length would be slightly longer due to the zig-zag path).

@mads-bertelsen

Copy link
Copy Markdown
Contributor

@willend I am trying to run mctest --testdir tmp --comp PowderN -n 1e6 but this throws the following error (that I don't understand):

File "mcstas-dev/share/mcstas/tools/Python/mctest/mctest.py", line 979, in main(args) ~~~~^^^^^^ File "mcstas-dev/share/mcstas/tools/Python/mctest/mctest.py", line 946, in main run_default_test(testdir, mccoderoot, limit, instrfilter, compfilter, suffix) ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "mcstas-dev/share/mcstas/tools/Python/mctest/mctest.py", line 648, in run_default_test (results, failed, num_compilefail, num_runfail, num_valfail, num_noexample) = mccode_test(mccoderoot, labeldir, limit, instrfilter, compfilter) ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "mcstas-dev/share/mcstas/tools/Python/mctest/mctest.py", line 426, in mccode_test formatstr = "%-" + "%ds: Display FAILED (%ds)" % (maxnamelen+1, test.displaytime) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TypeError: %d format: a real number is required, not NoneType

Have you seen this error before / know the work around? (The same command works for Single_crystal)

I also ran into this while trying to perform tests yesterday and did not figure it out. Will take a look soon.

@Lomholy

Lomholy commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

The PowderN bug makes sense. Why was there an order parameter in PowderN, thought it was only single scattering? Was it done in preparation to adding multiple scattering to the component?

No. It was done, because I had misunderstood that attenuating by the total scattering cross section was a way to account for multiple scattering. (Which it isn't)

I don't think it's wrong to only apply the absorption cross section for the exit path in Single_crystal when order is reached. After a scattering (which is known to have happened since the order is reached), the new wavevector will match the bragg condition of the crystal plane it already scattered in, and have a very high probability to scatter again, reducing the weight to almost 0 if it was used for the remaining path. What would happen if order was larger is the ray reflecting back and forth on that same reflection (and maybe more if it happens that more can be reached), so it would still have a big probability to continue in the direction order 1 predicted, but it would of course be attenuated by the path length, which is what the current order=1 approximates (the real path length would be slightly longer due to the zig-zag path).

Hmmm. I don't quite think I am following you here. The way I see it, when order is reached, we make a monte carlo choice, to set the probability of transmitting to 1. The probability to actually transmit is exp(-total sigma * l), and therefore the factor we should multiply onto the weight is exactly exp(-total sigma * l) and not exp(-abs * l).

With that said, my fix is not good enough, since the total scattering cross section that we access has not yet been modified for the direction of the neutron.

@mads-bertelsen

Copy link
Copy Markdown
Contributor

The PowderN bug makes sense. Why was there an order parameter in PowderN, thought it was only single scattering? Was it done in preparation to adding multiple scattering to the component?

No. It was done, because I had misunderstood that attenuating by the total scattering cross section was a way to account for multiple scattering. (Which it isn't)

Ah okay, so the order parameter that existed in PowderN had a different meaning than Single_crystal order.

I don't think it's wrong to only apply the absorption cross section for the exit path in Single_crystal when order is reached. After a scattering (which is known to have happened since the order is reached), the new wavevector will match the bragg condition of the crystal plane it already scattered in, and have a very high probability to scatter again, reducing the weight to almost 0 if it was used for the remaining path. What would happen if order was larger is the ray reflecting back and forth on that same reflection (and maybe more if it happens that more can be reached), so it would still have a big probability to continue in the direction order 1 predicted, but it would of course be attenuated by the path length, which is what the current order=1 approximates (the real path length would be slightly longer due to the zig-zag path).

Hmmm. I don't quite think I am following you here. The way I see it, when order is reached, we make a monte carlo choice, to set the probability of transmitting to 1. The probability to actually transmit is exp(-total sigma * l), and therefore the factor we should multiply onto the weight is exactly exp(-total sigma * l) and not exp(-abs * l).

With that said, my fix is not good enough, since the total scattering cross section that we access has not yet been modified for the direction of the neutron.

The scattering back and forth on the same reflection is quite common and the current approximation doesn't quite follow it either, as there would only be a 50% chance of the ray leaving with that direction. I think your suggested change would be more accurate (order=1 being a good approximation to order=0) for very small crystals, while for larger crystals I would guess the current approximation would be closer. Could do some tests similar to PR #1767

@willend

willend commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@Lomholy @mads-bertelsen the mctest issue above seems associated with a bad state of the mctest on your branch...
(probably forked off between me introducing a new feature, finding and fixing a bug...)

The one on main works nicely for me.

I have therefore used the "Update branch" button on this PR to rebase against main.

git pull, ./devel/bin/mccode-build-conda and you should hopefully be in a good place?

Otherwise let me know and I can debug with you via zoom tomorrow.

@willend

willend commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

(as a result a new set of tests just started ...)

@Lomholy

Lomholy commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

@Lomholy @mads-bertelsen the mctest issue above seems associated with a bad state of the mctest on your branch... (probably forked off between me introducing a new feature, finding and fixing a bug...)

The one on main works nicely for me.

I have therefore used the "Update branch" button on this PR to rebase against main.

git pull, ./devel/bin/mccode-build-conda and you should hopefully be in a good place?

Otherwise let me know and I can debug with you via zoom tomorrow.

Hi @willend I just tried to run the mctest with a git pull and conda build, as well as a deletion of the "old" mcstas stuff through export MCSTAS=$CONDA_PREFIX/share/mcstas/resources and
rm -rf $MCSTAS. I still get the error unfortunately

The scattering back and forth on the same reflection is quite common and the current approximation doesn't quite follow it either, as there would only be a 50% chance of the ray leaving with that direction. I think your suggested change would be more accurate (order=1 being a good approximation to order=0) for very small crystals, while for larger crystals I would guess the current approximation would be closer. Could do some tests similar to PR #1767

I will perform some tests like the linked PR, and we will see what happens!

@willend

willend commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Hi @willend I just tried to run the mctest with a git pull and conda build, as well as a deletion of the "old" mcstas stuff through export MCSTAS=$CONDA_PREFIX/share/mcstas/resources and
rm -rf $MCSTAS. I still get the error unfortunately

@Lomholy can you give me more details on version of OS, python, conda/micromamba etc? No chance to debug anything from what I currently have access to.

@Lomholy

Lomholy commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

@Lomholy can you give me more details on version of OS, python, conda/micromamba etc? No chance to debug anything from what I currently have access to.

Of Course. I am running a MacOs Tahoe 26.6.2, with a conda version 26.5.0 and a python version 3.14.4.

Would a zoom call be easier? I have time until 11, and after 13.

And I am doing it on an m4 max chip

@Lomholy

Lomholy commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

The scattering back and forth on the same reflection is quite common and the current approximation doesn't quite follow it either, as there would only be a 50% chance of the ray leaving with that direction. I think your suggested change would be more accurate (order=1 being a good approximation to order=0) for very small crystals, while for larger crystals I would guess the current approximation would be closer. Could do some tests similar to PR #1767

No Mads, It seems your original intuition was absolutely correct. I moved the attenuation down such that the beam is attenuated by the updated total cross section, and performed a test, as you can see at the bottom of this post, and it really does not work with the coherent cross section attenuating the beam.

As you said, when we reflect the neutron is very likely to reflect again. This makes the importance choice of always leaving the crystal a quite poor approximation as any peak is simply reduced back in intensity. Therefore it is "not too bad" to simply attenuate with the absorption.

However, is there then a reason not to attenuate with the incoherent cross section on the outgoing path?

Below I made an example with an thin (0.1 mm) YBCO crystal with a fictitiously high incoherent cross section:

billede

Here the left hand image the total (outgoing) attenuation is applied, on the center figure both the incoherent and the absorption is applied, and on the right hand side only the absorption is applied.

I am not quite sure if I have an argument for including the incoherent attenuation, other than the fact that it seems a better approximation, but neither do I have a proper mathematical argument for not including the total cross section (which provides a poor approximation).

Here you have the same plot for a broader theta range:
billede

@mads-bertelsen

Copy link
Copy Markdown
Contributor

@Lomholy can you give me more details on version of OS, python, conda/micromamba etc? No chance to debug anything from what I currently have access to.

Of Course. I am running a MacOs Tahoe 26.6.2, with a conda version 26.5.0 and a python version 3.14.4.

Would a zoom call be easier? I have time until 11, and after 13.

And I am doing it on an m4 max chip

I encountered the bug too and thought it was just something with my build. Looked through it with help of AI and believe I have a good fix on PR #2624

@mads-bertelsen

mads-bertelsen commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

The scattering back and forth on the same reflection is quite common and the current approximation doesn't quite follow it either, as there would only be a 50% chance of the ray leaving with that direction. I think your suggested change would be more accurate (order=1 being a good approximation to order=0) for very small crystals, while for larger crystals I would guess the current approximation would be closer. Could do some tests similar to PR #1767

No Mads, It seems your original intuition was absolutely correct. I moved the attenuation down such that the beam is attenuated by the updated total cross section, and performed a test, as you can see at the bottom of this post, and it really does not work with the coherent cross section attenuating the beam.

As you said, when we reflect the neutron is very likely to reflect again. This makes the importance choice of always leaving the crystal a quite poor approximation as any peak is simply reduced back in intensity. Therefore it is "not too bad" to simply attenuate with the absorption.

However, is there then a reason not to attenuate with the incoherent cross section on the outgoing path?

Below I made an example with an thin (0.1 mm) YBCO crystal with a fictitiously high incoherent cross section:

billede Here the left hand image the total (outgoing) attenuation is applied, on the center figure both the incoherent and the absorption is applied, and on the right hand side only the absorption is applied.

I am not quite sure if I have an argument for including the incoherent attenuation, other than the fact that it seems a better approximation, but neither do I have a proper mathematical argument for not including the total cross section (which provides a poor approximation).

Here you have the same plot for a broader theta range: billede

Great work! My argument for not including incoherent scattering in the attenuation is that the neutrons coming from that reflection to the detector should be attenuated, but rays that are scattered elsewhere should also be incoherently scattered towards this peak, but since we don't simulate the added intensity from the incoherent elsewhere, we are canceling out the effect. You can add extra_order to have additional incoherent scattering after the base order is reached, then you get both the scattering and attention for very little performance cost. It can mean a lot in crystals with significant incoherent cross section.

For some reason I can't download the graphs, but I do see including it seems to converge faster, so my intuition on not including incoherent seems wrong. Is it also better for a large crystal?

@willend

willend commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

I encountered the bug too and thought it was just something with my build. Looked through it with help of AI and believe I have a good fix on PR #2624

@mads-bertelsen thanks. I think I'd rather do this one by hand.

@mads-bertelsen

Copy link
Copy Markdown
Contributor

I encountered the bug too and thought it was just something with my build. Looked through it with help of AI and believe I have a good fix on PR #2624

@mads-bertelsen thanks. I think I'd rather do this one by hand.

All good! Hope the PR helps identify the underlying issue

willend pushed a commit that referenced this pull request Sep 3, 2026
mctest aborts a whole test run with

    TypeError: %d format: a real number is required, not NoneType

in the "Display OK/FAILED" status line whenever it re-enters an
instrument test dir that already contains results from an earlier run.
Reported by users on PR #2622 (crash at the "Display FAILED" line).

How it happens:
- If the per-instrument test dir already exists - e.g. a re-run with
  the same --testdir and --uid, which is exactly how the CI workflows
  invoke mctest - shutil.copytree fails and mctest falls back to
  InstrExampleTest.load() to rebuild the test objects from the JSON
  files saved by the previous run.
- load() restored every stored field except "displaytime", leaving the
  test object with displayed=<saved value> but displaytime=None. The
  "binary already exists" branch of the compile loop skips the
  mcdisplay step as well, so displaytime is never (re)measured.
- The run/status loop then formats test.displaytime with %d, which
  raises TypeError and kills the run before any results are written.
The "Display FAILED" variant (the one seen on PR #2622) occurs when
the first run recorded displayed=false, e.g. because mcdisplay hit the
60 s --displaymax timeout - seen for MCPL instruments such as
ESS_BEER_MCPL / ESS_KVASIR / ESS_butterfly_MCPL_test.

Recreate the error (before this fix):
    mctest --testdir tmp --instr ESS_KVASIR -n 1e4 --uid reproA
    # 1st run: SUCCESS, saves results JSON
    mctest --testdir tmp --instr ESS_KVASIR -n 1e4 --uid reproA
    # 2nd run: "WARNING: Skipped ESS_KVASIR test - did ... exist
    # already??" followed by the TypeError traceback above
The FAILED-branch variant is forced by making the display time out in
the first run, e.g. adding --displaymax 0.

Fix:
- InstrExampleTest.load() now restores displaytime from the saved JSON
  (via obj.get, so JSON files written without the key still load).
- The Display OK/FAILED status lines fall back to 0 s when displaytime
  is None, which also covers a binary present without saved display
  time (e.g. a first run interrupted between compile and saving).

Verified: both re-run scenarios above now complete with SUCCESS, and
fresh mctest runs are unaffected.
@Lomholy

Lomholy commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

For some reason I can't download the graphs, but I do see including it seems to converge faster, so my intuition on not including incoherent seems wrong. Is it also better for a large crystal?

Below I conducted the test again but changed the incoherent cross section a bit, and made the crystal thick. The convergence does not seem to be certain across parameters. Overall I think the approach with just setting the extra order high is probably better than this. I will remove the addition from this PR.

billede

Thank you both for swift responses and good discussions!

@Lomholy

Lomholy commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

The PowderN component lies within quite a lot of instruments, and I therefore only post screenshots of the failed tests.

billede billede billede

I will run it again at higher n count to see if the problem persists with the test_powders instrument. I am however uncertain as to how this change could have affected any display function, so the display errors are likely not from this PR.

@Lomholy

Lomholy commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

When running the Powders16 test again I get 88 % with ncount=1e7:

billede

And the display errors still apply.

@willend

willend commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@Lomholy I will run a series of tests just with Test_Powders to to check if we need to (slightly) update the test value for _16...

Am I correct that the PR title ended up being slightly off? (It looks like only PowderN was touched in the end?)

@Lomholy

Lomholy commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

@Lomholy I will run a series of tests just with Test_Powders to to check if we need to (slightly) update the test value for _16...

Cool Thank you!

Am I correct that the PR title ended up being slightly off? (It looks like only PowderN was touched in the end?)

Yes, I will update it. Only PowderN was touched upon in the end :D

@Lomholy Lomholy changed the title Powder n and single crystal minorfixes PowderN remove erroneous order parameter Sep 3, 2026
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