i/elliptic

Home Manual Packages Global Index Keywords Quick Reference |
    /*
     * elliptic.i -- $Id$
     * elliptic functions
     *
     * Copyright (c) 1999.  See accompanying LEGAL file for details.
     */

    /* Abramowitz and Stegun, sections 16.4, 17.2.17-19, 17.6 */

    local elliptic ;

    /* DOCUMENT elliptic, ell_am, ell_f, ell_e, dn_, ellip_k, ellip_e

         The elliptic integral of the first kind is:

            u = integral[0 to phi]( dt / sqrt(1-m*sin(t)^2) )

         The functions ell_f and ell_am compute this integral and its
         inverse:

            u   = ell_f(phi, m)
    	phi = ell_am(u, m)

         The Jacobian elliptic functions can be computed from the

         "amplitude" ell_am by means of:

            sn(u|m) = sin(ell_am(u,m))

    	cn(u|m) = cos(ell_am(u,m))

    	dn(u|m) = dn_(ell_am(u,m)) = sqrt(1-m*sn(u|m)^2)

         The other nine functions are sc=sn/cn, cs=cn/sn, nd=1/dn,

         cd=cn/dn, dc=dn/cn, ns=1/sn, sd=sn/dn, nc=1/cn, and ds=dn/sn.
         (The notation u|m does not means yorick's | operator; it is
         the mathematical notation, not valid yorick code!)

         The parameter M is given in three different notations:
           as M, the "parameter",
           as k, the "modulus", or
           as alpha, the "modular angle",

         which are related by: M = k^2 = sin(alpha)^2.  The yorick elliptic
         functions in terms of M may need to be written

           ell_am(u,k^2) or ell_am(u,sin(alpha)^2)
         in order to agree with the definitions in other references.
         Sections 17.2.17-19 of Abramowitz and Stegun explains these notations,
         and chapters 16 and 17 present a compact overview of the subject of

         elliptic functions in general.

         The parameter M must be a scalar; U may be an array.  The

         exceptions are the complete elliptic integrals ellip_k and

         ellip_e which accept an array of M values.

         The ell_am function uses the external variable ell_m if M is
         omitted, otherwise stores M in ell_m.  Hence, you may set ell_m,

         then simply call ell_am(u) if you have a series of calls with

         the same value of M; this also allows the dn_ function to work

         without a second specification of M.

         The elliptic integral of the second kind is:

            u = integral[0 to phi]( dt * sqrt(1-m*sin(t)^2) )

         The function ell_e computes this integral:

            u   = ell_e(phi, m)

         The special values ell_f(pi/2,m) and ell_e(pi/2,m) are the complete

         elliptic integrals of the first and second kinds; separate functions

         ellip_k and ellip_e are provided to compute them.

         Note that the function ellip_k is infinite for M=1 and for large

         negative M.  The "natural" range for M is 0<=M<=1; all other real

         values can be "reduced" to this range by various transformations;

         the logarithmic singularity of ellip_k is actually very mild, and

         other functions such as ell_am are perfectly well-defined there.

         Here are the sum formulas for elliptic functions:

           sn(u+v) = ( sn(u)*cn(v)*dn(v) + sn(v)*cn(u)*dn(u) ) /
                     ( 1 - m*sn(u)^2*sn(v)^2 )
           cn(u+v) = ( cn(u)*cn(v) - sn(u)*dn(u)*sn(v)*dn(v) ) /
                     ( 1 - m*sn(u)^2*sn(v)^2 )
           dn(u+v) = ( dn(u)*dn(v) - m*sn(u)*cn(u)*sn(v)*cn(v) ) /
                     ( 1 - m*sn(u)^2*sn(v)^2 )

         And the formulas for pure imaginary values:

           sn(1i*u,m) = 1i * sc(u,1-m)
           cn(1i*u,m) = nc(u,1-m)
           dn(1i*u,m) = dc(u,1-m)

       SEE ALSO: ell_am, ell_f, ell_e, dn_, ellip_k, ellip_e
     */

    func ell_am (u,m)
    /* DOCUMENT ell_am(u)
             or ell_am(u,m)

         returns the "amplitude" (an angle in radians) for the Jacobi

         elliptic functions at U, with parameter M.  That is,
            phi = ell_am(u,m)
         means that

            u = integral[0 to phi]( dt / sqrt(1-m*sin(t)^2) )

         Thus ell_am is the inverse of the incomplete elliptic function

         of the first kind ell_f.  See help,elliptic for more.

       SEE ALSO: elliptic
     */
    {
      /* set up the arithmetic-geometric mean scale */
      extern ell_m, _agm_m, _agm_n, _agm_coa, _agm_a, _agm_sn;
      if (is_void(m)) m= ell_m;
      if (m != ell_m) {
        ell_m= m= double(m);
        if (m<0.) {
          _agm_sn= 1./(1.-m);
          m*= -_agm_sn;
          _agm_sn= sqrt(_agm_sn);
        } else if (m>1.) {
          m= 1./m;
          _agm_sn= sqrt(m);
        }
        _agm_m= m;
        _agm_coa()= 0.;
        _agm_n= 0;
        _agm_a= 1.;
        if (m!=1.) {
          b= sqrt(1.-m);
          for (;;) {         /* maximum of 8 passes for 64-bit double */
    	c= 0.5*(_agm_a-b);
    	if (!c) break;
    	am= _agm_a-c;         /* arithmetic mean */
    	_agm_coa(++_agm_n)= c/am;
    	if (am==_agm_a) break;

    	b= sqrt(_agm_a*b);    /* geometric mean */
    	_agm_a= am;
          }
          _agm_a*= 2.^_agm_n;
        }
      }

      phi= _agm_a*u;
      if (m<0. || m>1.) phi/= _agm_sn;
      if (m!=1.)

        for (n=_agm_n ; n>0 ; n--) phi= 0.5*(phi+asin(_agm_coa(n)*sin(phi)));
      else

        phi= atan(tanh(phi), sech(phi));
      if (m<0.) {
        cn= cos(phi);
        phi= sin(phi);
        nd= 1./sqrt(1.-m*phi*phi);
        phi= atan(_agm_sn*phi*nd, cn*nd);
      } else if (m>1.) {
        phi= sin(phi);

        phi= atan(_agm_sn*phi, sqrt(1.-_agm_m*phi*phi));
      }
      return phi;
    }

    _agm_coa= array(0.,16);
    _agm_n= 0;
    _agm_m= 0.0;
    _agm_a= 1.0;
    _agm_sn= 1.0;
    ell_m= 0.0;

    func dn_ (phi, m)

    /* DOCUMENT dn_(ell_am(u,m))

         return the Jacobian elliptic function dn(u|m).  The external
         variable ell_m must be set properly before calling dn_.

       SEE ALSO: elliptic, ell_am
     */
    {
      if (is_void(m)) m= ell_m;
      phi= sin(phi);
      return sqrt(1.-m*phi*phi);
    }

    func ell_f (phi,m)
    /* DOCUMENT ell_f(phi,m)

         returns the incomplete elliptic integral of the first kind F(phi|M).
         That is,
            u = ell_f(phi,m)
         means that

            u = integral[0 to phi]( dt / sqrt(1-m*sin(t)^2) )

         See help,elliptic for more.

       SEE ALSO: elliptic, ell_e
     */
    {
      orig_m= m= double(m);
      if (m>1.) {
        scale= sqrt(m);

        phi= asin(scale*sin(phi));
        m= 1./m;
      } else if (m<0.) {
        scale= sqrt(1.-m);
        phi= 0.5*pi - phi;
        m/= m-1.;
      }

      if (m==1.) {

        phi= atanh(sin(phi));
        a= 0.;
      } else {          /* compute using arithmetic-geometric mean */
        sgn= sign(phi);
        phi= abs(phi);
        a= 1.;
        b= sqrt(1.-m);
        twon= 1.0;
        pi2= 0.5*pi;
        for (;;) {      /* maximum of 8 passes for 64-bit double */
          c= 0.5*(a-b);
          if (!c) break;
          phase= (phi+pi2)/pi;
          cycle= floor(phase);
          phi*= 1. + 1.e-15*(cycle==phase);

          phi+= atan((b/a)*tan(phi)) + pi*cycle;
          twon*= 2.0;
          am= a-c;
          if (am==a) break;
          b= sqrt(a*b);
          a= am;
        }
        phi/= twon*a*sgn;
      }

      if (orig_m>1.) {
        phi/= scale;
      } else if (orig_m<0.) {
        phi= (0.5*pi/a - phi)/scale;
      }

      return phi;
    }

    func ell_e (phi,m)
    /* DOCUMENT ell_e(phi,m)

         returns the incomplete elliptic integral of the second kind E(phi|M).
         That is,
            u = ell_e(phi,m)
         means that

            u = integral[0 to phi]( dt * sqrt(1-m*sin(t)^2) )

         See help,elliptic for more.

       SEE ALSO: elliptic, ell_f
     */
    {
      orig_m= m= double(m);
      if (m>1.) {
        scale= sqrt(m);

        phi= asin(scale*sin(phi));
        m= 1./m;
      } else if (m<0.) {
        scale= sqrt(1.-m);
        phi= 0.5*pi - phi;
        m/= m-1.;
      }

      if (m==1.) {

        per= floor((phi+0.5*pi)/pi);

        phi= sin(phi)*sign(0.5-abs(per%2.)) + 2.*per;
        a= 0.;
      } else {          /* compute using arithmetic-geometric mean */
        sgn= sign(phi);
        phi= abs(phi);
        a= 1.;
        b= sqrt(1.-m);
        eok= 1.-0.5*m;
        cs= 0.;
        twon= 1.0;
        pi2= 0.5*pi;
        for (;;) {      /* maximum of 8 passes for 64-bit double */
          c= 0.5*(a-b);
          if (!c) break;

          phi+= atan((b/a)*tan(phi)) + pi*floor((phi+pi2)/pi);
          cs+= c*sin(phi);
          eok-= twon*c*c;
          twon*= 2.0;
          am= a-c;
          if (am==a) break;
          b= sqrt(a*b);
          a= am;
        }
        f= sgn*phi/(twon*a);
        phi= eok*f + sgn*cs;
      }

      if (orig_m>1.) {
        phi= (phi-(1.-m)*f)/scale;
      } else if (orig_m<0.) {
        phi= (eok*0.5*pi/a - phi)*scale;
      }

      return phi;
    }

    func ellip_k (m)
    /* DOCUMENT ellip_k(m)

         returns the complete elliptic integral of the first kind K(M):

            K(M) = integral[0 to pi/2]( dt / sqrt(1-M*sin(t)^2) )

         See help,elliptic for more.

       SEE ALSO: elliptic, ellip_e, ell_f
     */
    {

      if (anyof(m>=1.)) error, "ellip_k(m) not computed for m>=1";

      m= double(m);
      mask= (m>=0.);
      list= where(mask);

      if (numberof(list)) scale= array(0.5*pi,numberof(list));
      list= where(!mask);
      if (numberof(list)) {
        sm= 1./(1.-m(list));
        m(list)*= -sm;

        sm= 0.5*pi*sqrt(sm);
      }
      scale= merge(scale,sm,mask);

      a= array(1.,dimsof(m));
      b= sqrt(1.-m);
      for (;;) {
        c= 0.5*(a-b);
        am= a-c;
        if (allof(am==a)) break;
        b= sqrt(a*b);
        a= am;
      }

      return scale/a;
    }

    func ellip_e (m)
    /* DOCUMENT ellip_e(m)

         returns the complete elliptic integral of the second kind E(M):

            E(M) = integral[0 to pi/2]( dt * sqrt(1-M*sin(t)^2) )

         See help,elliptic for more.

       SEE ALSO: elliptic, ellip_k, ell_e
     */
    {

      if (anyof(m>1.)) error, "ellip_e(m) not computed for m>1";

      m= double(m);
      mask= (m>=0.);
      list= where(mask);

      if (numberof(list)) scale= array(0.5*pi,numberof(list));
      list= where(!mask);
      if (numberof(list)) {
        sm= 1.-m(list);
        m(list)/= -sm;

        sm= 0.5*pi*sqrt(sm);
      }
      scale= merge(scale,sm,mask);

      mask= (m!=1.);
      list= where(mask);
      if (numberof(list)) {
        m= m(list);

        a= array(1.,numberof(m));
        b= sqrt(1.-m);
        e= 1.-0.5*m;
        twon= 1.0;
        for (n=0 ;;) {  /* maximum of 8 passes for 64-bit double */
          c= 0.5*(a-b);
          am= a-c;
          if (allof(am==a)) break;
          e-= twon*c*c;
          twon*= 2.0;
          b= sqrt(a*b);
          a= am;
        }
        e/= a;
      }
      list= where(!mask);

      if (numberof(list)) em= array(2./pi,numberof(list));

      return scale*merge(e,em,mask);
    }

    func ellip2_k (m)
    /* DOCUMENT ellip2_k(m)

         returns the complete elliptic integral of the first kind K(M):

            K(M) = integral[0 to pi/2]( dt / sqrt(1-M*sin(t)^2) )
         accurate to 2e-8 for 0<=M<1

       SEE ALSO: elliptic, ellip_e, ell_f
     */
    {
      m= 1.-m;

      return poly(m,1.38629436112,0.09666344259,0.03590092383,
    	      0.03742563713,0.01451196212) +

             log(1./m)*poly(m,0.5,0.12498593597,0.06880248576,
    			0.03328355346,0.00441787012);
    }

    func ellip2_e (m)
    /* DOCUMENT ellip2_e(m)

         returns the complete elliptic integral of the second kind E(M):

            E(M) = integral[0 to pi/2]( dt * sqrt(1-M*sin(t)^2) )
         accurate to 2e-8 for 0<=M<=1

       SEE ALSO: elliptic, ellip_k, ell_e
     */
    {
      m= 1.-m;

      return poly(m,1.,0.44325141463,0.06260601220,
    	      0.04757383546,0.01736506451) +

             log(1./m)*poly(m,0.,0.24998368310,0.09200180037,
    			0.04069697526,0.00526449639);
    }