replica nfl jerseysreplica nfl jerseyssoccer jerseyreplica nfl jerseys forums.silverfrost.com :: View topic - Puzzle asin(z) and acos(z)
forums.silverfrost.com Forum Index forums.silverfrost.com
Welcome to the Silverfrost forums
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Puzzle asin(z) and acos(z)

 
Post new topic   Reply to topic    forums.silverfrost.com Forum Index -> General
View previous topic :: View next topic  
Author Message
Kenneth_Smith



Joined: 18 May 2012
Posts: 797
Location: Hamilton, Lanarkshire, Scotland.

PostPosted: Thu Feb 27, 2025 10:53 am    Post subject: Puzzle asin(z) and acos(z) Reply with quote

The complex arcsin and arccos functions can each be expressed in two ways:
Code:
    arcsin(z) = -j*log( sqrt(1.0 - z*z) + j*z )    Eqn 1
   
              =  j*log( sqrt(1.0 - z*z) - j*z )    Eqn 2

    arccos(z) = -j*log( j*sqrt(1.0 � z*z) + z )    Eqn 3

              =  pi/2 � arcsin(z)                  Eqn 4

The square root term 1.0 � z*z is common to three of these, and the addition of +j*z or -j*z in eqns 1 and 2 determine what is passed to the LOG function for the arcsin.

Under certain conditions this could result in both the real and imaginary parts of the argument to the log function being zero.

Now consider the following program:
Code:
program p
use iso_fortran_env
implicit none
integer, parameter :: wp = kind(1.d0)
complex(kind=wp) :: z1,z2
print*, compiler_version()
print*
z1 = cmplx(500.0D6,500.0D6,kind=wp)
z2 = cmplx(500.0D6,-500.0D6,kind=wp)
print*, acos(z1)
print*, acos(z2)  !Real and imaginary parts of argument to LOG are both zero
print*, asin(z1)  !Real and imaginary parts of argument to LOG are both zero
print*, asin(z2)
end program p

With the problematic lines commented out, trying three different compilers:
Code:
FTN95 v9.10.0

  (0.78539816339,-21.0698394272)
  (0.78539816339,-21.0698394272)


GCC version 12.3.0

                             (1.5707963267948966,-Inf)
             (0.78539816339744828,-21.069839427226384)


 Intel(R) Fortran Intel(R) 64 Compiler Classic for applications running on Intel
 (R) 64, Version 2021.4.0 Build 20210910_000000

 (0.785398163397448,-21.0698394272264)
 (0.785398163397448,-21.0698394272264)           


We see that FTN95 returns the same values as Ifort, but gFortran does something different for ACOS(Z1).


Last edited by Kenneth_Smith on Thu Feb 27, 2025 11:00 am; edited 1 time in total
Back to top
View user's profile Send private message Visit poster's website
Kenneth_Smith



Joined: 18 May 2012
Posts: 797
Location: Hamilton, Lanarkshire, Scotland.

PostPosted: Thu Feb 27, 2025 10:57 am    Post subject: Reply with quote

Continued .....

For the full code above, with the lines problematic to FTN not commented out, we get:
Code:
GCC version 12.3.0

                             (1.5707963267948966,-Inf)
              (0.78539816339744828,21.069839427226384)
                              (0.0000000000000000,Inf)
             (0.78539816339744828,-21.069839427226384)

Intel(R) Fortran Intel(R) 64 Compiler Classic for applications running on Intel
 (R) 64, Version 2021.4.0 Build 20210910_000000

 (0.785398163397448,-21.0698394272264)
 (0.785398163397448,21.0698394272264)
 (0.785398163397448,21.0698394272264)
 (0.785398163397448,-21.0698394272264)


I think FTN95 uses just one of the two available functions for asin(z) and acos(z), while Ifort selects which equation to use based on the sign of the imaginary part of Z.

Using the sign of the imaginary part of Z may not be sufficient, since there are issues with logarithm branch cuts (when the complex number passed to the log function is a negative real number). If the imaginary part of z is very large the imaginary part goes towards + infinity, and � minus infinity for very large negative imaginary part. This extra complication is being considered by gFortran.

It seems that the performance of FTN95 could be improved here, or a user defined function defined for these functions, but what should be the benchmark to compare with?

I�ve pushed the envelope a bit here in terms of my knowledge of maths, but perhaps other forum reader can see something that I have missed.
Back to top
View user's profile Send private message Visit poster's website
PaulLaidler
Site Admin


Joined: 21 Feb 2005
Posts: 8165
Location: Salford, UK

PostPosted: Thu Feb 27, 2025 12:41 pm    Post subject: Reply with quote

Ken

I have made a note to look into this.
Back to top
View user's profile Send private message AIM Address
Kenneth_Smith



Joined: 18 May 2012
Posts: 797
Location: Hamilton, Lanarkshire, Scotland.

PostPosted: Thu Feb 27, 2025 1:58 pm    Post subject: Reply with quote

No worries Paul, this is not urgent - more of a frustrating curiosity.

The single precision SLATEC routines CASIN(z) and CACOS(z) align with iFort for z = cmplx(500.0e6,500.0e6), but differ for z = cmplx(500.0e6,-500.0e6)
Code:
Intel(R) Fortran Intel(R) 64 Compiler Classic for applications running on Intel
 (R) 64, Version 2021.4.0 Build 20210910_000000

 Test asin of  (5.0000000E+08,5.0000000E+08)
 From SLATEC (0.7853982,21.06984)
 From compiler (0.7853982,21.06984)

 Test acos of  (5.0000000E+08,5.0000000E+08)
 From SLATEC (0.7853981,-21.06984)
 From compiler (0.7853982,-21.06984)

 Test asin of  (5.0000000E+08,-5.0000000E+08)
 From SLATEC (-1.570796,3.465736)
 From compiler (0.7853982,-21.06984)

 Test acos of  (5.0000000E+08,-5.0000000E+08)
 From SLATEC (3.141593,-3.465736)
 From compiler (0.7853982,21.06984)

There is no alignment between SLATEC and gFortran
Code:
GCC version 12.3.0

 Test asin of              (500000000.,500000000.)
 From SLATEC            (0.785398245,21.0698395)
 From compiler                    (0.00000000,Inf)

 Test acos of              (500000000.,500000000.)
 From SLATEC           (0.785398126,-21.0698395)
 From compiler                   (1.57079637,-Inf)

 Test asin of             (500000000.,-500000000.)
 From SLATEC            (-1.57079625,3.46573591)
 From compiler           (0.785398185,-21.0698395)

 Test acos of             (500000000.,-500000000.)
 From SLATEC            (3.14159250,-3.46573591)
 From compiler            (0.785398185,21.0698395


Rather a big can of worms!
Back to top
View user's profile Send private message Visit poster's website
arctica



Joined: 10 Sep 2006
Posts: 146
Location: United Kingdom

PostPosted: Sat Mar 01, 2025 3:38 pm    Post subject: Reply with quote

Hi Ken

I tried a test code and get some values close to the expected result but it falls over for large values as a complex input to acos.

Using the explicit functions for acos and asin as functions:

Code:

module complex_acos_asin
    implicit none
contains
    function cacos_f(z) result(c_acos)
        implicit none
        complex*8, intent(in) :: z
        complex*8 :: c_acos, unit_i=(0.0d0, 1.0d0)
       
        ! cacos(z) = -i * log(z + i * sqrt(1 - z^2))
        c_acos = - unit_i * log(z + unit_i * sqrt(1.0d0 - z**2))
    end function cacos_f

    function casin_f(z) result(c_asin)
        implicit none
        complex*8, intent(in) :: z
        complex*8 :: c_asin, unit_i=(0.0d0, 1.0d0)
       
        ! casin(z) = -i * log(i*z + sqrt(1 - z^2))
        c_asin = - unit_i * log(unit_i * z + sqrt(1.0d0 - z**2))
    end function casin_f
   
end module complex_acos_asin

program acos_asin_computation
    use complex_acos_asin
    !use iso_fortran_env
    implicit none
    complex*8 :: z1, z2, z1_acos, z2_acos, z1_asin, z2_asin

    z1 = cmplx(5e8, 5e8, 2)
    z2 = cmplx(5e8, -5e8, 2)

        z1_acos = cacos_f(z1)
        z2_acos = cacos_f(z2)
        print *, "cacos(z1) = ", z1_acos
        print *, "cacos(z2) = ", z2_acos
        print *, ' asin(z1) from acos(z1) = ', 3.14159d0/2-z1_acos
        print *, 'acos(z) z=6.7e7-6.7e7i', acos(cmplx(6.7e7, -6.7e7, 2))

        z1_asin = casin_f(z1)
        z2_asin = casin_f(z2)
        print *, "casin(z1) = ", z1_asin
        print *, "casin(z2) = ", z2_asin
        print *, ' asin(z2) from acos(z2) = ', 3.14159d0/2-z2_acos

end program acos_asin_computation


I tested the values in Octave and Scilab and both give the expected solutions

Code:
z1=5e8 + %i*5e8
acos(z1) = 0.7853982 - 21.069839i
asin(z1) = 0.7853982 + 21.069839i

z1=5e8 - %i*5e8
acos(z2) = 0.7853982 + 21.069839i
asin(z2) = 0.7853982 - 21.069839i


Is it the sign of the imaginary part causing issues?

Lester
Back to top
View user's profile Send private message
Kenneth_Smith



Joined: 18 May 2012
Posts: 797
Location: Hamilton, Lanarkshire, Scotland.

PostPosted: Sun Mar 02, 2025 2:33 pm    Post subject: Reply with quote

Thanks Lester,

I think this is what is required:

Code:
  function arcsinCD(z) result (returnval)
  integer, parameter :: wp = kind(1.d0)
  complex(kind=wp), intent(in) :: z
  complex(kind=wp) :: returnval
  complex(kind=wp), parameter :: j = cmplx(0.0_wp,1.0_wp,kind=wp)
    if (aimag(z) .lt. 0.0_wp) then
      returnval = -j*log(sqrt(1.0_wp - z*z) + j*z)
    else
      returnval =  j*log(sqrt(1.0_wp - z*z) - j*z)
    end if
  end function arcsinCD

  function arccosCD(z) result (returnval)
  integer, parameter :: wp = kind(1.d0)
  complex(kind=wp), intent(in) :: z
  complex(kind=wp) :: returnval
  complex(kind=wp), parameter :: j   = cmplx(0.0_wp,1.0_wp,kind=wp)
  complex(kind=wp), parameter :: pi2 = 2.0_wp*atan(1.0_wp)
    if (aimag(z) .lt. 0.0_wp) then
      returnval = pi2 - arcsinCD(z)
    else
      returnval = -j*log(j*sqrt( 1.0_wp - z*z) + z)
    end if
  end function arccosCD


Here is a test program using these functions:

https://www.dropbox.com/scl/fi/guletuqk37wnocm0mjyk9/test.f95?rlkey=v9ulsz07iqs7enw77gfdy7nsa&st=jwsh8on9&dl=0

And here is the output produced using iFort

https://www.dropbox.com/scl/fi/ilncipneh6d9uh9qjrvr0/log.txt?rlkey=fvktbzfovpvtf5hbqrsn6snvz&st=3ulijujr&dl=0
Back to top
View user's profile Send private message Visit poster's website
arctica



Joined: 10 Sep 2006
Posts: 146
Location: United Kingdom

PostPosted: Sun Mar 02, 2025 5:25 pm    Post subject: Reply with quote

Hi Ken

Good to see you found a solution to the problem.

Lester
Back to top
View user's profile Send private message
Kenneth_Smith



Joined: 18 May 2012
Posts: 797
Location: Hamilton, Lanarkshire, Scotland.

PostPosted: Mon Mar 03, 2025 2:39 pm    Post subject: Reply with quote

Unfortunately the two functions I posted previously fail to catch the potential underflow to the log function for the case of Z with large (positive or negative) real part and zero imaginary part.

Here are the updated functions:

Code:
  function arcsinCD(z) result (returnval)
  integer, parameter :: wp = kind(1.d0)
  complex(kind=wp), intent(in) :: z
  complex(kind=wp) :: returnval
  complex(kind=wp), parameter :: j = cmplx(0.0_wp,1.0_wp,kind=wp)
  complex(kind=wp) :: z2, sqrt_term, arglog1, arglog2
     if (aimag(z) .lt. 0.0_wp) then
      ! Imaginary part is less than zero, use eqn 1
      returnval = -j*log(sqrt(1.0_wp - z*z) + j*z)
      return
    else if (aimag(z) .gt. 0.0_wp) then
      ! Imaginary part is greater than zero, use eqn 2
      returnval =  j*log(sqrt(1.0_wp - z*z) - j*z)
      return
    else
      ! Imaginary part is equal to zero
      if ( abs(real(z,kind=wp)) .le. 1.0_wp) then
        ! If real part is between -1 and 1, the result is not complex
        ! return the complex number =  (asin(real_part), 0.0)
        returnval = cmplx(asin(real(z,kind=wp)),0.0_wp,kind=wp)
        return
      else
        ! Deal with potential underflow for argument to log function
        ! for large real part
        z2 = z*z
        sqrt_term = sqrt(1.0_wp - z2)
        arglog1   = sqrt_term + j*z  ! depending on sign of the large real
        arglog2   = sqrt_term - j*z  ! part one of these may underflow to
                                     ! (0.0,0.0)
        ! Use the term with with largest magnitude to avoid (0.0,0.0)
        if ( abs (arglog1) .gt. abs(arglog2) ) then
          returnval = -j*log(arglog1)
        else
          returnval = j*log(arglog2)
        end if
        ! Flip sign of imaginary part if negative
        if ( aimag(returnval) .lt. 0.0_wp) then
          returnval = cmplx(real(returnval,kind=wp), - aimag(returnval), kind=wp)
        end if
      end if
    end if
  end function arcsinCD

  function arccosCD(z) result (returnval)
  integer, parameter :: wp = kind(1.d0)
  complex(kind=wp), intent(in) :: z
  complex(kind=wp) :: returnval
  complex(kind=wp), parameter :: pi2 = 2.0_wp*atan(1.0_wp)
      returnval = pi2 - arcsinCD(z)
  end function arccosCD
 


Here is my updated test program:
https://www.dropbox.com/scl/fi/guletuqk37wnocm0mjyk9/test.f95?rlkey=v9ulsz07iqs7enw77gfdy7nsa&st=3skgca30&dl=0

And the results obtained using iFort:
https://www.dropbox.com/scl/fi/ilncipneh6d9uh9qjrvr0/log.txt?rlkey=fvktbzfovpvtf5hbqrsn6snvz&st=419qv461&dl=0

No discrepancies between these functions and the native iFort acos(z).
Turned out to be a lot more "complex" than I suspected.
Back to top
View user's profile Send private message Visit poster's website
arctica



Joined: 10 Sep 2006
Posts: 146
Location: United Kingdom

PostPosted: Wed Mar 05, 2025 2:13 pm    Post subject: Reply with quote

Hi Ken

I had a look at tweaking the module to cover all domains and it seems to work (so far) with your modifications.

Code:
module complex_acos_asin
    implicit none

    interface arcsin
        module procedure arcsinR, arcsinCD
    end interface arcsin
   
    interface arccos
        module procedure arccosR, arccosCD
    end interface arccos
   
    contains

  function arcsinCD(z) result (returnval)
    integer, parameter :: wp = kind(1.d0)
    complex(kind=wp), intent(in) :: z
    complex(kind=wp) :: returnval
    complex(kind=wp), parameter :: j = cmplx(0.0_wp,1.0_wp,kind=wp)

    if (aimag(z) < 0.0_wp) then
      returnval = -j*log(sqrt(1.0_wp - z*z) + j*z)
    else
      returnval =  j*log(sqrt(1.0_wp - z*z) - j*z)
    end if
   
  end function arcsinCD

  function arccosCD(z) result (returnval)
    integer, parameter :: wp = kind(1.d0)
    complex(kind=wp), intent(in) :: z
    complex(kind=wp) :: returnval
    complex(kind=wp), parameter :: j   = cmplx(0.0_wp,1.0_wp,kind=wp)
    complex(kind=wp), parameter :: pi2 = 2.0_wp*atan(1.0_wp)

    if (aimag(z) < 0.0_wp) then
      returnval = pi2 - arcsinCD(z)
    else
      returnval = -j*log(j*sqrt( 1.0_wp - z*z) + z)
    end if
   
  end function arccosCD
 
  function arcsinR(x) result (returnval)
    integer, parameter :: wp = kind(1.d0)
    real(kind=wp), intent(in) :: x
    complex(kind=wp) :: returnval
   
    if (abs(x) <= 1.0_wp) then
      returnval = asin(x)
    else
      returnval = arcsinCD(cmplx(x, 0.0_wp, kind=wp))
    end if
       
  end function arcsinR
 
  function arccosR(x) result (returnval)
    integer, parameter :: wp = kind(1.d0)
    real(kind=wp), intent(in) :: x
    complex(kind=wp) :: returnval
   
    if (abs(x) <= 1.0_wp) then
      returnval = acos(x)
    else
      returnval = arccosCD(cmplx(x, 0.0_wp, kind=wp))
    end if
   
  end function arccosR
 
end module complex_acos_asin
Back to top
View user's profile Send private message
arctica



Joined: 10 Sep 2006
Posts: 146
Location: United Kingdom

PostPosted: Wed Mar 05, 2025 2:21 pm    Post subject: Reply with quote

Test program to check the results of various inputs

Code:
program test_asin_acos

use complex_acos_asin
use iso_fortran_env

implicit none

integer :: i
integer, parameter :: wp=kind(1.d0)
real(wp) :: real_part_sin, imag_part_sin, real_part_cos, imag_part_cos
complex(wp) :: asin_res, acos_res, z(2)

! Check that the results of the domain limits are correct for example x = -10 to 10

  write( *, '(a)') '    n             arcsin(n)                         arccos(n)             '
  write( *, '(a)') '=========================================================================='

do i = -10, 10, 1
  acos_res = arccos(real(i,kind=wp))
  asin_res = arcsin(real(i,kind=wp))

  real_part_sin = real(asin_res)
  imag_part_sin = aimag(asin_res)
  real_part_cos = real(acos_res)
  imag_part_cos = aimag(acos_res)

! Output formatted results in normal mathematical style
  if (imag_part_sin >= 0.0) then
     if (imag_part_cos >= 0.0) then
        write(*, '(i5,2x,f15.12, " + ", f12.10, "i", 2x, f15.12, " + ", f13.10, "i")') &
        i,real_part_sin, imag_part_sin, real_part_cos, imag_part_cos
     else
        write(*, '(i5,2x,f15.12, " + ", f12.10, "i", 2x, f15.12, " - ", f13.10, "i")') &
        i,real_part_sin, imag_part_sin, real_part_cos, abs(imag_part_cos)
     end if
  else
     if (imag_part_cos >= 0.0) then
        write(*, '(i5,2x,f15.12, " - ", f12.10, "i", 2x, f15.12, " + ", f13.10, "i")') &
        i,real_part_sin, abs(imag_part_sin), real_part_cos, imag_part_cos
     else
        write(*, '(i5,2x,f15.12, " - ", f12.10, "i", 2x, f15.12, " - ", f13.10, "i")') &
        i,real_part_sin, abs(imag_part_sin), real_part_cos, abs(imag_part_cos)
     end if
  end if

end do

! verify complex input
    z(1) = cmplx(5e8, 5e8, kind=wp)
    z(2) = cmplx(5e8, -5e8, kind=wp)

write(*,'()')

print *, 'z1 z2', z
print *, 'z1 (arcsin, arccos = ', arcsin(z(1)), arccos(z(1))
print *, 'z2 (arcsin, arccos = ', arcsin(z(2)), arccos(z(2))

print *, 'arcsin(1.0), arcsin(1.5) ', real(arcsin(1.0_wp)), arcsin(1.5_wp)
print *, arccos(1.0_wp), arccos(0.5_wp)

end program test_asin_acos
Back to top
View user's profile Send private message
Kenneth_Smith



Joined: 18 May 2012
Posts: 797
Location: Hamilton, Lanarkshire, Scotland.

PostPosted: Wed Mar 05, 2025 5:54 pm    Post subject: Reply with quote

Thanks Lester,

This program, which uses the module you posted today, shows the failure which caused me to post the two revised functions on Monday.

Code:
program test_asin_acos

use complex_acos_asin
use iso_fortran_env

implicit none

integer :: i
integer, parameter :: wp=kind(1.d0)
complex(wp) :: z(2)

! verify complex input
    z(1) = cmplx(5e8, 0.0, kind=wp)
    z(2) = cmplx(-5e8, 0.0, kind=wp)

write(*,'()')

print *, 'z1 z2', z
print *, 'z1 (arcsin, arccos = ', arcsin(z(1)), arccos(z(1)) ! Fails Re and Im parts to complex log both zero
print *, 'z2 (arcsin, arccos = ', arcsin(z(2)), arccos(z(2)) ! OK

end program test_asin_acos


Large real part and zero imaginary part causes the argument to complex LOG to be zero.

Here is a MathCad worksheet which shows why this happens:

https://www.dropbox.com/scl/fi/y7am7f8tk48a6zvjteyq9/Screenshot-2025-03-05-164523.jpg?rlkey=wmrsnyqts4yfecs9egyh1fprg&st=td6ims5k&dl=0
Back to top
View user's profile Send private message Visit poster's website
arctica



Joined: 10 Sep 2006
Posts: 146
Location: United Kingdom

PostPosted: Wed Mar 05, 2025 6:30 pm    Post subject: Reply with quote

Thanks for the update Ken. Certainly is an interesting problem.

Lester
Back to top
View user's profile Send private message
arctica



Joined: 10 Sep 2006
Posts: 146
Location: United Kingdom

PostPosted: Thu Mar 06, 2025 12:47 pm    Post subject: Reply with quote

Hi Ken

I updated the code with your latest revisions and it worked fine; the results match the output from Python using cmath as a check.

Interesting point, but Scilab and Octave both give the opposite sign for the imaginary part when testing arcsin(1.5), arccos(1.5).

Thanks again for the reference on this "complex" problem. It is detailed in the NIST Handbook of Mathematical Functions.

Lester
Back to top
View user's profile Send private message
Kenneth_Smith



Joined: 18 May 2012
Posts: 797
Location: Hamilton, Lanarkshire, Scotland.

PostPosted: Fri Mar 07, 2025 12:07 pm    Post subject: Reply with quote

Lester,

Thanks for the reference. That�s not a text I am familiar with, as there is a copy of Abramowitz and Stegun on my bookshelf � which is just a little older than I am! I�ll track down a copy of this alternative text.

The convention for computing the inverse cosine and inverse sine of values outside the range [−1,1] depends on how the principal branch of the complex logarithm function has been defined. There are two possibilities (both mathematically valid) which is why you see a difference between Scilab/Octave and Python.

At the moment the functions I posted on Wednesday succeed for any complex number on the complex plane within a circle of radius 1.0E+154. Beyond this the z*z term in sqrt(1 � z*z) overflows, since huge(1.0_wp) is approx. 1.0E308, so sqrt(huge(1.0_wp)) is 1.0E+154.

I will look at the possibility of normalising/scaling the sqrt(1 � z*z) calculation to increase the radius of the circle on the complex plane in which the functions succeed. Probably unnecessary, but I�m curious about this.
Back to top
View user's profile Send private message Visit poster's website
arctica



Joined: 10 Sep 2006
Posts: 146
Location: United Kingdom

PostPosted: Fri Mar 07, 2025 4:25 pm    Post subject: Reply with quote

Hi Ken

Here is a link to the NIST online mathematical library; it is updated on a regular basis from what I can tell and is a companion to the book (no longer in print).

https://dlmf.nist.gov/

Lester
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    forums.silverfrost.com Forum Index -> General All times are GMT + 1 Hour
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group