Matlab to Julia: Random number generators

I am writing unit tests for Matlab and Julia functions (Julia translated from Matlab), and both use the randn() function. Is there a way to get the same sequence of random numbers both in Matlab and Julia?

Without that it is difficult to write unit tests that give the same results for both languages.

Example of a function I want to test:

"""
    getWindDirT(::Direction_Constant_wErrorCov, WindDir, iT)

Return wind direction in SOWFA-deg for the requested turbine(s).

# Arguments
- `WindDir`: Struct with fields
    - `Data::Float64`: wind direction value
    - `CholSig::AbstractMatrix`: Cholesky factor of covariance matrix (nT x nT)
- `iT`: Vector of turbine indices (can be any indexable collection)

# Returns
- `phi`: Vector of wind directions for the selected turbines, including random perturbation
"""
function getWindDirT(::Direction_Constant_wErrorCov, WindDir, iT)
    n = length(iT)
    phi = fill(WindDir.Data, n)
    # randn(n) gives a vector of n normal random numbers
    # WindDir.CholSig should
    @assert issquare(WindDir.CholSig)
    phi .= phi .+ WindDir.CholSig * randn(n)
    return phi
end

Unit test in Julia:

    using Random
  
    Random.seed!(1234)
    dir_mode = Direction_Interpolation_wErrorCov()

    # Example wind direction data (time, phi)
    wind_data = [
        0.0  10.0;
        5.0  20.0;
        10.0 30.0
    ]

    # Example Cholesky factor (for 2 turbines)
    chol_sig = cholesky([1.0 0.5; 0.5 1.0]).L

    # Create WindDir instance
    WindDir = WindDirMatrix(wind_data, chol_sig)

    # Example turbine indices (for 2 turbines)
    iT = [1, 2]

    # Example time
    t = 7.0

    # Call the function
    phi = getWindDirT(dir_mode, WindDir, iT, t)
    @test size(phi) == (2,1)
    @test phi[1] ā‰ˆ 24.92903352636283
    @test phi[2] ā‰ˆ 24.363944731838128

But if I translate this unit test to Matlab it will not work, because the sequence of random numbers is different.

Record them in a file?

2 Likes

Ok, but then I need to replace the standard randn(n) function with a version that reads this file, but only for the unit tests.

How would you do that?

Something like this could do the ticket:

const DNG = Iterators.Stateful(parse(Float64, x) for x in eachline("rng-output.csv"))
randn() = popfirst!(DNG)
randn(n) = collect(Iterators.take(DNG, n))

Edit: oh, but that’ll only work if your randn calls are entirely in the scope of your unit test, which it looks like they’re called from your source. In such a case, I think you’d want to explicitly pass a RNG object instead of using the global one… and then you’d need to do a bit more work to satisfy the RNG interface. That’s more complicated, but I think there may be some existing RNG types that could do the ticket?

1 Like

Would it be possible to use MATLAB.jl to call MATLAB’s version of randn directly in Julia?

For similar purpose, I generally save the sequence generated by Matlab in a .mat file and open it in Julia using MAT.

2 Likes

The MATLAB docs say that it uses a Mersenne Twister RNG with seed 0 by default. So perhaps setting

julia> mersenne = Random.MersenneTwister(0)
MersenneTwister(0)

and using that

julia> randn(mersenne, 5)
5-element Vector{Float64}:
 -0.7587307822993239
  0.03249717326229417
  0.04868971510118324
  0.426553609186312
 -0.6455341387712752

could reproduce MATLAB’s sequence?

2 Likes

I would wager that the exact method of going from the MersenneTwister’s ā€œnativeā€ output (32 or 52 or 64 raw bits) to a normal distribution is going to be sufficiently complicated to not match. Saving the outputs to a one-column CSV would likely be simplest. For the purposes of unit-testing, you could probably get away with something like this:

struct DeterministicNumberGenerator{T}
    iter::T
end
DeterministicNumberGenerator(filename::String) = DeterministicNumberGenerator(Iterators.Stateful(parse(Float64, x) for x in eachline(filename)))
Random.randn(dng::DeterministicNumberGenerator) = popfirst!(dng.iter)
Random.randn(dng::DeterministicNumberGenerator, n) = collect(Iterators.take(dng.iter, n))

And then making your signatures accept an rng argument, calling randn(rng, n).

8 Likes

Well, it might be possible, but I want to have the unit tests of our Julia package FLORIDyn.jl running on Github without access to Matlab. So this is not a good option. The other way round it would be easier, but still more complicated than just sharing a file with values.

@mbauman solution seems best to me

ahhhh, right. I forgot that you would need to be paying for access to the MATLAB runtime…

You can always write your own simple RNG that you use for both Julia and matlab test cases.

4 Likes

There’s always

5 Likes

You can also try to design your test in a way that they don’t depend on specific random numbers..

1 Like

IMO if the test depends on randomness, the ā€œfix seed and use approxā€ combo is unreliable and might miss edge cases (what if all other seeds break everything?).

You could follow a statistical approach:

  1. In Matlab, run the test’s code with different seeds many times (1000x, the more the better). Modify the code to compute averages of quantities of interest (phi in your case) or write out all 1000 phis to a file.
  2. In Julia, run the code the same number of times, again computing the mean. Suppose phi_mean holds the mean of the vector phi. Then, @test abs(phi_mean[1] - phi_mean_matlab[1]) < 0.01. Same for the other component. The 0.01 here is an arbitrary small number that somehow depends on the number of samples (more samples => could use 0.005 as the threshold).
  3. Ideally, put the entire sample computed by Matlab in the file with your Julia tests and test the hypothesis E(phi_Julia[1] - phi_Matlab[1]) == 0 (same for phi[2]) with a two-sample test using HypothesisTests.jl.
  4. Since there’s a Normal distribution involved, perhaps it’s possible to compute the expected value exactly. Then you don’t even need Matlab: simply test whether phi_mean is sufficiently close to that true expectation.
1 Like

Octave is easier to access though, and there’s some code here to patch over initialisation differences between it and MATLAB, and have Octave produce a MATLAB compatible sequence. So that’s another option worth keeping in mind.

1 Like

Not much to add to the discussion other than that I asked this exact question over a decade ago now, but in the context of Julia and R. I got a response from Dirk Eddelbuettel here.

1 Like

If the random numbers don’t need to be high quality it will be easier to implement your own linear congruential generator.

1 Like

I am now using this Matlab script to create a sequence of random numbers:

clear all
rng(1234);
N=1000;

vec=randn(1,N);
filename = 'test/randn.mat';
save(filename, 'vec')

And this code to create a file based pseudo-random number generator in Julia:

# Copyright (c) 2025 Uwe Fechner
# SPDX-License-Identifier: BSD-3-Clause

using FLORIDyn
using Test
using LinearAlgebra
using MAT
using Random

if basename(pwd()) == "test"
    cd("..")
end

filename="test/randn.mat"
randn_vec=vec(matread(filename)["vec"])

mutable struct FileRNG <: AbstractRNG
    const data::Vector{Float64}
    idx::Int
end

FileRNG(data::Vector{Float64}) = FileRNG(data, 1)

function Base.rand(rng::FileRNG)
    x = rng.data[rng.idx]
    rng.idx += 1
    return x
end

function Base.rand(rng::FileRNG, ::Type{Float64})
    return rand(rng)
end

function Base.rand(rng::FileRNG, ::Type{T}) where {T}
    error("FileRNG only supports Float64")
end

Random.randn(rng::FileRNG) = rand(rng)

rng = FileRNG(randn_vec)
FLORIDyn.set_rng(rng)

include("test_windfield.jl")

In my package I defined:

# global variables
RNG::AbstractRNG = Random.default_rng()

function set_rng(rng)
    global RNG
    RNG = rng
end

This means, normally the default RNG is used, but for the unit tests the FileRNG is used.

5 Likes

Possibly you could try to use StableRNGs which is really a https://en.wikipedia.org/wiki/Lehmer_random_number_generator and as such seems to have a Matlab implementation https://fr.mathworks.com/matlabcentral/fileexchange/46174-a-function-to-implement-the-prng-using-lehmer-random-number-generator-algorithm

Caveat emptor : I have not tested they are really same (I dot not have a Matlab instance on my laptop)

Hope it could help,