I am confused using tanh(M)

Hello,
I am pritty new to Julia and try to find my first steps. I am a bit confused now.
In Python I had this (simplyfied) piece of code:

import numpy as np
a = [[1,2], [3,4]]
M = np.array(a)
np.tanh(M)

It brings me the expected result:

array([[0.76159416, 0.96402758],
           [0.99505475, 0.9993293 ]])

Now I try my luck with Julia:

M = [1 2
3 4]
tanh(M)

Output:

2×2 Array{Float64,2}:
 -0.0320733  0.472079
  0.708118   0.676045

My question, what is calculated here?

However if I do:

tanh.(M)

I get the expected result again. I think I understand concept of “.” as the elementwise operator. But I thought tanh() and similar are always elementwise. If I am wrong, what is tanh(M) without “.” calculating?
Thanks
Floriano

In julia, any operation on a matrix will not be element-wise unless you have a .. It turns out that for square matrices, you can take a hyperbolic tangent of a matrix. That’s the result you are seeing when you type tanh(M).

9 Likes

thank you

julia> tanh(1+2im)
1.1667362572409197 - 0.24345820118572525im

julia> tanh([1 -2; 2 1])
2×2 Array{Float64,2}:
  1.16674   0.243458
 -0.243458  1.16674 
4 Likes

As Oscar_Smith said, the component-wise tanh and (diagonalizable, square) matrix tanh are different in general. The matrix tanh is derived from a power series expansion of the scalar tanh, where the scalar powers are replaced with matrix powers. It can be computed by taking the eigenvalue decomposition U*diagm(evals)*U' of the matrix, taking the component-wise tanh on the eigenvalues, and then reconstructing:

julia> A = [1 -2; 2 1]
2×2 Array{Int64,2}:
 1  -2
 2   1

julia> using LinearAlgebra

julia> evals, U = eigen(A)
Eigen{Complex{Float64},Complex{Float64},Array{Complex{Float64},2},Array{Complex{Float64},1}}
eigenvalues:
2-element Array{Complex{Float64},1}:
 1.0 - 2.0000000000000004im
 1.0 + 2.0000000000000004im
eigenvectors:
2×2 Array{Complex{Float64},2}:
       0.0+0.707107im        0.0-0.707107im
 -0.707107-0.0im       -0.707107+0.0im     

julia> U*diagm(evals)*U'-A
2×2 Array{Complex{Float64},2}:
 -2.22045e-16+0.0im  -4.44089e-16+0.0im
  4.44089e-16+0.0im   2.22045e-16+0.0im

julia> U*diagm(tanh.(evals))*U'-tanh(A)
2×2 Array{Complex{Float64},2}:
 -4.44089e-16+0.0im  2.22045e-16+0.0im
   -2.498e-16+0.0im          0.0+0.0im

julia> tanh.(A)-tanh(A)
2×2 Array{Float64,2}:
 -0.405142  -1.20749 
  1.20749   -0.405142

The last line gives an example where the component-wise and matrix versions of tanh differ.

1 Like

Provided that the matrix is diagonalizable, of course.

1 Like

Yes, thanks for reminding us that the power series derivation of tanh(A) requires diagonalizability of A. But my demo used the decomposition A=U*diagm(evals)*U', so we need to further assume that A is unitarily diagonalizable for that to work, i.e., the transpose U' is also the inverse of U, which occurs when A is normal (A*A' == A'*A). It’s sufficient that A is (real) symmetric for normality to hold.