Complex Numbers Wrappers

Hi All,

I’ve been working kind of stealth on a Julia package to try and draw people into the language. The field I’m toying in heavily involves complex arithematic and when it doesn’t it’s still very convenient to sometimes represent real vectors as complex ones. I’d like to start off by saying I sincerely appreciate how complex numbers aren’t second class citizens in base julia, every operation I want to perform just works. Thank you for that.

I was wondering if I could start a discussion on whether it makes sense to add some additional wrapper functions. For example I often find myself doing things like the following:

a = (1:10) .+ 1.1im
Reals = real.(a);
Imags = imag.(a);

When it’d be more convenient to do something like this,

Components(x) = (real.(x), imag.(x))
(Reals, Imags) = Components(a)

Or maybe even for the vector case…

Components(x) = [real.(x) imag.(x)]
RealsAndImags = Components(a)

For Matrices/>1-tensors this is a slightly more interesting problem. For example say we have a 2-tensor with complex numbers but we want to decompose it into it’s real and imaginary parts for certain operations. It would increase the tensor order by 1.

Not sure 100% where I am going with this but for some operations convenient decomposition of complex numbers to their real and imaginary parts into vector constituents is really handy. Doing so with the real/imag operators is great, but, can be a little lack-luster.

I have other suggestions but want to see how this goes first,

If I get it correctly, I think what you are suggesting is already provided by https://github.com/piever/StructArrays.jl (for general structs, not just Complex Numbers).

2 Likes

@favba - interesting package. Although I do appreciate this solution, I was moreso hoping for changes to Julia’s base functionality rather then importing a package to conveniently do fundamental mathematical operations.

Depending on ones view, complex numbers naturally represent a vector with 2 elements. So it seems convenient, atleast to me, to include wrappers for these types of interchanges. Or maybe the lack of perspective is on my part.

Edit - If other’s agree I can draft some boilerplate generic code for what I am talking about. I would of course prefer other users working in this domain’s input because I’d hate to waste effort and make something other’s don’t like.

reinterpret is the most efficient way of doing this, because it gives you a real “view” of the array without making a copy:

julia> z = rand(ComplexF64, 4)
4-element Array{Complex{Float64},1}:
  0.8727435742095095 + 0.6802824713710667im 
 0.04080858213528504 + 0.1846189024172642im 
  0.4617624900983548 + 0.8237888120257533im 
  0.4935209874107416 + 0.21796464191831832im

julia> x = reinterpret(Float64, z)
8-element reinterpret(Float64, ::Array{Complex{Float64},1}):
 0.8727435742095095 
 0.6802824713710667 
 0.04080858213528504
 0.1846189024172642 
 0.4617624900983548 
 0.8237888120257533 
 0.4935209874107416 
 0.21796464191831832

julia> x[1] = 2
2

julia> z[1]
2.0 + 0.6802824713710667im
2 Likes

Woah, now that’s a very interesting solution. It doesn’t treat the real and imaginary parts as orthogonal, but very interesting way to go around it. Evens and Odds would designate whether or not something is real or imaginary. Then maybe pass by reference to a different array structure? Hm…

Or, something like this?

z = rand(ComplexF64, 4) 
New = permutedims(reshape( reinterpret( Float64, z) , 2, : ), (2, 1))

I don’t see a keyword argument for reshape to go by rows/cols. That might be a nice way around the final permuteddims statement.

That’s because reshape also avoids a copy if it can, which means that it is constrained by Julia’s underlying column-major memory layout. You can use transpose to get an in-place view.

For example, the matrix X below is an in-place view of the same data as the complex array z:

julia> z = rand(ComplexF64, 3)
3-element Array{Complex{Float64},1}:
 0.4206627856607843 + 0.12707128278694957im
 0.8793026231384475 + 0.7972474143096078im 
 0.1504386481370612 + 0.9674399126647735im 

julia> X = transpose(reshape(reinterpret(Float64, z), 2, :))
3×2 LinearAlgebra.Transpose{Float64,Base.ReshapedArray{Float64,2,Base.ReinterpretArray{Float64,1,Complex{Float64},Array{Complex{Float64},1}},Tuple{}}}:
 0.420663  0.127071
 0.879303  0.797247
 0.150439  0.96744 

julia> X[1,2] = 7;

julia> z
3-element Array{Complex{Float64},1}:
 0.4206627856607843 + 7.0im               
 0.8793026231384475 + 0.7972474143096078im
 0.1504386481370612 + 0.9674399126647735im
2 Likes

I see, I presumed incorrectly permuteddims was also a view.

I’m scratching my head trying to work out how to do this with say a Matrix, or a tensor. Tried a few naive things without success. Is there a more generic version of a Transpose that wraps positions? The code for how Transpose works is a bit hard to eyeball.

This is how you would convert a complex number to a vector using Grassmann

julia> using Grassmann; basis"2"
(⟨++⟩, v, v₁, v₂, v₁₂)

julia> 1+v12 # complex number
1 + 1v₁₂

julia> v1*ans # convert to vector
0 + 1v₁ + 1v₂

The nice thing about it is that Grassmann and Clifford numbers unify vector algebra and complex algebra.

1 Like