-
Notifications
You must be signed in to change notification settings - Fork 12
Adds conversion between material depth and attenuation #2049
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
Open
Matt-Carre
wants to merge
47
commits into
main
Choose a base branch
from
2041_interconversion_depth_attenuation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+172
−6
Open
Changes from 3 commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
6333d02
Initial commit of transmission conversions
Matt-Carre 1040280
added docstrings
Matt-Carre ef61984
initial commit
Matt-Carre 7e8a3f5
adds pydantic validation
Matt-Carre 64c844c
Added pydantic validation
Matt-Carre 4d48090
changes to NonNegativeFloat (accepting 0) where relevant
Matt-Carre da51855
changes to NonNegativeFloat (accepting 0) where relevant, considering…
Matt-Carre e5c341d
fixes linting. going to move tests next update.
Matt-Carre 5215f96
labels tests
Matt-Carre 3d6a920
Merge branch '2040_attenuation_transmission' into 2041_interconversio…
Matt-Carre 273cbff
added test labels
Matt-Carre c9ef824
split tests
Matt-Carre 7d94b52
split tests
Matt-Carre c32281d
merged in 2040
Matt-Carre 10f65f8
adds specific output
Matt-Carre 1a01d56
Merge branch 'main' into 2041_interconversion_depth_attenuation
Matt-Carre 8896dd1
Merge branch 'main' into 2040_attenuation_transmission
Matt-Carre 9c5f36b
Merge branch 'main' into 2041_interconversion_depth_attenuation
Matt-Carre a4fed4d
Merge branch 'main' into 2040_attenuation_transmission
Matt-Carre d4edcc1
Merge branch 'main' into 2041_interconversion_depth_attenuation
Matt-Carre 0e3b3a9
Merge branch 'main' into 2040_attenuation_transmission
Matt-Carre 1651348
Merge branch '2040_attenuation_transmission' of github.com:DiamondLig…
Matt-Carre c243228
rejoins tests
Matt-Carre 3516fcb
Merge branch '2040_attenuation_transmission' into 2041_interconversio…
Matt-Carre 788ad00
rejoined tests
Matt-Carre 7763449
Merge branch '2041_interconversion_depth_attenuation' of github.com:D…
Matt-Carre 3ce2471
updates based on review
Matt-Carre f1c30c5
renames test
Matt-Carre 1fd5e87
fixed more comments
Matt-Carre 7aa9941
fixed more comments
Matt-Carre b5e6663
adds bools to bad input
Matt-Carre 28fce46
fixed based on comments in 2041
Matt-Carre a706c3a
fixes spelling mistake
Matt-Carre a20395e
changes name
Matt-Carre d0874bf
Merge branch '2041_interconversion_depth_attenuation' into 2040_atten…
Matt-Carre 8b1f713
Merge branch 'main' into 2041_interconversion_depth_attenuation
Matt-Carre fbbd278
alters to make sure bool is rejected
Matt-Carre 99ae8fa
Merge branch '2041_interconversion_depth_attenuation' of github.com:D…
Matt-Carre 72389b2
accidentally deleted files, recovered. also ensures strictness in tra…
Matt-Carre 8be2371
work in progress. need to create a basemodel
Matt-Carre 60004d6
need to fix up non-negative floats but otherwise all up to date
Matt-Carre 7b3c237
adds a base model to check an input value. Changed back to strictfloa…
Matt-Carre d32b52b
Merge branch 'main' into 2041_interconversion_depth_attenuation
Matt-Carre 2d9a79d
Merge branch 'main' into 2041_interconversion_depth_attenuation
Matt-Carre b12b936
Merge branch 'main' into 2041_interconversion_depth_attenuation
Matt-Carre 26e0258
Merge branch 'main' into 2041_interconversion_depth_attenuation
Matt-Carre 68566cd
Merge branch 'main' into 2041_interconversion_depth_attenuation
Matt-Carre File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Empty file.
85 changes: 85 additions & 0 deletions
85
src/dodal/common/general_maths/material_absorption_maths.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| from dodal.common.general_maths import transmission_interconversion | ||
|
|
||
|
|
||
| def photon_mass_attenuation_per_unit_length( | ||
| energy_kev: float, | ||
| photon_absorption_factor_per_unit_length: float, | ||
| energy_dependence_exponent: float, | ||
| ): | ||
| """Calculates mass attenuation per unit length. | ||
|
|
||
| Args: | ||
| energy_kev (float): energy | ||
| photon_absorption_factor_per_unit_length (float): photon absorption factor per | ||
| unit length | ||
| energy_dependence_exponent (float): energy dependence exponent | ||
|
|
||
| Returns: | ||
| (float): mass attenuation per unit length. | ||
| """ | ||
| return photon_absorption_factor_per_unit_length * ( | ||
| energy_kev**energy_dependence_exponent | ||
| ) | ||
|
|
||
|
|
||
| def attenuation_at_depth_cm(depth_cm: float, absorption_coefficient_per_cm: float): | ||
| # TODO: check this - not sure if it really is the depth of the wedge? | ||
| """Calculates attenuation in Barnett units. | ||
|
Matt-Carre marked this conversation as resolved.
Outdated
|
||
|
|
||
| Args: | ||
| depth_cm (float): depth of wedge | ||
| absorption_coefficient_per_cm (float): absorption coefficient per cm | ||
|
|
||
| Raises: | ||
| ValueError: If either input value for either depth_cm or absorption_coefficient_ | ||
| per_cm <0.0, raises a value error. | ||
|
Matt-Carre marked this conversation as resolved.
Outdated
|
||
|
|
||
| Returns: | ||
| (float): attenuation in Barnett units | ||
| """ | ||
| if depth_cm < 0.0: | ||
| raise ValueError(f"Negative depth is an invalid input: {depth_cm}") | ||
| if absorption_coefficient_per_cm < 0.0: | ||
| raise ValueError( | ||
| "Invalid absorption, this calculator is no for systems with\ | ||
| optical gain." | ||
| ) | ||
| return transmission_interconversion.attenuation_from_natural_log_of_transmission( | ||
| -(depth_cm * absorption_coefficient_per_cm) | ||
|
Matt-Carre marked this conversation as resolved.
Outdated
|
||
| ) | ||
|
|
||
|
|
||
| def thickness_cm_required_to_attenuate( | ||
| target_attenuation_bn: float, | ||
| absorption_coefficient_per_cm: float, | ||
| ): | ||
| """Calculates material depth in cm. | ||
|
|
||
| Args: | ||
| target_attenuation_bn (float): Target attenuation to meet in Barnett attenuation | ||
|
Matt-Carre marked this conversation as resolved.
Outdated
|
||
| units. | ||
| absorption_coefficient_per_cm (float): absorption coefficient per cm | ||
|
|
||
|
|
||
| Raises: | ||
| ValueError: if attenuation is below zero, or absorption is below the minimum | ||
| meaningful absorption coefficient, a value error is raised | ||
|
|
||
| Returns: | ||
| (float): material depth in cm. | ||
| """ | ||
| minimum_meaningful_absorption_coefficient = 1.0e-14 | ||
|
Matt-Carre marked this conversation as resolved.
|
||
| if ( | ||
| target_attenuation_bn < 0.0 | ||
| or absorption_coefficient_per_cm < minimum_meaningful_absorption_coefficient | ||
| ): | ||
| raise ValueError( | ||
| "Invalid absorption - this calculator is not for transparent media nor thos\ | ||
| e with optical gain." | ||
| ) | ||
| ln_target_t = ( | ||
| transmission_interconversion.natural_log_of_transmission_from_attenuation( | ||
| target_attenuation_bn | ||
| ) | ||
| ) | ||
| return -(ln_target_t / absorption_coefficient_per_cm) | ||
56 changes: 56 additions & 0 deletions
56
src/dodal/common/general_maths/transmission_interconversion.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import math | ||
|
|
||
| canonical_barnett_conversion = -1.0e3 | ||
| reverse_barnett_conversion = -1.0e-3 | ||
|
|
||
|
|
||
| def attenuation_from_natural_log_of_transmission(ln_t: float): | ||
| """Converts from natural log of transmission fraction into Barnett attenuation units | ||
| . | ||
|
|
||
| Args: | ||
| ln_t (float): natural log of transmission fraction | ||
|
|
||
| Returns: | ||
| (float): Barnett attenuation units | ||
| """ | ||
| return canonical_barnett_conversion * ln_t | ||
|
|
||
|
|
||
| def attenuation_from_transmission(transmission_as_fraction: float): | ||
| """Converts from transmission fraction into Barnett attenuation units. | ||
|
|
||
| Args: | ||
| transmission_as_fraction (float): transmission fraction | ||
|
|
||
| Returns: | ||
| (float): Barnett attenuation units. | ||
| """ | ||
| ln_t = math.log(transmission_as_fraction) | ||
| return attenuation_from_natural_log_of_transmission(ln_t) | ||
|
|
||
|
|
||
| def natural_log_of_transmission_from_attenuation(attenuation_bn: float): | ||
| """Converts from Barnett attenuation units into natural log of transmission fraction | ||
| . | ||
|
|
||
| Args: | ||
| attenuation_bn (float): Barnett attenuation units | ||
|
|
||
| Returns: | ||
| (float): natural log of transmission fraction | ||
| """ | ||
| return reverse_barnett_conversion * attenuation_bn | ||
|
|
||
|
|
||
| def transmission_from_attenutation(attenuation_bn: float): | ||
| """Converts from Barnett attenuation units into transmission fraction. | ||
|
|
||
| Args: | ||
| attenuation_bn (float): Barnett attenuation units | ||
|
|
||
| Returns: | ||
| (float): transmission fraction | ||
| """ | ||
| ln_t = natural_log_of_transmission_from_attenuation(attenuation_bn) | ||
| return math.exp(ln_t) |
Empty file.
123 changes: 123 additions & 0 deletions
123
tests/common/general_maths/test_material_absorption_maths.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| import math | ||
|
|
||
| import pytest | ||
|
|
||
| from dodal.common.general_maths import material_absorption_maths | ||
|
|
||
|
|
||
| # happy path | ||
| @pytest.mark.parametrize( | ||
| "energy_kev,photon_absorption_factor_per_unit_length,energy_dependence_exponent," | ||
| "result", | ||
| [ | ||
| (5.042, 1.98e2, -2.717, 2.44170544), # Arbitrary | ||
| (8.3328, 2.5706e3, -2.83, 6.3708311), # Nickel | ||
| (11.9187, 1.48e3, -2.93, 1.03970725), # Gold-Three | ||
| (25.514, 6.48e3, -2.41, 2.63778077), # Silver | ||
| ], | ||
| ) | ||
| def test_photon_mass_attenuation_per_unit_length( | ||
| energy_kev, | ||
| photon_absorption_factor_per_unit_length, | ||
| energy_dependence_exponent, | ||
| result, | ||
| ): | ||
| assert material_absorption_maths.photon_mass_attenuation_per_unit_length( | ||
| energy_kev, photon_absorption_factor_per_unit_length, energy_dependence_exponent | ||
| ) == pytest.approx(result) | ||
|
Matt-Carre marked this conversation as resolved.
|
||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "depth_cm,absorption_coefficient_per_cm,result", | ||
| [(0.5, 2, 1000), (1.89, 0.316, 597.24), (0.0, 2.5, 0)], | ||
| ) | ||
| def test_attenuation_at_depth_cm(depth_cm, absorption_coefficient_per_cm, result): | ||
| assert material_absorption_maths.attenuation_at_depth_cm( | ||
| depth_cm, absorption_coefficient_per_cm | ||
| ) == pytest.approx(result) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "target_attenuation_bn,absorption_coefficient_per_cm,result", | ||
| [ | ||
| (0, 2.4, 0), | ||
|
Matt-Carre marked this conversation as resolved.
Outdated
|
||
| (248.461, 2.13, 0.1166483568), | ||
| ], | ||
| ) | ||
| def test_thickness_cm_required_to_attenuate( | ||
|
Matt-Carre marked this conversation as resolved.
|
||
| target_attenuation_bn, absorption_coefficient_per_cm, result | ||
| ): | ||
| assert material_absorption_maths.thickness_cm_required_to_attenuate( | ||
| target_attenuation_bn, absorption_coefficient_per_cm | ||
| ) == pytest.approx(result) | ||
|
|
||
|
|
||
| # inauspicious path | ||
| def test_attenuation_at_depth_cm_too_small_coefficient(): | ||
| with pytest.raises(ValueError): | ||
| material_absorption_maths.thickness_cm_required_to_attenuate(1, 1e-15) | ||
|
|
||
|
|
||
| def test_attenuation_at_depth_cm_too_negative_target(): | ||
| with pytest.raises(ValueError): | ||
| material_absorption_maths.thickness_cm_required_to_attenuate(-1, 1) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object()]) | ||
|
Matt-Carre marked this conversation as resolved.
Outdated
|
||
| def test_thickness_cm_required_to_attenuate_target_error(bad_input): | ||
| with pytest.raises(TypeError): | ||
| material_absorption_maths.thickness_cm_required_to_attenuate(bad_input, 1.0) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object()]) | ||
| def test_thickness_cm_required_to_attenuate_absorption_error(bad_input): | ||
| with pytest.raises(TypeError): | ||
| material_absorption_maths.thickness_cm_required_to_attenuate(1.0, bad_input) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("bad_input", [-1, -5, -0.1]) | ||
| def test_attenuation_at_depth_cm_absorption_coefficient_error(bad_input): | ||
| with pytest.raises(ValueError): | ||
| material_absorption_maths.attenuation_at_depth_cm(1.0, bad_input) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("bad_input", [-1, -5, -0.1]) | ||
| def test_attenuation_at_depth_cm_depth_negative_error(bad_input): | ||
| with pytest.raises(ValueError): | ||
| material_absorption_maths.attenuation_at_depth_cm(bad_input, 1.0) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object()]) | ||
| def test_attenuation_at_depth_cm_depth_depth_error(bad_input): | ||
| with pytest.raises(TypeError): | ||
| material_absorption_maths.attenuation_at_depth_cm(bad_input, 1.0) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object()]) | ||
| def test_attenuation_at_depth_cm_depth_absorption_error(bad_input): | ||
| with pytest.raises(TypeError): | ||
| material_absorption_maths.attenuation_at_depth_cm(1.0, bad_input) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object()]) | ||
| def test_photon_mass_attenuation_per_unit_length_errors_energy(bad_input): | ||
| with pytest.raises(TypeError): | ||
| material_absorption_maths.photon_mass_attenuation_per_unit_length( | ||
| bad_input, 1.0, 1.0 | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object()]) | ||
| def test_photon_mass_attenuation_per_unit_length_errors_absorp(bad_input): | ||
| with pytest.raises(TypeError): | ||
| material_absorption_maths.photon_mass_attenuation_per_unit_length( | ||
| 1.0, bad_input, 1.0 | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object()]) | ||
| def test_photon_mass_attenuation_per_unit_length_errors_expo(bad_input): | ||
| with pytest.raises(TypeError): | ||
| material_absorption_maths.photon_mass_attenuation_per_unit_length( | ||
| 1.0, 1.0, bad_input | ||
| ) | ||
76 changes: 76 additions & 0 deletions
76
tests/common/general_maths/test_transmission_interconversion.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import math | ||
|
|
||
| import pytest | ||
|
|
||
| from dodal.common.general_maths import transmission_interconversion | ||
|
|
||
|
|
||
| # happy: | ||
| @pytest.mark.parametrize( | ||
| "ln_t,result", [(-1, 1000), (0, 0), (-0.4367, 436.7), (-5.9017, 5901.7)] | ||
| ) | ||
| def test_attenuation_from_natural_log_of_transmission(ln_t, result): | ||
| assert transmission_interconversion.attenuation_from_natural_log_of_transmission( | ||
| ln_t | ||
| ) == pytest.approx(result) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "transmission_as_fraction,result", | ||
| [(1, 0), (0.37, 994.25227334), (0.871, 138.1133), (4.2e-4, 7775.2558)], | ||
| ) | ||
| def test_attenuation_from_transmission(transmission_as_fraction, result): | ||
| assert transmission_interconversion.attenuation_from_transmission( | ||
| transmission_as_fraction | ||
| ) == pytest.approx(result) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "attenuation_bn,result", | ||
| [(0, 0), (1e3, -1), (712.6, -0.7126), (8034.1, -8.0341)], | ||
| ) | ||
| def test_natural_log_of_transmission_from_attenuation(attenuation_bn, result): | ||
| assert transmission_interconversion.natural_log_of_transmission_from_attenuation( | ||
| attenuation_bn | ||
| ) == pytest.approx(result) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "attenuation_bn,result", | ||
| [(0, 1), (1e3, 0.3678794), (145.1, 0.8649358), (7221.9, 7.3041331435179e-4)], | ||
| ) | ||
| def test_transmission_from_attenutation(attenuation_bn, result): | ||
| assert transmission_interconversion.transmission_from_attenutation( | ||
| attenuation_bn | ||
| ) == pytest.approx(result) | ||
|
|
||
|
|
||
| # inauspicious: | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object()]) | ||
| def test_attenuation_from_natural_log_of_transmission_errors(bad_input): | ||
| with pytest.raises(TypeError): | ||
| transmission_interconversion.attenuation_from_natural_log_of_transmission( | ||
| bad_input | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object()]) | ||
| def test_attenuation_from_transmission_errors(bad_input): | ||
| with pytest.raises(TypeError): | ||
| transmission_interconversion.attenuation_from_transmission(bad_input) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object()]) | ||
| def test_natural_log_of_transmission_from_attenuation_errors(bad_input): | ||
| with pytest.raises(TypeError): | ||
| transmission_interconversion.natural_log_of_transmission_from_attenuation( | ||
| bad_input | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object()]) | ||
| def test_transmission_from_attenutation_errors(bad_input): | ||
| with pytest.raises(TypeError): | ||
| transmission_interconversion.transmission_from_attenutation(bad_input) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.