Concrete Example 0805: how to prevent using global variable in the module?

I learned from all of you that Julia discourage global variable. Thank you very much indeed!

I am translating my Fortran code to Julia. It is a Monte Carlo parametric expectation maximization code. Basically using gaussian mixture model.

Here I show a module call Mixture, as a small example. My purpose is simple.

I define a type Mean_covar, then I need an array called musigma. Each element of the musigma array is of the type Mean_covar.

The size of array musigma, and the value of its each element will be initialized by calling the function mean_covar_init in the main program which will use this β€˜Mixture’ module.

I also wish to export this array musigma, so that I can directly use it without do Mixture.musigma all the time.

Here is the module:

module Mixture

export musigma

mutable struct Mean_covar
    mu::Array{Float64,2}
    sigma::Array{Float64,2}
    w::Float64
end

global const musigma = Array{Mean_covar,1}()  

function mean_covar_init(kmix::Int64,dim_p::Int64
                        ,weight::Array{Float64,1}
                        ,sigma::Array{Float64,3}
                        ,mu::Array{Float64,2})
    @assert length(weight) == kmix
    @assert size(sigma) == (kmix,dim_p,dim_p)
    @assert size(mu) == (kmix,dim_p)
    resize!(musigma, kmix) 
    for k in 1:kmix
        musigma[k] = Mean_covar(zeros(dim_p,1),zeros(dim_p,dim_p),0.0)
        musigma[k].mu[1,1] = mu[k,1]
        musigma[k].mu[2,1] = mu[k,2]
        musigma[k].sigma[1,1] = sigma[k,1,1]
        musigma[k].sigma[2,2] = sigma[k,2,2]
        musigma[k].w = weight[k]
    end
    return nothing
end

end

Note that I added const for musigma array just to lock the type of the musigma array.

Now my question is, in order to export musigma array, do I have to declare it as a global array?

It will be much better if you initialize mussigma inside the init function and return it to the user.
Then pass mussigma to the next function that will use it as a parameter.

Something like:

module Mixture

  export init, compute

  function init(...)
     mussigma = Vector{...}(undef,...)
     ...
     return mussigma
  end

  function compute(mussigma)
    ...
    return result
  end

end

using Mixture

mussigma = init(...)

result = compute(mussigma)


Try to forget completely about the possibility of using global variables, that is the best advice.

3 Likes

I agree with leandromartinez98-san.

As I already mentioned, in my personal opinion, it is safer to avoid using constants to fix the types, especially when using Julia for scientific purposes.

For example, if you fix the type to Float64, you will not be able to use automatic differentiation. (cf. Avoid writing overly-specific types)

See also

In this case, there is no need to share something global with multiple functions without going through function arguments.

I have tried minimal changes.

Code

module O

export musigma

struct Mean_covar{T}
    mu::Vector{T}
    sigma::Matrix{T}
    w::Array{T,0}
end

function mean_covar_init(kmix, dim_p, 
        weight::AbstractVector{T},
        sigma::AbstractArray{T,3},
        mu::AbstractMatrix{T}) where T
    @assert size(weight) == (kmix,)
    @assert size(sigma) == (kmix, dim_p, dim_p)
    @assert size(mu) == (kmix, dim_p)
    musigma = Vector{Mean_covar{T}}(undef, kmix)
    for k in 1:kmix
        musigma[k] = Mean_covar(zeros(T, dim_p), zeros(T, dim_p, dim_p), fill(zero(T)))
        musigma[k].mu[1,1] = mu[k,1]
        musigma[k].mu[2,1] = mu[k,2]
        musigma[k].sigma[1,1] = sigma[k,1,1]
        musigma[k].sigma[2,2] = sigma[k,2,2]
        musigma[k].w[] = weight[k]
    end
    musigma
end

end
kmix = 5
dim_p = 3
weight = rand(kmix)
sigma = rand(kmix, dim_p, dim_p)
mu = rand(kmix, dim_p)

musigma64 = O.mean_covar_init(kmix, dim_p, weight, sigma, mu)
@show typeof(musigma64) size(musigma64)
@show musigma64[1].mu musigma64[1].sigma musigma64[1].w;

Result

typeof(musigma64) = Vector{Main.O.Mean_covar{Float64}}
size(musigma64) = (5,)
(musigma64[1]).mu = [0.25133930343144617, 0.6524185771129412, 0.0]
(musigma64[1]).sigma = [0.6391609541493193 0.0 0.0; 0.0 0.8803430485817509 0.0; 0.0 0.0 0.0]
(musigma64[1]).w = fill(0.6189491252747268)```

You can also use Float32 type.

weight32 = rand(Float32, kmix)
sigma32 = rand(Float32, kmix, dim_p, dim_p)
mu32 = rand(Float32, kmix, dim_p)

musigma32 = O.mean_covar_init(kmix, dim_p, weight32, sigma32, mu32)
@show typeof(musigma32) size(musigma32)
@show musigma32[1].mu musigma32[1].sigma musigma32[1].w;
typeof(musigma32) = Vector{Main.O.Mean_covar{Float32}}
size(musigma32) = (5,)
(musigma32[1]).mu = Float32[0.27483153, 0.21964681, 0.0]
(musigma32[1]).sigma = Float32[0.16200805 0.0 0.0; 0.0 0.9208745 0.0; 0.0 0.0 0.0]
(musigma32[1]).w = fill(0.2774073f0)

If there is a possibility of using GPU, it is important to be able to use Float32 as well. However, in the above example, the type of the fields of Mean_covar type are fixed to Array's, the design of which is insufficient to use GPU.

Changes

  • mutable struct Mean_covar β†’ immutable struct Mean_covar{T} (fields are all mutable)
  • type of mu β†’ Vector{T} = Array{T,1}
  • type of weight β†’ Matrix{T} = Array{T,2}
  • type of w β†’ Array{T,0}, 0-dimensional array (this is mutable)
  • delete global const musigma in the module
  • mean_covar_init constructs and returns musigma

Point: The module O above does not contain any concrete type names like Float64.

β€œIn general, you should use the most general applicable abstract types for arguments, and when in doubt, omit the argument types.”

2 Likes

Thank you very much.
Right, I agree, that is what I did in Fortran, whenever possible use generic functions or types.

It is just that I heard the code will be slow if Julia are always trying to do type conversion or something. So if I define just Float, instead of Float64, will Julia smart enough to detect it is Float64 or Float32?
I mean if some expressions are mixed of Float64 and Float32, will it be slower than simply defining everything with just Float64?

Thank you very much.
May I ask, what does the T mean?

Type parameters are introduced immediately after the type name, surrounded by curly braces

https://docs.julialang.org/en/v1/manual/types/#Parametric-Types

1 Like

About that you will find many many posts, because it is the core of how Julia works. But my note on that is this one: Type instability Β· JuliaNotes.jl

1 Like