-
Notifications
You must be signed in to change notification settings - Fork 3
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
transform_coords building blocks for gravity correction in theta vs wavelength transformations #50
base: main
Are you sure you want to change the base?
Changes from 7 commits
7987ca4
d42aeb5
3bdda75
90ccbfa
04d2951
91e88bc
9307247
ee573f1
6974b1e
a0023d3
a5294ae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import scipp as sc | ||
from scipp.constants import neutron_mass, h, g | ||
|
||
|
||
def to_velocity(wavelength): | ||
return sc.to_unit(h / (wavelength * neutron_mass), sc.units.m / sc.units.s, | ||
copy=False) | ||
|
||
|
||
# Derivative of y with respect to z | ||
def to_y_dash(wavelength, sample_position, detector_position): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this use |
||
diff = (detector_position - sample_position) | ||
velocity_sq = to_velocity(wavelength) | ||
velocity_sq *= velocity_sq | ||
gy = sc.vector(value=[0, -1, 0]) * g | ||
# dy due to gravity = -0.5gt^2 = -0.5g(dz/dv)^2 | ||
# therefore y'(z) = dy/dz - 0.5g.dz/dv^2 / dz | ||
return (-0.5 * sc.norm(gy) | ||
* diff.fields.z / velocity_sq) + (diff.fields.y / diff.fields.z) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is bound to break since it hard-codes the direction of gravity. I would recommend to require a |
||
|
||
|
||
def to_scattering_angle(w, wavelength, detector_id, position, sample_position): | ||
z_origin = sample_position.fields.z | ||
y_origin = sample_position.fields.y | ||
z_measured = position.fields.z | ||
y_dash = to_y_dash(wavelength, sample_position, position) | ||
height = y_dash * (z_measured - z_origin) + y_origin | ||
return sc.atan2(y=height, x=z_measured) - w | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hard coding some directions as well? Can we define/require, e.g., a sample-normal vector instead? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, fully agree. Would be much happier if we did this with configurable beam and sample normal (up) directions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would not call it |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import scipp as sc | ||
from scipp.constants import g, h, neutron_mass | ||
from ess.reflectometry import transform_coords | ||
import numpy as np | ||
|
||
|
||
def test_y_dash_for_gravitational_effect(): | ||
sample_position = sc.vector(value=[0, 0, 0], unit=sc.units.m) | ||
detector_position = sc.vector(value=[0, 0.5, 1], unit=sc.units.m) | ||
|
||
# Approximate cold-neutron velocities | ||
vel = 1000 * (sc.units.m / sc.units.s) | ||
wav = sc.to_unit(h / (vel * neutron_mass), | ||
unit=sc.units.angstrom) | ||
|
||
grad = transform_coords.to_y_dash(wavelength=wav, sample_position=sample_position, | ||
detector_position=detector_position) | ||
|
||
scattered_beam = detector_position - sample_position | ||
no_gravity_grad = scattered_beam.fields.y / scattered_beam.fields.z | ||
gravity_effect_grad = (-0.5 * g * scattered_beam.fields.z / (vel * vel)) | ||
assert sc.isclose(grad, no_gravity_grad + gravity_effect_grad).value | ||
|
||
|
||
def test_y_dash_with_different_velocities(): | ||
sample_position = sc.vector(value=[0, 0, 0], unit=sc.units.m) | ||
detector_position = sc.vector(value=[0, 1, 1], unit=sc.units.m) | ||
|
||
vel = 1000 * (sc.units.m / sc.units.s) | ||
wav = sc.to_unit(h / (vel * neutron_mass), | ||
unit=sc.units.angstrom) | ||
|
||
# In this setup the faster the neutrons the closer d'y(z) tends to 1.0 | ||
grad = transform_coords.to_y_dash(wavelength=wav, | ||
sample_position=sample_position, | ||
detector_position=detector_position) | ||
assert sc.less(grad, 1 * sc.units.one).value | ||
|
||
vel *= 2 | ||
wav = sc.to_unit(h / (vel * neutron_mass), | ||
unit=sc.units.angstrom) | ||
grad_fast = transform_coords.to_y_dash(wavelength=wav, | ||
sample_position=sample_position, | ||
detector_position=detector_position) | ||
# Testing that gravity has greater influence on slow neutrons. | ||
assert sc.less(grad, grad_fast).value | ||
|
||
|
||
def _angle(a, b): | ||
return sc.acos(sc.dot(a, b) / (sc.norm(a) * sc.norm(b))) | ||
|
||
|
||
def test_scattering_angle(): | ||
sample_position = sc.vector(value=[0, 0, 0], unit=sc.units.m) | ||
detector_position = sc.vector(value=[0, 1, 1], unit=sc.units.m) | ||
scattered_beam = detector_position - sample_position | ||
beam_direction = sc.vector(value=[0, 0, 1], unit=sc.units.m) | ||
no_gravity_angle = _angle(scattered_beam, beam_direction) | ||
|
||
vel = 1000 * (sc.units.m / sc.units.s) | ||
wav = sc.to_unit(h / (vel * neutron_mass), | ||
unit=sc.units.angstrom) | ||
|
||
angle = transform_coords.to_scattering_angle(w=0 * sc.units.rad, | ||
wavelength=wav, | ||
detector_id=None, | ||
position=detector_position, | ||
sample_position=sample_position) | ||
assert sc.less(angle, no_gravity_angle).value | ||
|
||
gravity_shift_y = -0.5 * g * (scattered_beam.fields.z ** 2 / vel ** 2) | ||
expected = _angle(scattered_beam + gravity_shift_y | ||
* sc.vector(value=[0, 1, 0]), beam_direction) | ||
assert sc.isclose(angle, expected).value | ||
|
||
|
||
def test_det_wavelength_to_wavelength_scattering_angle(): | ||
# comparible with cold-neutrons from moderator | ||
vel = 2000 * (sc.units.m / sc.units.s) | ||
wav = sc.to_unit(h / (vel * neutron_mass), | ||
unit=sc.units.angstrom) | ||
sample_position = sc.vector(value=[0, 0, 0], unit=sc.units.m) | ||
source_position = sc.vector(value=[0, 0, -10], unit=sc.units.m) | ||
detector_position = sc.vector(value=[0, 1, 1], unit=sc.units.m) | ||
|
||
coords = {} | ||
coords["sample_position"] = sample_position | ||
coords["source_position"] = source_position | ||
coords["position"] = detector_position | ||
coords["wavelength"] = wav | ||
coords["w"] = 0.0 * sc.units.rad | ||
coords["detector_id"] = 0.0 * sc.units.one | ||
measurement = sc.DataArray(data=1.0 * sc.units.one, coords=coords) | ||
|
||
settings = {'scattering_angle': transform_coords.to_scattering_angle} | ||
transformed = sc.transform_coords(x=measurement, | ||
coords=['wavelength', 'scattering_angle'], | ||
graph=settings) | ||
assert sc.isclose(transformed.coords['scattering_angle'], | ||
(np.pi / 4) * sc.units.rad, atol=1e-4 * sc.units.rad).value | ||
|
||
# We now check the sample angle. Setting to previous final scattering angle | ||
# should yield a scattering angle of 0. | ||
measurement.coords["w"] = transformed.coords['scattering_angle'] | ||
transformed = sc.transform_coords(x=measurement, | ||
coords=['wavelength', 'scattering_angle'], | ||
graph=settings) | ||
assert sc.isclose(transformed.coords['scattering_angle'], | ||
0.0 * sc.units.rad).value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rewrite this to do large operations last. As written here we first create multiple large intermediates, consuming a lot of memory and compute. Rewrite to multiply with
wavelength
as the very last step. Alsoto_unit
could be done before.