Exemplo n.º 1
0
def get_compton_fraction(energy):
    """
    Computes the compton angle from the Klein-Nishina equation.
    Determines the probability of absorption from this angle.

    Parameters
    ----------
    energy : float
        Photon energy

    Returns
    -------
    compton_angle : float
        Compton scattering angle
    compton_fraction : float
        Fraction of energy lost
    """
    theta_angles, theta_distribution = compton_theta_distribution(energy)

    z = np.random.random()

    # get Compton scattering angle
    compton_angle = np.interp(z, theta_distribution, theta_angles)
    # Energy calculations
    fraction = 1 / (
        1.0 + kappa_calculation(energy) * (1.0 - np.cos(compton_angle))
    )

    return compton_angle, fraction
Exemplo n.º 2
0
def photoabsorption_opacity_calculation_kasen(
    energy, number_density, proton_count
):
    """Calculates photoabsorption opacity for a given energy
    Approximate treatment from Kasen et al. (2006)

    Parameters
    ----------
    energy : float
        Photon energy
    number_density : float
        The number density of the ejecta for each atom
    proton_count : float
        Number of protons for each atom in the ejecta

    Returns
    -------
    float
        Photoabsorption opacity
    """
    kappa = kappa_calculation(energy)

    opacity = (FINE_STRUCTURE**4.0) * 8.0 * np.sqrt(2) * (kappa**-3.5)
    # Note- this should actually be atom_number_density * (atom_proton_number ** 5)
    return (
        SIGMA_T
        * opacity
        * np.sum((number_density / proton_count) * proton_count**5)
    )
Exemplo n.º 3
0
def get_compton_angle(energy):
    """
    Computes the compton angle from the Klein-Nishina equation.
    Computes the lost energy due to this angle

    Parameters
    ----------
    energy : float
        Photon energy

    Returns
    -------
    compton_angle : float
        Compton scattering angle
    lost_energy : float
        Energy lost based on angle
    new_energy : float
        Photon energy
    """
    theta_angles, theta_distribution = compton_theta_distribution(energy)

    z = np.random.random()

    # get Compton scattering angle
    compton_angle = np.interp(z, theta_distribution, theta_angles)
    # Energy calculations
    new_energy = energy / (
        1.0 + kappa_calculation(energy) * (1.0 - np.cos(compton_angle))
    )
    lost_energy = energy - new_energy

    return compton_angle, lost_energy, new_energy
Exemplo n.º 4
0
def test_kappa_calculation(energy, expected):
    """

    Parameters
    ----------
    energy : float
    expected : float
    """
    kappa = util.kappa_calculation(energy)
    npt.assert_almost_equal(kappa, expected)
Exemplo n.º 5
0
def get_compton_fraction_artis(energy):
    """Gets the Compton scattering/absorption fraction
    and angle following the scheme in ARTIS

    Parameters
    ----------
    energy : float
        Energy of the gamma-ray

    Returns
    -------
    float
        Scattering angle
    float
        Compton scattering fraction
    """
    energy_norm = kappa_calculation(energy)

    fraction_max = 1.0 + 2.0 * energy_norm
    fraction_min = 1.0

    normalization = np.random.random() * compton_opacity_partial(
        energy_norm, fraction_max
    )

    epsilon = 1.0e20
    count = 0

    while epsilon > 1.0e-4:
        fraction_try = (fraction_max + fraction_min) / 2.0
        sigma_try = compton_opacity_partial(energy_norm, fraction_try)

        if sigma_try > normalization:
            fraction_max = fraction_try
            epsilon = (sigma_try - normalization) / normalization
        else:
            fraction_min = fraction_try
            epsilon = (normalization - sigma_try) / normalization

        count += 1
        if count > 1000:
            print("Error, failure to get a Compton fraction")
            break

    angle = np.arccos(1.0 - ((fraction_try - 1) / energy_norm))

    return angle, fraction_try
Exemplo n.º 6
0
def test_klein_nishina(energy, theta_C):
    """

    Parameters
    ----------
    energy : float
    theta_C : float
        In radians
    """
    actual = util.klein_nishina(energy, theta_C)

    kappa = util.kappa_calculation(energy)

    expected = (R_ELECTRON_SQUARED / 2 * (1.0 + kappa *
                                          (1.0 - np.cos(theta_C)))**-2.0 *
                (1.0 + np.cos(theta_C)**2.0 + (kappa**2.0 *
                                               (1.0 - np.cos(theta_C))**2.0) /
                 (1.0 + kappa * (1.0 - np.cos(theta_C)))))

    npt.assert_almost_equal(actual, expected)
Exemplo n.º 7
0
def get_average_compton_fraction(energy):
    def f(x, mu):
        return 1.0 / (1.0 + x * (1.0 - mu))

    def cross_section(x, mu):
        return ((3.0 * SIGMA_T) / (16.0 * np.pi) * f(x, mu)**2.0 *
                (f(x, mu) + 1.0 / f(x, mu) - (1.0 - mu**2)))

    x = kappa_calculation(energy)
    mus = np.linspace(-1, 1, 100)
    dmu = mus[1] - mus[0]
    sum = 0
    norm = 0

    for mu in mus:
        sum += cross_section(x, mu) * f(x, mu) * dmu
        norm += cross_section(x, mu) * dmu

    integral = 1.0 - sum / norm

    return 1 - integral
Exemplo n.º 8
0
def compton_opacity_calculation(energy, electron_density):
    """Calculate the Compton scattering opacity for a given energy
    (Rybicki & Lightman, 1979)

    $
    \\rho / 2 p_m \\times 3/4 \\sigma_T ((1 + \kappa) / \kappa^3
    ((2\kappa(1 + \kappa)) / (1 + 2\kappa) - \ln(1 + 2\kappa) + 1/(2\kappa) \ln(1 + 2\kappa)
    - (1 + 3\kappa) / (1 + 2\kappa)^2)
    $

    Parameters
    ----------
    energy : float
        The energy of the photon
    ejecta_density : float
        The density of the ejecta

    Returns
    -------
    float
        The Compton scattering opacity
    """
    kappa = kappa_calculation(energy)

    a = 1.0 + 2.0 * kappa

    sigma_KN = (
        3.0
        / 4.0
        * SIGMA_T
        * (
            (1.0 + kappa)
            / kappa**3.0
            * ((2.0 * kappa * (1.0 + kappa)) / a - np.log(a))
            + 1.0 / (2.0 * kappa) * np.log(a)
            - (1.0 + 3 * kappa) / a**2.0
        )
    )

    return electron_density * sigma_KN
Exemplo n.º 9
0
def get_compton_fraction_urilight(energy):
    """Gets the Compton scattering/absorption fraction
    and angle following the scheme in Urilight

    Parameters
    ----------
    energy : float
        Energy of the gamma-ray

    Returns
    -------
    float
        Scattering angle
    float
        Compton scattering fraction
    """
    E0 = kappa_calculation(energy)

    x0 = 1.0 / (1.0 + 2.0 * E0)

    accept = False
    while not accept:

        z = np.random.random(3)
        alpha1 = np.log(1.0 / x0)
        alpha2 = (1.0 - x0**2.0) / 2.0
        if z[1] < alpha1 / (alpha1 + alpha2):
            x = x0 ** z[2]
        else:
            x = np.sqrt(x0**2.0 + (1.0 - x0**2.0) * z[2])

        f = (1.0 - x) / x / E0
        sin2t = f * (2.0 - f)
        ge = 1.0 - x / (1 + x**2.0) * sin2t
        if ge > z[3]:
            accept = True

    cost = 1.0 - f

    return np.arccos(cost), x
Exemplo n.º 10
0
def gamma_packet_loop(
    packets,
    grey_opacity,
    photoabsorption_opacity_type,
    pair_creation_opacity_type,
    electron_number_density_time,
    mass_density_time,
    inv_volume_time,
    iron_group_fraction_per_shell,
    inner_velocities,
    outer_velocities,
    times,
    dt_array,
    effective_time_array,
    energy_bins,
    energy_df_rows,
    energy_plot_df_rows,
    energy_out,
):
    """Propagates packets through the simulation

    Parameters
    ----------
    packets : list
        List of GXPacket objects
    grey_opacity : float
        Grey opacity value in cm^2/g
    electron_number_density_time : array float64
        Electron number densities with time
    mass_density_time : array float64
        Mass densities with time
    inv_volume_time : array float64
        Inverse volumes with time
    iron_group_fraction_per_shell : array float64
        Iron group fraction per shell
    inner_velocities : array float64
        Inner velocities of the shells
    outer_velocities : array float64
        Inner velocities of the shells
    times : array float64
        Simulation time steps
    dt_array : array float64
        Simulation delta-time steps
    effective_time_array : array float64
        Simulation middle time steps
    energy_bins : array float64
        Bins for escaping gamma-rays
    energy_df_rows : array float64
        Energy output
    energy_plot_df_rows : array float64
        Energy output for plotting
    energy_out : array float64
        Escaped energy array

    Returns
    -------
    array float64
        Energy output
    array float64
        Energy output for plotting
    array float64
        Escaped energy array

    Raises
    ------
    ValueError
        Packet time index less than zero
    """
    escaped_packets = 0
    scattered_packets = 0
    packet_count = len(packets)
    print("Entering gamma ray loop for " + str(packet_count) + " packets")

    deposition_estimator = np.zeros_like(energy_df_rows)

    for i in range(packet_count):
        packet = packets[i]
        time_index = get_index(packet.time_current, times)

        if time_index < 0:
            print(packet.time_current, time_index)
            raise ValueError("Packet time index less than 0!")

        scattered = False

        initial_energy = packet.energy_cmf

        while packet.status == GXPacketStatus.IN_PROCESS:
            # Get delta-time value for this step
            dt = dt_array[time_index]
            # Calculate packet comoving energy for opacities
            comoving_energy = H_CGS_KEV * packet.nu_cmf

            if grey_opacity < 0:
                doppler_factor = doppler_gamma(
                    packet.direction,
                    packet.location,
                    effective_time_array[time_index],
                )

                kappa = kappa_calculation(comoving_energy)

                # artis threshold for Thomson scattering
                if kappa < 1e-2:
                    compton_opacity = (
                        SIGMA_T *
                        electron_number_density_time[packet.shell, time_index])
                else:
                    compton_opacity = compton_opacity_calculation(
                        comoving_energy,
                        electron_number_density_time[packet.shell, time_index],
                    )

                if photoabsorption_opacity_type == "kasen":
                    # currently not functional, requires proton count and
                    # electron count per isotope
                    photoabsorption_opacity = 0
                    # photoabsorption_opacity_calculation_kasen()
                else:
                    photoabsorption_opacity = (
                        photoabsorption_opacity_calculation(
                            comoving_energy,
                            mass_density_time[packet.shell, time_index],
                            iron_group_fraction_per_shell[packet.shell],
                        ))

                if pair_creation_opacity_type == "artis":
                    pair_creation_opacity = pair_creation_opacity_artis(
                        comoving_energy,
                        mass_density_time[packet.shell, time_index],
                        iron_group_fraction_per_shell[packet.shell],
                    )
                else:
                    pair_creation_opacity = pair_creation_opacity_calculation(
                        comoving_energy,
                        mass_density_time[packet.shell, time_index],
                        iron_group_fraction_per_shell[packet.shell],
                    )

            else:
                compton_opacity = 0.0
                pair_creation_opacity = 0.0
                photoabsorption_opacity = (
                    grey_opacity * mass_density_time[packet.shell, time_index])

            # convert opacities to rest frame
            total_opacity = (compton_opacity + photoabsorption_opacity +
                             pair_creation_opacity) * doppler_factor

            packet.tau = -np.log(np.random.random())

            (
                distance_interaction,
                distance_boundary,
                distance_time,
                shell_change,
            ) = distance_trace(
                packet,
                inner_velocities,
                outer_velocities,
                total_opacity,
                effective_time_array[time_index],
                times[time_index + 1],
            )

            distance = min(distance_interaction, distance_boundary,
                           distance_time)

            packet.time_current += distance / C_CGS

            packet = move_packet(packet, distance)

            deposition_estimator[packet.shell, time_index] += (
                (initial_energy * 1000) * distance *
                (packet.energy_cmf / initial_energy) *
                deposition_estimator_kasen(
                    comoving_energy,
                    mass_density_time[packet.shell, time_index],
                    iron_group_fraction_per_shell[packet.shell],
                ))

            if distance == distance_time:
                time_index += 1

                if time_index > len(effective_time_array) - 1:
                    # Packet ran out of time
                    packet.status = GXPacketStatus.END
                else:
                    packet.shell = get_index(
                        packet.get_location_r(),
                        inner_velocities * effective_time_array[time_index],
                    )

            elif distance == distance_interaction:

                packet.status = scatter_type(
                    compton_opacity,
                    photoabsorption_opacity,
                    total_opacity,
                )

                packet, ejecta_energy_gained = process_packet_path(packet)

                # Save packets to dataframe rows
                # convert KeV to eV / s / cm^3
                energy_df_rows[packet.shell,
                               time_index] += (ejecta_energy_gained * 1000)

                energy_plot_df_rows[i] = np.array([
                    i,
                    ejecta_energy_gained * 1000
                    # * inv_volume_time[packet.shell, time_index]
                    / dt,
                    packet.get_location_r(),
                    packet.time_current,
                    packet.shell,
                    compton_opacity,
                    photoabsorption_opacity,
                    pair_creation_opacity,
                ])

                if packet.status == GXPacketStatus.PHOTOABSORPTION:
                    # Packet destroyed, go to the next packet
                    break
                else:
                    packet.status = GXPacketStatus.IN_PROCESS
                    scattered = True

            else:
                packet.shell += shell_change

                if packet.shell > len(mass_density_time[:, 0]) - 1:
                    rest_energy = packet.nu_rf * H_CGS_KEV
                    bin_index = get_index(rest_energy, energy_bins)
                    bin_width = (energy_bins[bin_index + 1] -
                                 energy_bins[bin_index])
                    energy_out[bin_index,
                               time_index] += rest_energy / (bin_width * dt)
                    packet.status = GXPacketStatus.END
                    escaped_packets += 1
                    if scattered:
                        scattered_packets += 1
                elif packet.shell < 0:
                    packet.energy_rf = 0.0
                    packet.energy_cmf = 0.0
                    packet.status = GXPacketStatus.END

    print("Escaped packets:", escaped_packets)
    print("Scattered packets:", scattered_packets)

    return energy_df_rows, energy_plot_df_rows, energy_out, deposition_estimator