magnus.earth
earth.py
Contains helper functions related to the Earth: its internal matter density and the geometry of neutrino trajectories through it.
Routine listings
- density_matter_func_prem - Returns the density inside the Earth
using the Preliminary Reference Earth Model (PREM), with an optional override of the outermost shell’s density
- prem_layer_edges_along_chord - Returns the positions at which a
chord through the Earth crosses the PREM layer boundaries
- distance_traveled_inside_earth - Returns the chord length for a
given neutrino direction, with either endpoint optionally underground
- earth_radial_distance_from_depth - Converts position along a
chord to radial distance from the center of the Earth
- dms_to_decimal - Converts (degree, minute, second) coordinates to
decimal degrees
- chord_length_inside_earth - Returns the chord length between two
locations on the surface of the Earth
- costhz_between_points_on_surface - Returns the zenith angle of
the chord between two locations on the surface of the Earth
- coordinates_of_named_location - Returns the coordinates of a
predefined location (e.g., a neutrino detector site)
- electron_fraction_func_prem - Returns Y_e at one or more radii,
resolved per PREM layer (iron core, rock mantle, crust, ocean)
- neutron_to_proton_ratio_from_electron_fraction - Returns the
neutron-to-proton ratio implied by an electron fraction, r = (1 - Y_e)/Y_e
Attributes
Module-level constant |
|
Module-level constant |
|
Module-level constant |
|
Module-level constant |
Functions
|
Returns the matter density inside the Earth according to the |
|
Returns the distance traveled by a neutrino inside the Earth, |
|
Returns the radial distance measured from the center of the |
|
Returns the positions along a chord through the Earth at which |
|
Converts (degree, minute, second) coordinates to decimal degrees. |
|
Returns the chord length between two locations on the surface of |
|
Returns the zenith angle of the chord between two locations on |
|
Returns the coordinates of a predefined location (e.g., a |
|
Electron fraction \(Y_e\) at one or more radii, by PREM layer. |
The neutron-to-proton ratio implied by an electron fraction. |
Module Contents
- magnus.earth.density_matter_func_prem(r: float | numpy.ndarray, tol: float | None = 1e-08, density_matter_ocean: float | None = None) float | numpy.ndarray[source]
Returns the matter density inside the Earth according to the Preliminary Reference Earth Model (PREM) [1].
Returns the matter density inside the Earth according to the PREM, for a given radial distance measured from the center of the Earth. Accepts a single radial distance or an array of radial distances; array input is evaluated in a single vectorized pass.
Added in version 1.0.0.
Changed in version 1.1.1: Added
density_matter_ocean, which replaces the density of PREM’s outermost shell. Left as None, the profile is unchanged.- Parameters:
r (float or np.ndarray) – Radial distance(s) measured from the center of the Earth [km].
tol (float, optional) – Relative tolerance by which a radial distance may exceed
globaldefs.EARTH_RADIUSbefore a ValueError is raised; radii within the tolerance are clamped onto the surface. Default: 1e-8.density_matter_ocean (float, optional) – Density of the outermost PREM shell, \(r > 6368\) km [\(\text{g cm}^{-3}\)]. PREM puts a global-average ocean there, at 1.020; a detector under continental rock sits under about 2.6 instead, and one under Antarctic ice under about 0.92. Pass
electron_fraction_oceanalongside it to set the composition of the same shell, whichelectron_fraction_func_prem()handles. Default: None, i.e. PREM’s own ocean.
- Returns:
Matter density [\(\text{g cm}^{-3}\)].
- Return type:
float or np.ndarray
- Raises:
ValueError – If any radial distance exceeds globaldefs.EARTH_RADIUS by more than the relative tolerance tol.
References
Examples
from magnus import earth for r in (0.0, 3000.0, 5000.0, 6371.0): print('r = %6.0f km -> %6.2f g/cm^3' % (r, earth.density_matter_func_prem(r)))
r = 0 km -> 13.09 g/cm^3 r = 3000 km -> 10.60 g/cm^3 r = 5000 km -> 4.79 g/cm^3 r = 6371 km -> 1.02 g/cm^3
- magnus.earth.distance_traveled_inside_earth(costhz: float, source_depth: float | None = 0.0, detector_depth: float | None = 0.0) float[source]
Returns the distance traveled by a neutrino inside the Earth, traveling with a cosine of zenith angle costhz.
Returns the length of the path traveled by a neutrino from its point of entry into the Earth, through it, until it reaches a detector. The direction of the neutrino is parametrized by the zenith angle of the neutrino, measured at the detector.
By default the source and the detector both sit on the surface, which is the geometry every earlier version assumed: the path is the full chord, and its length is zero for all values of costhz > 0. Burying either end moves the corresponding endpoint to a smaller radius. The zenith angle keeps its meaning throughout, since at zero depth the detector is on the surface; a buried detector sees a downward-going neutrino (costhz > 0) through its overburden, so the path length is then positive rather than zero.
Writing \(r_{\rm s} = R_\oplus - {}\)
source_depthand \(r_{\rm d} = R_\oplus - {}\)detector_depth, the impact parameter of the trajectory is \(b = r_{\rm d} \sqrt{1 - \cos^2\theta_z}\) and its length is\[L = \sqrt{r_{\rm s}^2 - b^2} - r_{\rm d} \cos\theta_z ~.\]Added in version 1.0.0.
Changed in version 1.1.1: Added
source_depthanddetector_depth. Their defaults of zero reproduce the surface-to-surface chord bit for bit, through the same expression as before.- Parameters:
costhz (float) – Cosine of the zenith angle of the neutrino, measured at the detector; must lie in [-1, 1]. The bound is enforced only when an endpoint is buried: with both ends on the surface, a costhz outside it returns a number rather than raising.
source_depth (float or None, optional) – Depth of the entry point below the surface [km]. None is read as 0.0. Default: 0.0, i.e. the neutrino enters at the surface.
detector_depth (float or None, optional) – Depth of the detector below the surface [km]. None is read as 0.0. Default: 0.0, i.e. the detector sits on the surface.
- Returns:
Path length inside the Earth [km]. Every
osc_prob_*baseline is in \(\text{eV}^{-1}\), so multiply bymagnus.globaldefs.UNIT_KMbefore passing this on; handing the raw value over returns a converged, unitary, wrong answer.- Return type:
- Raises:
ValueError – If
costhzis outside [-1, 1] and an endpoint is buried, if either depth is outside [0, R_earth), if the trajectory never reaches the source radius, which happens when the source is buried below the trajectory’s closest approach to the center, or if the resulting path length is negative, which happens when the source sits deeper than the detector on a downward-going trajectory.
Examples
from magnus import earth for costhz in (-0.2, -0.5, -1.0): print('costhz = %5.2f -> %8.1f km' % (costhz, earth.distance_traveled_inside_earth(costhz)))
costhz = -0.20 -> 2548.4 km costhz = -0.50 -> 6371.0 km costhz = -1.00 -> 12742.0 km
A detector 2 km underground sees a shorter upward-going path, and a downward-going one through its overburden:
for costhz in (-1.0, 0.5, 1.0): print('costhz = %5.2f -> %10.4f km' % (costhz, earth.distance_traveled_inside_earth( costhz, detector_depth=2.0)))
costhz = -1.00 -> 12740.0000 km costhz = 0.50 -> 3.9981 km costhz = 1.00 -> 2.0000 km
- magnus.earth.earth_radial_distance_from_depth(costhz: float, l: float | numpy.ndarray, tol: float | None = 1e-08, source_depth: float | None = 0.0, detector_depth: float | None = 0.0) float | numpy.ndarray[source]
Returns the radial distance measured from the center of the Earth to a position inside the Earth, given by costhz and l.
A neutrino with direction given by the cosine of the zenith angle, costhz, travels from l=0 to l=distance_traveled_inside_earth, computed below. The routine returns the radial distance to the neutrino when its distance from its point of entry into the Earth is l. Accepts a single distance or an array of distances; array input is evaluated in a single vectorized pass.
The two depths move the endpoints of that trajectory, exactly as they do in
distance_traveled_inside_earth(), and carry the same defaults: the entry point is on the surface and so is the detector. Whatever the depths,lis measured from the entry point, sol = 0returns the source radius andl = Lthe detector’s.Added in version 1.0.0.
Changed in version 1.1.1: Added
source_depthanddetector_depth. Their defaults of zero reproduce the surface-to-surface chord bit for bit, through the same expression as before.- Parameters:
costhz (float) – Cosine of the zenith angle of the neutrino, measured at the detector.
l (float or np.ndarray) – Distance(s) of the neutrino from its point of entry into the Earth [km].
tol (float, optional) – Absolute tolerance [km] by which
lmay exceed the distance traveled inside the Earth before a ValueError is raised; distances within the tolerance are clamped onto the exit point. Default: 1e-8.source_depth (float, optional) – Depth of the entry point below the surface [km]. Default: 0.0.
detector_depth (float, optional) – Depth of the detector below the surface [km]. Default: 0.0.
- Returns:
Radial distance to the neutrino [km].
- Return type:
float or np.ndarray
- Raises:
ValueError – If any l exceeds the distance traveled inside the Earth for this value of costhz by more than the tolerance tol, or if either depth is rejected by
distance_traveled_inside_earth().
Examples
from magnus import earth L = earth.distance_traveled_inside_earth(-0.8, detector_depth=2.0) for l in (0.0, 0.5*L, L): print('l = %9.3f km -> r = %8.3f km' % (l, earth.earth_radial_distance_from_depth( -0.8, l, detector_depth=2.0)))
l = 0.000 km -> r = 6371.000 km l = 5096.450 km -> r = 3821.400 km l = 10192.900 km -> r = 6369.000 km
- magnus.earth.prem_layer_edges_along_chord(costhz: float, source_depth: float | None = 0.0, detector_depth: float | None = 0.0) numpy.ndarray[source]
Returns the positions along a chord through the Earth at which the chord crosses the PREM layer boundaries.
A neutrino entering the Earth with direction
costhztravels along a chord from \(l = 0\) to \(l =\)distance_traveled_inside_earth()(costhz). The matter density along the chord is piecewise-smooth, with discontinuities (or kinks) where the chord crosses the boundaries between PREM shells. This routine returns those crossing positions, which are useful as mandatory slab edges for the Magnus expansion: high-order quadrature converges at its nominal order only if the Hamiltonian is smooth within each slab.The crossing positions solve \(r(l) = r_b\) for each boundary radius \(r_b\), which is a quadratic equation in \(l\): with \(u = d - l\) and \(d = -2 R \cos\theta_z\), one has
\[u^2 + 2 R \cos\theta_z\, u + \left(R^2 - r_b^2\right) = 0 .\]The two depths move the endpoints of the trajectory, exactly as they do in
distance_traveled_inside_earth(). Only crossings strictly inside the trajectory are returned, so burying an endpoint drops the boundaries the shortened path no longer reaches. An endpoint that lands exactly on a boundary radius is not a crossing: the density is smooth on the whole of a path that stops there.Added in version 1.0.0.
Changed in version 1.1.1: Added
source_depthanddetector_depth. Their defaults of zero reproduce the surface-to-surface chord bit for bit, through the same expression as before.- Parameters:
costhz (float) – Cosine of the zenith angle of the neutrino, measured at the detector. With both endpoints on the surface, crossings exist only for costhz < 0; a buried detector also sees a downward-going trajectory through its overburden.
source_depth (float, optional) – Depth of the entry point below the surface [km]. Default: 0.0.
detector_depth (float, optional) – Depth of the detector below the surface [km]. Default: 0.0.
- Returns:
Sorted crossing positions l [km], each strictly inside (0, d). Empty if the chord crosses no boundary.
- Return type:
np.ndarray
Examples
import numpy as np from magnus import earth edges = earth.prem_layer_edges_along_chord(-0.8) d = earth.distance_traveled_inside_earth(-0.8) print('%d crossings; the first three at %s km' % (len(edges), np.round(edges[:3], 1))) print('symmetric about the midpoint:', np.allclose(edges + edges[::-1], d))
14 crossings; the first three at [ 3.8 18.8 30.5] km symmetric about the midpoint: True
A buried detector loses the crossings its shortened path no longer reaches. Looking straight up, it keeps all eighteen until it passes below a boundary itself; looking straight down, its whole overburden lies inside PREM’s outermost shell, so it crosses nothing:
for depth in (0.0, 2.0, 20.0): print('%5.1f km down: %2d crossings looking up, %d looking down' % (depth, len(earth.prem_layer_edges_along_chord(-1.0, detector_depth=depth)), len(earth.prem_layer_edges_along_chord(1.0, detector_depth=depth))))
0.0 km down: 18 crossings looking up, 0 looking down 2.0 km down: 18 crossings looking up, 0 looking down 20.0 km down: 16 crossings looking up, 2 looking down
- magnus.earth.dms_to_decimal(degrees: float, minutes: float, seconds: float) float[source]
Converts (degree, minute, second) coordinates to decimal degrees.
A West longitude or a South latitude is negative. The sign is read from the first non-zero part, and the minutes and seconds are magnitudes that count away from zero in that direction, so
(-88, 15, 26)and(-88, -15, -26)both give \(-88.257^\circ\). A coordinate within a degree of the meridian or the equator carries its sign on the minutes,(0, -30, 0), or on the degrees as-0.0.Added in version 1.0.0.
Changed in version 1.1.1: The minutes and seconds now follow the sign of the coordinate. They used to be added as given, so
(-88, 15, 26), the form the built-in location table uses, came out \(-87.743^\circ\) and moved every West or South site toward zero by up to one degree: the chord from Fermilab to Homestake was 1207 km instead of 1285 km.
- magnus.earth.chord_length_inside_earth(lat1_dms: tuple[float, float, float], lon1_dms: tuple[float, float, float], lat2_dms: tuple[float, float, float], lon2_dms: tuple[float, float, float]) float[source]
Returns the chord length between two locations on the surface of the Earth.
Computes the straight-line (chord) distance between two locations on the surface of the Earth, assumed spherical, using the haversine formula for the central angle between the two locations and converting it to a chord length.
Added in version 1.0.0.
- Parameters:
lat1_dms (tuple of float) – Latitude of the first location, as (degrees, minutes, seconds).
lon1_dms (tuple of float) – Longitude of the first location, as (degrees, minutes, seconds).
lat2_dms (tuple of float) – Latitude of the second location, as (degrees, minutes, seconds).
lon2_dms (tuple of float) – Longitude of the second location, as (degrees, minutes, seconds).
- Returns:
Chord length between the two locations [km].
- Return type:
Examples
from magnus import earth fermilab = ((41.0, 49.0, 55.0), (-88.0, -15.0, -26.0)) sanford = ((44.0, 21.0, 12.0), (-103.0, -45.0, -5.0)) print('Fermilab to Sanford: %.1f km' % earth.chord_length_inside_earth(fermilab[0], fermilab[1], sanford[0], sanford[1]))
Fermilab to Sanford: 1284.7 km
- magnus.earth.costhz_between_points_on_surface(lat1_dms: tuple[float, float, float], lon1_dms: tuple[float, float, float], lat2_dms: tuple[float, float, float], lon2_dms: tuple[float, float, float]) float[source]
Returns the zenith angle of the chord between two locations on the surface of the Earth.
Computes the cosine of the zenith angle at which a neutrino would need to travel in a straight chord through the Earth to reach the second location from the first (e.g., a source and a detector both on the surface). Assumes a spherical Earth and a detector on the surface, not underground, so the returned value is always non-positive. A neutrino arriving from above,
costhz > 0, crosses no part of the Earth’s interior, andcosthz = 0grazes the surface horizontally. Two surface coordinates cannot describe a buried endpoint; to place one, give the zenith angle directly and passsource_depthordetector_depthtodistance_traveled_inside_earth().Added in version 1.0.0.
- Parameters:
lat1_dms (tuple of float) – Latitude of the first location, as (degrees, minutes, seconds).
lon1_dms (tuple of float) – Longitude of the first location, as (degrees, minutes, seconds).
lat2_dms (tuple of float) – Latitude of the second location, as (degrees, minutes, seconds).
lon2_dms (tuple of float) – Longitude of the second location, as (degrees, minutes, seconds).
- Returns:
Cosine of the zenith angle of the chord connecting the two locations.
- Return type:
- magnus.earth.coordinates_of_named_location(source_func_name: str, loc_name: str) numpy.ndarray[source]
Returns the coordinates of a predefined location (e.g., a neutrino detector site).
Looks up
loc_name(case-insensitively, spaces treated as underscores) in theloc_coords_dmsdictionary of predefined locations (neutrino telescopes/detector sites and a few reference points) and returns its latitude and longitude.Added in version 1.0.0.
- Parameters:
source_func_name (str) – Name of the calling function, used only to build a more informative error message if
loc_nameis not found. The message prefixes it withoscprob., so a caller from another module is reported under that name.loc_name (str) – Name of the predefined location (e.g.,
'kamioka','south_pole'). Seeearth.loc_coords_dmsfor the full list.
- Returns:
Array of shape
(2, 3)in degrees, arcminutes and arcseconds: row 0 the latitude, row 1 the longitude. The stored tuples are converted to floats.- Return type:
np.ndarray
- Raises:
ValueError – If
loc_nameis not one of the predefined locations.
- magnus.earth.Y_E_CORE_PREM = 0.4656[source]
Module-level constant
Electron fraction \(Y_e = \langle Z/A \rangle\) of the Earth’s core (\(r \le 3480\) km), taken as pure iron. Units: [Adimensional]
Added in version 1.0.0.
- Type:
- magnus.earth.Y_E_MANTLE_PREM = 0.4957[source]
Module-level constant
Electron fraction of the mantle (\(3480 < r \le 6346.6\) km), peridotite. Units: [Adimensional]
Added in version 1.0.0.
- Type:
- magnus.earth.Y_E_CRUST_PREM = 0.4952[source]
Module-level constant
Electron fraction of the crust (\(6346.6 < r \le 6368\) km), granitic. Within 0.1% of the mantle; separate for explicitness rather than for effect. Units: [Adimensional]
Added in version 1.0.0.
- Type:
- magnus.earth.Y_E_OCEAN_PREM = 0.5551[source]
Module-level constant
Electron fraction of PREM’s ocean layer (\(r > 6368\) km), seawater. Above 0.5 because hydrogen has \(Z/A = 1\). Units: [Adimensional]
Added in version 1.0.0.
- Type:
- magnus.earth.electron_fraction_func_prem(r, electron_fraction_core=None, electron_fraction_mantle=None, electron_fraction_crust=None, electron_fraction_ocean=None)[source]
Electron fraction \(Y_e\) at one or more radii, by PREM layer.
Added in version 1.0.0.
- Parameters:
r (float or np.ndarray) – Radial distance from the Earth’s center [km].
electron_fraction_core (float, optional) – \(Y_e\) for \(r \le 3480\) km. Default:
Y_E_CORE_PREM.electron_fraction_mantle (float, optional) – \(Y_e\) for \(3480 < r \le 6346.6\) km. Default:
Y_E_MANTLE_PREM.electron_fraction_crust (float, optional) – \(Y_e\) for \(6346.6 < r \le 6368\) km. Default:
Y_E_CRUST_PREM.electron_fraction_ocean (float, optional) – \(Y_e\) for \(r > 6368\) km. Default:
Y_E_OCEAN_PREM.
- Returns:
\(Y_e\) at each radius, with the shape of
r.- Return type:
np.ndarray
- magnus.earth.neutron_to_proton_ratio_from_electron_fraction(electron_fraction)[source]
The neutron-to-proton ratio implied by an electron fraction.
The two are not independent. With charge neutrality \(n_p = n_e = Y_e n_{\rm nucleon}\) and \(n_n = (1 - Y_e) n_{\rm nucleon}\), so
\[r = \frac{n_n}{n_p} = \frac{1 - Y_e}{Y_e} .\]Deriving one from the other is what keeps a medium physical: the two used to be independent arguments, so setting \(Y_e = 0.4656\) for an iron core while leaving \(r\) at its isoscalar default of 1.0 described matter that cannot exist – and silently, since \(r\) only shows up in the sterile sector.
Added in version 1.0.0.
- Parameters:
electron_fraction (float or np.ndarray) – \(Y_e\), in (0, 1]. Unchecked here: the domain is enforced by the caller,
magnus.oscprob._earth_composition. A zero returnsinfwith a NumPy divide warning, and a value outside the range returns a negative ratio in silence.- Returns:
\(r = n_n/n_p\), with the shape of the input.
- Return type:
np.ndarray