How should we define mutli-dimensional structs inside julia? - julia

I'm trying to define matrix-like structures. How should I define them? For example defining a matrix like this:
struct Mat
r11::Float64
r12::Float64
r21::Float64
r22::Float64
end
But when the matrix is big, it can't be written like that. How should I define multi-dimensional matrices to be memory efficient and fast when added, subtracted, etc.

The best way is to use the already existing StaticArrays. That's a high-quality, well-maintained package that implements Matrix-like structs.
StaticArrays uses an NTuple as the underlying data, which means it can vary in length. Its type definition is something like this:
struct StaticMatrix{T, R, C, N}
data::NTuple{N, T}
end
Operations on static arrays are extremely efficient. For example:
julia> using StaticArrays
julia> a = SMatrix{2,2}(rand(4));
julia> code_native(+, Tuple{typeof(a), typeof(a)}, debuginfo=:none)
.text
vmovupd (%rsi), %ymm0
vaddpd (%rdx), %ymm0, %ymm0
vmovupd %ymm0, (%rdi)
movq %rdi, %rax
vzeroupper
retq
nopw %cs:(%rax,%rax)

Related

Julia: What is undef in Array in Julia

This is a constructor for arrays:
Array{T}(undef, dims)
I am new to Julia, and don't have a good background in programming. In this syntax, why is undef used for creating the array?
What is a constructor in Julia, in what situation do we use a constructor?
If we don't type constructor, Julia will automatically create a constructor. Then, Why we use constructor?
First, you want to understand what is a constructor:
For that, I suggest you the Julia doc: Constructors in Julia
Now that you have the theory, let's break apart this expression:
a = Array{Int}(undef, (2, 2))
What this expression is saying is "I want a to be an Array of dimension (2, 2)". So Julia will ask for some memory space. When I write it on the Julia REPL:
julia> a = Array{Int}(undef, (2, 2))
2×2 Array{Int64,2}:
0 0
0 0
Now Array{T}(undef, dims) is the generalization of that. "Construct an array of a specific type T with a specific number of dimensions dims"
So far, I didn't explain what is undef. undef is a shortcut for UndefInitializer(). In this example, we wanted an uninitialized array. What does it mean? For that, you have to understand that variables are not created ex nihilo on your terminal. They are occupying a specific place in the memory of your computer. And sometimes, the same memory space was occupied by another variable. So the space my new variable can take might not be empty:
julia> a = Array{Float64}(undef, (2, 2))
2×2 Array{Float64,2}:
6.94339e-310 6.94339e-310
6.94339e-310 0.0
Here, I never asked for these values to be there. I could erase it to work with a clean variable. But that would mean to erase the value for each cell, and it's much more expensive for the computer to replace each value rather than declaring "here is the new variable".
So basically, undef and uninitialized arrays are used for performance purposes. If you want an array well initialized, you can use fill.

Fast multi-dimensional Walsh-Hadamard transforms in Julia?

I was looking for a fast implementation of FWHT(Fast Walsh-Hadamard transformation) to understand it and implement it in python (implementation should be able to handle an n-dimensional array and should be able to apply the transformation on any specific set of dimensions).
I came across the Julia implementation (https://github.com/stevengj/Hadamard.jl) which seems to be pretty good but as I am new to Julia I am not able to understand a part of the code:
for (Tr,Tc,fftw,lib) in ((:Float64,:Complex128,"fftw",FFTW.libfftw),
(:Float32,:Complex64,"fftwf",FFTW.libfftwf))
#eval function Plan_Hadamard{N}(X::StridedArray{$Tc,N}, Y::StridedArray{$Tc,N},
region, flags::Unsigned, timelimit::Real,
bitreverse::Bool)
set_timelimit($Tr, timelimit)
dims, howmany = dims_howmany(X, Y, [size(X)...], region)
dims = hadamardize(dims, bitreverse)
plan = ccall(($(string(fftw,"_plan_guru64_dft")),$lib),
PlanPtr,
(Int32, Ptr{Int}, Int32, Ptr{Int},
Ptr{$Tc}, Ptr{$Tc}, Int32, UInt32),
size(dims,2), dims, size(howmany,2), howmany,
X, Y, FFTW.FORWARD, flags)
set_timelimit($Tr, NO_TIMELIMIT)
if plan == C_NULL
error("FFTW could not create plan") # shouldn't normally happen
end
return cFFTWPlan{$Tc,FFTW.FORWARD,X===Y,N}(plan, flags, region, X, Y)
end
#eval function Plan_Hadamard{N}(X::StridedArray{$Tr,N}, Y::StridedArray{$Tr,N},
region, flags::Unsigned, timelimit::Real,
bitreverse::Bool)
set_timelimit($Tr, timelimit)
dims, howmany = dims_howmany(X, Y, [size(X)...], region)
dims = hadamardize(dims, bitreverse)
kind = Array{Int32}(size(dims,2))
kind[:] = R2HC
plan = ccall(($(string(fftw,"_plan_guru64_r2r")),$lib),
PlanPtr,
(Int32, Ptr{Int}, Int32, Ptr{Int},
Ptr{$Tr}, Ptr{$Tr}, Ptr{Int32}, UInt32),
size(dims,2), dims, size(howmany,2), howmany,
X, Y, kind, flags)
set_timelimit($Tr, NO_TIMELIMIT)
if plan == C_NULL
error("FFTW could not create plan") # shouldn't normally happen
end
return r2rFFTWPlan{$Tr,(map(Int,kind)...),X===Y,N}(plan, flags, region, X, Y)
end
end
In the above code what is the plan variable, how is it used, and where can I find its implementation?
What are the inputs in the curly braces for the below line?
cFFTWPlan{$Tc,FFTW.FORWARD,X===Y,N}
This is constructing an FFTW "plan" to perform a multidimensional FFT. The cFFTWPlan type is a wrapper around the C fftw_plan pointer, and is implemented in the FFTW.jl module. The arguments in curly braces are Julia type parameters: in this case, indicating the number type (Tc), the FFTW transform direction FORWARD, whether the transform is in-place (X===Y), and the dimensionality of the transform (N). There are two methods here, one for an FWHT of complex-number data that creates a cFFTWPlan (which calls fftw_plan_guru_dft) and one for real-number data that creates an r2rFFTWPlan (which calls fftw_plan_guru_r2r). (These internal types of FFTW.jl are undocumented. The low-level C calls directly to the FFTW library are documented in the FFTW manual.
It should, in principle, be possible to make similar calls to FFTW for NumPy arrays. However, the existing pyFFTW wrappers don't seem to support FFTW's r2r transforms (needed for FWHTs of real data), so you'd have to add that.
Or you could call the Julia Hadamard.jl module from Python via the pyjulia package. Or you could use some other Python FWHT package, like https://github.com/FALCONN-LIB/FFHT

Is Rust multi-dimensional array row major and tightly packed?

I'm writing a 3D math library for my project, I want to know is the Rust column major or row major? For example I have a 2 dimensional array as matrix and I want to serve it to a C library (like OpenGL or Vulkan), for those library this is important to have a tightly packed column major array.
Well, let's find out:
let arr: [[i8; 2]; 2] = [[1, 2], [8, 9]];
println!(
"{:?} {:?} {:?} {:?}",
&arr[0][0] as *const _,
&arr[0][1] as *const _,
&arr[1][0] as *const _,
&arr[1][1] as *const _,
);
Prints 0x7fff5584ae74 0x7fff5584ae75 0x7fff5584ae76 0x7fff5584ae77 for example. So: yes these arrays with length known to compile time are tightly packed and (considering the common definition of the terms) row major.
Note: the test above doesn't say that this always works! You can read more about this topic here.
But: usually you use heap allocated arrays since you can't know the length beforehand. For that purpose it's idiomatic to use Vec. But there are no special rules for this type, so Vec<Vec<T>> is not tightly packed! For that reason Vec<Vec<T>> is not idiomatic anymore -- you should use a simple Vec<T> and do the calculation of the index yourself.
Of course, writing the indexing calculation multiple times is not a good solution either. Instead, you should define some wrapper type which does the indexing for you. But as Sebastian Redl already mentioned: you are not the only one having this problem and there exist types exactly for this purpose already.

How do you declare non-commutable variables in julia?

How do you define non-commutable variables? I'm new to julia and have been reading the doc and found nothing so far.
By non-commutable I mean if variables (say symbols) a and b are multiplied. ab =\= ba
About commutativity: Julia does not assume that a*b is the same as b*a (example: let a and b be matrices). However, methods of the function *(a,b) for some specific combinations of types act in a commutative manner, such as when a and b are numbers.
Since you are talking about symbols, I guess that want to work with a symbolic representation of expressions. There's at least two ways to go about this:
Work with the AST of an expression.
You can quote any expression by :(expr) (sometimes :expr is enough, depends on operator precedence):
julia> ex = :(x*y*z)
:(x * y * z)
julia> typeof(ex)
Expr
Note that the order of the factors has been preserved (though the associativity has not).
You can inspect the Expr by looking at it's head and args fields:
julia> ex.head
:call
julia> ex.args
4-element Array{Any,1}:
:*
:x
:y
:z
The args may be e.g. symbols (of type Symbol), constant values, and other expressions.
This technique works well if you want to write your own macros that process expressions, since the input to a macro is the AST of its arguments.
Define your own types and overload the * function. Example: with
abstract Symbolic
# might as well make these immutable
immutable Sym <: Symbolic
name::Symbol
end
immutable Prod <: Symbolic
x::Symbolic
y::Symbolic
end
# The * function is imported by default.
# Usually, we need to import a function
# from Base before extending it.
*(x::Symbolic, y::Symbolic) = Prod(x, y)
you can do
julia> x, y = Sym(:x), Sym(:y)
(Sym(:x),Sym(:y))
julia> x*y
Prod(Sym(:x),Sym(:y))
Since our *(x::Symbolic, y::Symbolic) function preserves the order of the arguments, we can see it in the created Prod object.
In most languages, the second alternative is your only option. The first alternative is more direct since you don't have to write a new AST framework for yourself, and don't have to execute the expression just to get at it's representation. Which one is more suitable depends on the application, however. If you want to associate different properties with your variables, the second approach seems easier.
Was it something like this that you were looking for?

Set array's rank at runtime

I have written a program which reads a file containing multidimensional data (most commonly 3D, but 2D could occur as well). To heighten simplicity I would like to store the data in an array of the same rank (or something pretending to be one), i.e. using a three-dimensional array for 3D data, etc.; the trouble is that the program only learns about the dimensionality on reading the data file.
Currently I store all data in an array of rank one and calculate each element's index in that array from the element's coordinates (this was also suggested here). However, I have also read about pointer rank remapping, which seems very elegant and just what I have been looking for, as it would allow me to scrap my procedures for array index determination (which are probably far less efficient than what goes on behind the scenes). Now, however, it looks like I'm facing the same problem as with directly declaring a multidimensional array - how to do the declaration? Again, it requires information about the rank.
How could I use pointer rank remapping or some other, more suitable technique for setting an array's rank at runtime - in case this can be done at all. Or am I best off sticking to the rank one-array that I am currently using?
I once asked something similar, i.e. how to treat a two-dimensional array as one dimension, see here: changing array dimensions in fortran.
The answers were about the RESHAPE instrinsic of pointers, however there seems to be no way to use the same array name unless you use subroutine wrappers, but then you need callbacks to have the eventual subroutine with only one name, so the problems get larger.
program test
real, allocatable :: data(:)
allocate(data(n_data))
! read stuff, set is_2d and sizes
if (is_2d) then
call my_sub2(data, nX, nY)
else
call my_sub3(data, nX, nY, nZ)
end if
end program test
subroutine my_sub2(data, nX, nY)
real :: data(nx,nY)
! ...
end subroutine my_sub2
subroutine my_sub3(data, nX, nY, nZ)
real :: data(nx,nY,nZ)
! ...
end subroutine my_sub3
EDIT: as an alternative, set the third rank to 1:
program test
real, allocatable, target:: data(:)
real, pointer:: my_array(:,:,:)
logical is_2d
n_data = 100
allocate(data(n_data))
! read stuff, determine is_2d and n
if (is_2d) then
i=n
j=n
k=1
else
i=n
j=n
k=n
end if
my_array(1:i,1:j,1:k) => data
write(*,*) my_array
end program test
Then you handle the 2D case as a special 3D case with third dimension 1.
EDIT2: also, beware when passing non-contiguous arrays to subroutines with explicit-shape arrays: http://software.intel.com/sites/products/documentation/hpc/compilerpro/en-us/fortran/lin/compiler_f/optaps/fortran/optaps_prg_arrs_f.htm
If I understand correctly, you read in data in and 1-D array and want to assign it to 2D or 3D arrays, which you know only after reading the file. Why not declare both 2D and 3D arrays as allocatable arrays, and allocate only one of them base on your data shape? You could use the intrinsic function RESHAPE to do this conveniently.
REAL,DIMENSION(:,:), ALLOCATABLE :: arr2d
REAL,DIMENSION(:,:,:),ALLOCATABLE :: arr3d
...
! Read data into 1-D array, arr1d;
...
IF(L2d)THEN
ALLOCATE(arr2d(im,jm))
arr2d=RESHAPE(arr1d,(/im,jm/))
ELSEIF(L3d)THEN
ALLOCATE(arr3d(im,jm,km))
arr3d=RESHAPE(arr1d,(/im,jm,km/))
ENDIF
You could use the EQUIVALENCE statement like this:
Program ranks
integer a_1d(12)
integer a_2d(2, 6)
integer a_3d(2, 2, 3)
equivalence (a_1d, a_2d, a_3d)
! fill array 1d
a_1d = (/1,2,3,4,5,6,7,8,9,10,11,12/)
print *, a_1d
print *, a_2d(1,1:6)
print *, a_2d(2,1:6)
print *, a_3d(1,1,1:3)
print *, a_3d(2,1,1:3)
print *, a_3d(1,2,1:3)
print *, a_3d(2,2,1:3)
end program ranks
You can write a subroutine for different ranks of array and create an interface
Here in example I have shown that how to populate an array of different array using interface statement `
program main
use data
implicit none
real,dimension(:,:,:),allocatable::data
integer::nx,ny,nz
nx = 5
ny = 10
nz = 7
call populate(nx,ny,nz,data)
print *,data
end program main `
data module is here
module data
private
public::populate
interface populate
module procedure populate_1d
module procedure populate_2d
module procedure populate_3d
end interface
contains
subroutine populate_1d(x,data)
implicit none
integer,intent(in)::x
real,dimension(:),allocatable,intent(out):: data
allocate(data(x))
data=rand()
end subroutine populate_1d
subroutine populate_2d(x,y,data)
implicit none
integer,intent(in)::x,y
real,dimension(:,:),allocatable,intent(out):: data
allocate(data(x,y))
data=rand()
end subroutine populate_2d
subroutine populate_3d(x,y,z,data)
implicit none
integer,intent(in)::x,y,z
real,dimension(:,:,:),allocatable,intent(out):: data
allocate(data(x,y,z))
data=rand()
end subroutine populate_3d
end module data
There is an interface to populate 1d, 2d and 3d arrays. you can call populate interface instead of calling individual subroutines. It will automatically pick the relevant one.

Resources