JacobiTheta.java

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

  18. import org.hipparchus.complex.Complex;
  19. import org.hipparchus.util.FastMath;
  20. import org.hipparchus.util.FieldSinCos;
  21. import org.hipparchus.util.Precision;

  22. /** Algorithm computing Jacobi theta functions.
  23.  * @since 2.0
  24.  */
  25. public class JacobiTheta {

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

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

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

  32.     /** ∜q. */
  33.     private final double qFourth;

  34.     /** Simple constructor.
  35.      * <p>
  36.      * The nome {@code q} can be computed using ratios of complete elliptic integrals
  37.      * ({@link org.hipparchus.special.elliptic.legendre.LegendreEllipticIntegral#nome(double)
  38.      * LegendreEllipticIntegral.nome(m)} which are themselves defined in term of parameter m,
  39.      * where m=k² and k is the elliptic modulus.
  40.      * </p>
  41.      * @param q nome
  42.      */
  43.     public JacobiTheta(final double q) {
  44.         this.q       = q;
  45.         this.qSquare = q * q;
  46.         this.qFourth = FastMath.sqrt(FastMath.sqrt(q));
  47.     }

  48.     /** Get the nome.
  49.      * @return nome
  50.      */
  51.     public double getQ() {
  52.         return q;
  53.     }

  54.     /** Evaluate the Jacobi theta functions.
  55.      * @param z argument of the functions
  56.      * @return container for the four Jacobi theta functions θ₁(z|τ), θ₂(z|τ), θ₃(z|τ), and θ₄(z|τ)
  57.      */
  58.     public Theta values(final Complex z) {

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

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

  64.         // recursion rules initialization
  65.         double               sgn   = 1.0;
  66.         double               qNN   = 1.0;
  67.         double               qTwoN = 1.0;
  68.         double               qNNp1 = 1.0;
  69.         FieldSinCos<Complex> sc2n1 = sc1;

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

  76.             sgn   = -sgn;              // (-1)ⁿ⁻¹     ← (-1)ⁿ
  77.             qNN   = qNN   * qTwoN * q; // q⁽ⁿ⁻¹⁾⁽ⁿ⁻¹⁾ ← qⁿⁿ
  78.             qTwoN = qTwoN * qSquare;   // q²⁽ⁿ⁻¹⁾     ← q²ⁿ
  79.             qNNp1 = qNNp1 * qTwoN;     // q⁽ⁿ⁻¹⁾ⁿ     ← qⁿ⁽ⁿ⁺¹⁾

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

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

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

  90.         }

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

  95.     }

  96. }