SolidTide#

class SolidTide(tide_bodies: tuple = None, central_body: str = 'EARTH', love_h2: float = None, shida_l2: float = None, love_k: dict = None, remove_permanent_tide: bool = False, rate_step: float = 60.0, model: str = 'degree2')#

Bases: object

Solid tide displacement of a surface point.

A station is not fixed in its body-fixed frame. Nearby masses deform the solid body it stands on, moving it by tens of centimetres on a diurnal and semidiurnal cycle. That is well above the noise of a modern ranging system, so a station geometry that omits it carries a signature no estimated parameter is supposed to absorb.

By default the degree-2 in-phase term of the IERS Conventions (2010), Chapter 7, Section 7.1.1, step 1; model="IERS2010" evaluates the complete IERS routine instead. The degree-2 term is

\[\delta\mathbf r = \sum_j \frac{GM_j}{GM_\oplus} \frac{R^4}{R_j^3} \left\{ h_2 \hat{\mathbf r} \left[\tfrac{3}{2}(\hat{\mathbf R}_j\cdot\hat{\mathbf r})^2 - \tfrac{1}{2}\right] + 3 l_2 (\hat{\mathbf R}_j\cdot\hat{\mathbf r}) \left[\hat{\mathbf R}_j - (\hat{\mathbf R}_j\cdot\hat{\mathbf r})\hat{\mathbf r} \right]\right\}\]

summed over the tide-raising bodies.

One tide model serves two consumers. Attach it to GroundStation.surface_displacement to move a station on the surface, and to SphericalHarmonicsGravity.solid_tide to perturb the gravity field.

Nothing in the model is specific to Earth. Earth is the one body with a nominal response built in, because it is the one whose Love and Shida numbers are pinned by a convention; a station on any other body gets the same treatment once its numbers are supplied.

Parameters:
tide_bodiestuple of str, optional

Tide-raising bodies. Defaults to the Moon and the Sun for Earth, where everything else is below a millimetre. Required for any other central body.

central_bodystr, optional

Deformed body, default EARTH. Its GM and equatorial radius set the scale, and the tide-raiser positions are taken relative to it. Nothing in the formulation is Earth-specific – a station on Mars gets its tide from this same class, given Mars values.

love_h2, shida_l2float, optional

Degree-2 Love and Shida numbers. Default to the IERS nominal values of Constants.EARTH_SOLID_TIDE, 0.6078 and 0.0847, for Earth only. For any other body they are required: the tidal response varies by a factor of several between bodies (Mars is near h2 = 0.17), so a default would be a large silent error rather than an approximation.

love_kdict, optional

Potential Love numbers \(k_{nm}\) keyed by (degree, order), used only by geopotential_coefficients(). Defaults to the IERS Table 6.3 values for Earth, degrees 2 and 3, from Constants.EARTH_SOLID_TIDE. These are a table rather than one number for the same reason the field itself is: they vary with order (\(k_{20}\) and \(k_{22}\) differ by about 1%) and fall by a factor of three from degree 2 to degree 3. Supplying a subset restricts the perturbation to those terms.

These are a different quantity from love_h2: \(h\) says how far the surface moves, \(k\) how much the external gravity field changes. Confusing them is a factor-of-two error.

remove_permanent_tidebool, optional

Subtract the zero-frequency (permanent) deformation, IERS eq. 7.14. Default false, which is what conventional tide-free station coordinates need. See Notes – this is a real fork, not a preference.

rate_stepfloat, optional

Half-interval in seconds for the central difference that supplies the body-fixed rate, default 60.

model{“degree2”, “IERS2010”}, optional

"degree2" (default) is the degree-2 in-phase term above. "IERS2010" is the complete IERS Conventions (2010) routine DEHANTTIDEINEL, Earth only, with its own constants (Constants.EARTH_SOLID_TIDE): step 1 in and out of phase for degrees 2 and 3 with latitude-dependent Love and Shida numbers, and the step-2 frequency corrections. It differs from the degree-2 term by up to about 13 mm, mostly the diurnal K1 correction.

Attributes:
love_h2
love_k
shida_l2
tide_bodies

Methods

displacement(position_body_fixed, epoch[, frame])

Tidal displacement of a body-fixed point.

displacement_partials(position_body_fixed, epoch)

Sensitivity of the displacement to the Love and Shida numbers.

geopotential_coefficients(epoch[, frame])

Tidal perturbation of the body's spherical-harmonic coefficients.

geopotential_partials(epoch[, frame])

Sensitivity of the coefficient perturbation to each \(k_{nm}\).

iers2010_displacement(xsta, fhr, t, xsun, xmon)

IERS Conventions (2010) solid-tide station displacement, DEHANTTIDEINEL.

state_displacement(position_body_fixed, epoch)

Tidal displacement and its body-fixed rate.

state_displacement_partials(...[, frame])

Love/Shida sensitivity of the displacement and of its body-fixed rate.

Notes

The degree-2 model retains the degree-2 in-phase term, which is the whole of the effect at the decimetre level – up to about 0.4 m radially at Earth.

The permanent-tide constants are Earth values from IERS eq. 7.14, kept in Constants.EARTH_SOLID_TIDE, so remove_permanent_tide is only meaningful for Earth.

The degree-2 model omits, with magnitudes: degree 3 (about 2 mm), the out-of-phase diurnal and semidiurnal contributions and the latitude dependence of \(h\) and \(l\) (a few mm), and the frequency-dependent step-2 corrections (up to about 13 mm, the diurnal K1 term above all). model="IERS2010" includes all of these. Neither includes ocean and atmospheric loading (about 1 cm and a few mm) or the pole tide (about 1 cm). displacement_partials() is the degree-2 term’s sensitivity to the Love and Shida numbers under either model.

The permanent tide is a genuine convention fork rather than a refinement. The formula above yields the total deformation including a zero-frequency part – roughly -12 cm radially at the poles and +6 cm at the equator. Conventional tide-free coordinates, which is what the ITRF and DSN tables publish, already exclude that permanent deformation, so it must be restored by this formula and remove_permanent_tide stays false. Set it true only for coordinates that are already mean-tide or zero-tide, or the permanent part is applied twice.

The choice is made on the coordinates’ stated convention, not on residuals: on the C2 and C3 deliveries the two settings differ by 0.010 m in the mean range residual and 0.0002 m in its scatter, so the data does not discriminate between them.

See orientation-station-estimation for how the model enters the station geometry and which higher-order terms are left out.

References

IERS Conventions (2010), IERS Technical Note 36, Chapter 7. https://iers-conventions.obspm.fr/content/chapter7/icc7.pdf

classmethod iers2010_displacement(xsta, fhr, t, xsun, xmon) → ndarray#

IERS Conventions (2010) solid-tide station displacement, DEHANTTIDEINEL.

What displacement() evaluates for model="IERS2010", with the routine’s own interface and units.

Parameters:
xstaarray_like, shape (3,)

Station position, m, Earth-fixed (ITRF).

fhrfloat

UTC hours of the day.

tfloat

TT Julian centuries from J2000.

xsun, xmonarray_like, shape (3,)

Geocentric Sun and Moon positions, m, in the same Earth-fixed axes.

Returns:
numpy.ndarray

Displacement in m, Earth-fixed axes: step 1 in and out of phase, degrees 2 and 3, with latitude-dependent Love and Shida numbers, and the step-2 diurnal and long-period frequency corrections. The permanent tide is included, as for conventional tide-free coordinates.

Notes

The numbers are Constants.EARTH_SOLID_TIDE; the reference radius is Constants.EARTH.equatorial_radius.

displacement(position_body_fixed, epoch: float, frame: str = None) → ArrayWUnits#

Tidal displacement of a body-fixed point.

Parameters:
position_body_fixedArrayWUnits, ArrayWFrame or array_like, shape (3,)

Undisplaced position in frame, relative to the body centre. Bare numbers are km.

epochfloat or EpochArray

TDB seconds past J2000.

framestr or Frame, optional

Body-fixed axes the position is given in and the displacement is returned in. Defaults to None, 'IAU_' plus the central body’s name.

Returns:
ArrayWUnits

Displacement in km, shape (3,).

Raises:
ValueError

At the body centre, where the local vertical is undefined.

displacement_partials(position_body_fixed, epoch: float, frame: str = None) → ArrayWUnits#

Sensitivity of the displacement to the Love and Shida numbers.

Parameters:
position_body_fixedArrayWUnits, ArrayWFrame or array_like, shape (3,)

Undisplaced position in frame. Bare numbers are km.

epochfloat or EpochArray

TDB seconds past J2000.

framestr or Frame, optional

Body-fixed axes; see displacement(). Defaults to None.

Returns:
ArrayWUnits

(3, 2) Jacobian, columns \(\partial\delta r/\partial h_2\) and \(\partial\delta r/\partial l_2\), in km per unit.

Raises:
ValueError

At the body centre, where the local vertical is undefined.

Notes

The displacement is linear in \(h_2\) and \(l_2\), so these columns are the two terms evaluated with a unit coefficient and are exact rather than a linearization. The permanent-tide term does not depend on the Love numbers – it is a tabulated IERS correction – so it contributes nothing here whichever way that option is set.

geopotential_coefficients(epoch: float, frame: str = None) → dict#

Tidal perturbation of the body’s spherical-harmonic coefficients.

Parameters:
epochfloat

TDB seconds past J2000.

framestr, optional

Body-fixed axes the tide-raiser positions are taken in. Defaults to None, 'IAU_' plus the central body’s name.

Returns:
dict

(degree, order) mapped to (dC, dS), normalized, for every (degree, order) present in love_k. Add these to the nominal field.

Notes

IERS Conventions (2010), Chapter 6, eq. 6.6, step 1:

\[\Delta\bar C_{nm} - i\,\Delta\bar S_{nm} = \frac{k_{nm}}{2n+1} \sum_j \frac{GM_j}{GM} \left(\frac{R}{r_j}\right)^{n+1} \bar P_{nm}(\sin\Phi_j)\, e^{-im\lambda_j}\]

This is the other half of the same tide that displacement() moves the surface with: the deformed mass changes the external field. It is driven by love_k, not by \(h_2\) and \(l_2\).

Every (degree, order) in love_k is summed, so the resolution follows that table rather than being fixed at degree 2. At Earth this reaches about 5e-9 in \(\Delta\bar C_{20}\), which is 1e-5 of the nominal \(\bar C_{20}\); the degree-3 terms are three orders of magnitude smaller again.

Each \(\Delta\bar C_{nm}\) depends on its own \(k_{nm}\) and no other, so the sensitivity is diagonal – see geopotential_partials().

Frequency-dependent step-2 corrections and the anelastic \(k^{+}\) term are not included; both are well below the step-1 terms retained here.

Beware the base field’s own tide convention. A geopotential published as zero-tide already contains the permanent deformation, so adding this on top counts it twice; a tide-free field does not. The difference is about 1e-8 in \(\bar C_{20}\), comparable to the whole tidal signal, and this method does not know which convention the loaded coefficients use.

geopotential_partials(epoch: float, frame: str = None) → dict#

Sensitivity of the coefficient perturbation to each \(k_{nm}\).

Parameters:
epochfloat

TDB seconds past J2000.

framestr, optional

Body-fixed axes; see geopotential_coefficients(). Defaults to None.

Returns:
dict

(degree, order) mapped to (dC/dk, dS/dk) for that same (degree, order).

Notes

\(\Delta\bar C_{nm}\) depends on \(k_{nm}\) alone and linearly, so the Jacobian is diagonal and each entry is exact rather than a linearization – this is the same table evaluated at unit \(k_{nm}\).

state_displacement(position_body_fixed, epoch: float, frame: str = None) → ArrayWUnits#

Tidal displacement and its body-fixed rate.

Parameters:
position_body_fixedArrayWUnits, ArrayWFrame or array_like, shape (3,)

Undisplaced position in frame. Bare numbers are km.

epochfloat or EpochArray

TDB seconds past J2000.

framestr or Frame, optional

Body-fixed axes; see displacement(). Defaults to None.

Returns:
ArrayWUnits

Six components in km and km/s, shape (6,).

Raises:
ValueError

At the body centre, where the local vertical is undefined.

Notes

The rate is a central difference over rate_step, which is ample for a signal whose shortest period is twelve hours. It reaches roughly 0.03 mm/s at Earth, comparable to the frame-rotation term the state transformation adds on top, and both matter against Doppler noise quoted near 1 mm/s.

state_displacement_partials(position_body_fixed, epoch: float, frame: str = None) → ArrayWUnits#

Love/Shida sensitivity of the displacement and of its body-fixed rate.

Parameters:
position_body_fixedArrayWUnits, ArrayWFrame or array_like, shape (3,)

Undisplaced position in frame. Bare numbers are km.

epochfloat or EpochArray

TDB seconds past J2000.

framestr or Frame, optional

Body-fixed axes; see displacement(). Defaults to None.

Returns:
ArrayWUnits

(6, 2) Jacobian in km and km/s per unit Love or Shida number, columns \(h_2\) and \(l_2\) as in displacement_partials().

Raises:
ValueError

At the body centre, where the local vertical is undefined.

Notes

The rate rows use the same central difference as state_displacement(), so the partial is the derivative of what the forward model actually applies.

MODELS = ('degree2', 'IERS2010')#

Displacement models: the degree-2 in-phase term, or the complete IERS Conventions (2010) routine.

NOMINAL_RESPONSE = {'EARTH': SolidTideInfo(love_h2=0.6078, shida_l2=0.0847, love_k={(2, 0): 0.29525, (2, 1): 0.2947, (2, 2): 0.29801, (3, 0): 0.093, (3, 1): 0.093, (3, 2): 0.093, (3, 3): 0.094}, tide_bodies=('MOON', 'SUN'), love_h3=0.292, shida_l3=0.015, love_h2_latitude=-0.0006, shida_l2_latitude=0.0002, out_of_phase_diurnal=(-0.0025, -0.0007), out_of_phase_semidiurnal=(-0.0022, -0.0007), shida_l1=(0.0012, 0.0024), mass_ratio_sun=332946.0482, mass_ratio_moon=0.0123000371, step2_diurnal=((-3, 0, 2, 0, 0, -0.01, 0, 0, 0), (-3, 2, 0, 0, 0, -0.01, 0, 0, 0), (-2, 0, 1, -1, 0, -0.02, 0, 0, 0), (-2, 0, 1, 0, 0, -0.08, 0, -0.01, 0.01), (-2, 2, -1, 0, 0, -0.02, 0, 0, 0), (-1, 0, 0, -1, 0, -0.1, 0, 0, 0), (-1, 0, 0, 0, 0, -0.51, 0, -0.02, 0.03), (-1, 2, 0, 0, 0, 0.01, 0, 0, 0), (0, -2, 1, 0, 0, 0.01, 0, 0, 0), (0, 0, -1, 0, 0, 0.02, 0, 0, 0), (0, 0, 1, 0, 0, 0.06, 0, 0, 0), (0, 0, 1, 1, 0, 0.01, 0, 0, 0), (0, 2, -1, 0, 0, 0.01, 0, 0, 0), (1, -3, 0, 0, 1, -0.06, 0, 0, 0), (1, -2, 0, -1, 0, 0.01, 0, 0, 0), (1, -2, 0, 0, 0, -1.23, -0.07, 0.06, 0.01), (1, -1, 0, 0, -1, 0.02, 0, 0, 0), (1, -1, 0, 0, 1, 0.04, 0, 0, 0), (1, 0, 0, -1, 0, -0.22, 0.01, 0.01, 0), (1, 0, 0, 0, 0, 12.0, -0.8, -0.67, -0.03), (1, 0, 0, 1, 0, 1.73, -0.12, -0.1, 0), (1, 0, 0, 2, 0, -0.04, 0, 0, 0), (1, 1, 0, 0, -1, -0.5, -0.01, 0.03, 0), (1, 1, 0, 0, 1, 0.01, 0, 0, 0), (0, 1, 0, 1, -1, -0.01, 0, 0, 0), (1, 2, -2, 0, 0, -0.01, 0, 0, 0), (1, 2, 0, 0, 0, -0.11, 0.01, 0.01, 0), (2, -2, 1, 0, 0, -0.01, 0, 0, 0), (2, 0, -1, 0, 0, -0.02, 0, 0, 0), (3, 0, 0, 0, 0, 0, 0, 0, 0), (3, 0, 0, 1, 0, 0, 0, 0, 0)), step2_long_period=((0, 0, 0, 1, 0, 0.47, 0.16, 0.23, 0.07), (0, 2, 0, 0, 0, -0.2, -0.11, -0.12, -0.05), (1, 0, -1, 0, 0, -0.11, -0.09, -0.08, -0.04), (2, 0, 0, 0, 0, -0.13, -0.15, -0.11, -0.07), (2, 0, 0, 1, 0, -0.05, -0.06, -0.05, -0.03)), step2_arguments={'s': (218.31664563, 481267.88194, -0.0014663889, 1.85139e-06), 'h': (280.46645, 36000.7697489, 0.00030322222, 2e-08, -6.54e-09), 'p': (83.35324312, 4069.01363525, -0.01032172222, -1.24991e-05, 5.263e-08), "N'": (234.95544499, 1934.13626197, -0.00207561111, -2.13944e-06, 1.65e-08), 'ps': (282.93734098, 1.71945766667, 0.00045688889, -1.778e-08, -3.34e-09), 'pr': (0.0, 1.396971278, 0.000308889, 2.1e-08, 7e-09), 'GMST': (280.4606184, 36000.7700536, 0.00038793, -2.58e-08)}, permanent_tide_radial=(-0.1206, 0.0001), permanent_tide_north=(-0.0252, -0.0001))}#

Central bodies with a nominal response, each a SolidTideInfo from Constants. Only bodies whose response is actually measured belong here: guessing one is a factor-of-several error, not a rounding one, so an unlisted body must be given explicit values.

STATE_UNITS = (km, km, km, km/sec, km/sec, km/sec)#

Row units of state_displacement() and of its Love/Shida partials.

central_body: str = 'EARTH'#
love_h2: float = None#
love_k: dict = None#
model: str = 'degree2'#
rate_step: float = 60.0#
remove_permanent_tide: bool = False#
shida_l2: float = None#
tide_bodies: tuple = None#
exception UnknownModelError#

Bases: ValueError

The displacement model is not one of MODELS, or the IERS 2010 routine was asked for with a body or Love numbers it does not define: it is Earth’s, with its own fixed numbers.

exception UnknownTidalResponseError#

Bases: ValueError

No tidal response is known for this body and none was supplied.

Raised by the constructor. The degree-2 Love and Shida numbers differ by a factor of several between bodies, so falling back on Earth’s would be a large silent error. Supply love_h2, shida_l2 and tide_bodies explicitly, or, once its response is actually known, give it a SolidTideInfo in Constants and list it in NOMINAL_RESPONSE.