How to prevent using global arrays in iteration in the module

I have an a little detailed question about how to prevent using global array in iterations in the module. Hopefully someone could give some advice on how to prevent using global arrays.

module Mod
global AAA=Array{Float64,1}(undef, 999999999) # 999999999 is just to say the array is big. Its size is actually a variable which needs to be read in from an input file, the size is not a fixed number. 
for  i in 1:100
      f(AAA, AAAupdate)   # function f take in AAA, update it and output the updated array as AAAupdate
      g(AAAupdate,AAA)  # function g take in the updatedAAA, and update it again to make a new AAA, and output the new AAA as AAA.        
end
end

As we can see, for each i loop,

the big array AAA first need to be input to function f, then updated to AAAupdate.

then function g takein the AAAupdate array, and further update it and form an updated array AAA.

Then in the next loop, the newly updated AAA will be read in function f again, and the process repeat 100 times.

Finally, after the 100 loops, AAA will be completely updated.

So my question is, in this case, again since Julia discourage global stuff, then, is there ways to prevent making AAA a global array?

Thanks in advance!

Just pass it to your functions:

function main()
    AAA = zeros(9_999_999_999)
    f!(AAA)
    g!(AAA)
    return AAA
end

then you just need to define your f! and g! functions accordingly. Note that I appended a bang ! to the function name to indicate that it mutates its arguments - this is a naming convention in Julia, not a syntactic transformation (i.e. if I omit the bang it still mutates). Example:

julia> function f!(x)
           x .+= 1
       end
f! (generic function with 1 method)

julia> function g!(x)
           x .*= 3
       end
g! (generic function with 1 method)

julia> function main()
           AAA = zeros(5)
           f!(AAA)
           g!(AAA)
           return AAA
       end
main (generic function with 1 method)

julia> main()
5-element Vector{Float64}:
 3.0
 3.0
 3.0
 3.0
 3.0
2 Likes

Julia modules are libraries. There should not be logic depending on user input in a module.

Looks like you’re trying to use a module as a subroutine which is the first thing that’s wrong, not the globals in said module.

3 Likes

Thank you very much.

  1. About Julia, then what is the Julia way to do such a thing?
  2. About Fortran, Is there a better way to do it in Fortran?

Thank you very much.
I have a loop or iteration as you can see in my example, inside which the AAA is updated repeatedly.
But in your code, it seems there is no loop, am I missing something?

Sorry that was a simplified example, but a loop wouldn’t change much:

julia> function main()
           AAA = zeros(5)
           for i ∈ 1:2
               f!(AAA)
               g!(AAA)
           end
           return AAA
       end
main (generic function with 1 method)

julia> main()
5-element Vector{Float64}:
 12.0
 12.0
 12.0
 12.0
 12.0

I have interpreted that AAAupdate is a temporary variable used as a workspace and the size of AAAupdate is determined from the size of AAA.

In this case, it is terribly inconvenient to fix the sizes of AAA and AAAupdate by hardcoding them in the module.

To avoid this, you can prepare functions to set the initial values of AAA and AAAupdate in the module, and use them outside the module.

The basic pattern is as follows:

module Foo
struct Board{...} ...including state and tmp... end
function initialboard(...) ...; Board(...) end
function update!(board::Board, niters) ...update state with tmp... end
end

# Construct initial board as a global variable
board = Foo.initialboard(...)

# Update the board by the `Foo.update!` method
Foo.update!(board, 1000)

Working example: Game of Life

Jupyter notebook: https://github.com/genkuroki/public/blob/main/0014/Game%20of%20Life.ipynb

The size of the internal temporary workspace tmp is automatically determined by the size of the given state of board.

module LifeGame (53 lines)
module LifeGame

using Plots
using ProgressMeter

struct Board{T}
    state::T
    tmp::T
end
Board(state) = Board(state, similar(state))
function fivexfive(n=200)
    state = zeros(Int8, n, n)
    m = n Γ· 2
    state[m-2:m+2, m-2:m+2] .= [1 1 1 0 1; 1 0 0 0 0; 0 0 0 1 1; 0 1 1 0 1; 1 0 1 0 1]
    Board(state)
end
randboard(n=200) = Board(rand(Int8[0, 1], n, n))

P(i, m) = ifelse(i == m, 1, i+1)
Q(i, m) = ifelse(i == 1, m, i-1)
function _update!(v, u)
    m, n = size(u)
    @inbounds for j in 1:n, i in 1:m
        iβ‚Š, iβ‚‹, jβ‚Š, jβ‚‹ = P(i, m), Q(i, m), P(j, n), Q(j, n)
        N = u[iβ‚‹,jβ‚‹] + u[i,jβ‚‹] + u[iβ‚Š,jβ‚‹] + u[iβ‚‹,j] + u[iβ‚Š,j] + u[iβ‚‹,jβ‚Š] + u[i,jβ‚Š] + u[iβ‚Š,jβ‚Š]
        v[i,j] = N == 3 ? 1 : (N == 2 && !iszero(u[i,j])) ? 1 : 0
    end
end

function update!(board::Board, niters=1)
    state, tmp = board.state, board.tmp
    for _ in 1:nitersΓ·2
        _update!(tmp, state)
        _update!(state, tmp)
    end
    if isodd(niters)
        _update!(tmp, state)
        state .= tmp
    end
end

function gif!(board::Board, niters, nskips=1; gifname="life.gif", fps=20, size=(240, 240))
    prog = Progress(niters, 0)
    anim = @animate for t in 1:niters
        heatmap(board.state; size, colorbar=false, ticks=false, axis=false, frame=false)
        update!(board, nskips)
        next!(prog)
    end
    gif(anim, gifname; fps)
end

end
boardrandom = LifeGame.randboard()
LifeGame.gif!(boardrandom, 500; gifname="liferandom.gif")
Progress: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| Time: 0:00:26

liferandom

board5x5 = LifeGame.fivexfive()
LifeGame.gif!(board5x5, 2000; gifname="life5x5.gif", fps=60)
Progress: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| Time: 0:01:05

life5x5

2 Likes

Also in Fortran passing variables as parameters is a good idea for code reuse and readability. What is common to share as global variables are truly constant data (the speed of light, the planck constant, etc). For these, use const in Julia.

In Julia, however, that is not only a good idea, but turns out to be very important to ensure performance. In Fortran performance is not dependent on that pattern. However, the comparison is not exactly fair. In Julia, by following that pattern, the function is used independent of the data in a typical section. For example:

### this module would be finally in a package
julia> module MyModule
         const hbar = 6.62e-34 # this is really constant
         function f!(x)
           for i in eachindex(x)
             x[i] = hbar*(x[i] + 1)
           end
         end
         return nothing
       end
Main.MyModule

julia> #### now the user side of things

julia> using .MyModule

julia> x = rand(1000);

julia> MyModule.f!(x)

julia> x[1]
1.2190537919428275e-33

In Fortran, for the user to provide the x array, it must be an input variable of the program, so how the user interacts with the code is completely different. But in both cases it is a good practice to use shared values in global scope only if they are constants. And everything else should be passed as parameters.

1 Like

Thank you very much.

Was there anything wrong in the example I posted?

That is fine, thank you very much!

No.

The function LifeGame.gif!(board, niters; ...) in my example contains the for loop updating niters times the global variables initialized by boardrandom = LifeGame.randboard() and board5x5 = LifeGame.fivexfive() outside the module LifeGame and plotting each step to gif animations.

In the module LifeGame:

Outside the module LifeGame:

LifeGame.update!(board, niters) is not used in the example, but can update a LifeGame.Board type variable without plotting.

In the module LifeGame:

The array tmp, which is used as a temporary workspace, is created by the constructor of LifeGame.Board objects.

Example of LifeGame.update!: Glider

Updating a global variable with a for loop can be done by passing it as an argument to a function containing a for loop.

state = Int8[
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    0 1 1 0 0 0 0
    1 0 1 0 0 0 0
    0 0 1 0 0 0 0
]
board = LifeGame.Board(state)
board.state
7Γ—7 Matrix{Int8}:
 0  0  0  0  0  0  0
 0  0  0  0  0  0  0
 0  0  0  0  0  0  0
 0  0  0  0  0  0  0
 0  1  1  0  0  0  0
 1  0  1  0  0  0  0
 0  0  1  0  0  0  0

↓
↓ update the global variable board 12 times
↓ by the for loop in the function LifeGame.update!
↓

LifeGame.update!(board, 12)
board.state
7Γ—7 Matrix{Int8}:
 0  0  0  0  0  0  0
 0  0  0  0  1  1  0
 0  0  0  1  0  1  0
 0  0  0  0  0  1  0
 0  0  0  0  0  0  0
 0  0  0  0  0  0  0
 0  0  0  0  0  0  0

↓
↓ update and animate the global variable board
↓

LifeGame.gif!(board, 27; gifname="lifeglider.gif", fps=5)

image

Have fun!

Jupyter notebook: https://github.com/genkuroki/public/blob/main/0014/Game%20of%20Life.ipynb