-
Notifications
You must be signed in to change notification settings - Fork 21
q_to_tth & tth_to_q #178
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
q_to_tth & tth_to_q #178
Changes from 4 commits
7841ff2
1b0fa19
4e85b0a
a33482b
3efde28
a53461f
2a5a819
64d8693
728ff36
490486c
d481253
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 |
---|---|---|
|
@@ -763,25 +763,27 @@ def q_to_tth(self): | |
|
||
2\theta_n = 2 \arcsin\left(\frac{\lambda q}{4 \pi}\right) | ||
|
||
Function adapted from scikit-beam. Thanks to those developers | ||
|
||
Parameters | ||
---------- | ||
q : array | ||
An array of :math:`q` values | ||
The array of :math:`q` values | ||
|
||
wavelength : float | ||
Wavelength of the incoming x-rays | ||
|
||
Function adapted from scikit-beam. Thanks to those developers | ||
|
||
Returns | ||
------- | ||
two_theta : array | ||
An array of :math:`2\theta` values in radians | ||
The array of :math:`2\theta` values in radians | ||
""" | ||
q = self.on_q[0] | ||
q = np.asarray(q) | ||
wavelength = float(self.wavelength) | ||
pre_factor = wavelength / (4 * np.pi) | ||
if np.any(np.abs(q * pre_factor) > 1): | ||
raise ValueError("Please check if you entered an incorrect wavelength or q value.") | ||
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. I think we would want to raise a value error here because otherwise the code proceeds with tth = nan. |
||
return np.rad2deg(2.0 * np.arcsin(q * pre_factor)) | ||
|
||
def tth_to_q(self): | ||
|
@@ -800,25 +802,28 @@ def tth_to_q(self): | |
|
||
q = \frac{4 \pi \sin\left(\frac{2\theta}{2}\right)}{\lambda} | ||
|
||
|
||
Function adapted from scikit-beam. Thanks to those developers. | ||
|
||
Parameters | ||
---------- | ||
two_theta : array | ||
An array of :math:`2\theta` values in units of degrees | ||
The array of :math:`2\theta` values in units of degrees | ||
|
||
wavelength : float | ||
Wavelength of the incoming x-rays | ||
|
||
Function adapted from scikit-beam. Thanks to those developers. | ||
|
||
Returns | ||
------- | ||
q : array | ||
An array of :math:`q` values in the inverse of the units | ||
The array of :math:`q` values in the inverse of the units | ||
of ``wavelength`` | ||
""" | ||
two_theta = np.asarray(np.deg2rad(self.on_tth[0])) | ||
if np.any(two_theta > np.pi): | ||
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. as above, I think this isn't doing quite what we want. |
||
raise ValueError( | ||
"Two theta exceeds 180 degrees. Please check if invalid values were entered " | ||
"or if degrees were incorrectly specified as radians." | ||
) | ||
wavelength = float(self.wavelength) | ||
pre_factor = (4 * np.pi) / wavelength | ||
return pre_factor * np.sin(two_theta / 2) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -231,6 +231,97 @@ def test_diffraction_objects_equality(inputs1, inputs2, expected): | |
assert (diffraction_object1 == diffraction_object2) == expected | ||
|
||
|
||
def test_q_to_tth(): | ||
# Valid q values that should result in 0-180 tth values after conversion | ||
# expected tth values are 2*arcsin(q) in degrees | ||
actual = DiffractionObject(wavelength=4 * np.pi) | ||
setattr(actual, "on_q", [[0, 0.2, 0.4, 0.6, 0.8, 1], [1, 2, 3, 4, 5, 6]]) | ||
actual_tth = actual.q_to_tth() | ||
expected_tth = [0, 23.07392, 47.15636, 73.73980, 106.26020, 180] | ||
assert np.allclose(actual_tth, expected_tth) | ||
|
||
|
||
params_q_to_tth_bad = [ | ||
# UC1: user did not specify wavelength | ||
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. Do we want to not allow missing wavelengths? This makes these DOs harder to use, but also makes them less useful. There may be a middle ground where we allow it, but let the user know that much of the functionality goes away without it. We could also trigger a workflow that requests the wavelength, but still allows users to override that. This would encourage them to enter a wavelength but not insist? 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 this sounds good. I think user can also directly set attributes so that they can use DO without a wavelength. I would suggest to prompt user inputs in the 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. For now, how about we just print a warning message if no wavelength is supplied. Something like "INFO: no wavelength has been specified. You can continue to use the DiffractionObject but some of it's powerful features will not be available. To specify a wavelength...." |
||
( | ||
[None, [0, 0.2, 0.4, 0.6, 0.8, 1]], | ||
"Wavelength is not specified. Please provide a valid wavelength, " | ||
"e.g., DiffractionObject(wavelength=0.71).", | ||
), | ||
# UC2: user specified invalid q values that result in tth > 180 degrees | ||
( | ||
[4 * np.pi, [0.2, 0.4, 0.6, 0.8, 1, 1.2]], | ||
"Wavelength * q > 4 * pi. Please check if you entered an incorrect wavelength or q value.", | ||
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. I think this behavior is good. The first sentence is a bit mathematical and cryptic. Is there a more "chemist friendly" way of saying it? 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. Does "The combination of wavelength and q values is too large" sound good? 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. How about: "the supplied q-array and wavelength will result in an impossible two-theta. Please check these values and re-instantiate the DiffractionObject" |
||
), | ||
# UC3: user specified a wrong wavelength that result in tth > 180 degrees | ||
( | ||
[100, [0, 0.2, 0.4, 0.6, 0.8, 1]], | ||
"Wavelength * q > 4 * pi. Please check if you entered an incorrect wavelength or q value.", | ||
), | ||
# UC4: user specified an empty q array | ||
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. It is ok to specify no q-array (you can instantiate on tth for example). Presumably this is only an error if the the DO is being created with q data. So the behavior is ok, but we may want to tweak the error message. I think a more general error is if the x and y arrays are not the same length, whether they are q, tth or d. I suggest to handle that more general case? Finally, do we also want to specify what error is raised as well as the message? |
||
([4 * np.pi, []], "Q array is empty. Please provide valid q values."), | ||
# UC5: user specified a non-numeric value in q array | ||
( | ||
[4 * np.pi, [0, 0.2, 0.4, 0.6, 0.8, "invalid"]], | ||
"Invalid value found in q array. Please ensure all values are numeric.", | ||
), | ||
] | ||
|
||
|
||
@pytest.mark.parametrize("inputs, expected", params_q_to_tth_bad) | ||
def test_q_to_tth_bad(inputs, expected): | ||
actual = DiffractionObject(wavelength=inputs[0]) | ||
setattr(actual, "on_q", [inputs[1], [1, 2, 3, 4, 5, 6]]) | ||
with pytest.raises(ValueError): | ||
actual.q_to_tth() | ||
|
||
|
||
def test_tth_to_q(): | ||
# Valid tth values between 0-180 degrees | ||
# expected q vales are sin15, sin30, sin45, sin60, sin90 | ||
actual = DiffractionObject(wavelength=4 * np.pi) | ||
setattr(actual, "on_tth", [[0, 30, 60, 90, 120, 180], [1, 2, 3, 4, 5, 6]]) | ||
actual_q = actual.tth_to_q() | ||
expected_q = [0, 0.258819, 0.5, 0.707107, 0.866025, 1] | ||
assert np.allclose(actual_q, expected_q) | ||
|
||
|
||
params_tth_to_q_bad = [ | ||
# UC1: user did not specify wavelength | ||
( | ||
[None, [0, 30, 60, 90, 120, 180]], | ||
"Wavelength is not specified. Please provide a valid wavelength, " | ||
"e.g., DiffractionObject(wavelength=0.71).", | ||
), | ||
# UC2: user specified an invalid tth value of > 180 degrees | ||
( | ||
[4 * np.pi, [0, 30, 60, 90, 120, 181]], | ||
"Two theta exceeds 180 degrees. Please check the input values for errors.", | ||
), | ||
# UC3: user did not specify wavelength and specified invalid tth values | ||
( | ||
[None, [0, 30, 60, 90, 120, 181]], | ||
"Wavelength is not specified. Please provide a valid wavelength, " | ||
"e.g., DiffractionObject(wavelength=0.71).", | ||
), | ||
# UC4: user specified an empty two theta array | ||
([4 * np.pi, []], "Two theta array is empty. Please provide valid two theta values."), | ||
# UC5: user specified a non-numeric value in two theta array | ||
( | ||
[4 * np.pi, [0, 30, 60, 90, 120, "invalid"]], | ||
"Invalid value found in two theta array. Please ensure all values are numeric.", | ||
), | ||
] | ||
|
||
|
||
@pytest.mark.parametrize("inputs, expected", params_tth_to_q_bad) | ||
def test_tth_to_q_bad(inputs, expected): | ||
actual = DiffractionObject(wavelength=inputs[0]) | ||
setattr(actual, "on_tth", [inputs[1], [1, 2, 3, 4, 5, 6]]) | ||
with pytest.raises(ValueError, match=expected): | ||
actual.tth_to_q() | ||
|
||
|
||
def test_dump(tmp_path, mocker): | ||
x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) | ||
directory = Path(tmp_path) | ||
|
Uh oh!
There was an error while loading. Please reload this page.