view mc_rotate.py @ 0:f40d05521dca draft default tip

planemo upload for repository https://github.com/esg-epfl-apc/tools-astro/tree/main/tools commit de01e3c02a26cd6353a6b9b6f8d1be44de8ccd54
author astroteam
date Fri, 25 Apr 2025 19:33:20 +0000
parents
children
line wrap: on
line source

import math

import numpy as np


def mc_rotate(
    mc_file, jet_half_size_deg, jet_direction_deg, psf_deg=180, rotated_file=None
):
    if rotated_file is None:
        rotated_file = f"{mc_file}_{jet_half_size_deg}_{jet_direction_deg}_{psf_deg}"

    jet_half_size = (
        np.longdouble(jet_half_size_deg) / 180.0 * math.pi
    )  # jet openning angle
    jet_direction = (
        np.longdouble(jet_direction_deg) / 180.0 * math.pi
    )  # angle between direction to source and jet axes

    if jet_half_size > math.pi or jet_half_size <= 0:
        jet_half_size = math.pi
    # here we assume that jet axis is located in y-z plane if angle=0, then jet points towards observer
    jet_axes = np.array(
        [0, np.sin(jet_direction), np.cos(jet_direction)], dtype=np.longdouble
    )

    if jet_direction > math.pi or jet_direction < 0:
        raise ValueError(
            "invalid jet_direction_deg param [0, 180] range expected")

    with open(mc_file, "r") as lines, open(rotated_file, "w") as rotated:
        column_names = []
        for line in lines:
            columns = line.split()
            if len(columns) == 0:
                continue
            if columns[0] == "#":
                if "weight" in columns:
                    column_names = (
                        columns[1:3]
                        + ["Theta", "Phi", "dT_raw/T", "dT_calc/T"]
                        + columns[9:]
                    )
                    print("# " + "\t".join(column_names), file=rotated)
                else:
                    print(line.strip(), file=rotated)
            else:
                particle = np.array(list(map(np.longdouble, columns)))
                beta = phi = 0 #dTcalc = 0

                r = particle[2:5]
                dz = r[
                    2
                    # dz is roughly equal to time delay in units of full path (assuming very small deflection angle)
                ]

                # negative dz may apper in data as a result of rounding errors
                dz = max(dz, 0)  # replacing negative values by zeros

                r[2] = 1.0 - dz  # now r is position of particle

                # direction of momentum
                n = particle[5:8]
                n[2] = 1.0 - n[2]
                # compensate rounding errors for n
                mod_n = np.dot(n, n) ** 0.5
                n = 1 / mod_n * n
                t = 0  # calculated delay
                if dz > 0:  # there is some delay
                    r2 = np.dot(r, r)
                    # assert r2 < 1
                    if r2 > 1:
                        r *= 1.0 / np.sqrt(r2)
                        r2 = 1.0
                    # we continue trajectory in the direction of momentun until it reaches the sphere of radius 1.
                    # to find the delay t, we need to solve the following quadratic equation with respect to variable t:
                    # [(x + V_x)*t]^2 + [(y + V_y)*t]^2 + [(z + V_z)*t]^2 = 1
                    # with extra condition t > 0
                    # below is the solution:
                    rV = np.dot(n, r)
                    t = np.sqrt(1.0 - r2 + rV * rV) - rV

                    # calculating the end point of the trajectory
                    r = r + t * n

                # normalizing r to 1 to fix the rounding errors
                modR = np.dot(r, r) ** 0.5
                r = 1.0 / modR * r

                # now we will construct the rotation of coordinates which converts vector r to 0,0,1
                # to find the starting direction of the particle momentum
                dr2 = r[0] ** 2 + r[1] ** 2
                if dr2 > 0:
                    # building rotation matrix to move x,y,z to 0,0,1
                    norm = 1.0 / (dr2**0.5)
                    # rotation axes:
                    ux = norm * r[1]
                    uy = -norm * r[0]
                    # uz=0
                    cos = r[2]
                    sin = (1 - cos**2) ** 0.5

                    toCenter = np.array(
                        [
                            [cos + ux * ux * (1 - cos), ux *
                             uy * (1 - cos), uy * sin],
                            [ux * uy * (1 - cos), cos + uy *
                             uy * (1 - cos), -ux * sin],
                            [-uy * sin, ux * sin, cos],
                        ]
                    )

                    zAxes = np.array([0.0, 0.0, 1.0], dtype=np.longdouble)
                    # test
                    error = np.fabs(np.dot(toCenter, r) - zAxes).max()
                    assert error < 1e-7
                    # finding starting direction of the momentum
                    nInitial = np.dot(toCenter, zAxes)
                    norm_nInitial = np.sqrt(np.dot(nInitial, nInitial))
                    nInitial *= (
                        1.0 / norm_nInitial
                    )  # normalize after rotation to avoid rounding errors
                    # calculating angle between starting direction and jet axis
                    startCos = np.dot(nInitial, jet_axes)
                    startAngle = 0
                    if (
                        startCos < 1
                    ):  # avoid roundout errors if angle is very close to zero
                        startAngle = np.arccos(startCos)
                    # TODO: here instead of using step function we can modify weight depending
                    # on angular distance from jet axes
                    if (
                        startAngle < jet_half_size
                    ):  # if the particle is inside the cone (passes cut)
                        # calculate angle between arrival direction and direction to source
                        cosBeta = np.dot(
                            r, n
                        )  # this angle is invariant (doesn't depend on turns)
                        if (
                            cosBeta < 1
                        ):  # avoid rounding errors if angle is very close to zero
                            beta = np.arccos(cosBeta)
                            nFinal = np.dot(toCenter, n).transpose()
                            cosPhi = nFinal[0] / np.sin(
                                beta
                                # second angle (could be useful in assymetric tasks) if beta=0 it is undefined (could be any)
                            )
                            if cosPhi < 1 and cosPhi > -1:
                                phi = np.arccos(
                                    cosPhi
                                )  # returns value in interval [0,pi]
                                if nFinal[1] < 0:
                                    phi = 2 * math.pi - phi

                        # s = math.sin(startAngle+beta)
                        # if s>0.:
                        #     dTcalc = (math.sin(startAngle)+math.sin(beta))/s - 1.
                        # dTcalc = (1.-min(modR, 1.))/cosBeta  # condition modR > 1 can only occur due to rounding error
                    else:
                        continue
                elif (
                    jet_direction > jet_half_size
                ):  # no deflection: check if jet is pointing towards observer
                    continue

                if beta * 180.0 / math.pi > psf_deg:
                    continue

                output = [
                    particle[0],
                    particle[1],
                    beta * 180.0 / math.pi,
                    phi * 180.0 / math.pi,
                    dz,
                    t,
                ] + list(particle[8:])
                output = ["{0:g}".format(i) for i in output]
                print(*output, file=rotated)

    return rotated_file