Skip to content
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
6333d02
Initial commit of transmission conversions
Matt-Carre May 5, 2026
1040280
added docstrings
Matt-Carre May 5, 2026
a09a197
added docstrings
Matt-Carre May 5, 2026
cb6df54
initial commit
Matt-Carre May 5, 2026
ef61984
initial commit
Matt-Carre May 5, 2026
0691886
adds roll_off back
Matt-Carre May 5, 2026
7e8a3f5
adds pydantic validation
Matt-Carre May 5, 2026
64c844c
Added pydantic validation
Matt-Carre May 5, 2026
4d48090
changes to NonNegativeFloat (accepting 0) where relevant
Matt-Carre May 5, 2026
da51855
changes to NonNegativeFloat (accepting 0) where relevant, considering…
Matt-Carre May 5, 2026
e5c341d
fixes linting. going to move tests next update.
Matt-Carre May 5, 2026
5215f96
labels tests
Matt-Carre May 6, 2026
1eb350b
Merge branch '2040_attenuation_transmission' into 2039_photon_mass_calc
Matt-Carre May 6, 2026
3d6a920
Merge branch '2040_attenuation_transmission' into 2041_interconversio…
Matt-Carre May 6, 2026
273cbff
added test labels
Matt-Carre May 6, 2026
c9ef824
split tests
Matt-Carre May 6, 2026
3d48b32
Merge branch '2040_attenuation_transmission' into 2039_photon_mass_calc
Matt-Carre May 6, 2026
7d94b52
split tests
Matt-Carre May 6, 2026
c32281d
merged in 2040
Matt-Carre May 6, 2026
10f65f8
adds specific output
Matt-Carre May 6, 2026
1a01d56
Merge branch 'main' into 2041_interconversion_depth_attenuation
Matt-Carre May 11, 2026
8896dd1
Merge branch 'main' into 2040_attenuation_transmission
Matt-Carre May 11, 2026
436c253
Merge branch 'main' into 2039_photon_mass_calc
Matt-Carre May 11, 2026
9c5f36b
Merge branch 'main' into 2041_interconversion_depth_attenuation
Matt-Carre May 13, 2026
a280aca
Merge branch 'main' into 2039_photon_mass_calc
Matt-Carre May 13, 2026
a4fed4d
Merge branch 'main' into 2040_attenuation_transmission
Matt-Carre May 13, 2026
47a46e4
Merge branch 'main' into 2039_photon_mass_calc
Matt-Carre May 13, 2026
d4edcc1
Merge branch 'main' into 2041_interconversion_depth_attenuation
Matt-Carre May 13, 2026
0e3b3a9
Merge branch 'main' into 2040_attenuation_transmission
Matt-Carre May 13, 2026
915a226
rejoined tests
Matt-Carre May 13, 2026
90c2c1a
Merge branch '2039_photon_mass_calc' of github.com:DiamondLightSource…
Matt-Carre May 13, 2026
1651348
Merge branch '2040_attenuation_transmission' of github.com:DiamondLig…
Matt-Carre May 13, 2026
c243228
rejoins tests
Matt-Carre May 13, 2026
3516fcb
Merge branch '2040_attenuation_transmission' into 2041_interconversio…
Matt-Carre May 13, 2026
788ad00
rejoined tests
Matt-Carre May 13, 2026
7763449
Merge branch '2041_interconversion_depth_attenuation' of github.com:D…
Matt-Carre May 13, 2026
3ce2471
updates based on review
Matt-Carre May 13, 2026
f1c30c5
renames test
Matt-Carre May 13, 2026
1fd5e87
fixed more comments
Matt-Carre May 13, 2026
7aa9941
fixed more comments
Matt-Carre May 13, 2026
b5e6663
adds bools to bad input
Matt-Carre May 13, 2026
e85d2ac
Merge branch '2041_interconversion_depth_attenuation' into 2039_photo…
Matt-Carre May 13, 2026
36b6243
fixes tests
Matt-Carre May 13, 2026
c7ffa7e
removed file
Matt-Carre May 13, 2026
87717e4
Merge branch 'main' into 2039_photon_mass_calc
Matt-Carre May 13, 2026
ef4ec39
adds validation to guard against bools
Matt-Carre May 13, 2026
3574eb1
Merge branch '2039_photon_mass_calc' of github.com:DiamondLightSource…
Matt-Carre May 13, 2026
2104f29
fixes based on comments
Matt-Carre May 13, 2026
b9f0586
Merge branch 'main' into 2039_photon_mass_calc
Matt-Carre May 14, 2026
3e29077
Merge branch 'main' into 2039_photon_mass_calc
Matt-Carre May 14, 2026
58c8e78
Merge branch 'main' into 2039_photon_mass_calc
Matt-Carre May 14, 2026
cb0a165
Merge branch 'main' into 2039_photon_mass_calc
Matt-Carre May 14, 2026
0fba9cc
Merge branch 'main' into 2039_photon_mass_calc
Matt-Carre May 15, 2026
ac891f9
removes 'arbitrary' from element tests
Matt-Carre May 15, 2026
6734d07
Merge branch 'main' into 2039_photon_mass_calc
Matt-Carre May 15, 2026
aa583c6
Merge branch '2039_photon_mass_calc' of github.com:DiamondLightSource…
Matt-Carre May 15, 2026
f8fb92e
Merge branch 'main' into 2039_photon_mass_calc
Matt-Carre May 15, 2026
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
22 changes: 22 additions & 0 deletions src/dodal/common/general_maths/material_absorption_maths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from pydantic import StrictFloat, validate_call


@validate_call
def photon_mass_attenuation_per_unit_length(
energy_kev: StrictFloat,
photon_absorption_factor_per_unit_length: StrictFloat,
energy_dependence_exponent: StrictFloat,
) -> float:
"""Calculates mass attenuation per unit length.

Args:
energy_kev (StrictFloat): energy
photon_absorption_factor_per_unit_length (StrictFloat): photon absorption factor per
unit length
energy_dependence_exponent (StrictFloat): energy dependence exponent

Returns:
(float): mass attenuation per unit length.
"""
Comment thread
Matt-Carre marked this conversation as resolved.
roll_off = energy_kev**energy_dependence_exponent
return photon_absorption_factor_per_unit_length * roll_off
62 changes: 62 additions & 0 deletions src/dodal/common/general_maths/transmission_interconversion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import math
Comment thread
Matt-Carre marked this conversation as resolved.

from pydantic import StrictFloat, validate_call

_CANONICAL_BARNETT_CONVERSION = -1.0e3
_REVERSE_BARNETT_CONVERSION = -1.0e-3


@validate_call
def attenuation_from_natural_log_of_transmission(ln_t: StrictFloat) -> float:
"""Converts from natural log of transmission fraction into Barnett attenuation units
.

Args:
ln_t (StrictFloat): natural log of transmission fraction

Returns:
(float): Barnett attenuation units
"""
return _CANONICAL_BARNETT_CONVERSION * ln_t


@validate_call
def attenuation_from_transmission(transmission_as_fraction: StrictFloat) -> float:
"""Converts from transmission fraction into Barnett attenuation units.

Args:
transmission_as_fraction (StrictFloat): transmission fraction

Returns:
(float): Barnett attenuation units.
"""
ln_t = math.log(transmission_as_fraction)
return attenuation_from_natural_log_of_transmission(ln_t)


@validate_call
def natural_log_of_transmission_from_attenuation(attenuation_bn: StrictFloat) -> float:
"""Converts from Barnett attenuation units into natural log of transmission fraction
.

Args:
attenuation_bn (StrictFloat): Barnett attenuation units

Returns:
(float): natural log of transmission fraction
"""
return _REVERSE_BARNETT_CONVERSION * attenuation_bn


@validate_call
def transmission_from_attenutation(attenuation_bn: StrictFloat) -> float:
"""Converts from Barnett attenuation units into transmission fraction.

Args:
attenuation_bn (StrictFloat): Barnett attenuation units

Returns:
(float): transmission fraction
"""
ln_t = natural_log_of_transmission_from_attenuation(attenuation_bn)
return math.exp(ln_t)
51 changes: 51 additions & 0 deletions tests/common/general_maths/test_material_absorption_maths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import math

import pytest
from pydantic import ValidationError

from dodal.common.general_maths.material_absorption_maths import (
photon_mass_attenuation_per_unit_length,
)


# 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 Energy
(8.3328, 2.5706e3, -2.83, 6.3708311), # Arbitrary Nickel
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: the wording "arbitrary {element}" is extremely unclear.

(11.9187, 1.48e3, -2.93, 1.03970725), # Arbitrary Gold-Three
(25.514, 6.48e3, -2.41, 2.63778077), # Arbitrary Silver
],
)
def test_photon_mass_attenuation_per_unit_length(
energy_kev,
photon_absorption_factor_per_unit_length,
energy_dependence_exponent,
result,
):
assert photon_mass_attenuation_per_unit_length(
energy_kev, photon_absorption_factor_per_unit_length, energy_dependence_exponent
) == pytest.approx(result, 5.0e-8)


# inauspicious path
@pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object(), False])
def test_photon_mass_attenuation_per_unit_length_errors_with_invalid_energy(bad_input):
with pytest.raises(ValidationError):
photon_mass_attenuation_per_unit_length(bad_input, 1.0, 1.0)


@pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object(), False])
def test_photon_mass_attenuation_per_unit_length_errors_with_invalid_factor(bad_input):
with pytest.raises(ValidationError):
photon_mass_attenuation_per_unit_length(3500.0, bad_input, 1.0)


@pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object(), False])
def test_photon_mass_attenuation_per_unit_length_errors_with_invalid_exponent(
bad_input,
):
with pytest.raises(ValidationError):
photon_mass_attenuation_per_unit_length(3500.0, 1.0, bad_input)
123 changes: 123 additions & 0 deletions tests/common/general_maths/test_transmission_interconversion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import math

import pytest
from pydantic import ValidationError

from dodal.common.general_maths.transmission_interconversion import (
attenuation_from_natural_log_of_transmission,
attenuation_from_transmission,
natural_log_of_transmission_from_attenuation,
transmission_from_attenutation,
)


@pytest.mark.parametrize(
"attenuation_bn,result",
[
(0, 1), # tests transparency from zero attenuation (canonical)
(
1e3,
0.3678794,
), # tests transmission from canonical attenuation is 1/e (canonical)
(
145.1,
0.8649358,
), # tests transmission from arbitrary weak attenuation is weak attenuation
(
7221.9,
7.3041331435179e-4,
), # tests transmission from arbitrary strong attenuation is strong attenuation
],
)
def test_transmission_from_attenutation(attenuation_bn, result):
assert transmission_from_attenutation(attenuation_bn) == pytest.approx(result)


@pytest.mark.parametrize(
"attenuation_bn,result",
[
(0, 0), # tests natural log of transparency from attenuation (canonical)
(
1e3,
-1,
), # tests natural log of transmission from canonical transmission is negative
# unity (canonical)
(
712.6,
-0.7126,
), # tests attenuation from natural log arbitrary high transmission
(
8034.1,
-8.0341,
), # tests attenuation from natural log arbitrary low transmission
],
)
def test_natural_log_of_transmission_from_attenuation(attenuation_bn, result):
assert natural_log_of_transmission_from_attenuation(
attenuation_bn
) == pytest.approx(result)


@pytest.mark.parametrize(
"transmission_as_fraction,result",
[
(1, 0), # tests attenuation from transparency is zero (canonical)
(0.37, 994.25227334), # tests attenuation from 37% is close to 1000
(
0.871,
138.1133,
), # tests attenuation from arbitrary high transmission
(
4.2e-4,
7775.2558,
), # tests attenuation from arbitrary high attenuation
],
)
def test_attenuation_from_transmission(transmission_as_fraction, result):
assert attenuation_from_transmission(transmission_as_fraction) == pytest.approx(
result
)


@pytest.mark.parametrize(
"ln_t,result",
[
(-1, 1000), # tests negative unity log of transmission is 1000 (canonical)
(0, 0), # tests natural log of transparency is zero (canonical)
(
-0.4367,
436.7,
), # tests log from arbitrary high transmission
(
-5.9017,
5901.7,
), # tests log from arbitrary low transmission
],
)
def test_attenuation_from_natural_log_of_transmission(ln_t, result):
assert attenuation_from_natural_log_of_transmission(ln_t) == pytest.approx(result)


# inauspicious:
@pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object(), False])
def test_natural_log_of_transmission_from_attenuation_raises_error(bad_input):
with pytest.raises(ValidationError):
natural_log_of_transmission_from_attenuation(bad_input)


@pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object(), False])
def test_transmission_from_attenutation_raises_error(bad_input):
with pytest.raises(ValidationError):
transmission_from_attenutation(bad_input)


@pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object(), False])
def test_attenuation_from_transmission_raises_error(bad_input):
with pytest.raises(ValidationError):
attenuation_from_transmission(bad_input)


@pytest.mark.parametrize("bad_input", ["a", [], None, math.sin, object(), False])
def test_attenuation_from_natural_log_of_transmission_raises_error(bad_input):
with pytest.raises(ValidationError):
attenuation_from_natural_log_of_transmission(bad_input)
Loading