예제 #1
0
def semi_circular_loop(length: u.m, theta0: u.deg = 0 * u.deg):
    """
    Return a Heliographic Stonyhurst coordinate object with points of a semi circular loop in it.
    """
    r_sun = const.R_sun

    def r_2_func(x):
        return np.arccos(0.5 * x / r_sun.to(u.cm).value) - np.pi + length.to(
            u.cm).value / 2. / x

    r_2 = scipy.optimize.bisect(r_2_func,
                                length.to(u.cm).value / (2 * np.pi),
                                length.to(u.cm).value / np.pi) * u.cm
    alpha = np.arccos(0.5 * (r_2 / r_sun).decompose())
    phi = np.linspace(-np.pi * u.rad + alpha, np.pi * u.rad - alpha, 2000)

    # Quadratic formula to find r
    a = 1.
    b = -2 * (r_sun.to(u.cm) * np.cos(phi.to(u.radian)))
    c = r_sun.to(u.cm)**2 - r_2.to(u.cm)**2
    r = (-b + np.sqrt(b**2 - 4 * a * c)) / 2 / a
    # Choose only points above the surface
    i_r = np.where(r > r_sun)
    r = r[i_r]
    phi = phi[i_r]
    hcc_frame = frames.Heliocentric(
        observer=SkyCoord(lon=0 * u.deg,
                          lat=theta0,
                          radius=r_sun,
                          frame='heliographic_stonyhurst'))

    return SkyCoord(x=r.to(u.cm) * np.sin(phi.to(u.radian)),
                    y=u.Quantity(r.shape[0] * [0 * u.cm]),
                    z=r.to(u.cm) * np.cos(phi.to(u.radian)),
                    frame=hcc_frame).transform_to('heliographic_stonyhurst')
예제 #2
0
def semi_circular_loop(length: u.m, theta0: u.deg=0*u.deg):
    """
    Return a Heliographic Stonyhurst coordinate object with points of a semi circular loop in it.
    """
    r_sun = const.R_sun

    def r_2_func(x):
        return np.arccos(0.5 * x / r_sun.to(u.cm).value) - np.pi + length.to(u.cm).value / 2. / x

    r_2 = scipy.optimize.bisect(r_2_func,
                                length.to(u.cm).value / (2 * np.pi),
                                length.to(u.cm).value / np.pi) * u.cm
    alpha = np.arccos(0.5 * (r_2 / r_sun).decompose())
    phi = np.linspace(-np.pi * u.rad + alpha, np.pi * u.rad - alpha, 2000)

    # Quadratic formula to find r
    a = 1.
    b = -2 * (r_sun.to(u.cm) * np.cos(phi.to(u.radian)))
    c = r_sun.to(u.cm)**2 - r_2.to(u.cm)**2
    r = (-b + np.sqrt(b**2 - 4 * a * c)) / 2 / a
    # Choose only points above the surface
    i_r = np.where(r > r_sun)
    r = r[i_r]
    phi = phi[i_r]
    hcc_frame = frames.Heliocentric(
        observer=SkyCoord(lon=0 * u.deg, lat=theta0, radius=r_sun, frame='heliographic_stonyhurst'))

    return SkyCoord(
        x=r.to(u.cm) * np.sin(phi.to(u.radian)),
        y=u.Quantity(r.shape[0] * [0 * u.cm]),
        z=r.to(u.cm) * np.cos(phi.to(u.radian)),
        frame=hcc_frame).transform_to('heliographic_stonyhurst')
예제 #3
0
    def fwd(self, lat: u.deg, lon: u.deg, azimuth: u.deg, distance: u.m):
        """Solve forward geodetic problem.

        Returns latitudes, longitudes and back azimuths of terminus points
        given latitudes `lat` and longitudes `lon` of initial points, plus
        forward `azimuth`s and `distance`s.

        This method use `pyproj.Geod.fwd` as a backend.

        Parameters
        ----------
        lat : ~astropy.units.Quantity
            Geodetic latitude of the initial point.
        lon : ~astropy.units.Quantity
            Longitude of the initial point.
        azimuth : ~astropy.units.Quantity
            Geodetic azimuth.
        distance : ~astropy.units.Quantity
            Distance.

        Returns
        -------
        lat : ~astropy.units.Quantity
            Geodetic latitude of the terminus point.
        lon : ~astropy.units.Quantity
            Longitude of the terminus point.
        back_azimuth : ~astropy.units.Quantity
            Back geodetic azimuth.
        """
        out_lon, out_lat, out_baz = self.geod.fwd(lon.to('radian').value,
                                                  lat.to('radian').value,
                                                  azimuth.to('radian').value,
                                                  distance.to('m').value,
                                                  radians=True)
        return out_lat * u.rad, out_lon * u.rad, out_baz * u.rad
예제 #4
0
def enu_to_ecef(x: u.m,
                y: u.m,
                z: u.m,
                origin: tuple[u.deg, u.deg, u.m],
                ell=None):
    """Convert local cartesian to geocentric cartesian coordinates.

    Convert local east-north-up (ENU) local cartesian
    coordinate system with the origin in (`lat0`, `lon0`, `height0`)
    to the Earth Centered Earth Fixed (ECEF) cartesian coordinates.

    Parameters
    ----------
    x, y, z : ~astropy.units.Quantity
        Local east-north-uo cartesian coordinates.
    origin : tuple of ~astropy.units.Quantity
        Ggeocentric (spherical) or geodetic coordinates of the origin
        (`lat0`, `lon0`, `r0`) or (`lat0`, `lon0`, `h0`).
    ell : instance of the `pygeoid.coordinates.ellipsoid.Ellipsoid`
        Reference ellipsoid to which geodetic coordinates are referenced to.
        Default is None, meaning spherical coordinates instead of geodetic.

    Returns
    -------
    x, y, z : ~astropy.units.Quantity
        Geocentric cartesian coordinates, in metres.
    """
    rotation_matrix = _ecef_to_enu_rotation_matrix(origin[0], origin[1]).T

    out_shape = x.shape

    x, y, z = _np.dot(
        rotation_matrix,
        _np.array([
            _np.asarray(x.to('m').value).flatten(),
            _np.asarray(y.to('m').value).flatten(),
            _np.asarray(z.to('m').value).flatten()
        ])) * u.m

    if ell is None:
        x0, y0, z0 = spherical_to_cartesian(*origin)
    else:
        x0, y0, z0 = geodetic_to_cartesian(*origin, ell=ell)

    return ((x + x0).reshape(out_shape), (y + y0).reshape(out_shape),
            (z + z0).reshape(out_shape))
예제 #5
0
def rms(diameter: u.m,
        wavelength: u.nm,
        fried_param: u.m,
        outer_scale: u.m,
        freqs=None):
    '''
    Von Karman wavefront rms value over a circular aperture

    Parameters
    ----------
    diameter: `~astropy.units.quantity.Quantity` equivalent to meter
        Aperture diameter

    wavelength: `~astropy.units.quantity.Quantity` equivalent to nanometer
        wavelength

    fried_param: `~astropy.units.quantity.Quantity` equivalent to meter
        Fried parameter r0 defined at the specified wavelength

    outer_scale: `~astropy.units.quantity.Quantity` equivalent to meter
        Outer scale L0. Use np.inf for Kolmogorov spectrum

    Other Parameters
    ----------
    freqs:  array of `~astropy.units.quantity.Quantity` equivalent to 1/meter
        spatial frequencies array. Default logspace(-8, 4, 1000) m^-1

    Returns
    -------
    rms: `~astropy.units.quantity.Quantity` equivalent to nm
        wavefront rms for the specified von Karman turbulence
    '''
    R = 0.5 * diameter.to(u.m).value
    wl = wavelength.to(u.nm).value
    r0 = fried_param.to(u.m).value
    L0 = outer_scale.to(u.m).value
    psd = VonKarmanPsd(r0, L0)
    if freqs is None:
        freqs = np.logspace(-8, 4, 1000) / u.m
    freqs = freqs.to(1 / u.m).value
    bess = jv(1, 2 * np.pi * R * freqs)
    psdTotal = psd.spatial_psd(freqs)
    psdPistonRem = psdTotal * (1 - (bess / (np.pi * R * freqs))**2)
    varInRad2 = np.trapz(psdPistonRem * 2 * np.pi * freqs, freqs)
    return np.sqrt(varInRad2) * wl / 2 / np.pi * u.nm
예제 #6
0
    def __init__(self, normal, center: u.m, radius: u.m, current: u.A, n=300):
        self.normal = normal / np.linalg.norm(normal)
        self.center = center.to(u.m).value
        if radius > 0:
            self.radius = radius.to(u.m).value
        else:
            raise ValueError("Radius should bu larger than 0")
        self.current = current.to(u.A).value

        # parametric equation
        # find other two axises in the disc plane
        z = np.array([0, 0, 1])
        axis_x = np.cross(z, self.normal)
        axis_y = np.cross(self.normal, axis_x)

        if np.linalg.norm(axis_x) == 0:
            axis_x = np.array([1, 0, 0])
            axis_y = np.array([0, 1, 0])
        else:
            axis_x = axis_x / np.linalg.norm(axis_x)
            axis_y = axis_y / np.linalg.norm(axis_y)

        self.axis_x = axis_x
        self.axis_y = axis_y

        def curve(t):
            if isinstance(t, np.ndarray):
                t = np.expand_dims(t, 0)
                axis_x_mat = np.expand_dims(axis_x, 1)
                axis_y_mat = np.expand_dims(axis_y, 1)
                return self.radius*(np.matmul(axis_x_mat, np.cos(t))
                                    + np.matmul(axis_y_mat, np.sin(t))) \
                    + np.expand_dims(self.center, 1)
            else:
                return self.radius * (np.cos(t) * axis_x +
                                      np.sin(t) * axis_y) + self.center

        self.curve = curve

        self.roots_legendre = roots_legendre(n)
        self.n = n
예제 #7
0
def cartesian_to_geodetic(x: u.m, y: u.m, z: u.m, ell):
    """Convert 3D cartesian to geodetic coordinates.

    Parameters
    ----------
    x, y, z : ~astropy.units.Quantity
        Cartesian coordinates.
    ell : instance of the `pygeoid.coordinates.ellipsoid.Ellipsoid`
        Reference ellipsoid to which geodetic coordinates are referenced to.

    Returns
    -------
    lat : ~astropy.units.Quantity
        Geodetic latitude.
    lon : ~astropy.units.Quantity
        Geodetic longitude.
    height : float or array_like of floats
        Geodetic height.

    Notes
    -----
    The algorithm of H. Vermeille is used for this transformation [1]_.

    References
    ----------
    .. [1] Vermeille, H., 2011. An analytical method to transform geocentric
    into geodetic coordinates. Journal of Geodesy, 85(2), pp.105-117.
    """

    lat, lon, height = _cartesian_to_geodetic(x.to('m').value,
                                              y.to('m').value,
                                              z.to('m').value,
                                              ell=ell,
                                              degrees=True)

    return lat * u.deg, lon * u.deg, height * u.m
예제 #8
0
def pz90_atm_corr(height: u.m) -> u.mGal:
    """Return PZ-90 atmospheric correction, in mGal.

    Parameters
    ----------
    height : ~astropy.units.Quantity
        Height above sea level.

    Returns
    -------
    ~astropy.units.Quantity
        Atmospheric correction.

    """
    height = height.to('km').value
    return 0.87 * np.exp(-0.116 * (height)**(1.047)) * u.mGal
예제 #9
0
def grs80_atm_corr_interp(height: u.m, kind: str = 'linear') -> u.mGal:
    """Return GRS 80 atmospheric correction, in mGal.

    Interpolated from the table data [1]_.

    Note: If height < 0 m or height > 40000 m, then correction is extrapolated

    Parameters
    ----------
    height : ~astropy.units.Quantity
        Height above sea level.
    kind : str or int, optional
        Specifies the kind of interpolation as a string
        ('linear', 'nearest', 'zero', 'slinear', 'quadratic', 'cubic'
        where 'zero', 'slinear', 'quadratic' and 'cubic' refer to a spline
        interpolation of zeroth, first, second or third order) or as an
        integer specifying the order of the spline interpolator to use.
        Default is 'linear'.

    Returns
    -------
    ~astropy.units.Quantity
        Atmospheric correction.

    References
    ----------
    .. [1] Moritz, H. (1980). Geodetic reference system 1980.
    Bulletin Géodésique, 54(3), 395-405

    """
    fname = os.path.join(os.path.dirname(__file__),
                         'data/IAG_atmosphere_correction_table.txt')
    table_heights, corr = np.loadtxt(fname,
                                     unpack=True,
                                     delimiter=',',
                                     skiprows=4,
                                     dtype=float)
    interp = interp1d(table_heights * 1000,
                      corr,
                      kind=kind,
                      fill_value='extrapolate',
                      assume_sorted=True)
    return interp(height.to('m').value) * u.mGal
예제 #10
0
def wenzel_atm_corr(height: u.m) -> u.mGal:
    """Return atmospheric correction by Wenzel, in mGal.

    Parameters
    ----------
    height : ~astropy.units.Quantity
        Height above sea level.

    Returns
    -------
    ~astropy.units.Quantity
        Atmospheric correction.

    References
    ----------
    .. [1] Wenzel, H., 1985, Hochauflosende Kugelfunktionsmodelle fur des
    Gravitationspotential der Erde [1]: Wissenschaftliche arbeiten der
    Fachrichtung Vermessungswesen der Universitat Hannover, 137
    """
    height = height.to('m').value
    return (0.874 - 9.9e-5 * height + 3.56e-9 * height**2) * u.mGal
예제 #11
0
def semi_circular_loop(length: u.m, latitude: u.deg = 0 * u.deg):
    """
    Return a Heliographic Stonyhurst coordinate object with points of a semi circular loop in it.
    """
    r_sun = constants.radius

    def r_2_func(x):
        return np.arccos(0.5 * x / r_sun.to(u.cm).value) - np.pi + length.to(
            u.cm).value / 2. / x

    # Find the loop radius corresponding to the loop length
    r_2 = scipy.optimize.bisect(r_2_func,
                                length.to(u.cm).value / (2 * np.pi),
                                length.to(u.cm).value / np.pi) * u.cm
    alpha = np.arccos(0.5 * (r_2 / r_sun))
    phi = np.linspace(-np.pi * u.rad + alpha, np.pi * u.rad - alpha, 2000)

    hcc_frame = frames.Heliocentric(observer=frames.HeliographicStonyhurst(
        lon=0 * u.deg, lat=latitude, radius=1 * u.AU))

    return SkyCoord(x=r_2 * np.sin(phi),
                    y=0 * u.cm,
                    z=r_2 * np.cos(phi) + r_sun,
                    frame=hcc_frame).transform_to('heliographic_stonyhurst')
예제 #12
0
 def __init__(self, moment: u.A * u.m**2, p0: u.m):
     self.moment = moment.to(u.A * u.m * u.m).value
     self.p0 = p0.to(u.m).value
예제 #13
0
 def __init__(self, direction, p0: u.m, current: u.A):
     self.direction = direction / np.linalg.norm(direction)
     self.p0 = p0.to(u.m).value
     self.current = current.to(u.A).value
예제 #14
0
 def __init__(self, p1: u.m, p2: u.m, current: u.A):
     self.p1 = p1.to(u.m).value
     self.p2 = p2.to(u.m).value
     if np.all(p1 == p2):
         raise ValueError("p1, p2 should not be the same point.")
     self.current = current.to(u.A).value
예제 #15
0
    def __init__(
        self,
        # scalars
        pixel_diameter: u.m,
        pixel_fill_factor,
        focal_length: u.m / u.radian,
        mirror_area: u.m**2,
        # arrays (vs wavelength)
        telescope_transmissivity,
        mirror_reflectivity,
        window_transmissivity,
        pde,
    ):
        """
        Calculate parameters related to the camera efficiency

        Formulae and data obtained from the excel files at:
        https://www.mpi-hd.mpg.de/hfm/CTA/MC/Prod4/Config/Efficiencies
        Credit: Konrad Bernloehr
        """
        self.wavelength = np.arange(200, 1000) << u.nm
        size = self.wavelength.size
        if not telescope_transmissivity.size == size:
            raise ValueError(
                "All arrays must specify values over full wavelength range")
        if not mirror_reflectivity.size == size:
            raise ValueError(
                "All arrays must specify values over full wavelength range")
        if not window_transmissivity.size == size:
            raise ValueError(
                "All arrays must specify values over full wavelength range")
        if not pde.size == size:
            raise ValueError(
                "All arrays must specify values over full wavelength range")

        self.pixel_diameter = pixel_diameter.to('m')
        self.pixel_fill_factor = pixel_fill_factor
        self.focal_length = focal_length.to("m/radian")
        self.mirror_area = mirror_area.to("m2")
        self.telescope_transmissivity = telescope_transmissivity
        self.mirror_reflectivity = mirror_reflectivity
        self.window_transmissivity = window_transmissivity
        self._pde = pde
        self._pde_scale = 1
        self._cherenkov_scale = 1

        # Read environment arrays
        df_env = pd.read_csv(PATH_ENV)
        diff_flux_unit = u.Unit('10^9 / (nm s m^2 sr)')
        self._nsb_diff_flux = df_env['nsb_site'].values << diff_flux_unit
        self._moonlight_diff_flux = df_env['moonlight'].values << diff_flux_unit
        self._atmospheric_transmissivity = df_env[
            'atmospheric_transmissivity'].values << 1 / u.nm

        # Scale cherenkov spectrum to match normalisation
        # scaled to 100 photons/m2 in the wavelength range from 300–600 nm
        # a value typical for γ-ray showers of about 500 GeV viewed at small core distances
        # (from https://jama.cta-observatory.org/perspective.req#/items/28666)
        # TODO: check wrt area
        cherenkov_integral = self._integrate_cherenkov(
            self._cherenkov_diff_flux_on_ground, u.Quantity(300, u.nm),
            u.Quantity(600, u.nm))
        self._cherenkov_scale = 100 / cherenkov_integral

        self._nsb_flux_300_650 = self._integrate_nsb(
            self._nsb_diff_flux_on_ground, u.Quantity(300, u.nm),
            u.Quantity(650, u.nm))

        self._moonlight_flux_300_650 = self._integrate_nsb(
            self._moonlight_diff_flux_on_ground, u.Quantity(300, u.nm),
            u.Quantity(650, u.nm))