Source code for snf_simulations.physics

"""Module for atomic physics calculations."""

from typing import NamedTuple

import numpy as np


[docs] class DecayChain(NamedTuple): """Class to represent a decay chain from parent to daughter isotope.""" parent: str daughter: str branching_ratio: float = 1.0
[docs] def get_isotope_activity( mass: float, molar_mass: float, half_life: float, removal_time: float, ) -> float: """Calculate the activity of an isotope after a given time since removal. Calculates the current activity of an isotope given its initial mass and the time elapsed since removal from the reactor, accounting for radioactive decay. Args: mass: Mass of the isotope in kg. molar_mass: Molar mass of the isotope in g/mol. half_life: Half-life of the isotope in years. removal_time: Time since removal from reactor in years. Returns: Activity of the isotope in decays per second (Becquerels) after the given removal time. """ # Convert mass to number of atoms (kg to g, then to moles, then to atoms) number_of_atoms = (mass * 1000 / molar_mass) * 6.022e23 # Calculate initial activity (in decays per second aka Becquerels) lambda_ = np.log(2) / (half_life * 365 * 24 * 60 * 60) initial_activity = number_of_atoms * lambda_ # Calculate activity after removal time (still in decays per second) activity = initial_activity * np.exp( -1 * lambda_ * removal_time * 365 * 24 * 60 * 60, ) return activity
[docs] def get_decay_mass( time_elapsed: float, parent_mass: float, parent_half_life: float, daughter_half_life: float, branching_ratio: float = 1, ) -> float: """Calculate the mass of a daughter isotope created from parent decay. Computes the mass of a daughter isotope that has been created from the radioactive decay of its parent isotope using first-order decay equations. Args: time_elapsed: Time elapsed since initial measurement (years). parent_mass: Initial mass of parent isotope (kg). parent_half_life: Half-life of parent isotope (years). daughter_half_life: Half-life of daughter isotope (years). branching_ratio: Branching ratio for this decay chain. Returns: Mass of the daughter isotope (kg). """ # Decay constants (natural log of 2 divided by half-life) parent_decay_constant = np.log(2) / parent_half_life daughter_decay_constant = np.log(2) / daughter_half_life # Bateman equation for daughter isotope mass daughter_mass = ( branching_ratio * (parent_decay_constant / (daughter_decay_constant - parent_decay_constant)) * parent_mass * ( np.exp(-parent_decay_constant * time_elapsed) - np.exp(-daughter_decay_constant * time_elapsed) ) ) return daughter_mass
[docs] def calculate_flux_at_distance( total_flux: float, distance: float, ) -> float: """Calculate the antineutrino flux at a given distance from the source. We assume the source is a point source emitting isotropically in all directions, so the flux decreases with the square of the distance from the source. Args: total_flux: The total antineutrino flux emitted by the source in s^-1 . distance: Distance from the source in meters. Returns: Antineutrino flux at the given distance in cm^-2 s^-1. """ # Note we convert distance to cm flux = (1 / (4 * np.pi * (distance * 100) ** 2)) * (total_flux) return flux
[docs] def calculate_event_rate( flux: float, lower_efficiency: float = 0.2, upper_efficiency: float = 0.4, ) -> tuple[float, float]: """Calculate the expected event rate in the VIDARR detector for a given flux. Args: flux: Antineutrino flux in cm^-2 s^-1. lower_efficiency: Lower limit on detection efficiency. upper_efficiency: Upper limit on detection efficiency. Returns: Tuple of lower and upper event rates in s^-1. """ # Calculate the number of target protons in the detector # VIDARR is a plastic scintillator detector with a volume of (1.52 x 1.52 x 0.7) m^3 # TODO: these are all currently hardcoded, ideally they should be input parameters # or read from a detector config file. detector_volume = 1.52 * 1.52 * 0.7 # m^3 detector_volume = detector_volume * 1e6 # convert to cm^3 proton_density = 4.6e22 # number density of protons in cm^-3 number_of_protons = detector_volume * proton_density # Calculate the event rate using the flux and number of target protons cross_section = 1e-44 # cm^2, approximate IBD cross-section event_rate = number_of_protons * cross_section * flux # Apply detection efficiency rate_lower = event_rate * lower_efficiency rate_upper = event_rate * upper_efficiency return rate_lower, rate_upper