FieldJacobiTheta.java

/*
 * Licensed to the Hipparchus project under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The Hipparchus project licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.hipparchus.special.elliptic.jacobi;

import org.hipparchus.CalculusFieldElement;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.FieldSinCos;

/** Algorithm computing Jacobi theta functions.
 * @param <T> the type of the field elements
 * @since 2.0
 */
public class FieldJacobiTheta<T extends CalculusFieldElement<T>> {

    /** Maximum number of terms in the Fourier series. */
    private static final int N_MAX = 100;

    /** Nome. */
    private final T q;

    /** q². */
    private final T qSquare;

    /** ∜q. */
    private final T qFourth;

    /** Simple constructor.
     * <p>
     * The nome {@code q} can be computed using ratios of complete elliptic integrals
     * ({@link org.hipparchus.special.elliptic.legendre.LegendreEllipticIntegral#nome(CalculusFieldElement)
     * LegendreEllipticIntegral.nome(m)} which are themselves defined in term of parameter m,
     * where m=k² and k is the elliptic modulus.
     * </p>
     * @param q nome
     */
    public FieldJacobiTheta(final T q) {
        this.q       = q;
        this.qSquare = q.multiply(q);
        this.qFourth = FastMath.sqrt(FastMath.sqrt(q));
    }

    /** Get the nome.
     * @return nome
     */
    public T getQ() {
        return q;
    }

    /** Evaluate the Jacobi theta functions.
     * @param z argument of the functions
     * @return container for the four Jacobi theta functions θ₁(z|τ), θ₂(z|τ), θ₃(z|τ), and θ₄(z|τ)
     */
    public FieldTheta<T> values(final T z) {

        // the computation is based on Fourier series,
        // see Digital Library of Mathematical Functions section 20.2
        // https://dlmf.nist.gov/20.2
        final T zero = q.getField().getZero();
        final T one  = q.getField().getOne();

        // base angle for Fourier Series
        final FieldSinCos<T> sc1 = FastMath.sinCos(z);

        // recursion rules initialization
        double         sgn   = 1.0;
        T              qNN   = one;
        T              qTwoN = one;
        T              qNNp1 = one;
        FieldSinCos<T> sc2n1 = sc1;
        final double   eps   = FastMath.ulp(one).getReal();

        // Fourier series
        T sum1 = sc1.sin();
        T sum2 = sc1.cos();
        T sum3 = zero;
        T sum4 = zero;
        for (int n = 1; n < N_MAX; ++n) {

            sgn   = -sgn;                            // (-1)ⁿ⁻¹     ← (-1)ⁿ
            qNN   = qNN.multiply(qTwoN).multiply(q); // q⁽ⁿ⁻¹⁾⁽ⁿ⁻¹⁾ ← qⁿⁿ
            qTwoN = qTwoN.multiply(qSquare);         // q²⁽ⁿ⁻¹⁾     ← q²ⁿ
            qNNp1 = qNNp1.multiply(qTwoN);           // q⁽ⁿ⁻¹⁾ⁿ     ← qⁿ⁽ⁿ⁺¹⁾

            sc2n1 = FieldSinCos.sum(sc2n1, sc1); // {sin|cos}([2n-1] z) ← {sin|cos}(2n z)
            sum3  = sum3.add(sc2n1.cos().multiply(qNN));
            sum4  = sum4.add(sc2n1.cos().multiply(qNN.multiply(sgn)));

            sc2n1 = FieldSinCos.sum(sc2n1, sc1); // {sin|cos}(2n z) ← {sin|cos}([2n+1] z)
            sum1  = sum1.add(sc2n1.sin().multiply(qNNp1.multiply(sgn)));
            sum2  = sum2.add(sc2n1.cos().multiply(qNNp1));

            if (qNNp1.norm() <= eps) {
                // we have reach convergence
                break;
            }

        }

        return new FieldTheta<>(sum1.multiply(qFourth.multiply(2)),
                                sum2.multiply(qFourth.multiply(2)),
                                sum3.multiply(2).add(1),
                                sum4.multiply(2).add(1));

    }

}