Skip to content

The Nuclear Physics Module

Parent module of all the Nuclear physics related modules

Exports

NuclearDataMap, NucleusData, generate_nucleus_id

Modules:

Name Description
target

Contains classes and methods for GasTarget and SolidTarget energy loss analysis

momentum

Contains methods for momentum 4-vector analysis

GasMixtureTarget

Source code in src/spyral_utils/nuclear/target.py
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
class GasMixtureTarget:
    def __init__(
        self,
        components: list[list[tuple[int, int, int]]],
        volume_fractions: list[float],
        pressure: float,
        nuclear_map: NuclearDataMap,
    ):
        """An AT-TPC gas mixutre target

        Gas mixture target for which energy loss can be calculated using pycatima. Can perform several types of
        energy loss calculations (straggling, dEdx, energy lost, etc.)

        Parameters
        ----------
        components: list[list[tuple[int, int, int]]]
            A list specifiying compound elements in the target compound as (Z, A, S)
        volume_fractions: list[float]
            A list of mixture fractions by volume (in same order as components)
        pressure: float
            The gas pressure in Torr
        nuclear_data: NuclearDataMap
            The nucleus data

        Attributes
        ----------
        components: list[list[tuple[int, int, int]]]
            A list specifiying compound elements in the target compound as (Z, A, S)
        volume_fractions: list[float]
            A list of mixture fractions by volume (in same order as components)
        pressure: float
            The gas pressure in Torr
        ugly_string: str
            A string representation without rich formatting
        pretty_string: str
            A string representation with rich formatting
        material: catima.Material
            The catima representation of the target
        density: float
            The target density in g/cm^3

        Methods
        -------
        get_dedx(projectile_data: NucleusData, projectile_energy: float) -> float
            get the stopping power (dEdx) for a projectile in this target
        get_angular_straggling(projectile_data: NucleusData, projectile_energy: float) -> float:
            get the angular straggling for a projectile in this target
        get_energy_loss(projectile_data: NucleusData, projectile_energy: float, distances: ndarray) -> ndarray:
            get the energy loss values for a projectile travelling distances through the target
        """
        self.components = components
        self.volume_fractions = volume_fractions
        self.pressure = pressure
        self.equivalent_compound: list[tuple[int, int, int]] = []
        self.average_molar_mass = 0.0
        self.density = 0.0
        self.pretty_string: str = "(GasMix)"
        self.ugly_string: str = "(GasMix)"
        for fraction, component in zip(self.volume_fractions, self.components):
            self.pretty_string += f"[{fraction * 100.0:.0}%-"
            self.ugly_string += f"[{fraction * 100.0:.0}%-"
            for z, a, s in component:
                self.pretty_string += (
                    f"{nuclear_map.get_data(z, a).pretty_iso_symbol}<sub>{s}</sub>"
                )
                self.ugly_string += f"{nuclear_map.get_data(z, a).isotopic_symbol}{s}"
            self.pretty_string += "]"
            self.ugly_string += "]"

        for compound, fraction in zip(self.components, self.volume_fractions):
            scale = int(100.0 * fraction)
            for element_z, element_a, element_s in compound:
                self.equivalent_compound.append(
                    (element_z, element_a, element_s * scale)
                )
                self.average_molar_mass += element_a * element_s * fraction

        self.density = (
            self.average_molar_mass * self.pressure / (GAS_CONSTANT * ROOM_TEMPERATURE)
        )
        # Construct the target material
        self.material = catima.Material()
        for (
            z,
            a,
            s,
        ) in self.equivalent_compound:
            self.material.add_element(
                nuclear_map.get_data(z, a).atomic_mass, z, float(s)
            )
        self.material.density(self.density)

    def get_dedx(self, projectile_data: NucleusData, projectile_energy: float) -> float:
        """Calculate the stopping power of the target for a projectile

        Parameters
        ----------
        projectile_data: NucleusData
            the projectile type
        projectile_energy: float
            the projectile kinetic energy in MeV

        Returns
        -------
        float
            dEdx in MeV/g/cm^2
        """
        mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
        projectile = catima.Projectile(mass_u, projectile_data.Z)
        projectile.T(projectile_energy / mass_u)
        return catima.dedx(projectile, self.material)

    def get_angular_straggling(
        self, projectile_data: NucleusData, projectile_energy: float
    ) -> float:
        """Calculate the angular straggling for a projectile

        Parameters
        ----------
        projectile_data: NucleusData
            the projectile type
        projectile_energy: float
            the projectile kinetic energy in MeV

        Returns
        -------
        float
            angular straggling in radians
        """
        mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
        projectile = catima.Projectile(
            mass_u, projectile_data.Z, T=projectile_energy / mass_u
        )
        return catima.calculate(projectile, self.material).get_dict()["sigma_a"]

    def get_energy_loss(
        self,
        projectile_data: NucleusData,
        projectile_energy: float,
        distances: np.ndarray,
    ) -> np.ndarray:
        """
        Calculate the energy loss of a projectile traveling over a set of distances

        Parameters
        ----------
        projectile_data: NucleusData
            the projectile type
        projectile_energy: float
            the projectile kinetic energy in MeV
        distances: ndarray
            a set of distances in meters over which to calculate the energy loss

        Returns
        -------
        ndarray
            set of energy losses
        """
        mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
        projectile = catima.Projectile(
            mass_u, projectile_data.Z, T=projectile_energy / mass_u
        )
        eloss = np.zeros(len(distances))
        for idx, distance in enumerate(distances):
            self.material.thickness_cm(distance * 100.0)
            projectile.T(projectile_energy / mass_u)
            eloss[idx] = catima.calculate(projectile, self.material).get_dict()["Eloss"]
        return eloss

    def get_range(
        self, projectile_data: NucleusData, projectile_energy: float
    ) -> float:
        """Calculate the range of a projectile in the target

        Parameters
        ----------
        projectile_data: NucleusData
            the projectile type
        projectile_energy: float
            the projectile kinetic energy in MeV

        Returns
        -------
        float
            The range in m
        """
        mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
        projectile = catima.Projectile(
            mass_u, projectile_data.Z, T=projectile_energy / mass_u
        )
        range_gcm2 = catima.calculate(projectile, self.material).get_dict()["range"]
        range_m = range_gcm2 / self.material.density() * 0.01
        return range_m

    def get_number_density(self) -> float:
        """Get the number density of gas molecules

        Returns
        -------
        Number density of gas molecules in molecules/cm^3
        """
        return self.material.number_density()

__init__(components, volume_fractions, pressure, nuclear_map)

An AT-TPC gas mixutre target

Gas mixture target for which energy loss can be calculated using pycatima. Can perform several types of energy loss calculations (straggling, dEdx, energy lost, etc.)

Parameters:

Name Type Description Default
components list[list[tuple[int, int, int]]]

A list specifiying compound elements in the target compound as (Z, A, S)

required
volume_fractions list[float]

A list of mixture fractions by volume (in same order as components)

required
pressure float

The gas pressure in Torr

required
nuclear_data

The nucleus data

required

Attributes:

Name Type Description
components list[list[tuple[int, int, int]]]

A list specifiying compound elements in the target compound as (Z, A, S)

volume_fractions list[float]

A list of mixture fractions by volume (in same order as components)

pressure float

The gas pressure in Torr

ugly_string str

A string representation without rich formatting

pretty_string str

A string representation with rich formatting

material Material

The catima representation of the target

density float

The target density in g/cm^3

Functions:

Name Description
get_dedx

get the stopping power (dEdx) for a projectile in this target

get_angular_straggling

get the angular straggling for a projectile in this target

get_energy_loss

get the energy loss values for a projectile travelling distances through the target

Source code in src/spyral_utils/nuclear/target.py
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
def __init__(
    self,
    components: list[list[tuple[int, int, int]]],
    volume_fractions: list[float],
    pressure: float,
    nuclear_map: NuclearDataMap,
):
    """An AT-TPC gas mixutre target

    Gas mixture target for which energy loss can be calculated using pycatima. Can perform several types of
    energy loss calculations (straggling, dEdx, energy lost, etc.)

    Parameters
    ----------
    components: list[list[tuple[int, int, int]]]
        A list specifiying compound elements in the target compound as (Z, A, S)
    volume_fractions: list[float]
        A list of mixture fractions by volume (in same order as components)
    pressure: float
        The gas pressure in Torr
    nuclear_data: NuclearDataMap
        The nucleus data

    Attributes
    ----------
    components: list[list[tuple[int, int, int]]]
        A list specifiying compound elements in the target compound as (Z, A, S)
    volume_fractions: list[float]
        A list of mixture fractions by volume (in same order as components)
    pressure: float
        The gas pressure in Torr
    ugly_string: str
        A string representation without rich formatting
    pretty_string: str
        A string representation with rich formatting
    material: catima.Material
        The catima representation of the target
    density: float
        The target density in g/cm^3

    Methods
    -------
    get_dedx(projectile_data: NucleusData, projectile_energy: float) -> float
        get the stopping power (dEdx) for a projectile in this target
    get_angular_straggling(projectile_data: NucleusData, projectile_energy: float) -> float:
        get the angular straggling for a projectile in this target
    get_energy_loss(projectile_data: NucleusData, projectile_energy: float, distances: ndarray) -> ndarray:
        get the energy loss values for a projectile travelling distances through the target
    """
    self.components = components
    self.volume_fractions = volume_fractions
    self.pressure = pressure
    self.equivalent_compound: list[tuple[int, int, int]] = []
    self.average_molar_mass = 0.0
    self.density = 0.0
    self.pretty_string: str = "(GasMix)"
    self.ugly_string: str = "(GasMix)"
    for fraction, component in zip(self.volume_fractions, self.components):
        self.pretty_string += f"[{fraction * 100.0:.0}%-"
        self.ugly_string += f"[{fraction * 100.0:.0}%-"
        for z, a, s in component:
            self.pretty_string += (
                f"{nuclear_map.get_data(z, a).pretty_iso_symbol}<sub>{s}</sub>"
            )
            self.ugly_string += f"{nuclear_map.get_data(z, a).isotopic_symbol}{s}"
        self.pretty_string += "]"
        self.ugly_string += "]"

    for compound, fraction in zip(self.components, self.volume_fractions):
        scale = int(100.0 * fraction)
        for element_z, element_a, element_s in compound:
            self.equivalent_compound.append(
                (element_z, element_a, element_s * scale)
            )
            self.average_molar_mass += element_a * element_s * fraction

    self.density = (
        self.average_molar_mass * self.pressure / (GAS_CONSTANT * ROOM_TEMPERATURE)
    )
    # Construct the target material
    self.material = catima.Material()
    for (
        z,
        a,
        s,
    ) in self.equivalent_compound:
        self.material.add_element(
            nuclear_map.get_data(z, a).atomic_mass, z, float(s)
        )
    self.material.density(self.density)

get_angular_straggling(projectile_data, projectile_energy)

Calculate the angular straggling for a projectile

Parameters:

Name Type Description Default
projectile_data NucleusData

the projectile type

required
projectile_energy float

the projectile kinetic energy in MeV

required

Returns:

Type Description
float

angular straggling in radians

Source code in src/spyral_utils/nuclear/target.py
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
def get_angular_straggling(
    self, projectile_data: NucleusData, projectile_energy: float
) -> float:
    """Calculate the angular straggling for a projectile

    Parameters
    ----------
    projectile_data: NucleusData
        the projectile type
    projectile_energy: float
        the projectile kinetic energy in MeV

    Returns
    -------
    float
        angular straggling in radians
    """
    mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
    projectile = catima.Projectile(
        mass_u, projectile_data.Z, T=projectile_energy / mass_u
    )
    return catima.calculate(projectile, self.material).get_dict()["sigma_a"]

get_dedx(projectile_data, projectile_energy)

Calculate the stopping power of the target for a projectile

Parameters:

Name Type Description Default
projectile_data NucleusData

the projectile type

required
projectile_energy float

the projectile kinetic energy in MeV

required

Returns:

Type Description
float

dEdx in MeV/g/cm^2

Source code in src/spyral_utils/nuclear/target.py
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
def get_dedx(self, projectile_data: NucleusData, projectile_energy: float) -> float:
    """Calculate the stopping power of the target for a projectile

    Parameters
    ----------
    projectile_data: NucleusData
        the projectile type
    projectile_energy: float
        the projectile kinetic energy in MeV

    Returns
    -------
    float
        dEdx in MeV/g/cm^2
    """
    mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
    projectile = catima.Projectile(mass_u, projectile_data.Z)
    projectile.T(projectile_energy / mass_u)
    return catima.dedx(projectile, self.material)

get_energy_loss(projectile_data, projectile_energy, distances)

Calculate the energy loss of a projectile traveling over a set of distances

Parameters:

Name Type Description Default
projectile_data NucleusData

the projectile type

required
projectile_energy float

the projectile kinetic energy in MeV

required
distances ndarray

a set of distances in meters over which to calculate the energy loss

required

Returns:

Type Description
ndarray

set of energy losses

Source code in src/spyral_utils/nuclear/target.py
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
def get_energy_loss(
    self,
    projectile_data: NucleusData,
    projectile_energy: float,
    distances: np.ndarray,
) -> np.ndarray:
    """
    Calculate the energy loss of a projectile traveling over a set of distances

    Parameters
    ----------
    projectile_data: NucleusData
        the projectile type
    projectile_energy: float
        the projectile kinetic energy in MeV
    distances: ndarray
        a set of distances in meters over which to calculate the energy loss

    Returns
    -------
    ndarray
        set of energy losses
    """
    mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
    projectile = catima.Projectile(
        mass_u, projectile_data.Z, T=projectile_energy / mass_u
    )
    eloss = np.zeros(len(distances))
    for idx, distance in enumerate(distances):
        self.material.thickness_cm(distance * 100.0)
        projectile.T(projectile_energy / mass_u)
        eloss[idx] = catima.calculate(projectile, self.material).get_dict()["Eloss"]
    return eloss

get_number_density()

Get the number density of gas molecules

Returns:

Type Description
Number density of gas molecules in molecules/cm^3
Source code in src/spyral_utils/nuclear/target.py
696
697
698
699
700
701
702
703
def get_number_density(self) -> float:
    """Get the number density of gas molecules

    Returns
    -------
    Number density of gas molecules in molecules/cm^3
    """
    return self.material.number_density()

get_range(projectile_data, projectile_energy)

Calculate the range of a projectile in the target

Parameters:

Name Type Description Default
projectile_data NucleusData

the projectile type

required
projectile_energy float

the projectile kinetic energy in MeV

required

Returns:

Type Description
float

The range in m

Source code in src/spyral_utils/nuclear/target.py
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
def get_range(
    self, projectile_data: NucleusData, projectile_energy: float
) -> float:
    """Calculate the range of a projectile in the target

    Parameters
    ----------
    projectile_data: NucleusData
        the projectile type
    projectile_energy: float
        the projectile kinetic energy in MeV

    Returns
    -------
    float
        The range in m
    """
    mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
    projectile = catima.Projectile(
        mass_u, projectile_data.Z, T=projectile_energy / mass_u
    )
    range_gcm2 = catima.calculate(projectile, self.material).get_dict()["range"]
    range_m = range_gcm2 / self.material.density() * 0.01
    return range_m

GasTarget

An AT-TPC gas target

Gas target for which energy loss can be calculated using pycatima. Can perform several types of energy loss calculations (straggling, dEdx, energy lost, etc.)

Parameters:

Name Type Description Default
compound list[tuple[int, int, int]]

A list specifiying elements in the target compound as (Z, A, S)

required
pressure float

The gas pressure in Torr

required
nuclear_data NuclearDataMap

The nucleus data

required

Attributes:

Name Type Description
compound list[tuple[int, int, int]]

A list specifiying elements in the target compound as (Z, A, S)

pressure float

The gas pressure in Torr

ugly_string str

A string representation without rich formatting

pretty_string str

A string representation with rich formatting

material Material

The catima representation of the target

density float

The target density in g/cm^3

Methods:

Name Description
get_dedx

get the stopping power (dEdx) for a projectile in this target

get_angular_straggling

get the angular straggling for a projectile in this target

get_energy_loss

get the energy loss values for a projectile travelling distances through the target

Source code in src/spyral_utils/nuclear/target.py
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
class GasTarget:
    """An AT-TPC gas target

    Gas target for which energy loss can be calculated using pycatima. Can perform several types of
    energy loss calculations (straggling, dEdx, energy lost, etc.)

    Parameters
    ----------
    compound: list[tuple[int, int, int]]
        A list specifiying elements in the target compound as (Z, A, S)
    pressure: float
        The gas pressure in Torr
    nuclear_data: NuclearDataMap
        The nucleus data

    Attributes
    ----------
    compound: list[tuple[int, int, int]]
        A list specifiying elements in the target compound as (Z, A, S)
    pressure: float
        The gas pressure in Torr
    ugly_string: str
        A string representation without rich formatting
    pretty_string: str
        A string representation with rich formatting
    material: catima.Material
        The catima representation of the target
    density: float
        The target density in g/cm^3

    Methods
    -------
    get_dedx(projectile_data: NucleusData, projectile_energy: float) -> float
        get the stopping power (dEdx) for a projectile in this target
    get_angular_straggling(projectile_data: NucleusData, projectile_energy: float) -> float:
        get the angular straggling for a projectile in this target
    get_energy_loss(projectile_data: NucleusData, projectile_energy: float, distances: ndarray) -> ndarray:
        get the energy loss values for a projectile travelling distances through the target
    """

    def __init__(
        self,
        compound: list[tuple[int, int, int]],
        pressure: float,
        nuclear_data: NuclearDataMap,
    ):
        self.compound = compound
        self.pressure = pressure  # Torr
        molar_mass: float = 0.0
        for z, a, s in self.compound:
            molar_mass += a * s
        self.density: float = (
            molar_mass * self.pressure / (GAS_CONSTANT * ROOM_TEMPERATURE)
        )
        self.pretty_string: str = "(Gas)" + "".join(
            [
                f"{nuclear_data.get_data(z, a).pretty_iso_symbol}<sub>{s}</sub>"
                for (z, a, s) in self.compound
            ]
        )
        self.ugly_string: str = "(Gas)" + "".join(
            [
                f"{nuclear_data.get_data(z, a).isotopic_symbol}{s}"
                for (z, a, s) in self.compound
            ]
        )

        # Construct the target material
        self.material = catima.Material()
        for (
            z,
            a,
            s,
        ) in self.compound:
            self.material.add_element(
                nuclear_data.get_data(z, a).atomic_mass, z, float(s)
            )
        self.material.density(self.density)

    def __str__(self) -> str:
        return self.pretty_string

    def get_dedx(self, projectile_data: NucleusData, projectile_energy: float) -> float:
        """Calculate the stopping power of the target for a projectile

        Parameters
        ----------
        projectile_data: NucleusData
            the projectile type
        projectile_energy: float
            the projectile kinetic energy in MeV

        Returns
        -------
        float
            dEdx in MeV/g/cm^2
        """
        mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
        projectile = catima.Projectile(mass_u, projectile_data.Z)
        projectile.T(projectile_energy / mass_u)
        return catima.dedx(projectile, self.material)

    def get_angular_straggling(
        self, projectile_data: NucleusData, projectile_energy: float
    ) -> float:
        """Calculate the angular straggling for a projectile

        Parameters
        ----------
        projectile_data: NucleusData
            the projectile type
        projectile_energy: float
            the projectile kinetic energy in MeV

        Returns
        -------
        float
            angular straggling in radians
        """
        mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
        projectile = catima.Projectile(
            mass_u, projectile_data.Z, T=projectile_energy / mass_u
        )
        return catima.calculate(projectile, self.material).get_dict()["sigma_a"]

    def get_energy_loss(
        self,
        projectile_data: NucleusData,
        projectile_energy: float,
        distances: np.ndarray,
    ) -> np.ndarray:
        """
        Calculate the energy loss of a projectile traveling over a set of distances

        Parameters
        ----------
        projectile_data: NucleusData
            the projectile type
        projectile_energy: float
            the projectile kinetic energy in MeV
        distances: ndarray
            a set of distances in meters over which to calculate the energy loss

        Returns
        -------
        ndarray
            set of energy losses
        """
        mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
        projectile = catima.Projectile(
            mass_u, projectile_data.Z, T=projectile_energy / mass_u
        )
        eloss = np.zeros(len(distances))
        for idx, distance in enumerate(distances):
            self.material.thickness_cm(distance * 100.0)
            projectile.T(projectile_energy / mass_u)
            eloss[idx] = catima.calculate(projectile, self.material).get_dict()["Eloss"]
        return eloss

    def get_range(
        self, projectile_data: NucleusData, projectile_energy: float
    ) -> float:
        """Calculate the range of a projectile in the target

        Parameters
        ----------
        projectile_data: NucleusData
            the projectile type
        projectile_energy: float
            the projectile kinetic energy in MeV

        Returns
        -------
        float
            The range in m
        """
        mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
        projectile = catima.Projectile(
            mass_u, projectile_data.Z, T=projectile_energy / mass_u
        )
        range_gcm2 = catima.calculate(projectile, self.material).get_dict()["range"]
        range_m = range_gcm2 / self.material.density() * 0.01
        return range_m

    def get_number_density(self) -> float:
        """Get the number density of gas molecules

        Returns
        -------
        Number density of gas molecules in molecules/cm^3
        """
        return self.material.number_density()

get_angular_straggling(projectile_data, projectile_energy)

Calculate the angular straggling for a projectile

Parameters:

Name Type Description Default
projectile_data NucleusData

the projectile type

required
projectile_energy float

the projectile kinetic energy in MeV

required

Returns:

Type Description
float

angular straggling in radians

Source code in src/spyral_utils/nuclear/target.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def get_angular_straggling(
    self, projectile_data: NucleusData, projectile_energy: float
) -> float:
    """Calculate the angular straggling for a projectile

    Parameters
    ----------
    projectile_data: NucleusData
        the projectile type
    projectile_energy: float
        the projectile kinetic energy in MeV

    Returns
    -------
    float
        angular straggling in radians
    """
    mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
    projectile = catima.Projectile(
        mass_u, projectile_data.Z, T=projectile_energy / mass_u
    )
    return catima.calculate(projectile, self.material).get_dict()["sigma_a"]

get_dedx(projectile_data, projectile_energy)

Calculate the stopping power of the target for a projectile

Parameters:

Name Type Description Default
projectile_data NucleusData

the projectile type

required
projectile_energy float

the projectile kinetic energy in MeV

required

Returns:

Type Description
float

dEdx in MeV/g/cm^2

Source code in src/spyral_utils/nuclear/target.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def get_dedx(self, projectile_data: NucleusData, projectile_energy: float) -> float:
    """Calculate the stopping power of the target for a projectile

    Parameters
    ----------
    projectile_data: NucleusData
        the projectile type
    projectile_energy: float
        the projectile kinetic energy in MeV

    Returns
    -------
    float
        dEdx in MeV/g/cm^2
    """
    mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
    projectile = catima.Projectile(mass_u, projectile_data.Z)
    projectile.T(projectile_energy / mass_u)
    return catima.dedx(projectile, self.material)

get_energy_loss(projectile_data, projectile_energy, distances)

Calculate the energy loss of a projectile traveling over a set of distances

Parameters:

Name Type Description Default
projectile_data NucleusData

the projectile type

required
projectile_energy float

the projectile kinetic energy in MeV

required
distances ndarray

a set of distances in meters over which to calculate the energy loss

required

Returns:

Type Description
ndarray

set of energy losses

Source code in src/spyral_utils/nuclear/target.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def get_energy_loss(
    self,
    projectile_data: NucleusData,
    projectile_energy: float,
    distances: np.ndarray,
) -> np.ndarray:
    """
    Calculate the energy loss of a projectile traveling over a set of distances

    Parameters
    ----------
    projectile_data: NucleusData
        the projectile type
    projectile_energy: float
        the projectile kinetic energy in MeV
    distances: ndarray
        a set of distances in meters over which to calculate the energy loss

    Returns
    -------
    ndarray
        set of energy losses
    """
    mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
    projectile = catima.Projectile(
        mass_u, projectile_data.Z, T=projectile_energy / mass_u
    )
    eloss = np.zeros(len(distances))
    for idx, distance in enumerate(distances):
        self.material.thickness_cm(distance * 100.0)
        projectile.T(projectile_energy / mass_u)
        eloss[idx] = catima.calculate(projectile, self.material).get_dict()["Eloss"]
    return eloss

get_number_density()

Get the number density of gas molecules

Returns:

Type Description
Number density of gas molecules in molecules/cm^3
Source code in src/spyral_utils/nuclear/target.py
221
222
223
224
225
226
227
228
def get_number_density(self) -> float:
    """Get the number density of gas molecules

    Returns
    -------
    Number density of gas molecules in molecules/cm^3
    """
    return self.material.number_density()

get_range(projectile_data, projectile_energy)

Calculate the range of a projectile in the target

Parameters:

Name Type Description Default
projectile_data NucleusData

the projectile type

required
projectile_energy float

the projectile kinetic energy in MeV

required

Returns:

Type Description
float

The range in m

Source code in src/spyral_utils/nuclear/target.py
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
def get_range(
    self, projectile_data: NucleusData, projectile_energy: float
) -> float:
    """Calculate the range of a projectile in the target

    Parameters
    ----------
    projectile_data: NucleusData
        the projectile type
    projectile_energy: float
        the projectile kinetic energy in MeV

    Returns
    -------
    float
        The range in m
    """
    mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
    projectile = catima.Projectile(
        mass_u, projectile_data.Z, T=projectile_energy / mass_u
    )
    range_gcm2 = catima.calculate(projectile, self.material).get_dict()["range"]
    range_m = range_gcm2 / self.material.density() * 0.01
    return range_m

NuclearDataMap

Maps nuclear numbers (Z,A) to mass information

Reads in AME 2020 mass evaluation and creates a map of nucleus numbers (Z, A) to nuclear mass information

Attributes:

Name Type Description
map dict[int, NucleusData]

dictionary mapping nucleus id's to associated data

Methods:

Name Description
get_data

retrieve the mass data for a given nucleus

Source code in src/spyral_utils/nuclear/nuclear_map.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
class NuclearDataMap:
    """Maps nuclear numbers (Z,A) to mass information

    Reads in AME 2020 mass evaluation and creates a map of nucleus numbers (Z, A)
    to nuclear mass information

    Attributes
    ----------
    map: dict[int, NucleusData]
        dictionary mapping nucleus id's to associated data

    Methods
    -------
    get_data(z: int, a: int) -> NucleusData
        retrieve the mass data for a given nucleus
    """

    def __init__(self):
        self.map = {}

        data_handle = files("spyral_utils.nuclear").joinpath("amdc_2020.txt")
        with as_file(data_handle) as data_path:
            data_file = open(data_path, "r")
            data_file.readline()  # Header
            for line in data_file:
                entries = line.split()
                data = NucleusData()
                data.Z = int(entries[0])  # Column 1: Z
                data.A = int(entries[1])  # Column 2: A
                data.element_symbol = entries[2]  # Column 3: Element
                data.atomic_mass = float(entries[3])
                data.mass = (
                    (float(entries[3]) - float(data.Z) * ELECTRON_MASS_U) * AMU_2_MEV
                )  # Remove electron masses to obtain nuclear masses, Column 4
                data.isotopic_symbol = f"{data.A}{entries[2]}"
                data.pretty_iso_symbol = f"<sup>{data.A}</sup>{entries[2]}"
                self.map[generate_nucleus_id(data.Z, data.A)] = data
            data_file.close()

    def get_data(self, z: int, a: int) -> NucleusData:
        """retrieve the mass data for a given nucleus

        Parameters
        ----------
        z: int
            atomic number
        a: int
            mass number

        Returns
        -------
        NucleusData
            associated mass data
        """
        return self.map[generate_nucleus_id(z, a)]

get_data(z, a)

retrieve the mass data for a given nucleus

Parameters:

Name Type Description Default
z int

atomic number

required
a int

mass number

required

Returns:

Type Description
NucleusData

associated mass data

Source code in src/spyral_utils/nuclear/nuclear_map.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def get_data(self, z: int, a: int) -> NucleusData:
    """retrieve the mass data for a given nucleus

    Parameters
    ----------
    z: int
        atomic number
    a: int
        mass number

    Returns
    -------
    NucleusData
        associated mass data
    """
    return self.map[generate_nucleus_id(z, a)]

NucleusData dataclass

Nucleus data from AME dataclass

Contains information on nuclear masses and symbols

Attributes:

Name Type Description
mass float

mass of the nucleus in MeV

atomic_mass float

atomic mass in amu

element_symbol str

atomic symbol (H, He, Li, etc.)

isotopic_symbol str

isotopic symbol without formatting (1H, 2H, 3H, 4He, etc.)

pretty_iso_symbol str

isotopic symbol with rich text formatting (1H, etc.)

Z int

atomic number

A int

mass number

Methods:

Name Description
__str__

string representation, isotopic_symbol

get_latex_rep

get a LaTeX style represenation, for use with matplotlib etc.

Source code in src/spyral_utils/nuclear/nuclear_map.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
@dataclass
class NucleusData:
    """Nucleus data from AME dataclass

    Contains information on nuclear masses and symbols

    Attributes
    ----------
    mass: float
        mass of the nucleus in MeV
    atomic_mass: float
        atomic mass in amu
    element_symbol: str
        atomic symbol (H, He, Li, etc.)
    isotopic_symbol: str
        isotopic symbol without formatting (1H, 2H, 3H, 4He, etc.)
    pretty_iso_symbol: str
        isotopic symbol with rich text formatting (<sup>1</sup>H, etc.)
    Z: int
        atomic number
    A: int
        mass number

    Methods
    -------
    __str__()
        string representation, isotopic_symbol
    get_latex_rep()
        get a LaTeX style represenation, for use with matplotlib etc.

    """

    mass: float = 0.0  # nuclear mass, MeV
    atomic_mass: float = 0.0  # atomic mass (includes electrons), amu
    element_symbol: str = ""  # Element symbol (H, He, Li, etc.)
    isotopic_symbol: str = ""  # Isotopic symbol w/o formating (1H, 2H, 3H, 4He, etc.)
    pretty_iso_symbol: str = (
        ""  # Isotopic symbol w/ rich text formating (<sup>1</sup>H, etc.)
    )
    Z: int = 0
    A: int = 0

    def __str__(self) -> str:
        return self.isotopic_symbol

    def get_latex_rep(self) -> str:
        """Get the LaTeX representation of the isotopic symbol

        Returns
        -------
        str
            a string of the isotopic symbol in LaTeX format
        """

        return "$^{" + str(self.A) + "}$" + self.element_symbol

get_latex_rep()

Get the LaTeX representation of the isotopic symbol

Returns:

Type Description
str

a string of the isotopic symbol in LaTeX format

Source code in src/spyral_utils/nuclear/nuclear_map.py
72
73
74
75
76
77
78
79
80
81
def get_latex_rep(self) -> str:
    """Get the LaTeX representation of the isotopic symbol

    Returns
    -------
    str
        a string of the isotopic symbol in LaTeX format
    """

    return "$^{" + str(self.A) + "}$" + self.element_symbol

ParticleID dataclass

Thin wrapper over spyral-utils Cut2D that attaches a NucleusData

Used to gate on particle groups in Brho and dEdx

Attributes:

Name Type Description
cut Cut2D

A spyral-utils Cut2D on brho and dEdx estimated parameters

nucleus NucleusData

The nucleus species associated with this ID

Source code in src/spyral_utils/nuclear/particle_id.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@dataclass
class ParticleID:
    """Thin wrapper over spyral-utils Cut2D that attaches a NucleusData

    Used to gate on particle groups in Brho and dEdx

    Attributes
    ----------
    cut: spyral_utils.plot.Cut2D
        A spyral-utils Cut2D on brho and dEdx estimated parameters
    nucleus: NucleusData
        The nucleus species associated with this ID

    """

    cut: Cut2D
    nucleus: NucleusData

SolidTarget

An AT-TPC solid target

Solid target for which energy loss can be calculated using pycatima. Can perform several types of energy loss calculations (straggling, dEdx, energy lost, etc.)

Parameters:

Name Type Description Default
compound list[tuple[int, int, int]]

A list specifiying elements in the target compound as (Z, A, S)

required
thickness float

The target thickness in ug/cm^2

required
nuclear_data NuclearDataMap

The nucleus data

required

Attributes:

Name Type Description
compound list[tuple[int, int, int]]

A list specifiying elements in the target compound as (Z, A, S)

thickness float

The target thickness in ug/cm^2

ugly_string str

A string representation without rich formatting

pretty_string str

A string representation with rich formatting

material Material

The catima representation of the target

Methods:

Name Description
get_dedx

get the stopping power (dEdx) for a projectile in this target

get_angular_straggling

get the angular straggling for a projectile in this target

get_energy_loss

get the energy loss values for a projectile travelling at angles through the target

Source code in src/spyral_utils/nuclear/target.py
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
class SolidTarget:
    """An AT-TPC solid target

    Solid target for which energy loss can be calculated using pycatima. Can perform several types of
    energy loss calculations (straggling, dEdx, energy lost, etc.)

    Parameters
    ----------
    compound: list[tuple[int, int, int]]
        A list specifiying elements in the target compound as (Z, A, S)
    thickness: float
        The target thickness in ug/cm^2
    nuclear_data: NuclearDataMap
        The nucleus data

    Attributes
    ----------
    compound: list[tuple[int, int, int]]
        A list specifiying elements in the target compound as (Z, A, S)
    thickness: float
        The target thickness in ug/cm^2
    ugly_string: str
        A string representation without rich formatting
    pretty_string: str
        A string representation with rich formatting
    material: catima.Material
        The catima representation of the target

    Methods
    -------
    get_dedx(projectile_data: NucleusData, projectile_energy: float) -> float
        get the stopping power (dEdx) for a projectile in this target
    get_angular_straggling(projectile_data: NucleusData, projectile_energy: float) -> float:
        get the angular straggling for a projectile in this target
    get_energy_loss(projectile_data: NucleusData, projectile_energy: float, angles: ndarray) -> ndarray:
        get the energy loss values for a projectile travelling at angles through the target
    """

    UG2G: float = 1.0e-6  # convert ug to g

    def __init__(
        self,
        compound: list[tuple[int, int, int]],
        thickness: float,
        nuclear_data: NuclearDataMap,
    ):
        self.compound = compound
        self.thickness = thickness

        self.pretty_string: str = "(Solid)" + "".join(
            [
                f"{nuclear_data.get_data(z, a).pretty_iso_symbol}<sub>{s}</sub>"
                for (z, a, s) in self.compound
            ]
        )
        self.ugly_string: str = "(Solid)" + "".join(
            [
                f"{nuclear_data.get_data(z, a).isotopic_symbol}{s}"
                for (z, a, s) in self.compound
            ]
        )

        # Construct the target material
        self.material = catima.Material()
        for (
            z,
            a,
            s,
        ) in self.compound:
            self.material.add_element(
                nuclear_data.get_data(z, a).atomic_mass, z, float(s)
            )
        self.material.thickness(self.thickness * self.UG2G)  # Convert ug/cm^2 to g/cm^2

    def get_dedx(self, projectile_data: NucleusData, projectile_energy: float) -> float:
        """Calculate the stopping power of the target for a projectile

        Parameters
        ----------
        projectile_data: NucleusData
            the projectile type
        projectile_energy: float
            the projectile kinetic energy in MeV

        Returns
        -------
        float
            dEdx in MeV/g/cm^2
        """
        mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
        projectile = catima.Projectile(mass_u, projectile_data.Z)
        projectile.T(projectile_energy / mass_u)
        return catima.dedx(projectile, self.material)

    def get_angular_straggling(
        self, projectile_data: NucleusData, projectile_energy: float
    ) -> float:
        """Calculate the angular straggling for a projectile

        Parameters
        ----------
        projectile_data: NucleusData
            the projectile type
        projectile_energy: float
            the projectile kinetic energy in MeV

        Returns
        -------
        float
            angular straggling in radians
        """
        mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
        projectile = catima.Projectile(
            mass_u, projectile_data.Z, T=projectile_energy / mass_u
        )
        return catima.calculate(projectile, self.material).get_dict()["sigma_a"]

    def get_energy_loss(
        self,
        projectile_data: NucleusData,
        projectile_energy: float,
        incident_angles: np.ndarray,
    ) -> np.ndarray:
        """Calculate the energy loss of a projectile traveling through the solid target for a given set of incident angles

        Assumes travelling through the entire target at an incident angle.

        Parameters
        ----------
        projectile_data: NucleusData
            the projectile type
        projectile_energy: float
            the projectile kinetic energy in MeV
        incident_angles: ndarray
            a set of incident angles, describing the angle between the particle trajectory and the normal of the target surface

        Returns
        -------
        ndarray
            set of energy losses
        """
        mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
        projectile = catima.Projectile(
            mass_u, projectile_data.Z, T=projectile_energy / mass_u
        )
        eloss = np.zeros(len(incident_angles))
        nominal_thickness = self.material.thickness()
        for idx, angle in enumerate(incident_angles):
            self.material.thickness(nominal_thickness / abs(np.cos(angle)))
            projectile.T(projectile_energy / mass_u)
            eloss[idx] = catima.calculate(projectile, self.material).get_dict()["Eloss"]
        self.material.thickness(nominal_thickness)
        return eloss

    def get_range(
        self, projectile_data: NucleusData, projectile_energy: float
    ) -> float:
        """Calculate the range of a projectile in the target in g/cm^2

        Parameters
        ----------
        projectile_data: NucleusData
            the projectile type
        projectile_energy: float
            the projectile kinetic energy in MeV

        Returns
        -------
        float
            The range in g/cm^2
        """
        mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
        projectile = catima.Projectile(
            mass_u, projectile_data.Z, T=projectile_energy / mass_u
        )

        return catima.calculate(projectile, self.material).get_dict()["range"]

get_angular_straggling(projectile_data, projectile_energy)

Calculate the angular straggling for a projectile

Parameters:

Name Type Description Default
projectile_data NucleusData

the projectile type

required
projectile_energy float

the projectile kinetic energy in MeV

required

Returns:

Type Description
float

angular straggling in radians

Source code in src/spyral_utils/nuclear/target.py
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
def get_angular_straggling(
    self, projectile_data: NucleusData, projectile_energy: float
) -> float:
    """Calculate the angular straggling for a projectile

    Parameters
    ----------
    projectile_data: NucleusData
        the projectile type
    projectile_energy: float
        the projectile kinetic energy in MeV

    Returns
    -------
    float
        angular straggling in radians
    """
    mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
    projectile = catima.Projectile(
        mass_u, projectile_data.Z, T=projectile_energy / mass_u
    )
    return catima.calculate(projectile, self.material).get_dict()["sigma_a"]

get_dedx(projectile_data, projectile_energy)

Calculate the stopping power of the target for a projectile

Parameters:

Name Type Description Default
projectile_data NucleusData

the projectile type

required
projectile_energy float

the projectile kinetic energy in MeV

required

Returns:

Type Description
float

dEdx in MeV/g/cm^2

Source code in src/spyral_utils/nuclear/target.py
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
def get_dedx(self, projectile_data: NucleusData, projectile_energy: float) -> float:
    """Calculate the stopping power of the target for a projectile

    Parameters
    ----------
    projectile_data: NucleusData
        the projectile type
    projectile_energy: float
        the projectile kinetic energy in MeV

    Returns
    -------
    float
        dEdx in MeV/g/cm^2
    """
    mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
    projectile = catima.Projectile(mass_u, projectile_data.Z)
    projectile.T(projectile_energy / mass_u)
    return catima.dedx(projectile, self.material)

get_energy_loss(projectile_data, projectile_energy, incident_angles)

Calculate the energy loss of a projectile traveling through the solid target for a given set of incident angles

Assumes travelling through the entire target at an incident angle.

Parameters:

Name Type Description Default
projectile_data NucleusData

the projectile type

required
projectile_energy float

the projectile kinetic energy in MeV

required
incident_angles ndarray

a set of incident angles, describing the angle between the particle trajectory and the normal of the target surface

required

Returns:

Type Description
ndarray

set of energy losses

Source code in src/spyral_utils/nuclear/target.py
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
def get_energy_loss(
    self,
    projectile_data: NucleusData,
    projectile_energy: float,
    incident_angles: np.ndarray,
) -> np.ndarray:
    """Calculate the energy loss of a projectile traveling through the solid target for a given set of incident angles

    Assumes travelling through the entire target at an incident angle.

    Parameters
    ----------
    projectile_data: NucleusData
        the projectile type
    projectile_energy: float
        the projectile kinetic energy in MeV
    incident_angles: ndarray
        a set of incident angles, describing the angle between the particle trajectory and the normal of the target surface

    Returns
    -------
    ndarray
        set of energy losses
    """
    mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
    projectile = catima.Projectile(
        mass_u, projectile_data.Z, T=projectile_energy / mass_u
    )
    eloss = np.zeros(len(incident_angles))
    nominal_thickness = self.material.thickness()
    for idx, angle in enumerate(incident_angles):
        self.material.thickness(nominal_thickness / abs(np.cos(angle)))
        projectile.T(projectile_energy / mass_u)
        eloss[idx] = catima.calculate(projectile, self.material).get_dict()["Eloss"]
    self.material.thickness(nominal_thickness)
    return eloss

get_range(projectile_data, projectile_energy)

Calculate the range of a projectile in the target in g/cm^2

Parameters:

Name Type Description Default
projectile_data NucleusData

the projectile type

required
projectile_energy float

the projectile kinetic energy in MeV

required

Returns:

Type Description
float

The range in g/cm^2

Source code in src/spyral_utils/nuclear/target.py
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
def get_range(
    self, projectile_data: NucleusData, projectile_energy: float
) -> float:
    """Calculate the range of a projectile in the target in g/cm^2

    Parameters
    ----------
    projectile_data: NucleusData
        the projectile type
    projectile_energy: float
        the projectile kinetic energy in MeV

    Returns
    -------
    float
        The range in g/cm^2
    """
    mass_u = projectile_data.mass / AMU_2_MEV  # convert to u
    projectile = catima.Projectile(
        mass_u, projectile_data.Z, T=projectile_energy / mass_u
    )

    return catima.calculate(projectile, self.material).get_dict()["range"]

deserialize_particle_id(path, nuclear_map)

Load a ParticleID from a JSON file

Parameters:

Name Type Description Default
path Path

The path to a JSON file containing a ParticleID

required
nuclear_map NuclearDataMap

An instance of a spyral_utils.nuclear.NuclearDataMap

required

Returns:

Type Description
ParticleID | None

The deserialized ParticleID or None on failure

Source code in src/spyral_utils/nuclear/particle_id.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def deserialize_particle_id(
    path: Path, nuclear_map: NuclearDataMap
) -> ParticleID | None:
    """Load a ParticleID from a JSON file

    Parameters
    ----------
    path: Path
        The path to a JSON file containing a ParticleID
    nuclear_map: NuclearDataMap
        An instance of a spyral_utils.nuclear.NuclearDataMap

    Returns
    -------
    ParticleID | None
        The deserialized ParticleID or None on failure
    """
    try:
        with open(path, "r") as cut_file:
            json_data = load(cut_file)
            if (
                "name" not in json_data
                or "vertices" not in json_data
                or "Z" not in json_data
                or "A" not in json_data
            ):
                print(
                    f"ParticleID could not load cut in {path}, the requested data is not present."
                )
                return None

            xaxis = DEFAULT_CUT_AXIS
            yaxis = DEFAULT_CUT_AXIS
            if "xaxis" in json_data and "yaxis" in json_data:
                xaxis = json_data["xaxis"]
                yaxis = json_data["yaxis"]

            pid = ParticleID(
                Cut2D(
                    json_data["name"], json_data["vertices"], x_axis=xaxis, y_axis=yaxis
                ),
                nuclear_map.get_data(json_data["Z"], json_data["A"]),
            )

            if pid.nucleus.A == 0:
                print(
                    f"Nucleus Z: {json_data['Z']} A: {json_data['A']} requested by ParticleID {json_data['name']} does not exist."
                )
                return None

            return pid
    except Exception as error:
        print(f"Could not deserialize ParticleID with error: {error}")
        return None

generate_nucleus_id(z, a)

get a unqiue nucleus id

Use the Szudzik pairing function to generate a unique nucleus ID

Parameters:

Name Type Description Default
z int

Nucleus atomic number

required
a int

Nucleus mass number

required

Returns:

Type Description
int

Unique identifier for this nucleus

Source code in src/spyral_utils/nuclear/nuclear_map.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def generate_nucleus_id(z: int, a: int) -> int:
    """get a unqiue nucleus id

    Use the Szudzik pairing function to generate a unique nucleus ID

    Parameters
    ----------
    z: int
        Nucleus atomic number
    a: int
        Nucleus mass number

    Returns
    -------
    int
        Unique identifier for this nucleus

    """
    return z * z + z + a if z == max(z, a) else a * a + z

load_target(target_path, nuclear_map)

Load a target from a JSON file

Read the JSON data, and construct the appropriate target type.

Parameters:

Name Type Description Default
target_path Path

Path to JSON data

required
nuclear_map NuclearDataMap

Nucleus mass data

required

Returns:

Type Description
GasTarget | SolidTarget | GasMixtureTarget

Return a GasTarget, GasMixtureTarget, or SolidTarget where appropriate. Raises a TargetError on failure.

Source code in src/spyral_utils/nuclear/target.py
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
def load_target(
    target_path: Path, nuclear_map: NuclearDataMap
) -> GasTarget | SolidTarget | GasMixtureTarget:
    """Load a target from a JSON file

    Read the JSON data, and construct the appropriate target type.

    Parameters
    ----------
    target_path: Path
        Path to JSON data
    nuclear_map: NuclearDataMap
        Nucleus mass data

    Returns
    -------
    GasTarget | SolidTarget | GasMixtureTarget
        Return a GasTarget, GasMixtureTarget, or SolidTarget where appropriate. Raises a
        TargetError on failure.
    """
    gas = deserialize_gas_target_data(target_path, nuclear_map)
    if gas is not None:
        return gas
    solid = deserialize_solid_target_data(target_path, nuclear_map)
    if solid is not None:
        return solid
    mix = deserialize_mixture_data(target_path, nuclear_map)
    if mix is not None:
        return mix
    raise TargetError(f"Invalid format for target data at {target_path}")

save_target(target_path, target)

Write a target to a JSON file

Create the JSON data, and write to disk.

Parameters:

Name Type Description Default
target_path Path

Path to JSON data file

required
target GasTarget | SolidTarget | GasMixtureTarget

Target to be written

required
Source code in src/spyral_utils/nuclear/target.py
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
def save_target(target_path: Path, target: GasTarget | SolidTarget | GasMixtureTarget):
    """Write a target to a JSON file

    Create the JSON data, and write to disk.

    Parameters
    ----------
    target_path: Path
        Path to JSON data file
    target: GasTarget | SolidTarget | GasMixtureTarget
        Target to be written
    """
    if isinstance(target, GasTarget):
        serialize_gas_target_data(target_path, target)
    elif isinstance(target, SolidTarget):
        serialize_solid_target_data(target_path, target)
    elif isinstance(target, GasMixtureTarget):
        serialize_mixture_data(target_path, target)
    else:
        raise TargetError(
            f"Object {target} does not match any known target type and could not be saved"
        )