NeutroWater API

The easiest way to get started with NeutroWater is to look at the Example. NeutroWater includes a Python API that enables running the simulation and analyzing the results. The three public modules are listed and documented below:

Module Usage
neutrowater.diffusing_neutrons Simulation set-up and running.
neutrowater.post.plot Plotting neutron trajectories.
neutrowater.post.measure Post simulation analysis on neutrons.



neutrowater.diffusing_neutrons

This module provides a class to simulate the diffusion of neutrons in a medium.

Classes:

Name Description
Parameters

Class to store the parameters for the simulation.

DiffusingNeutrons

Class to simulate the diffusion of neutrons in a medium.

DiffusingNeutrons

Class that simulates multiple neutrons from diffusing in a medium.

Parameters:
  • p (Parameters) –

    Parameters for the simulation.

Source code in neutrowater/diffusing_neutrons.py
 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
229
230
231
232
233
234
235
236
237
238
239
class DiffusingNeutrons:
    """
    Class that simulates multiple neutrons from diffusing in a medium.

    Args:
        p (Parameters): Parameters for the simulation.
    """

    def __init__(
        self,
        p: Parameters,
    ):
        self.kT = (
            1.380649e-23 * p.temperature * 6.24150907 * 10**18
        )  # [m^2 kg / (s^2 K) * K * (eV/J) = J*(eV/J) = eV]
        self.nCollisions = 0
        self.mol_struc = p.molecule_structure
        self.tank = Tank(p.radius_tank, p.height_tank, p.position_tank)

        # For interpolating the total cross section data and computing the
        # mean-free-path.
        self.total_processor = TotalProcessor(p.total_data)

        # For interpolating the scattering and absorption cross section and
        # computing the aborption ratio.
        self.absorption_processor = AbsorptionProcessor(
            p.scattering_data, p.absorption_data
        )

        # Sample from the interpolated energy spectrum
        spectrum_processor = SpectrumProcessor(p.spectrum_data)
        initial_energies = spectrum_processor.sample(num_samples=p.nNeutrons)

        # Convert the energies MeV -> eV
        initial_energies = [energy * 10**6 for energy in initial_energies]

        # All neutrons start at the origin
        initial_positions = [np.array([0, 0, 0]) for _ in range(p.nNeutrons)]
        self.neutrons = Neutrons(initial_energies, initial_positions)

        # For handling the collisions with the atomic nuclei, hydrogen and oxygen.
        self.nuclei_masses = p.nuclei_masses

        # Maxwell-Boltzmann distribution for the thermal energy
        self.mw = MaxwellBoltzmann(T=p.temperature)

        # For handling the angular distribution of the scattering.
        self.angular_processor = AngularProcessor(p.angular_data, p.nuclei_masses)

    def diffuse(self, nCollisions: int):
        """
        Let the neutrons diffuse in the medium.

        Args:
            nCollisions (int): number of times each neutron collides with an
                atomic nucleus.
        """
        self.nCollisions += nCollisions
        # Using not all cores but 2 less than the total number of cores, tends
        # to be faster
        num_processes = multiprocessing.cpu_count() - 2

        # Split the neutrons into chunks
        chunk_size = (len(self.neutrons) + num_processes - 1) // num_processes
        chunks = [
            self.neutrons[i : i + chunk_size]
            for i in range(0, len(self.neutrons), chunk_size)
        ]

        # Diffuse the chuncks of neutrons in parallel
        with multiprocessing.Pool(processes=num_processes) as pool:
            results = list(
                process_map(self._diffuse_chunk, chunks, [nCollisions] * len(chunks)),
            )

        # Update the neutrons list with the results from the parallel processess
        self.neutrons = [neutron for chunk in results for neutron in chunk]

    def _diffuse_chunk(self, chunk: Neutrons, nCollisions: int):
        """
        Diffuse a chunk of all neutrons in the medium.

        Args:
            chunk (Sequence[Neutron]): chunk of neutrons to diffuse
            nCollisions (int): number of times each neutron collides with an atomic
            nucleus.

        Returns: the neutrons after diffusing in the medium.
        """
        np.random.seed()  # Set a new seed for each process
        return [self._diffuse_neutron(neutron, nCollisions) for neutron in chunk]

    def _diffuse_neutron(self, neutron: Neutron, nCollisions: int) -> Neutron:
        """
        Diffuse a single neutron in the medium.

        Args:
            neutron (Neutron): Neutron to diffuse
            nCollisions (int): number of times the neutron collides with an atomic
            nucleus.

        Returns: the neutron after diffusing in the medium.
        """
        neutron.direction = random_direction()

        for _ in range(nCollisions):

            if not self.tank.inside(neutron.position):
                break

            # Sample the distance the neutron travels from an exponential distribution
            # with mean free path as the scale parameter.
            l = -self.total_processor.get_mfp(neutron.energy) * np.log(random())
            neutron.travel(l)

            # Determine the nucleus the neutron collides with
            mass = (
                self.nuclei_masses[0]
                if random() < self.total_processor.get_ratio(neutron.energy)
                else self.nuclei_masses[1]
            )

            collision = Collision(
                initial_E=neutron.energy,
                initial_direction=neutron.direction,
                mass=mass,
                scattering_cosine=self.angular_processor.get_CM_cosines(
                    mass, neutron.energy, 1
                )[0],
                absorption=self._absorbed(neutron, self.nuclei_masses.index(mass)),
                thermal=neutron.energy < 10 * self.kT,
            )

            # Update the neutron's energy and direction
            neutron.collide(collision.energy_loss_frac, collision.scattering_direction)

            # If the neutron is absorbed, break the loop
            if collision.energy_loss_frac == 1:
                break

        return neutron

    def _absorbed(self, neutron: Neutron, index: int) -> bool:
        """
        Check if the neutron is absorbed by the nucleus.

        Args:
            neutron (Neutron): Neutron to check.
            index (int): index of the nucleus in the molecule.

        Returns: True if the neutron is absorbed, False otherwise.
        """
        if (
            random()
            < self.absorption_processor.get_absorption_rates(neutron.energy)[index]
        ):
            return True
        else:
            return False

diffuse(nCollisions)

Let the neutrons diffuse in the medium.

Parameters:
  • nCollisions (int) –

    number of times each neutron collides with an atomic nucleus.

Source code in neutrowater/diffusing_neutrons.py
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
def diffuse(self, nCollisions: int):
    """
    Let the neutrons diffuse in the medium.

    Args:
        nCollisions (int): number of times each neutron collides with an
            atomic nucleus.
    """
    self.nCollisions += nCollisions
    # Using not all cores but 2 less than the total number of cores, tends
    # to be faster
    num_processes = multiprocessing.cpu_count() - 2

    # Split the neutrons into chunks
    chunk_size = (len(self.neutrons) + num_processes - 1) // num_processes
    chunks = [
        self.neutrons[i : i + chunk_size]
        for i in range(0, len(self.neutrons), chunk_size)
    ]

    # Diffuse the chuncks of neutrons in parallel
    with multiprocessing.Pool(processes=num_processes) as pool:
        results = list(
            process_map(self._diffuse_chunk, chunks, [nCollisions] * len(chunks)),
        )

    # Update the neutrons list with the results from the parallel processess
    self.neutrons = [neutron for chunk in results for neutron in chunk]

Parameters dataclass

Class to store the parameters for the simulation.

Parameters:
  • nNeutrons (int) –

    Number of neutrons to simulate

  • molecule_structure (Sequence, default: (2, 1) ) –

    number of atoms in the molecule.

  • nuclei_masses (Sequence, default: (1, 16) ) –

    Masses of the nuclei in the molecule, should be in the same order as the molecule_structure.

  • radius_tank (float, default: 1 ) –

    Radius of the tank in meters.

  • height_tank (float, default: 1 ) –

    Height of the tank in meters.

  • position_tank (ndarray, default: (0.0, 0.0, 0.0) ) –

    Position of the tank.

  • temperature (float, default: 293 ) –

    Temperature [K] of the medium.

Source code in neutrowater/diffusing_neutrons.py
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
@dataclass
class Parameters:
    """
    Class to store the parameters for the simulation.

    Args:
        nNeutrons (int): Number of neutrons to simulate
        molecule_structure (Sequence): number of atoms in the molecule.
        nuclei_masses (Sequence): Masses of the nuclei in the molecule, should be
            in the same order as the molecule_structure.
        radius_tank (float): Radius of the tank in meters.
        height_tank (float): Height of the tank in meters.
        position_tank (np.ndarray): Position of the tank.
        temperature (float): Temperature [K] of the medium.
    """

    nNeutrons: int
    molecule_structure: Sequence = (2, 1)
    nuclei_masses: Sequence = (1, 16)
    radius_tank: float = 1
    height_tank: float = 1
    position_tank: tuple = (0.0, 0.0, 0.0)
    temperature: float = 293

    def __post_init__(self):
        files = resources.files(data)

        self.total_data: Sequence[pd.DataFrame] = [
            pd.read_csv(files / "h_cross_t.txt", sep=r"\s+"),
            pd.read_csv(files / "o_cross_t.txt", sep=r"\s+"),
        ]
        self.scattering_data: Sequence[pd.DataFrame] = [
            pd.read_csv(files / "h_cross_s.txt", sep=r"\s+"),
            pd.read_csv(files / "o_cross_s.txt", sep=r"\s+"),
        ]
        self.absorption_data: Sequence[pd.DataFrame] = [
            pd.read_csv(files / "h_cross_a.txt", sep=r"\s+"),
            pd.read_csv(files / "o_cross_a.txt", sep=r"\s+"),
        ]
        self.angular_data: Sequence[pd.DataFrame] = [
            pd.read_csv(files / "H_angular.txt", sep=r"\s+"),
            pd.read_csv(files / "O_angular.txt", sep=r","),
        ]
        self.spectrum_data: pd.DataFrame = pd.read_csv(
            files / "neutron_spectrum_normalized.txt", sep=","
        )




neutrowater.post.plot

This module provides a function to plot the trajectories of the neutrons.

Functions:

Name Description
trajectories

Show the trajectories of the neutrons.

trajectories(diffusing_neutrons)

Show the trajectories of the neutrons.

Parameters:
Source code in neutrowater/post/plot.py
17
18
19
20
21
22
23
24
25
26
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
def trajectories(diffusing_neutrons: DiffusingNeutrons):
    """
    Show the trajectories of the neutrons.

    Args:
        diffusing_neutrons (DiffusingNeutrons): Object of the class
            DiffusingNeutrons.
    """

    df = diffusing_neutrons
    measurer = measure.Measurer(df)

    radius = df.tank.radius
    height = df.tank.height
    tank_position = df.tank.position

    fig = plt.figure(figsize=(8, 8))
    ax = fig.add_subplot(111, projection="3d")

    for position in measurer.positions():
        x = [p[0] for p in position]
        y = [p[1] for p in position]
        z = [p[2] for p in position]
        ax.plot(x, y, z, c="k", linewidth=0.5, alpha=0.7)

    ax.plot(0, 0, 0, "o", c="r")
    ax.set_xlabel("$x(m)$", fontsize=16)
    ax.set_ylabel("$y(m)$", fontsize=16)
    ax.set_box_aspect(aspect=None, zoom=2)
    ax.set_zlabel("$z(m)$", fontsize=16)
    ax.set_title("Neutron trajectories")
    ax.set_xlim(-0.4, 0.4)
    ax.set_ylim(-0.4, 0.4)
    ax.axis("off")
    ax.margins(x=0, y=0)
    ax.spines[["right", "top"]].set_visible(False)

    if df.tank is not None:
        u = np.linspace(0, 2 * np.pi, 100)
        v = np.linspace(0, height, 100)
        x = radius * np.outer(np.cos(u), np.ones_like(v)) + tank_position[0]
        y = radius * np.outer(np.sin(u), np.ones_like(v)) + tank_position[1]
        z = (
            np.outer(np.ones_like(u), v) - height / 2 + tank_position[2]
        )  # Adjust the height of the cylinder
        ax.plot_surface(x, y, z, color="cornflowerblue", alpha=0.3)

    plt.show()




neutrowater.post.measure

This module provides a class to measure the properties of the neutrons.

Classes:

Name Description
Measurer

Class to measure the properties of the neutrons after the simulation.

Measurer

Class to measure the properties of the neutrons after the simulation.

Methods:

Name Description
positions

Get the positions of the neutrons.

energies

Get the energies of the neutrons.

number_total

Get the total number of neutrons.

number_escaped

Get the number of neutrons that escaped the tank.

number_thermal

Get the number of neutrons that are thermalized.

thermalize_positions

Get the positions of the neutrons where their energy is thermal for the first time.

thermalize_distances

Get the distance of the neutrons where their energy is thermal for the first time.

number_absorbed

Get the number of neutrons that were absorbed.

absorbed_positions

Get the positions of the neutrons that were absorbed.

absorbed_distances

Get the distances of the neutrons that were absorbed.

flux

Get the flux of the neutrons at a given radius r.

energy_spectrum

Get the energy spectrum of the neutrons at a given radius r.

energy_spectrum_escaped

Get the energy spectrum of the neutrons that escaped the tank.

number_above_energy

Get the number of neutrons with energy above a given energy.

Source code in neutrowater/post/measure.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 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
 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
229
class Measurer:
    """Class to measure the properties of the neutrons after the simulation.

    Methods:
        positions: Get the positions of the neutrons.
        energies: Get the energies of the neutrons.
        number_total: Get the total number of neutrons.
        number_escaped: Get the number of neutrons that escaped the tank.
        number_thermal: Get the number of neutrons that are thermalized.
        thermalize_positions: Get the positions of the neutrons where their energy is thermal for the first time.
        thermalize_distances: Get the distance of the neutrons where their energy is thermal for the first time.
        number_absorbed: Get the number of neutrons that were absorbed.
        absorbed_positions: Get the positions of the neutrons that were absorbed.
        absorbed_distances: Get the distances of the neutrons that were absorbed.
        flux: Get the flux of the neutrons at a given radius r.
        energy_spectrum: Get the energy spectrum of the neutrons at a given radius r.
        energy_spectrum_escaped: Get the energy spectrum of the neutrons that escaped the tank.
        number_above_energy: Get the number of neutrons with energy above a given energy.

    """

    def __init__(self, sim: DiffusingNeutrons):
        self.neutrons = sim.neutrons
        self.sim = sim

    def positions(self) -> list[list[Vector]]:
        """
        Get the positions of the neutrons.

        Returns:
            a list where each element is a list of length nNeutrons
                [[np.ndarray, np.ndarray, ...], [np.ndarray, np.ndarray, ...], ...]
        """
        return [neutron.positions for neutron in self.neutrons]

    def energies(self) -> list[list[float]]:
        """
        Get the energies of the neutrons.

        Returns:
            a list where each element is a list of length nNeutrons
                [[float, float, ...], [float, float, ...], ...]
        """
        return [neutron.energies for neutron in self.neutrons]

    def number_total(self) -> int:
        """
        Get the total number of neutrons.

        Returns:
            an int.
        """
        return len(self.neutrons)

    def number_escaped(self) -> int:
        """
        Get the number of neutrons that escaped the tank.

        Returns:
            an int.
        """
        return sum(
            [
                1
                for neutron in self.neutrons
                if not self.sim.tank.inside(neutron.position)
            ],
            start=0,
        )

    def number_thermal(self) -> int:
        """
        Get the number of neutrons that are thermalized.

        Returns:
            an int.
        """
        n = 0
        for energies in self.energies():
            if energies[-1] < (10 * self.sim.kT):
                n += 1
        return n

    def thermalize_positions(self) -> list:
        """
        Get the positions of the neutrons where their energy is thermal for
        the first time.

        Returns:
            a list of np.ndarray.
        """
        thermalize_positions = []
        for energies, positions in zip(self.energies(), self.positions()):
            # Get the first element of the list of energies that is less than 10kT
            res = list(filter((lambda val: val < (10 * self.sim.kT)), energies))
            if len(res) > 0:
                index = energies.index(res[0])
                thermalize_positions.append(positions[index])

        return thermalize_positions

    def thermalize_distances(self) -> list:
        """
        Get the distance of the neutrons where their energy is thermal for
        the first time.

        Returns:
            a list of floats.
        """
        return [np.linalg.norm(pos) for pos in self.thermalize_positions()]

    def number_absorbed(self) -> int:
        """
        Get the number of neutrons that were absorbed.

        Returns:
            an int.
        """
        n = 0
        for neutron in self.neutrons:
            if len(neutron.positions) < self.sim.nCollisions and self.sim.tank.inside(
                neutron.position
            ):
                n += 1
        return n

    def absorbed_positions(self) -> list[Vector]:
        """
        Get the positions of the neutrons that were absorbed.

        Returns:
            a list of np.ndarray.
        """
        return [
            neutron.positions[-1]
            for neutron in self.neutrons
            if len(neutron.positions) < self.sim.nCollisions
            and self.sim.tank.inside(neutron.position)
        ]

    def absorbed_distances(self) -> list[np.float64]:
        """
        Get the distances of the neutrons that were absorbed.

        Returns:
            a list of floats.
        """
        return [np.linalg.norm(pos) for pos in self.absorbed_positions()]

    def flux(self, r: float) -> float:
        """
        Get the flux of the neutrons at a given radius r.

        Args:
            r (float): radius at which to compute the flux.

        Returns:
            a float value of the flux.
        """
        count = 0
        for positions in self.positions():
            positions = np.array(positions)
            norms = np.linalg.norm(positions, axis=1)
            # Count the number of neutrons that have been in the shell
            count += np.sum((norms[:-1] < r) & (r < norms[1:])) + np.sum(
                (norms[1:] < r) & (r < norms[:-1])
            )
        return count / (4 * np.pi * r**2)

    def energy_spectrum(self, r: float) -> list[float]:
        """
        Get the energy spectrum of the neutrons at a given radius r.

        Args:
            r (float): radius at which to compute the energy spectrum.

        Returns:
            a list of floats.
        """
        result_energies = []
        for positions, energies in zip(self.positions(), self.energies()):
            positions = np.array(positions)
            energies = np.array(energies)
            norms = np.linalg.norm(positions, axis=1)
            # Create boolean arrays for the conditions
            cond1 = np.concatenate([(norms[:-1] < r) & (r < norms[1:]), [False]])
            cond2 = np.concatenate([[False], (norms[1:] < r) & (r < norms[:-1])])
            # Get the energies of the neutrons that have been in the shell
            result_energies += list(energies[cond1]) + list(energies[cond2])
        return result_energies

    def energy_spectrum_escaped(self) -> list[float]:
        """
        Get the energy spectrum of the neutrons that escaped the tank.

        Returns:
            a list of floats.
        """
        return [
            neutron.energy
            for neutron in self.neutrons
            if not self.sim.tank.inside(neutron.position)
        ]

    def number_above_energy(self, E: float) -> int:
        """
        Get the number of neutrons with energy above a given energy.

        Args:
            E (float): energy threshold.

        Returns:
            an int.
        """
        return sum([1 for neutron in self.neutrons if neutron.energies[0] > E])

absorbed_distances()

Get the distances of the neutrons that were absorbed.

Returns:
  • list[float64]

    a list of floats.

Source code in neutrowater/post/measure.py
155
156
157
158
159
160
161
162
def absorbed_distances(self) -> list[np.float64]:
    """
    Get the distances of the neutrons that were absorbed.

    Returns:
        a list of floats.
    """
    return [np.linalg.norm(pos) for pos in self.absorbed_positions()]

absorbed_positions()

Get the positions of the neutrons that were absorbed.

Returns:
  • list[Vector]

    a list of np.ndarray.

Source code in neutrowater/post/measure.py
141
142
143
144
145
146
147
148
149
150
151
152
153
def absorbed_positions(self) -> list[Vector]:
    """
    Get the positions of the neutrons that were absorbed.

    Returns:
        a list of np.ndarray.
    """
    return [
        neutron.positions[-1]
        for neutron in self.neutrons
        if len(neutron.positions) < self.sim.nCollisions
        and self.sim.tank.inside(neutron.position)
    ]

energies()

Get the energies of the neutrons.

Returns:
  • list[list[float]]

    a list where each element is a list of length nNeutrons [[float, float, ...], [float, float, ...], ...]

Source code in neutrowater/post/measure.py
50
51
52
53
54
55
56
57
58
def energies(self) -> list[list[float]]:
    """
    Get the energies of the neutrons.

    Returns:
        a list where each element is a list of length nNeutrons
            [[float, float, ...], [float, float, ...], ...]
    """
    return [neutron.energies for neutron in self.neutrons]

energy_spectrum(r)

Get the energy spectrum of the neutrons at a given radius r.

Parameters:
  • r (float) –

    radius at which to compute the energy spectrum.

Returns:
  • list[float]

    a list of floats.

Source code in neutrowater/post/measure.py
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
def energy_spectrum(self, r: float) -> list[float]:
    """
    Get the energy spectrum of the neutrons at a given radius r.

    Args:
        r (float): radius at which to compute the energy spectrum.

    Returns:
        a list of floats.
    """
    result_energies = []
    for positions, energies in zip(self.positions(), self.energies()):
        positions = np.array(positions)
        energies = np.array(energies)
        norms = np.linalg.norm(positions, axis=1)
        # Create boolean arrays for the conditions
        cond1 = np.concatenate([(norms[:-1] < r) & (r < norms[1:]), [False]])
        cond2 = np.concatenate([[False], (norms[1:] < r) & (r < norms[:-1])])
        # Get the energies of the neutrons that have been in the shell
        result_energies += list(energies[cond1]) + list(energies[cond2])
    return result_energies

energy_spectrum_escaped()

Get the energy spectrum of the neutrons that escaped the tank.

Returns:
  • list[float]

    a list of floats.

Source code in neutrowater/post/measure.py
206
207
208
209
210
211
212
213
214
215
216
217
def energy_spectrum_escaped(self) -> list[float]:
    """
    Get the energy spectrum of the neutrons that escaped the tank.

    Returns:
        a list of floats.
    """
    return [
        neutron.energy
        for neutron in self.neutrons
        if not self.sim.tank.inside(neutron.position)
    ]

flux(r)

Get the flux of the neutrons at a given radius r.

Parameters:
  • r (float) –

    radius at which to compute the flux.

Returns:
  • float

    a float value of the flux.

Source code in neutrowater/post/measure.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
def flux(self, r: float) -> float:
    """
    Get the flux of the neutrons at a given radius r.

    Args:
        r (float): radius at which to compute the flux.

    Returns:
        a float value of the flux.
    """
    count = 0
    for positions in self.positions():
        positions = np.array(positions)
        norms = np.linalg.norm(positions, axis=1)
        # Count the number of neutrons that have been in the shell
        count += np.sum((norms[:-1] < r) & (r < norms[1:])) + np.sum(
            (norms[1:] < r) & (r < norms[:-1])
        )
    return count / (4 * np.pi * r**2)

number_above_energy(E)

Get the number of neutrons with energy above a given energy.

Parameters:
  • E (float) –

    energy threshold.

Returns:
  • int

    an int.

Source code in neutrowater/post/measure.py
219
220
221
222
223
224
225
226
227
228
229
def number_above_energy(self, E: float) -> int:
    """
    Get the number of neutrons with energy above a given energy.

    Args:
        E (float): energy threshold.

    Returns:
        an int.
    """
    return sum([1 for neutron in self.neutrons if neutron.energies[0] > E])

number_absorbed()

Get the number of neutrons that were absorbed.

Returns:
  • int

    an int.

Source code in neutrowater/post/measure.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def number_absorbed(self) -> int:
    """
    Get the number of neutrons that were absorbed.

    Returns:
        an int.
    """
    n = 0
    for neutron in self.neutrons:
        if len(neutron.positions) < self.sim.nCollisions and self.sim.tank.inside(
            neutron.position
        ):
            n += 1
    return n

number_escaped()

Get the number of neutrons that escaped the tank.

Returns:
  • int

    an int.

Source code in neutrowater/post/measure.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def number_escaped(self) -> int:
    """
    Get the number of neutrons that escaped the tank.

    Returns:
        an int.
    """
    return sum(
        [
            1
            for neutron in self.neutrons
            if not self.sim.tank.inside(neutron.position)
        ],
        start=0,
    )

number_thermal()

Get the number of neutrons that are thermalized.

Returns:
  • int

    an int.

Source code in neutrowater/post/measure.py
85
86
87
88
89
90
91
92
93
94
95
96
def number_thermal(self) -> int:
    """
    Get the number of neutrons that are thermalized.

    Returns:
        an int.
    """
    n = 0
    for energies in self.energies():
        if energies[-1] < (10 * self.sim.kT):
            n += 1
    return n

number_total()

Get the total number of neutrons.

Returns:
  • int

    an int.

Source code in neutrowater/post/measure.py
60
61
62
63
64
65
66
67
def number_total(self) -> int:
    """
    Get the total number of neutrons.

    Returns:
        an int.
    """
    return len(self.neutrons)

positions()

Get the positions of the neutrons.

Returns:
  • list[list[Vector]]

    a list where each element is a list of length nNeutrons [[np.ndarray, np.ndarray, ...], [np.ndarray, np.ndarray, ...], ...]

Source code in neutrowater/post/measure.py
40
41
42
43
44
45
46
47
48
def positions(self) -> list[list[Vector]]:
    """
    Get the positions of the neutrons.

    Returns:
        a list where each element is a list of length nNeutrons
            [[np.ndarray, np.ndarray, ...], [np.ndarray, np.ndarray, ...], ...]
    """
    return [neutron.positions for neutron in self.neutrons]

thermalize_distances()

Get the distance of the neutrons where their energy is thermal for the first time.

Returns:
  • list

    a list of floats.

Source code in neutrowater/post/measure.py
116
117
118
119
120
121
122
123
124
def thermalize_distances(self) -> list:
    """
    Get the distance of the neutrons where their energy is thermal for
    the first time.

    Returns:
        a list of floats.
    """
    return [np.linalg.norm(pos) for pos in self.thermalize_positions()]

thermalize_positions()

Get the positions of the neutrons where their energy is thermal for the first time.

Returns:
  • list

    a list of np.ndarray.

Source code in neutrowater/post/measure.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def thermalize_positions(self) -> list:
    """
    Get the positions of the neutrons where their energy is thermal for
    the first time.

    Returns:
        a list of np.ndarray.
    """
    thermalize_positions = []
    for energies, positions in zip(self.energies(), self.positions()):
        # Get the first element of the list of energies that is less than 10kT
        res = list(filter((lambda val: val < (10 * self.sim.kT)), energies))
        if len(res) > 0:
            index = energies.index(res[0])
            thermalize_positions.append(positions[index])

    return thermalize_positions