FieldJacobiTheta.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.CalculusFieldElement;
  19. import org.hipparchus.util.FastMath;
  20. import org.hipparchus.util.FieldSinCos;

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

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

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

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

  32.     /** ∜q. */
  33.     private final T 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(CalculusFieldElement)
  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 FieldJacobiTheta(final T q) {
  44.         this.q       = q;
  45.         this.qSquare = q.multiply(q);
  46.         this.qFourth = FastMath.sqrt(FastMath.sqrt(q));
  47.     }

  48.     /** Get the nome.
  49.      * @return nome
  50.      */
  51.     public T 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 FieldTheta<T> values(final T 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.         final T zero = q.getField().getZero();
  63.         final T one  = q.getField().getOne();

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

  66.         // recursion rules initialization
  67.         double         sgn   = 1.0;
  68.         T              qNN   = one;
  69.         T              qTwoN = one;
  70.         T              qNNp1 = one;
  71.         FieldSinCos<T> sc2n1 = sc1;
  72.         final double   eps   = FastMath.ulp(one).getReal();

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

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

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

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

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

  93.         }

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

  98.     }

  99. }