Skip to content

beam

converter.converter.shieldhit.beam

T module-attribute

T = TypeVar('T', bound='LabelledEnum')

BeamConfig dataclass

Class mapping of the beam.dat config file.

Source code in yaptide/converter/converter/shieldhit/beam.py
 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
229
230
@dataclass
class BeamConfig:
    """Class mapping of the beam.dat config file."""

    particle: int = 2
    particle_name: Optional[str] = None
    heavy_ion_a: int = 1
    heavy_ion_z: int = 1
    energy: float = 150.  # [MeV]
    energy_spread: float = 1.5  # [MeV]
    energy_low_cutoff: Optional[float] = None  # [MeV]
    energy_high_cutoff: Optional[float] = None  # [MeV]
    beam_ext_x: float = -0.1  # [cm]
    beam_ext_y: float = 0.1  # [cm]
    sad_x: Optional[float] = None  # [cm]
    sad_y: Optional[float] = None  # [cm]
    n_stat: int = 10000
    beam_pos: tuple[float, float, float] = (0, 0, 0)  # [cm]
    beam_dir: tuple[float, float, float] = (0, 0, 1)  # [cm]
    delta_e: float = 0.03  # [a.u.]
    nuclear_reactions: bool = True

    modulator: Optional[BeamModulator] = None

    straggling: StragglingModel = StragglingModel.VAVILOV
    multiple_scattering: MultipleScatteringMode = MultipleScatteringMode.MOLIERE

    heavy_ion_template = "HIPROJ       	{a} {z}           ! A and Z of the heavy ion"
    energy_cutoff_template = "TCUT0       {energy_low_cutoff} {energy_high_cutoff}  ! energy cutoffs [MeV]"
    sad_template = "BEAMSAD {sad_x} {sad_y}  ! BEAMSAD value [cm]"
    beam_source_type: BeamSourceType = BeamSourceType.SIMPLE
    beam_source_filename: Optional[str] = None
    beam_source_file_content: Optional[str] = None

    beam_dat_template: str = """
RNDSEED      	89736501     ! Random seed
JPART0       	{particle}            ! Incident particle type{particle_optional_comment}
{optional_heavy_ion_line}
TMAX0      	{energy} {energy_spread}       ! Incident energy and energy spread; both in (MeV/nucl)
{optional_energy_cut_off_line}
NSTAT       {n_stat:d}    0       ! NSTAT, Step of saving
STRAGG          {straggling}            ! Straggling: 0-Off 1-Gauss, 2-Vavilov
MSCAT           {multiple_scattering}            ! Mult. scatt 0-Off 1-Gauss, 2-Moliere
NUCRE           {nuclear_reactions}            ! Nucl.Reac. switcher: 1-ON, 0-OFF
{optional_beam_modulator_lines}
BEAMPOS         {pos_x} {pos_y} {pos_z} ! Position of the beam
BEAMDIR         {theta} {phi} ! Direction of the beam
BEAMSIGMA       {beam_ext_x} {beam_ext_y}  ! Beam extension
{optional_sad_parameter_line}
DELTAE          {delta_e}   ! relative mean energy loss per transportation step
"""

    @staticmethod
    def cartesian2spherical(vector: tuple[float, float, float]) -> tuple[float, float, float]:
        """
        Transform cartesian coordinates to spherical coordinates.

        :param vector: cartesian coordinates
        :return: spherical coordinates
        """
        x, y, z = vector
        r = m.sqrt(x**2 + y**2 + z**2)
        # acos returns the angle in radians between 0 and pi
        theta = m.degrees(m.acos(z / r))
        # atan2 returns the angle in radians between -pi and pi
        phi = m.degrees(m.atan2(y, x))
        # lets ensure the angle in degrees is always between 0 and 360, as SHIELD-HIT12A requires
        if phi < 0.:
            phi += 360.
        return theta, phi, r

    def __str__(self) -> str:
        """Return the beam.dat config file as a string."""
        theta, phi, _ = BeamConfig.cartesian2spherical(self.beam_dir)

        # if particle name is defined, add the comment to the template
        particle_optional_comment = ""
        if self.particle_name:
            particle_optional_comment = f" ({self.particle_name})"

        # if particle is heavy ion, add the heavy ion line to the template
        heavy_ion_line = "! no heavy ion"
        if self.particle == 25:
            heavy_ion_line = BeamConfig.heavy_ion_template.format(a=self.heavy_ion_a, z=self.heavy_ion_z)

        # if energy cutoffs are defined, add them to the template
        cutoff_line = "! no energy cutoffs"
        if self.energy_low_cutoff is not None and self.energy_high_cutoff is not None:
            cutoff_line = BeamConfig.energy_cutoff_template.format(
                energy_low_cutoff=self.energy_low_cutoff,
                energy_high_cutoff=self.energy_high_cutoff
            )

        # if sad was defined, add it to the template
        sad_line = "! no BEAMSAD value"
        if self.sad_x is not None or self.sad_y is not None:
            sad_y_value = self.sad_y if self.sad_y is not None else ""
            sad_line = BeamConfig.sad_template.format(
                sad_x=self.sad_x,
                sad_y=sad_y_value)

        # if beam modulator was defined, add it to the template
        mod_lines = str(self.modulator) if self.modulator is not None else '! no beam modulator'

        # prepare main template
        result = self.beam_dat_template.format(
            particle=self.particle,
            particle_optional_comment=particle_optional_comment,
            optional_heavy_ion_line=heavy_ion_line,
            energy=float(self.energy),
            energy_spread=float(self.energy_spread),
            optional_energy_cut_off_line=cutoff_line,
            optional_sad_parameter_line=sad_line,
            optional_beam_modulator_lines=mod_lines,
            n_stat=self.n_stat,
            pos_x=self.beam_pos[0],
            pos_y=self.beam_pos[1],
            pos_z=self.beam_pos[2],
            beam_ext_x=self.beam_ext_x,
            beam_ext_y=self.beam_ext_y,
            theta=theta,
            phi=phi,
            delta_e=self.delta_e,
            nuclear_reactions=1 if self.nuclear_reactions else 0,
            straggling=self.straggling.value,
            multiple_scattering=self.multiple_scattering.value
        )

        # if beam source type is file, add the file name to the template
        if self.beam_source_type == BeamSourceType.FILE:
            result += "USECBEAM   sobp.dat   ! Use custom beam source file"

        return result

beam_dat_template class-attribute instance-attribute

beam_dat_template = "\nRNDSEED      \t89736501     ! Random seed\nJPART0       \t{particle}            ! Incident particle type{particle_optional_comment}\n{optional_heavy_ion_line}\nTMAX0      \t{energy} {energy_spread}       ! Incident energy and energy spread; both in (MeV/nucl)\n{optional_energy_cut_off_line}\nNSTAT       {n_stat:d}    0       ! NSTAT, Step of saving\nSTRAGG          {straggling}            ! Straggling: 0-Off 1-Gauss, 2-Vavilov\nMSCAT           {multiple_scattering}            ! Mult. scatt 0-Off 1-Gauss, 2-Moliere\nNUCRE           {nuclear_reactions}            ! Nucl.Reac. switcher: 1-ON, 0-OFF\n{optional_beam_modulator_lines}\nBEAMPOS         {pos_x} {pos_y} {pos_z} ! Position of the beam\nBEAMDIR         {theta} {phi} ! Direction of the beam\nBEAMSIGMA       {beam_ext_x} {beam_ext_y}  ! Beam extension\n{optional_sad_parameter_line}\nDELTAE          {delta_e}   ! relative mean energy loss per transportation step\n"

beam_dir class-attribute instance-attribute

beam_dir = (0, 0, 1)

beam_ext_x class-attribute instance-attribute

beam_ext_x = -0.1

beam_ext_y class-attribute instance-attribute

beam_ext_y = 0.1

beam_pos class-attribute instance-attribute

beam_pos = (0, 0, 0)

beam_source_file_content class-attribute instance-attribute

beam_source_file_content = None

beam_source_filename class-attribute instance-attribute

beam_source_filename = None

beam_source_type class-attribute instance-attribute

beam_source_type = SIMPLE

delta_e class-attribute instance-attribute

delta_e = 0.03

energy class-attribute instance-attribute

energy = 150.0

energy_cutoff_template class-attribute instance-attribute

energy_cutoff_template = "TCUT0       {energy_low_cutoff} {energy_high_cutoff}  ! energy cutoffs [MeV]"

energy_high_cutoff class-attribute instance-attribute

energy_high_cutoff = None

energy_low_cutoff class-attribute instance-attribute

energy_low_cutoff = None

energy_spread class-attribute instance-attribute

energy_spread = 1.5

heavy_ion_a class-attribute instance-attribute

heavy_ion_a = 1

heavy_ion_template class-attribute instance-attribute

heavy_ion_template = "HIPROJ       \t{a} {z}           ! A and Z of the heavy ion"

heavy_ion_z class-attribute instance-attribute

heavy_ion_z = 1

modulator class-attribute instance-attribute

modulator = None

multiple_scattering class-attribute instance-attribute

multiple_scattering = MOLIERE

n_stat class-attribute instance-attribute

n_stat = 10000

nuclear_reactions class-attribute instance-attribute

nuclear_reactions = True

particle class-attribute instance-attribute

particle = 2

particle_name class-attribute instance-attribute

particle_name = None

sad_template class-attribute instance-attribute

sad_template = (
    "BEAMSAD {sad_x} {sad_y}  ! BEAMSAD value [cm]"
)

sad_x class-attribute instance-attribute

sad_x = None

sad_y class-attribute instance-attribute

sad_y = None

straggling class-attribute instance-attribute

straggling = VAVILOV

__init__

__init__(
    particle=2,
    particle_name=None,
    heavy_ion_a=1,
    heavy_ion_z=1,
    energy=150.0,
    energy_spread=1.5,
    energy_low_cutoff=None,
    energy_high_cutoff=None,
    beam_ext_x=-0.1,
    beam_ext_y=0.1,
    sad_x=None,
    sad_y=None,
    n_stat=10000,
    beam_pos=(0, 0, 0),
    beam_dir=(0, 0, 1),
    delta_e=0.03,
    nuclear_reactions=True,
    modulator=None,
    straggling=StragglingModel.VAVILOV,
    multiple_scattering=MultipleScatteringMode.MOLIERE,
    heavy_ion_template="HIPROJ       \t{a} {z}           ! A and Z of the heavy ion",
    energy_cutoff_template="TCUT0       {energy_low_cutoff} {energy_high_cutoff}  ! energy cutoffs [MeV]",
    sad_template="BEAMSAD {sad_x} {sad_y}  ! BEAMSAD value [cm]",
    beam_source_type=BeamSourceType.SIMPLE,
    beam_source_filename=None,
    beam_source_file_content=None,
    beam_dat_template="\nRNDSEED      \t89736501     ! Random seed\nJPART0       \t{particle}            ! Incident particle type{particle_optional_comment}\n{optional_heavy_ion_line}\nTMAX0      \t{energy} {energy_spread}       ! Incident energy and energy spread; both in (MeV/nucl)\n{optional_energy_cut_off_line}\nNSTAT       {n_stat:d}    0       ! NSTAT, Step of saving\nSTRAGG          {straggling}            ! Straggling: 0-Off 1-Gauss, 2-Vavilov\nMSCAT           {multiple_scattering}            ! Mult. scatt 0-Off 1-Gauss, 2-Moliere\nNUCRE           {nuclear_reactions}            ! Nucl.Reac. switcher: 1-ON, 0-OFF\n{optional_beam_modulator_lines}\nBEAMPOS         {pos_x} {pos_y} {pos_z} ! Position of the beam\nBEAMDIR         {theta} {phi} ! Direction of the beam\nBEAMSIGMA       {beam_ext_x} {beam_ext_y}  ! Beam extension\n{optional_sad_parameter_line}\nDELTAE          {delta_e}   ! relative mean energy loss per transportation step\n",
)

__str__

__str__()

Return the beam.dat config file as a string.

Source code in yaptide/converter/converter/shieldhit/beam.py
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
229
230
def __str__(self) -> str:
    """Return the beam.dat config file as a string."""
    theta, phi, _ = BeamConfig.cartesian2spherical(self.beam_dir)

    # if particle name is defined, add the comment to the template
    particle_optional_comment = ""
    if self.particle_name:
        particle_optional_comment = f" ({self.particle_name})"

    # if particle is heavy ion, add the heavy ion line to the template
    heavy_ion_line = "! no heavy ion"
    if self.particle == 25:
        heavy_ion_line = BeamConfig.heavy_ion_template.format(a=self.heavy_ion_a, z=self.heavy_ion_z)

    # if energy cutoffs are defined, add them to the template
    cutoff_line = "! no energy cutoffs"
    if self.energy_low_cutoff is not None and self.energy_high_cutoff is not None:
        cutoff_line = BeamConfig.energy_cutoff_template.format(
            energy_low_cutoff=self.energy_low_cutoff,
            energy_high_cutoff=self.energy_high_cutoff
        )

    # if sad was defined, add it to the template
    sad_line = "! no BEAMSAD value"
    if self.sad_x is not None or self.sad_y is not None:
        sad_y_value = self.sad_y if self.sad_y is not None else ""
        sad_line = BeamConfig.sad_template.format(
            sad_x=self.sad_x,
            sad_y=sad_y_value)

    # if beam modulator was defined, add it to the template
    mod_lines = str(self.modulator) if self.modulator is not None else '! no beam modulator'

    # prepare main template
    result = self.beam_dat_template.format(
        particle=self.particle,
        particle_optional_comment=particle_optional_comment,
        optional_heavy_ion_line=heavy_ion_line,
        energy=float(self.energy),
        energy_spread=float(self.energy_spread),
        optional_energy_cut_off_line=cutoff_line,
        optional_sad_parameter_line=sad_line,
        optional_beam_modulator_lines=mod_lines,
        n_stat=self.n_stat,
        pos_x=self.beam_pos[0],
        pos_y=self.beam_pos[1],
        pos_z=self.beam_pos[2],
        beam_ext_x=self.beam_ext_x,
        beam_ext_y=self.beam_ext_y,
        theta=theta,
        phi=phi,
        delta_e=self.delta_e,
        nuclear_reactions=1 if self.nuclear_reactions else 0,
        straggling=self.straggling.value,
        multiple_scattering=self.multiple_scattering.value
    )

    # if beam source type is file, add the file name to the template
    if self.beam_source_type == BeamSourceType.FILE:
        result += "USECBEAM   sobp.dat   ! Use custom beam source file"

    return result

cartesian2spherical staticmethod

cartesian2spherical(vector)

Transform cartesian coordinates to spherical coordinates.

:param vector: cartesian coordinates :return: spherical coordinates

Source code in yaptide/converter/converter/shieldhit/beam.py
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
@staticmethod
def cartesian2spherical(vector: tuple[float, float, float]) -> tuple[float, float, float]:
    """
    Transform cartesian coordinates to spherical coordinates.

    :param vector: cartesian coordinates
    :return: spherical coordinates
    """
    x, y, z = vector
    r = m.sqrt(x**2 + y**2 + z**2)
    # acos returns the angle in radians between 0 and pi
    theta = m.degrees(m.acos(z / r))
    # atan2 returns the angle in radians between -pi and pi
    phi = m.degrees(m.atan2(y, x))
    # lets ensure the angle in degrees is always between 0 and 360, as SHIELD-HIT12A requires
    if phi < 0.:
        phi += 360.
    return theta, phi, r

BeamModulator dataclass

Beam modulator card dataclass used in BeamConfig.

Source code in yaptide/converter/converter/shieldhit/beam.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
@dataclass(frozen=True)
class BeamModulator():
    """Beam modulator card dataclass used in BeamConfig."""

    filename: str
    file_content: str
    zone_id: int
    simulation: ModulatorSimulationMethod = ModulatorSimulationMethod.MODULUS
    mode: ModulatorInterpretationMode = ModulatorInterpretationMode.MATERIAL

    def __str__(self) -> str:
        """Returns the string representation of the beam modulator card"""
        modulator_template = """USEBMOD         {zone} {filename} ! Zone# and file name for beam modulator
BMODMC          {simulation}            ! Simulation method for beam modulator (0-Modulus, 1-Monte Carlo sampling)
BMODTRANS       {mode}            ! Interpretation of thicknesses data in the config file (0-Material, 1-Vacuum)"""
        return modulator_template.format(
            zone=self.zone_id,
            filename=self.filename,
            simulation=self.simulation,
            mode=self.mode)

file_content instance-attribute

file_content

filename instance-attribute

filename

mode class-attribute instance-attribute

mode = MATERIAL

simulation class-attribute instance-attribute

simulation = MODULUS

zone_id instance-attribute

zone_id

__init__

__init__(
    filename,
    file_content,
    zone_id,
    simulation=ModulatorSimulationMethod.MODULUS,
    mode=ModulatorInterpretationMode.MATERIAL,
)

__str__

__str__()

Returns the string representation of the beam modulator card

Source code in yaptide/converter/converter/shieldhit/beam.py
86
87
88
89
90
91
92
93
94
95
    def __str__(self) -> str:
        """Returns the string representation of the beam modulator card"""
        modulator_template = """USEBMOD         {zone} {filename} ! Zone# and file name for beam modulator
BMODMC          {simulation}            ! Simulation method for beam modulator (0-Modulus, 1-Monte Carlo sampling)
BMODTRANS       {mode}            ! Interpretation of thicknesses data in the config file (0-Material, 1-Vacuum)"""
        return modulator_template.format(
            zone=self.zone_id,
            filename=self.filename,
            simulation=self.simulation,
            mode=self.mode)

BeamSourceType

Bases: LabelledEnum

Beam source type

Source code in yaptide/converter/converter/shieldhit/beam.py
34
35
36
37
38
39
@unique
class BeamSourceType(LabelledEnum):
    """Beam source type"""

    SIMPLE = (0, "simple")
    FILE = (1, "file")

FILE class-attribute instance-attribute

FILE = (1, 'file')

SIMPLE class-attribute instance-attribute

SIMPLE = (0, 'simple')

LabelledEnum

Bases: IntEnum

Base class for enums with a label attribute

Source code in yaptide/converter/converter/shieldhit/beam.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class LabelledEnum(IntEnum):
    """Base class for enums with a label attribute"""

    label: str

    def __new__(cls, value, label):

        if not isinstance(value, int):
            raise TypeError("Value must be an integer")
        obj = int.__new__(cls, value)
        obj._value_ = value
        obj.label = label
        return obj

    @classmethod
    def from_str(cls: Type[T], value: str) -> T:
        """Converts a string to a LabelledEnum"""
        for method in cls:
            if method.label == value:
                return method

        raise ValueError(f"{cls.__name__} has no value matching {value}")

label instance-attribute

label

__new__

__new__(value, label)
Source code in yaptide/converter/converter/shieldhit/beam.py
15
16
17
18
19
20
21
22
def __new__(cls, value, label):

    if not isinstance(value, int):
        raise TypeError("Value must be an integer")
    obj = int.__new__(cls, value)
    obj._value_ = value
    obj.label = label
    return obj

from_str classmethod

from_str(value)

Converts a string to a LabelledEnum

Source code in yaptide/converter/converter/shieldhit/beam.py
24
25
26
27
28
29
30
31
@classmethod
def from_str(cls: Type[T], value: str) -> T:
    """Converts a string to a LabelledEnum"""
    for method in cls:
        if method.label == value:
            return method

    raise ValueError(f"{cls.__name__} has no value matching {value}")

ModulatorInterpretationMode

Bases: LabelledEnum

Modulator interpretation mode of data in the input files loaded with the USEBMOD card

Source code in yaptide/converter/converter/shieldhit/beam.py
50
51
52
53
54
55
@unique
class ModulatorInterpretationMode(LabelledEnum):
    """Modulator interpretation mode of data in the input files loaded with the USEBMOD card"""

    MATERIAL = (0, "material")
    VACUMM = (1, "vacumm")

MATERIAL class-attribute instance-attribute

MATERIAL = (0, 'material')

VACUMM class-attribute instance-attribute

VACUMM = (1, 'vacumm')

ModulatorSimulationMethod

Bases: LabelledEnum

Modulator simulation method for beam.dat file

Source code in yaptide/converter/converter/shieldhit/beam.py
42
43
44
45
46
47
@unique
class ModulatorSimulationMethod(LabelledEnum):
    """Modulator simulation method for beam.dat file"""

    MODULUS = (0, "modulus")
    SAMPLING = (1, "sampling")

MODULUS class-attribute instance-attribute

MODULUS = (0, 'modulus')

SAMPLING class-attribute instance-attribute

SAMPLING = (1, 'sampling')

MultipleScatteringMode

Bases: LabelledEnum

Multiple scattering mode

Source code in yaptide/converter/converter/shieldhit/beam.py
67
68
69
70
71
72
73
@unique
class MultipleScatteringMode(LabelledEnum):
    """Multiple scattering mode"""

    GAUSSIAN = (1, "Gaussian")
    MOLIERE = (2, "Moliere")
    NO_SCATTERING = (0, "no scattering")

GAUSSIAN class-attribute instance-attribute

GAUSSIAN = (1, 'Gaussian')

MOLIERE class-attribute instance-attribute

MOLIERE = (2, 'Moliere')

NO_SCATTERING class-attribute instance-attribute

NO_SCATTERING = (0, 'no scattering')

StragglingModel

Bases: LabelledEnum

Straggle model

Source code in yaptide/converter/converter/shieldhit/beam.py
58
59
60
61
62
63
64
@unique
class StragglingModel(LabelledEnum):
    """Straggle model"""

    GAUSSIAN = (1, "Gaussian")
    VAVILOV = (2, "Vavilov")
    NO_STRAGGLING = (0, "no straggling")

GAUSSIAN class-attribute instance-attribute

GAUSSIAN = (1, 'Gaussian')

NO_STRAGGLING class-attribute instance-attribute

NO_STRAGGLING = (0, 'no straggling')

VAVILOV class-attribute instance-attribute

VAVILOV = (2, 'Vavilov')