Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cylinder absorp corr #532

Merged
merged 84 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
df1bab0
feat: absorption correction
jokasimr Jul 26, 2024
3a35667
fix: translate quadrature
jokasimr Jul 26, 2024
7455339
fix: deal with case when quadrature and physical cylinder are already…
jokasimr Jul 26, 2024
f53d083
fix: use standard spherical coordinates
jokasimr Jul 26, 2024
39135ea
tests: add cylinder intersection and quadrature tests
jokasimr Jul 29, 2024
520eedb
quadrature tests
jokasimr Jul 29, 2024
19107c1
more quadrature tests
jokasimr Jul 29, 2024
cc7b9ed
remove unused
jokasimr Jul 29, 2024
13bea5a
better names for quadratures
jokasimr Jul 29, 2024
1a85b8e
docs: add usage example
jokasimr Jul 29, 2024
7dceab7
fix: better handling of input dims
jokasimr Jul 30, 2024
00de9f8
fix: don't adapt quadrature with R/H ratio, it doesn't seem to make a…
jokasimr Aug 2, 2024
0b21888
feat: allow computing transmission map at any point (not only infinit…
jokasimr Aug 2, 2024
4b2587e
fix: better name
jokasimr Aug 2, 2024
5844be2
docs: add to index
jokasimr Aug 2, 2024
131859a
test: more cylinder intersection tests
jokasimr Aug 5, 2024
a2ae114
add material example
jokasimr Aug 5, 2024
a23032c
fix: adapt quadrature to cylinder shape
jokasimr Aug 5, 2024
21c5329
absorption coefficient -> attenuation coefficient
jokasimr Aug 5, 2024
00e6738
fix docs
jokasimr Aug 5, 2024
a1d7079
fix: handle units
jokasimr Sep 23, 2024
2e3a599
docs: better docstrings
jokasimr Sep 23, 2024
da87e1d
fix: handle units
jokasimr Sep 23, 2024
86f25ed
typo
jokasimr Sep 23, 2024
db12bdd
feat: reduce memory usage to avoid oom
jokasimr Sep 24, 2024
3abdb60
make interface explicit
jokasimr Sep 24, 2024
8ae2693
test: mc method
jokasimr Sep 24, 2024
ce57d0a
test: transmission correction
jokasimr Sep 24, 2024
44bf714
feat: choose mc samples
jokasimr Sep 24, 2024
7b74c61
refactor test and some docs
jokasimr Sep 24, 2024
f9ea772
type annotations
jokasimr Sep 25, 2024
792dcb3
fix: reduce size before broadcasting
jokasimr Sep 27, 2024
7bf4e98
Update src/scippneutron/absorption/cylinder.py
jokasimr Oct 2, 2024
e8480cc
docs: add references to source of quadratures
jokasimr Oct 2, 2024
7a08bda
Merge branch 'cylinder-absorp-corr' of github.com:scipp/scippneutron …
jokasimr Oct 2, 2024
36c3c74
docs: some more context on the quadratures
jokasimr Oct 2, 2024
8b588c4
fixup
jokasimr Oct 2, 2024
c5d6c8d
fix: set default values to avoid having to specify lots of None value…
jokasimr Oct 9, 2024
06bbabb
fix: use the atoms module to get scattering params
jokasimr Oct 9, 2024
880ff8f
fix: update the example
jokasimr Oct 9, 2024
2cfb05b
Update src/scippneutron/absorption/quadratures.py
jokasimr Nov 5, 2024
ab2dacd
feat: absorption correction
jokasimr Jul 26, 2024
fe24d09
fix: translate quadrature
jokasimr Jul 26, 2024
85ea46b
fix: deal with case when quadrature and physical cylinder are already…
jokasimr Jul 26, 2024
4469f9f
fix: use standard spherical coordinates
jokasimr Jul 26, 2024
43b8095
tests: add cylinder intersection and quadrature tests
jokasimr Jul 29, 2024
78df37d
quadrature tests
jokasimr Jul 29, 2024
6463766
more quadrature tests
jokasimr Jul 29, 2024
249bbe2
remove unused
jokasimr Jul 29, 2024
9833957
better names for quadratures
jokasimr Jul 29, 2024
2e58017
docs: add usage example
jokasimr Jul 29, 2024
3d0dbcb
fix: better handling of input dims
jokasimr Jul 30, 2024
af5f2b1
fix: don't adapt quadrature with R/H ratio, it doesn't seem to make a…
jokasimr Aug 2, 2024
07dfd37
feat: allow computing transmission map at any point (not only infinit…
jokasimr Aug 2, 2024
eb56e6c
fix: better name
jokasimr Aug 2, 2024
181447c
docs: add to index
jokasimr Aug 2, 2024
1612bcb
test: more cylinder intersection tests
jokasimr Aug 5, 2024
646f885
add material example
jokasimr Aug 5, 2024
0ee8132
fix: adapt quadrature to cylinder shape
jokasimr Aug 5, 2024
20b3e45
absorption coefficient -> attenuation coefficient
jokasimr Aug 5, 2024
4feec1b
fix docs
jokasimr Aug 5, 2024
64fedc9
fix: handle units
jokasimr Sep 23, 2024
1cfc598
docs: better docstrings
jokasimr Sep 23, 2024
3b00f4f
fix: handle units
jokasimr Sep 23, 2024
8fb23bd
typo
jokasimr Sep 23, 2024
f5de322
feat: reduce memory usage to avoid oom
jokasimr Sep 24, 2024
7ecb742
make interface explicit
jokasimr Sep 24, 2024
6dd2f36
test: mc method
jokasimr Sep 24, 2024
56eb7fe
test: transmission correction
jokasimr Sep 24, 2024
339257c
feat: choose mc samples
jokasimr Sep 24, 2024
727c2b8
refactor test and some docs
jokasimr Sep 24, 2024
1be8b86
type annotations
jokasimr Sep 25, 2024
256c15d
fix: reduce size before broadcasting
jokasimr Sep 27, 2024
030e439
docs: add references to source of quadratures
jokasimr Oct 2, 2024
fc8417b
Update src/scippneutron/absorption/cylinder.py
jokasimr Oct 2, 2024
bb8ebc9
docs: some more context on the quadratures
jokasimr Oct 2, 2024
fb4a70b
fixup
jokasimr Oct 2, 2024
ef32b4b
fix: set default values to avoid having to specify lots of None value…
jokasimr Oct 9, 2024
c08166f
fix: use the atoms module to get scattering params
jokasimr Oct 9, 2024
220aebf
fix: update the example
jokasimr Oct 9, 2024
ac63431
Update src/scippneutron/absorption/quadratures.py
jokasimr Nov 5, 2024
990bcfe
docs: improve example
jokasimr Nov 7, 2024
51f2f41
Merge branch 'cylinder-absorp-corr' of github.com:scipp/scippneutron …
jokasimr Nov 7, 2024
dd2451c
Merge remote-tracking branch 'origin/main' into cylinder-absorp-corr
jokasimr Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions docs/user-guide/absorption-correction.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a6de8431-db39-49a5-b5dc-1afee82bc7d2",
"metadata": {},
"source": [
"# Absorption correction example\n",
"\n",
"Assuming single scattering, the neutron intensity per unit solid angle at the position $\\mathbf{p}$ is modeled as\n",
"$$\n",
"I(\\lambda, \\mathbf{x}) = \\int_{sample} S(\\lambda, \\widehat{\\mathbf{p}-\\mathbf{x}}) T(\\lambda, \\mathbf{p}, \\mathbf{x}) \\ d\\mathbf{x}\n",
"$$\n",
"where $S(\\lambda, \\hat{\\mathbf{s}})$ is the probability that a neutron with wavelength $\\lambda$ scatters in the direction $\\hat{\\mathbf{s}}$ and $T(\\lambda, \\mathbf{p}, \\mathbf{x})$ is the transmission rate for neutrons scattering at $\\mathbf{x}$ towards $\\mathbf{p}$. Here the $\\widehat{\\quad \\cdot\\quad}$ means that the vector is normalized to length 1.\n",
"\n",
"If the detector is far away from the sample relative to the size of the sample then $\\widehat{\\mathbf{p}-\\mathbf{x}}$ is close to constant in the integral, or if $S$ represents inhomogeneous scattering, the $S$ term does not depend on $\\mathbf{x}$ and can be moved outside of the integtral:\n",
"$$\n",
"I(\\lambda, \\mathbf{x}) \\approx S(\\lambda, \\widehat{\\mathbf{p} -\\bar{\\mathbf{x}}}) \\int_{sample} T(\\lambda, \\mathbf{p}, \\mathbf{x}) \\ d\\mathbf{x}\n",
"$$\n",
"where $\\bar{\\mathbf{x}}$ denotes the center of the sample.\n",
"\n",
"To compute the scattering probabiltiy $S$ from the intensity $I$ we need to estimate the term\n",
"$$\n",
"C(\\lambda, \\mathbf{p}) = \\int_{sample} T(\\lambda, \\mathbf{p}, \\mathbf{x}) \\ d\\mathbf{x}.\n",
"$$\n",
"This is the \"absorption correction\" term.\n",
"\n",
"The transmission fraction is a function of path length $L$ of the neutron going through the sample\n",
"$$\n",
"T(\\lambda, \\mathbf{p}, \\mathbf{x}) = \\exp{\\big(-\\mu(\\lambda) L(\\hat{\\mathbf{b}}, \\mathbf{p}, \\mathbf{x})\\big)}\n",
"$$\n",
"where $\\mu$ is material dependent and $\\hat{\\mathbf{b}}$ is the direction of the incoming neutron.\n",
"\n",
"The path length through the sample depends on the shape of the sample, the scattering point $\\mathbf{x}$ and the detection position $\\mathbf{p}$.\n",
"\n",
"To compute $C(\\lambda, \\mathbf{p})$ you can use\n",
"```python\n",
"scippneutron.absorption.base.compute_transmission_map(\n",
" shape,\n",
" material,\n",
" beam_direction,\n",
" wavelength,\n",
" detector_position\n",
")\n",
"```\n",
"where `shape` and `material` are sample properties:\n",
"\n",
"* `shape` defines `L`\n",
"* `material` defines $\\mu$\n",
"* `beam_direction` is $\\hat{\\mathbf{b}}$\n",
"* `wavelength` is $\\lambda$\n",
"* `detector_position` is $\\mathbf{p}$"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "07bd8cdb-c18a-48e4-a60a-041a0d4cd07c",
"metadata": {},
"outputs": [],
"source": [
"import scipp as sc\n",
"import numpy as np\n",
"\n",
"from scippneutron.absorption import compute_transmission_map, Cylinder, Material\n",
"from scippneutron.atoms import ScatteringParams\n",
"\n",
"\n",
"# Create a material with scattering parameters to represent a Vanadium sample\n",
"sample_material = Material(scattering_params=ScatteringParams.for_isotope('V'), effective_sample_number_density=sc.scalar(1, unit='1/angstrom**3'))\n",
"\n",
"# Create a sample shape\n",
"sample_shape = Cylinder(\n",
" symmetry_line=sc.vector([0, 1, 0]),\n",
" # center_of_base is placed slightly below the origin, so that the center of the cylinder is at the origin\n",
" center_of_base=sc.vector([0, -.5, 0], unit='cm'),\n",
" radius=sc.scalar(1., unit='cm'),\n",
" height=sc.scalar(0.3, unit='cm')\n",
")\n",
"\n",
"# Create detector positions, in this case the detector positions are placed in a sphere around the sample\n",
"theta = sc.linspace('theta', 0, np.pi, 60, endpoint=True, unit='rad')\n",
"phi = sc.linspace('phi', 0, 2 * np.pi, 120, endpoint=False, unit='rad')\n",
"r = sc.array(dims='r', values=[5.], unit='m')\n",
"dims = (*r.dims, *theta.dims, *phi.dims)\n",
"\n",
"# Detector positions in grid on a sphere around the origin\n",
"detector_positions = sc.vectors(\n",
" dims=dims,\n",
" values=sc.concat(\n",
" [\n",
" r * sc.sin(theta) * sc.cos(phi),\n",
" sc.broadcast(r * sc.cos(theta), sizes={**r.sizes, **theta.sizes, **phi.sizes}),\n",
" r * sc.sin(theta) * sc.sin(phi),\n",
" ],\n",
" dim='row',\n",
" )\n",
" .transpose([*dims, 'row'])\n",
" .values,\n",
" unit=r.unit,\n",
")\n",
"\n",
"def transmission_fraction(quadrature_kind):\n",
" 'Evaluate the transmission fraction using the selected quadrature kind'\n",
" da = compute_transmission_map(\n",
" sample_shape,\n",
" sample_material,\n",
" beam_direction=sc.vector([0, 0, 1]),\n",
" wavelength=sc.linspace('wavelength', 0.5, 2.5, 10, unit='angstrom'),\n",
" detector_position=detector_positions,\n",
" quadrature_kind=quadrature_kind,\n",
" )\n",
" da.coords['phi'] = phi\n",
" da.coords['theta'] = theta\n",
" return da\n",
"\n",
"\n",
"def show_correction_map(da):\n",
" 'Plotting utility'\n",
" return (\n",
" da['wavelength', 0]['r', 0].plot() /\n",
" da['wavelength', 0]['r', 0]['theta', da.sizes['theta']//2].plot() /\n",
" da['wavelength', 0]['r', 0]['theta', da.sizes['theta']//2]['phi', da.sizes['phi']//4 - da.sizes['phi']//6:da.sizes['phi']//4 + da.sizes['phi']//6].plot() /\n",
" da['wavelength', 0]['r', 0]['theta', da.sizes['theta']//2]['phi', da.sizes['phi']//2 - da.sizes['phi']//6:da.sizes['phi']//2 + da.sizes['phi']//6].plot() /\n",
" da['wavelength', 0]['r', 0]['phi', da.sizes['phi']//2].plot()\n",
" )"
Copy link
Member

Choose a reason for hiding this comment

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

Please add y-axis labels.

]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6f03111b-8d1c-4c82-a9e9-b2fcde5c1003",
"metadata": {},
"outputs": [],
"source": [
"transmission_fraction('medium')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "abb0d783-9a57-4daa-b4dc-7295044beb78",
"metadata": {},
"outputs": [],
"source": [
"show_correction_map(transmission_fraction('cheap'))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1512c244-118e-40a0-ac88-1510504603da",
"metadata": {},
"outputs": [],
"source": [
"show_correction_map(transmission_fraction('medium'))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d34de43c-0341-4656-b739-656cb3aff4a1",
"metadata": {},
"outputs": [],
"source": [
"show_correction_map(transmission_fraction('expensive'))"
]
},
{
"cell_type": "markdown",
"id": "c04f6dcc-5ad9-4204-9de6-68a88650b067",
"metadata": {},
"source": [
"## What quadrature should I use?\n",
"\n",
"Use `medium` first, if it'd not good enough try `expensive`."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
1 change: 1 addition & 0 deletions docs/user-guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ recipes
masking-tool
wfm/index
algorithms-background/index
absorption-correction
```
6 changes: 6 additions & 0 deletions src/scippneutron/absorption/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .base import compute_transmission_map
from .cylinder import Cylinder
from .material import Material


__all__ = ('compute_transmission_map', 'Cylinder', 'Material')
101 changes: 101 additions & 0 deletions src/scippneutron/absorption/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from functools import partial
from typing import Any

import scipp as sc

from .material import Material
from .types import SampleShape


def compute_transmission_map(
sample_shape: SampleShape,
sample_material: Material,
beam_direction: sc.Variable,
wavelength: sc.Variable,
detector_position: sc.Variable,
quadrature_kind: Any = 'medium',
) -> sc.DataArray:
points, weights = sample_shape.quadrature(quadrature_kind)
transmission = _integrate_transmission_fraction(
partial(
_single_scatter_distance_through_sample,
sample_shape,
points,
beam_direction,
),
partial(_transmission_fraction, sample_material),
points,
weights,
detector_position,
wavelength,
)
return sc.DataArray(
data=transmission / sample_shape.volume,
coords={'detector_position': detector_position, 'wavelength': wavelength},
)


def _single_scatter_distance_through_sample(
sample_shape, scatter_point, initial_direction, scatter_direction
):
L1 = sample_shape.beam_intersection(scatter_point, -initial_direction)
L2 = sample_shape.beam_intersection(scatter_point, scatter_direction)
return L1 + L2


def _transmission_fraction(material, distance_through_sample, wavelength):
return sc.exp(
-(material.attenuation_coefficient(wavelength) * distance_through_sample).to(
unit='dimensionless', copy=False
)
)


def _integrate_transmission_fraction(
distance_through_sample,
transmission,
points,
weights,
detector_position,
wavelengths,
):
# If size after broadcast is too large
# then don't vectorize the operation to avoid OOM
if points.size * detector_position.size > 20_000_000:
out = []
dim = detector_position.dims[0]
for i in range(detector_position.sizes[dim]):
out.append( # noqa: PERF401
_integrate_transmission_fraction(
distance_through_sample,
transmission,
points,
weights,
detector_position[dim, i],
wavelengths,
)
)

return sc.concat(
out,
dim=dim,
)

scatter_direction = detector_position - points.to(unit=detector_position.unit)
scatter_direction /= sc.norm(scatter_direction)
Ltot = distance_through_sample(scatter_direction)

# The Ltot array is already large, to avoid OOM, don't vectorize this operation
return sc.concat(
[
# Instead of broadcast multiply and sum, use matvec for efficiency
# this becomes relevant when the number of wavelength points grows
sc.array(
dims=(tf := transmission(Ltot, w)).dims[:-1],
values=tf.values @ weights.values,
unit=tf.unit * weights.unit,
)
for w in wavelengths
],
dim=wavelengths.dim,
)
Loading