JacobiTheta.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.complex.Complex;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.FieldSinCos;
import org.hipparchus.util.Precision;

/** Algorithm computing Jacobi theta functions.
 * @since 2.0
 */
public class JacobiTheta {

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

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

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

    /** ∜q. */
    private final double 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(double)
     * 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 JacobiTheta(final double q) {
        this.q       = q;
        this.qSquare = q * q;
        this.qFourth = FastMath.sqrt(FastMath.sqrt(q));
    }

    /** Get the nome.
     * @return nome
     */
    public double 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 Theta values(final Complex z) {

        // the computation is based on Fourier series,
        // see Digital Library of Mathematical Functions section 20.2
        // https://dlmf.nist.gov/20.2

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

        // recursion rules initialization
        double               sgn   = 1.0;
        double               qNN   = 1.0;
        double               qTwoN = 1.0;
        double               qNNp1 = 1.0;
        FieldSinCos<Complex> sc2n1 = sc1;

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

            sgn   = -sgn;              // (-1)ⁿ⁻¹     ← (-1)ⁿ
            qNN   = qNN   * qTwoN * q; // q⁽ⁿ⁻¹⁾⁽ⁿ⁻¹⁾ ← qⁿⁿ
            qTwoN = qTwoN * qSquare;   // q²⁽ⁿ⁻¹⁾     ← q²ⁿ
            qNNp1 = qNNp1 * 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(sgn * qNN));

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

            if (FastMath.abs(qNNp1) <= Precision.EPSILON) {
                // we have reach convergence
                break;
            }

        }

        return new Theta(sum1.multiply(2 * qFourth),
                         sum2.multiply(2 * qFourth),
                         sum3.multiply(2).add(1),
                         sum4.multiply(2).add(1));

    }

}