legendre.py 7.04 KB
Newer Older
Boris Bonev's avatar
Boris Bonev committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# coding=utf-8

# SPDX-FileCopyrightText: Copyright (c) 2022 The torch-harmonics Authors. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

Thorsten Kurth's avatar
Thorsten Kurth committed
32
33
34
from typing import Optional
import math
import torch
Boris Bonev's avatar
Boris Bonev committed
35

Thorsten Kurth's avatar
Thorsten Kurth committed
36
37
38
39
from torch_harmonics.cache import lru_cache


def clm(l: int, m: int) -> float:
Boris Bonev's avatar
Boris Bonev committed
40
41
42
    """
    defines the normalization factor to orthonormalize the Spherical Harmonics
    """
Thorsten Kurth's avatar
Thorsten Kurth committed
43
    return math.sqrt((2*l + 1) / 4 / math.pi) * math.sqrt(math.factorial(l-m) / math.factorial(l+m))
Boris Bonev's avatar
Boris Bonev committed
44

Thorsten Kurth's avatar
Thorsten Kurth committed
45
def legpoly(mmax: int, lmax: int, x: torch.Tensor, norm: Optional[str]="ortho", inverse: Optional[bool]=False, csphase: Optional[bool]=True) -> torch.Tensor:
46
    r"""
47
48
49
    Computes the values of (-1)^m c^l_m P^l_m(x) at the positions specified by x.
    The resulting tensor has shape (mmax, lmax, len(x)). The Condon-Shortley Phase (-1)^m
    can be turned off optionally.
Boris Bonev's avatar
Boris Bonev committed
50
51
52
53
54
55
56
57
58

    method of computation follows
    [1] Schaeffer, N.; Efficient spherical harmonic transforms aimed at pseudospectral numerical simulations, G3: Geochemistry, Geophysics, Geosystems.
    [2] Rapp, R.H.; A Fortran Program for the Computation of Gravimetric Quantities from High Degree Spherical Harmonic Expansions, Ohio State University Columbus; report; 1982;
        https://apps.dtic.mil/sti/citations/ADA123406
    [3] Schrama, E.; Orbit integration based upon interpolated gravitational gradients
    """

    # compute the tensor P^m_n:
Boris Bonev's avatar
Boris Bonev committed
59
    nmax = max(mmax,lmax)
Thorsten Kurth's avatar
Thorsten Kurth committed
60
    vdm = torch.zeros((nmax, nmax, len(x)), dtype=torch.float64, requires_grad=False)
Boris Bonev's avatar
Boris Bonev committed
61
        
Thorsten Kurth's avatar
Thorsten Kurth committed
62
    norm_factor = 1. if norm == "ortho" else math.sqrt(4 * math.pi)
Boris Bonev's avatar
Boris Bonev committed
63
64
65
    norm_factor = 1. / norm_factor if inverse else norm_factor

    # initial values to start the recursion
Thorsten Kurth's avatar
Thorsten Kurth committed
66
    vdm[0,0,:] = norm_factor / math.sqrt(4 * math.pi)
Boris Bonev's avatar
Boris Bonev committed
67
68

    # fill the diagonal and the lower diagonal
Boris Bonev's avatar
Boris Bonev committed
69
    for l in range(1, nmax):
Thorsten Kurth's avatar
Thorsten Kurth committed
70
71
        vdm[l-1, l, :] = math.sqrt(2*l + 1) * x * vdm[l-1, l-1, :]
        vdm[l, l, :] = torch.sqrt( (2*l + 1) * (1 + x) * (1 - x) / 2 / l ) * vdm[l-1, l-1, :]
Boris Bonev's avatar
Boris Bonev committed
72
73

    # fill the remaining values on the upper triangle and multiply b
Boris Bonev's avatar
Boris Bonev committed
74
    for l in range(2, nmax):
Boris Bonev's avatar
Boris Bonev committed
75
        for m in range(0, l-1):
Thorsten Kurth's avatar
Thorsten Kurth committed
76
77
            vdm[m, l, :] = x * math.sqrt((2*l - 1) / (l - m) * (2*l + 1) / (l + m)) * vdm[m, l-1, :] \
                            - math.sqrt((l + m - 1) / (l - m) * (2*l + 1) / (2*l - 3) * (l - m - 1) / (l + m)) * vdm[m, l-2, :]
Boris Bonev's avatar
Boris Bonev committed
78
79

    if norm == "schmidt":
Boris Bonev's avatar
Boris Bonev committed
80
        for l in range(0, nmax):
Boris Bonev's avatar
Boris Bonev committed
81
            if inverse:
Thorsten Kurth's avatar
Thorsten Kurth committed
82
                vdm[:, l, : ] = vdm[:, l, : ] * math.sqrt(2*l + 1)
Boris Bonev's avatar
Boris Bonev committed
83
            else:
Thorsten Kurth's avatar
Thorsten Kurth committed
84
                vdm[:, l, : ] = vdm[:, l, : ] / math.sqrt(2*l + 1)
Boris Bonev's avatar
Boris Bonev committed
85

86
    vdm = vdm[:mmax, :lmax]
Boris Bonev's avatar
Boris Bonev committed
87

Boris Bonev's avatar
Boris Bonev committed
88
89
    if csphase:
        for m in range(1, mmax, 2):
90
91
92
93
            vdm[m] *= -1

    return vdm

Thorsten Kurth's avatar
Thorsten Kurth committed
94
95
96
@lru_cache(typed=True, copy=True)
def _precompute_legpoly(mmax: int , lmax: int, t: torch.Tensor,
                        norm: Optional[str]="ortho", inverse: Optional[bool]=False, csphase: Optional[bool]=True) -> torch.Tensor:
97
98
99
100
101
102
103
104
105
106
107
    r"""
    Computes the values of (-1)^m c^l_m P^l_m(\cos \theta) at the positions specified by t (theta).
    The resulting tensor has shape (mmax, lmax, len(x)). The Condon-Shortley Phase (-1)^m
    can be turned off optionally.

    method of computation follows
    [1] Schaeffer, N.; Efficient spherical harmonic transforms aimed at pseudospectral numerical simulations, G3: Geochemistry, Geophysics, Geosystems.
    [2] Rapp, R.H.; A Fortran Program for the Computation of Gravimetric Quantities from High Degree Spherical Harmonic Expansions, Ohio State University Columbus; report; 1982;
        https://apps.dtic.mil/sti/citations/ADA123406
    [3] Schrama, E.; Orbit integration based upon interpolated gravitational gradients
    """
Boris Bonev's avatar
Boris Bonev committed
108

Thorsten Kurth's avatar
Thorsten Kurth committed
109
    return legpoly(mmax, lmax, torch.cos(t), norm=norm, inverse=inverse, csphase=csphase)
Boris Bonev's avatar
Boris Bonev committed
110

Thorsten Kurth's avatar
Thorsten Kurth committed
111
112
113
@lru_cache(typed=True, copy=True)
def _precompute_dlegpoly(mmax: int, lmax: int, t: torch.Tensor,
                         norm: Optional[str]="ortho", inverse: Optional[bool]=False, csphase: Optional[bool]=True) -> torch.Tensor:
114
    r"""
Boris Bonev's avatar
Boris Bonev committed
115
    Computes the values of the derivatives $\frac{d}{d \theta} P^m_l(\cos \theta)$
116
    at the positions specified by t (theta), as well as $\frac{1}{\sin \theta} P^m_l(\cos \theta)$,
Boris Bonev's avatar
Boris Bonev committed
117
    needed for the computation of the vector spherical harmonics. The resulting tensor has shape
118
    (2, mmax, lmax, len(t)).
Boris Bonev's avatar
Boris Bonev committed
119
120
121
122
123

    computation follows
    [2] Wang, B., Wang, L., Xie, Z.; Accurate calculation of spherical and vector spherical harmonic expansions via spectral element grids; Adv Comput Math.
    """

124
    pct = _precompute_legpoly(mmax+1, lmax+1, t, norm=norm, inverse=inverse, csphase=False)
Boris Bonev's avatar
Boris Bonev committed
125

Thorsten Kurth's avatar
Thorsten Kurth committed
126
    dpct = torch.zeros((2, mmax, lmax, len(t)), dtype=torch.float64, requires_grad=False)
Boris Bonev's avatar
Boris Bonev committed
127
128
129
130
131

    # fill the derivative terms wrt theta
    for l in range(0, lmax):

        # m = 0
Thorsten Kurth's avatar
Thorsten Kurth committed
132
        dpct[0, 0, l] = - math.sqrt(l*(l+1)) * pct[1, l]
Boris Bonev's avatar
Boris Bonev committed
133
134
135

        # 0 < m < l
        for m in range(1, min(l, mmax)):
Thorsten Kurth's avatar
Thorsten Kurth committed
136
            dpct[0, m, l] = 0.5 * ( math.sqrt((l+m)*(l-m+1)) * pct[m-1, l] - math.sqrt((l-m)*(l+m+1)) * pct[m+1, l] )
Boris Bonev's avatar
Boris Bonev committed
137
138
139

        # m == l
        if mmax > l:
Thorsten Kurth's avatar
Thorsten Kurth committed
140
            dpct[0, l, l] = math.sqrt(l/2) * pct[l-1, l]
Boris Bonev's avatar
Boris Bonev committed
141
142
143
144
145
146

        # fill the - 1j m P^m_l / sin(phi). as this component is purely imaginary,
        # we won't store it explicitly in a complex array
        for m in range(1, min(l+1, mmax)):
            # this component is implicitly complex
            # we do not divide by m here as this cancels with the derivative of the exponential
Thorsten Kurth's avatar
Thorsten Kurth committed
147
148
            dpct[1, m, l] = 0.5 * math.sqrt((2*l+1)/(2*l+3)) * \
                ( math.sqrt((l-m+1)*(l-m+2)) * pct[m-1, l+1] + math.sqrt((l+m+1)*(l+m+2)) * pct[m+1, l+1] )
Boris Bonev's avatar
Boris Bonev committed
149
150
151
152
153

    if csphase:
        for m in range(1, mmax, 2):
            dpct[:, m] *= -1

Thorsten Kurth's avatar
Thorsten Kurth committed
154
    return dpct